본문 바로가기
python

[머신러닝] 데이터 전처리 K-최근접 이웃 KNN (표준점수로 특성의 스케일 변환)

by datadeveloper 2022. 4. 8.
반응형

[머신러닝] 데이터 전처리 K-최근접 이웃 KNN (표준점수로 특성의 스케일 변환)

 

 

1) 데이터 준비 : 도미 방어의 길이, 무게 데이터

 

 

fish_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, 9.8, 
                10.5, 10.6, 11.0, 11.2, 11.3, 11.8, 11.8, 12.0, 12.2, 12.4, 13.0, 14.3, 15.0]
fish_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, 6.7, 
                7.5, 7.0, 9.7, 9.8, 8.7, 10.0, 9.9, 9.8, 12.2, 13.4, 12.2, 19.7, 19.9]

 

 

넘파이는 두 개의 리스트를 붙여준다.

 

 

import numpy as np

np.column_stack(([1,2,3],[4,5,6]))
array([[1, 4],
       [2, 5],
       [3, 6]])
fish_data=np.column_stack((fish_length,fish_weight))
print(fish_data[:5]) # 0부터 4까지 보여달라
[[ 25.4 242. ]
 [ 26.3 290. ]
 [ 26.5 340. ]
 [ 29.  363. ]
 [ 29.  430. ]]

 

 

이제 결과값(타겟 데이터)을 만들어준다

 

 

print(np.ones(5)) # [1. 1. 1. 1. 1.]

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

print(fish_target)
[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.
 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
 0.]

 

 

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

 

 

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)
print(test_target) # 3.3 : 1
(36, 2) (13, 2)
(36,) (13,)
[1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

 

 

위의 코드처럼 나누면 훈련 데이터, 테스트 데이터에 도미 방어가 골고루 안 들어가진다. 비율이 안 맞으면 샘플링 편향이 나타나게 된다. 도미 방어는 골고루 나누기 위해 startify 매개변수에 타깃 데이터를 전달하여 클리스 비율에 맞게 데이터를 나눈다.

 

 

train_input, test_input, train_target, test_target = train_test_split(fish_data,fish_target,stratify=fish_target, random_state=42 )
print(test_target) # 2.25 : 1
[0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]

 

 

모델링을 해보고 예측을 해보면

 

 

from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input,train_target)
kn.score(test_input,test_target) # 1.0 (100% 인식 성공!)

 

 

그런데 여기서 문제.. 도미인데 방어로 잘못 인식하는 경우가 생긴다.

 

 

print(kn.predict([[25,150]])) # 방어로 인식함.
distances, indexes = kn.kneighbors([[25,150]])
print(distances, indexes)
# [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]] [[21 33 19 30  1]]


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('weight')

방어 도미 산점도

 

 

왜? 도미쪽 데이터(위쪽) 보다 방어 쪽 데이터(아래쪽)가 더 가깝기 때문에. 아래쪽 데이터를 선택했기 때문. 우리 눈에는 위쪽 데이터가 더 가까워 보이는데?  x축, y축 비율이 달라서 그렇게 보이는 것임. 실제로 수치상 게산해 보면 아래쪽이 더 가까움. 이렇게 되면 생선의 길이는 가까운 이웃을 찾는데 아무 영향을 못 미치고 오로지 무게만 고려대상이 됨.

 

 

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)) # x 축 비율 1000으로 맞춤
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

x축, y축 비율을 동일하게 맞춘 그림

 

 

3) 표준점수로 특성 기준 맞추기

 

 

샘플 간의 거리에 영향을 많이 받으므로 제대로 사용하려면 특성값을 일정한 기준으로 맞춰 주어야 한다. 가장 널리 사용하는 전처리 방법 중 하나는 표준점수이다. 표준점수는 각 특성 값이 0에서 표준편차의 몇 배만큼 떨어져 있는지를 나타냅니다. 이를 통해 실제 특성 값의 크기와 상관없이 동일한 조건으로 비교할 수 있다.

 

 

계산하는 방법은 평균을 빼고 표준편차를 나누어주면 된다.

 

 

mean = np.mean(train_input,axis=0)
std = np.std(train_input, axis=0)
print(mean,std) # [ 27.29722222 454.09722222] [  9.98244253 323.29893931]
train_scaled = (train_input - mean) / std

plt.scatter(train_scaled[:,0],train_scaled[:,1])
plt.scatter(25,150,marker='^') # 새로 평가해야하는 샘플
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

표준점수로 변환한 후 그린 산점도

new = ([25,150]-mean) /std # 샘플도 훈련데이터의 표준점수로 동일하게 맞춰줘야함
plt.scatter(train_scaled[:,0],train_scaled[:,1])
plt.scatter(new[0],new[1],marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

샘플데이터 표준화

 

 

4) 전처리된 데이터로 KNN 모델 훈련하기

 

 

kn.fit(train_scaled,train_target)

test_scaled = (test_input - mean) / std # 테스트세트도 훈련세트의 평균과 표준편차로 변환해야함

kn.score(test_scaled,test_target) # 모델 평가 결과 1.0 (100% 분류 성공)

print(kn.predict([new])) # [1.] 도미로 예측!

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('weight')
plt.show()

데이터의 특성을 표준화한 산점도

 

 

샘플 데이터에서 가장 가까운 샘플은 모두 도미이다. 길이와 무게 두 특성의 스케일을 맞춰주었다. 그래야 무게만으로 생선을 구별하지 않고 무게, 길이 두 가지 특성에 의해 생선을 구별할 수 있게 된다. 대부분의 머신러닝은 특성의 스케일이 다르면 잘 작동하지 않는다.

 

 

반응형