AI/혼자공부하는머신러닝딥러닝

[ML] 02-2 데이터 전처리

inthyes 2024. 1. 20. 01:09

넘파이로 데이터 준비하기

1. 데이터 준비

bream_length = [25.4, 26.3, 26.5, 29.0, 29.0, 29.7, 29.7, 30.0, 30.0, 30.7, 31.0, 31.0, 
                31.5, 32.0, 32.0, 32.0, 33.0, 33.0, 33.5, 33.5, 34.0, 34.0, 34.5, 35.0, 
                35.0, 35.0, 35.0, 36.0, 36.0, 37.0, 38.5, 38.5, 39.5, 41.0, 41.0]
bream_weight = [242.0, 290.0, 340.0, 363.0, 430.0, 450.0, 500.0, 390.0, 450.0, 500.0, 475.0, 500.0, 
                500.0, 340.0, 600.0, 600.0, 700.0, 700.0, 610.0, 650.0, 575.0, 685.0, 620.0, 680.0, 
                700.0, 725.0, 720.0, 714.0, 850.0, 1000.0, 920.0, 955.0, 925.0, 975.0, 950.0]

 

넘파이 column_stack() : 전달받은 리스트를 일렬로 세운 후 차례대로 나란히 연결하는 함수

import numpy as np
np.column_stack(([1, 2, 3], [4, 5, 6]))

np.column_stack() 결과값

 

위 함수를 활용해서 fish_data를 생성한 후 0~4까지 출력해보면 아래와 같은 결과를 갖게 된다.

fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])

 

 

np.ones(), np.zeros() 함수를 통해 원하는 개수의 1과 0을 채운 배열 생성이 가능하다.

print(np.ones(5))
print(np.zeros(5))

 

위 함수와 배열을 연결하는 np.concatenate() 함수를 활용해 fish_target을 생성한다.

fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target)

 

사이킷런으로 훈련 세트와 테스트 세트  나누기

train_test_split()함수에 존재하는 랜덤 시드를 지정하는 random_state 매개변수를 활용하여 훈련 세트와 테스트 세트를 나눈다.

from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, random_state = 42)
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)

훈련 데이터와 테스트 데이터를 각각 36개, 13개로 나누었고 입력데이터는 2차원 배열, 타깃 데이터는 1차원 배열임을 아래 결과를 통해 알 수 있다.

 

test_target 출력을 통해 1, 0의 비율을 확인해보면 아래와 같은 결과를 얻는다. 13개의 테스트 세트 중에서 10개가 결과값 1, 3개가 0의 결과값을 가진다.

두 생선의 기존 비율은 2.5:1인 반면, test_target의 결과 비율은 3.3:1임을 확인할 수 있고, 샘플링 편향이 발생됨을 확인할 수 있다.

print(test_target)

 

 

train_test_split()함수의 stratify 매개변수에 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나누는 것이 가능하다.

동일한 비율로 맞출 수는 없지만 비슷한 비율의 데이터 지정이 가능하다.

train_input, test_input, train_target, test_target = train_test_split(fish_data, fish_target, stratify=fish_target, random_state = 42)
print(test_target)

 

수상한 도미 한 마리

k-최근접 이웃을 훈련하기 위해 훈련 데이터로 모델을 훈련하고 테스트 데이터로 모델을 평가한다.

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)

위 모델로 도미 데이터(결과값 1)를 넣고 결과를 확인해보면 1이 나와야하지만 아래와 같이 0이 출력됨을 알 수 있다.

print(kn.predict([[25, 150]]))

 

예상치 못한 결과값의 원인을 파악하기 위해 산점도를 출력해보면 아래와 같다.

import matplotlib.pyplot as plt
plt.scatter(train_input[:, 0], train_input[:, 1])
plt.scatter(25, 150, marker = '^')
plt.xlabel('length'); plt.ylabel('weigth')
plt.show()

이 때 [25, 150] 데이터에 대한 빠른 파악을 위해 삼각형으로 표기되는 marker = '^'을 활용한다.

k-최근접 이웃은 주변의 샘플 중에서 다수인 클래스를 예측으로 사용한다.

이 샘플의 주변 샘플을 알아보기 위해 KNeighborsClassifier 클래스의 kneighbors()를 활용한다.

 

KNeighborsClassifier클래스의 이웃 개수인 n_neighbors의 기본값은 5이므로 5개의 이웃이 반환된다.

이 때 n을 입력하지 않으면 5개, n에 원하는 이웃개수를 작성하면 원하는 이웃개수만큼의 이웃이 지정된다.

distances, indexes = kn.kneighbors([[25, 150]], n) # n 위치에 원하는 이웃 개수 작성

 

marker='D'를 통해 마름모로 산점도를 그려 이웃을 확인할 수 있다.

 

plt.scatter(train_input[:,0], train_input[:, 1])
plt.scatter(25, 150, marker = '^')
plt.scatter(train_input[indexes, 0], train_input[indexes, 1], marker = 'D')
plt.xlabel('length'); plt.ylabel('weigth')
plt.show()

산점도를 통해 가장 가까운 이웃에 도미(1)이 1개, 빙어(0)이 4개 포함됨을 확인할 수 있다.

 

이를 정확히 파악하기 위해 train_input, train_target의 indexes를 출력해보면 아래와 같다.

print(train_input[indexes])
print(train_target[indexes])

distances 배열에는 이웃 샘플까지의 거리가 담겨있다. 이를 출력하면 아래와 같은 결과값을 갖는다.

print(distances)

 

기준을 맞춰라

y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산되는 것을 distances 출력값과 산점도를 통해 확인할 수 있었다.

정확히 확인하기 위해 x축의 범위를 동일하게 0 ~ 1,000으로 맞추도록 한다.

xlim()함수를 사용하여 x축의 범위를 지정한다.

plt.scatter(train_input[:,0], train_input[:, 1])
plt.scatter(25, 150, marker = '^')
plt.scatter(train_input[indexes, 0], train_input[indexes, 1], marker = 'D')
plt.xlim((0, 1000))
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

산점도를 통해 생선의 무게(y축)만 고려 대상이 되는 것을 확인할 수 있다.

이런 상황을 두 특성의 스케일이 다르다고 말하며 이럴 경우 알고리즘이 올바르게 예측하는 것이 불가능하기 때문에 데이터 전처리 작업을 수행해야 한다.

 

가장 널리 사용하는 전처리 방법 중 하나는 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내는 표준점수 방식이다.

이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교가 가능해진다.

 

np.mean함수 : 평균 계산

np.std()함수 : 표준편차 계산

axis = 0 : 행을 따라 각 열의 통계값 계산 (1일 경우 열에 따라 각 행의 통계값 계산)

 

 

mean = np.mean(train_input, axis = 0)
std = np.std(train_input, axis = 0)
print(mean, std)

 

표준 점수로 변환하기 위해 원본 데이터에서 평균을 빼고 표준편차로 나눈다.

train_scaled = (train_input - mean) / std

 

전처리 데이터로 모델 훈련하기

이 때 [25, 150]을 동일한 기준으로 변환하지 않으면 아래와 같은 산점도가 그려진다.

산점도를 통해 [25, 150]이 엉뚱한 곳에 위치함을 알 수 있다.

plt.scatter(train_scaled[:, 0], train_scaled[:, 1])
plt.scatter(25, 150, marker = '^')
plt.xlabel('length'); plt.ylabel('weigth')
plt.show()

new변수에 동일한 기준으로 변환된 값을 삽입하고 출력하면 표준편차로 변환하기 전 산점도와 거의 동일한 그래프가 도출됨을 알 수 있다.

이 데이터셋으로 k-최근접 이웃 모델을 다시 훈련한다.

이 때 테스트 세트로 평가를 할 때 동일한 기준으로 변환을 해야 같은 비율로 산점도를 그릴 수 있기 때문에 test_scaled도 동일한 조건으로 변환해야한다.

kn.fit(train_scaled, train_target)
test_scaled = (test_input - mean) / std

 

모델평가를 진행하면 1로 테스트 샘플을 완벽하게 분류해냄을 확인할 수 있다.

 

kn.score(test_scaled, test_target)

 

예측도 [25,150]에 대해 제대로 분류하는 것을 확인할 수 있다.

print(kn.predict([new]))

 

다시 이웃되는 것들을 마름모(marker = 'D')로 지정한 후 산점도를 그려보면 특성값의 스케일에 민감하지 않고 안정적인 예측이 가능한 모델을 생성하였음을 알 수 있다.

distances, indexes = kn.kneighbors([new])
plt.scatter(train_scaled[:, 0], train_scaled[:, 1])
plt.scatter(new[0], new[1], marker = '^')
plt.scatter(train_scaled[indexes, 0], train_scaled[indexes, 1], marker = 'D')
plt.xlabel('length'); plt.ylabel('weigth')
plt.show()

 

새로운 샘플에 대해 잘못된 예측을 수행하는 것은 샘플의 스케일이 다르기 때문이며 이를 해결하기 위해 표준 점수 변환을 진행하였다.

표준 점수 변환은 데이터 전처리에서 많이 사용하는 방법이며, 표준 점수 변환을 수행할 경우 훈련 세트를 변환한 방식 그대로 테스트 세트를 변환해야 한다.