Python/ML & DL 공부

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

dori_0 2022. 3. 5. 22:11

혼자 공부하는 머신러닝+딥러닝 책을 바탕으로 공부한 내용입니다.

 

CH2 데이터 다루기 ②

스케일이 다른 특성 처리

 


 

 

올바른 결과 도출을 위해 데이터 전처리 과정을 거쳐보자.
전처리 과정을 거친 데이터로 훈련했을 때의 차이를 알고 표준점수로 특성의 스케일을 변환하는 방법을 배우자

 

이전에 만든 모델은 100%의 정확도를 달성했다.
그런데 '길이가 25cm, 무게가 150g인 도미를 빙어로 예측한다'는 문제가 생겼다.

 

 

▶ 넘파이로 데이터 준비하기

먼저, 도미와 빙어의 데이터를 준비하자

# 도미, 빙어의 길이와 무게 데이터
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]

 

넘파이의 colunm_stack() 함수를 이용해 두 리스트를 나란히 만들어보자

import numpy as np
fish_data = np.column_stack((fish_length, fish_weight))
fish_data

이렇게 두 리스트로 잘 만들어졌다.

 

이번에는 numpy의 np.ones()와 np.zeros() 함수를 이용하여 타깃 데이터를 만들어보자

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

1은 35개, 0은 14개로 타깃 데이터가 잘 만들어졌다.

 

 

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

이전에는 슬라이싱을 이용하여 직접 train, test으로 나눠줬었다.

이번에는 train_test_split() 함수를 이용하여 비율에 맞게 train, test로 나눠보자.

from sklearn.model_selection import train_test_split

# train set, test set으로 나누기
# 이 함수는 기본적으로 25%를 test set으로 지정
train_input, test_input, train_target, test_target = train_test_split(
    fish_data, fish_target, stratify = fish_target, random_state=42)
  • 이 함수는 기본적으로 25%를 test set으로 지정한다
  • stratify 매개변수에 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나눈다

 

train_input.shape, test_input.shape  #((36, 2), (13, 2))
train_target.shape, test_target.shape  #((36,), (13,))

잘 나눠진 것이 확인된다.

이제 처음 제시되었던 문제점을 살펴보자.

 

 

▶ 수상한 도미 한 마리

먼저, 앞에서 준비한 데이터로 k-최근접 이웃 훈련을 해보자

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

# (25, 150) 예측해보기
kn.predict([[25,150]]) #array([0.])
  • 모델의 정확도는 100%이다
  • 그런데 (25,150)인 도미 데이터에 대해서 빙어(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()

  • 오른쪽 도미 데이터에 더 가까운데 왜 빙어 데이터에 가깝다고 판단한 것인지 의문이 생긴다

 

 

KNeighborsClassifier 클래스에서 제공하는 kneighbors() 메서드를 이용해 어떤 데이터를 가까운 데이터로 파악한 것인지 확인해보자

# 이웃 샘플을 따로 구분해서 그려보자
distances, indexes = kn.kneighbors([[25,150]])

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

  • 가까운 이웃으로 포함한 것 중에 도미 데이터는 하나밖에 포함되지 않았다.
  • 이는, x축과 y축의 거리 범위/비율이 다르기 때문이다.

 

 

▶ 기준 맞추기 & 데이터 전처리

먼저, x축의 범위도 y축과 동일하게 0-1000으로 맞춰보자 (xlim() 함수 이용)

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

  • x축(길이)는 가까운 이웃을 찾는데 큰 영향이 없을 것으로 예상된다.
  • y축(무게)만 고려 대상이 될 것으로 보인다.
  • 두 특성의 스케일이 다르다

 

 

 

데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없다.

k-최근접 이웃과 같은 알고리즘은 샘플 간의 거리에 영향을 많이 받으므로 데이터 전처리를 해주어야 한다.

 

 

 

  • 표준점수 : 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타낸다.
  • 표준점수를 이용하면 실제 특성값의 크기와 상관없이 동일한 조건으로 비교할 수 있다.
mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)

# 브로드캐스팅
train_scaled = (train_input - mean) / std

 

 

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

train set를 표준화했기 때문에 샘플 [25, 150]에 대해서도 동일한 비율로 변환해야 한다

# [25, 150]에 대해서도 표준화해야함
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()

 

 

이제 전처리한 데이터로 모델 학습 후 정확도를 확인해보자

  • 훈련 후 테스트 세트를 평가할 때는 훈련 세트 기준으로 테스트 세트도 변환해줘야 두 데이터의 스케일이 같아진다.

 

kn.fit(train_scaled, train_target)

# 테스트 세트도 변환
test_scaled = (test_input - mean) / std

kn.score(test_scaled, test_target)  #1.0
kn.predict([new])  #array([1.])
  • 이제는 (25, 150) 데이터를 도미로 잘 예측하는 것을 확인할 수 있다.

 

 

마지막으로 k-최근접 이웃 산점도를 그려보자

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

  • 데이터 전처리를 하고 나니 위에서와는 다르게 제대로 이웃 데이터를 파악하고 있다.

 

 

 

▶ 정리

  1. 머신러닝 알고리즘은 특성의 스케일이 다르면 잘 작동하지 않는다.
  2. 특성의 스케일을 조정하는 방법으로 '특성을 표준점수로 변환' 하였다.
  3. 데이터 전처리 할 때 주의할 점은 train set을 변환한 방식 그대로 test set도 변환해야 한다는 점이다. 그래야 훈련 세트로 훈련한 모델이 제대로 작동한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Python > ML & DL 공부' 카테고리의 다른 글

[ML] 03-3 특성 공학과 규제  (0) 2022.03.07
[ML] 03-2 선형 회귀  (2) 2022.03.06
[ML] 03-1 k-최근접 이웃 회귀  (0) 2022.03.06
[ML] 02-1 훈련 세트와 테스트 세트  (0) 2022.03.03
[ML] 01-3 마켓과 머신러닝  (0) 2022.03.03