본문 바로가기
Book Review/[혼공머신] 리뷰

데이터 전처리 기초 with python (2)

by 3n952 2022. 11. 24.

 

이번 포스트에서는 머신러닝에서 올바른 결과 도출을 위해 데이터 전처리 하는 법을 예제 코드를 통해 알아보겠습니다.

그 중 표준 점수로 특성의 스케일을 변환하는 법에 대해 알아보도록 하겠습니다.

 


 

데이터를 먼저 준비하겠습니다.

이전 포스트에서 계속 다루던 도미와 빙어 데이터로 준비했습니다.

 

import numpy as np

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]

fish_data = np.column_stack((fish_length, fish_weight))

 

이전과 다른점은 길이, 무게 데이터를 하나의 데이터로 합칠 때 넘파이의 column_stack()함수를 사용했다는 점입니다.

np.column_stack()은 튜플의 형태를 입력받아 전달받은 리스트를 일렬로 세워 나란히 옆으로 (= 열(column)로) 나란히 연결합니다.

가령, np.column_stack(([1,2,3], [4,5,6])) 를 하게되면,

(** [1,2,3], [4,5,6] 두 개의 리스트를 튜플 ( a,b )형태로 전달했습니다** ) 

 

([[1, 4],

   [2, 5],

   [3, 6]])  의 형태를 반환합니다.

 

이전에는 for문으로 각 데이터를 불러와 리스트 형태로 저장했었던 것과 같은 내용입니다.

 

 

1이 35개, 0이 14개인 타깃 데이터도 넘파이로 간단하게 구현할 수 있습니다.

코드는 다음과 같습니다.

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

np.ones()과 np.zeros()는 각각 원하는 개수만큼 1과 0을 채워 배열을 만들어주는 넘파이 함수입니다.

가령, np.ones(5)를 하면 [1,1,1,1,1]가 만들어지는 것과 같습니다.

 

np.concatenate()함수는 np.column_stack()함수와 달리 튜플형태로 입력받은 리스트를 일렬로 늘려 나란히 옆에 붙이는 것입니다.

가령, np.concatenate(([1,2,3], [4,5,6])) 를 하게되면, 

[1,2,3,4,5,6]의 값을 반환합니다.

 

이제 데이터가 준비가 되었으니 올바른 모델 평가를 위한 훈련 세트와 테스트 세트 나누기가 필요합니다.

이 또한 간단한 함수로 구현할 수 있습니다.

 

바로 사이킷런의 model_selection 모듈 안에 있는 train_test_split()함수 입니다.

바로 예시 코드를 통해 설명해보겠습니다.

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)

train_test_split()함수를 사용하면 이처럼 쉽게 데이터를 훈련 세트와 테스트 세트로 나눌 수 있습니다.

이 함수는 기본적으로 25%를 전체 데이터에서 떼어 테스트 세트로 할당합니다.

몇 퍼센트로 나눌지도 저희가 직접 정할 수 있습니다.

 

데이터 준비가 끝났습니다. 이제는 준비한 데이터로 k-최근접 이웃을 훈련해보겠습니다.

코드는 다음과 같습니다.

 

from sklearn.neighbors import KNeighborsClassifier 

kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)

코드를 실행시키면 score가 1.0이 나옵니다. 잘 분류를 하고있다는 의미겠죠.

 

도미로 예측되는 데이터를 넣고 새로운 예측값을 확인해보겠습니다.

데이터는 [25, 150] 입니다.

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

출력되는 값은 0으로 0번째 정답 레이블 인덱스를 가르키므로 도미로 예측되는 데이터를 빙어로 예측한 것입니다.

 

어디서 문제가 있는지는 이 샘플과 전체 데이터의 산점도를 그려보면 직관적으로 확인 할 수 있습니다.

산점도를 그리는 코드는 다음과 같습니다.

import matplotlib.pyplot as plt

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

인덱스와 슬라이싱을 생각해보면 x값에는 길이, y값에는 무게로 산점도를 그린 것을 알 수 있습니다.

새로운 샘플[25,150]에 대해서는 삼각형으로 표시했습니다.

도미,빙어 + 새로운 샘플의 산점도

살펴보면 삼각형은 오른쪽 위의 도미(1) 데이터와 더 가까워 보입니다.

그런데 왜 이 데이터를 빙어(0)라고 구분했을까요?

이는 거리 알고리즘에 의해 가까운 데이터 5개(knn알고리즘의 디폴트 k값)의 대부분이 빙어이기 때문입니다.

이러한 현상이 나타나는 이유는 length의 값의 범위와 weight의 값의 범위가 매우 다르기 때문입니다.

즉, 길이는 0~40의 값을 가지는 반면 무게는 0~1000의 값을 가져 산점도로 보았을 때 왜곡이 생기는 것입니다.

 

거리 알고리즘에 의해 실제로 가까운 5개의 데이터: 빙어

 

마찬가지로 데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없을 것 입니다.

특히 knn과 같은 거리 기반의 알고리즘이 특히 그렇습니다.

이런 알고리즘은 샘플들 간의 거리에 영향을 많이 받기 때문에 특성의 값들을 일정한 기준으로 맞추는 과정이 필요합니다.

 

이러한 작업을 데이터 전처리(data preprocessing)이라고 부릅니다.

대표적인 데이터 전처리 방법 중 하나는 표준점수를 사용하는 것 입니다.

표준 점수란 각각의 특성값(예시에서는 길이,무게의 값)이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내는 것을 의미합니다.

 

계산하는 방법은 평균을 빼고 표준편차를 나누워주면 됩니다. 넘파이가 이것을 편리하게 함수로 제공해줍니다.

코드로 예시를 구현해보겠습니다.

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

 

전체 데이터에서 각 행에 대한 열의 값들을 계산하기위해 axis = 0으로 지정했습니다.

2차원 배열에서 axis = 0은 세로로 axis =1 은 가로로 값을 측정한다고 생각하면 편합니다.

 

평균과 표준편차를 구했으니 표준점수를 구해보겠습니다.

코드는 다음과 같습니다.

train_scaled = (train_input - mean) / std

train_input의 shape는 (36,2)이고 mean과 std는 (1,2)인데 어떻게 이게 가능한 연산인지 궁금할 수 있습니다.

 

이러한 연산이 가능한 것도 넘파이의 연산 처리 기능 덕분입니다. 

이런 기능을 브로드캐스팅(broadcasting)이라고 부릅니다.

 

브로드캐스팅 설명 예시

 

혼공머신 책에 있는 그림을 가져왔습니다. 저런 형태로 연산이 되기 때문에 일일히 행마다 계산을 일일히 해주지 않아도 

브로드캐스팅으로 한번에 계산할 수 있습니다.

 

훈련시킬 데이터를 표준점수로 전처리 하였으니 테스트 데이터에 대해서도 표준점수로 전처리 해주어야

올바르게 예측하고 평가할 수 있을 것입니다.

코드는 다음과 같습니다.

new = ([25,150] - mean) / std

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

print(kn.predict([new]))

 

전처리한 훈련 데이터를 knn객체에 학습킨후 테스트 데이터를 전처리하여 모델을 평가해보았습니다.

그후 predict()메소드를 사용하여 새로운 데이터([25, 150]를 전처리한 샘플)의 분류를 예측해보면

결과값으로 [1.]이 나옵니다. 즉, 도미(1)로 예측을 한 것이죠!

 

해당 값이 잘 나왔는지 직관적으로 확인하기 위해서 산점도를 그려보겠습니다.

코드는 다음과 같습니다.

plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(new[0], new[1], marker = '^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

전처리한 데이터의 산점도

전처리한 데이터이기 때문에 거리에 왜곡이 생기는 것이 사라졌습니다.

-1.5~1.5 사이의 값을 가지는 것으로 기준이 잡힌 것이죠.

이를 바탕으로 보면 딱 봐도 오른쪽위의 도미 데이터와 가까운 것을 확인 할 수 있습니다.

예측을 잘 했다고 직관적으로 알 수 있겠네요.

 

 

이번 포스트의 내용을 요약하면 스케일이 다른 특성들을 데이터 전처리하여 훈련 세트와 테스트 세트를

나누어 knn모델에 학습시키고 모델을 평가하고 예측하는 것을 알아보았습니다.