데이터 전처리 ch2_2

데이터 전처리

  • 데이터 전처리는 머신러닝 모델에 훈련 데이터를 주입하기 전에 가공하는 단계를 말한다.
  • 표준점수는 훈련 세트의 스케일(단위)을 바꾸는 대표적인 방법 중 하나이다. 표준점수를 얻으려면 특성의 평균을 빼고 표준편차로 나눈다.
  • 브로드캐스팅은 크기가 다른 넘파이 배열에서 자동으로 사칙 연산을 모든 행이나 열로 확장하여 수행하는 기능이다.
  • train_test_split, kneighbors()

넘파이로 데이터 준비하기

1
2
3
4
5
6
7
8
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]
1
import numpy as np
  • 넘파이의 column_stack() 함수는 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결한다.
1
np.column_stack(([1, 2, 3], [4, 5, 6]))
array([[1, 4],
       [2, 5],
       [3, 6]])
  • 이 방법으로 fish_length와 fish_weight를 합친다.
1
2
3
4
fish_data = np.column_stack((fish_length, fish_weight))

# 두 리스트가 잘 연결 되었는지 확인
print(fish_data[:5])
[[ 25.4 242. ]
 [ 26.3 290. ]
 [ 26.5 340. ]
 [ 29.  363. ]
 [ 29.  430. ]]

타깃 데이터

1
2
print(np.ones(5))
print(np.zeros(5))
[1. 1. 1. 1. 1.]
[0. 0. 0. 0. 0.]
  • 이 두 함수를 가지고 1이 35개인 배열과 0이 14개인 배열을 만든다.
  • 그다음 두 배열을 연결한다.
  • np.column_stack() 함수를 사용하지 않고 첫 번째 차원을 따라 배열을 연결하는 np.concatenate() 함수를 사용한다.
1
2
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.]
  • 데이터가 클수록 파이썬 리스트는 비효율적이므로 넘파이 배열을 사용하는 게 좋다.

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

train_test_split() 함수

  • 훈련 데이터를 훈련 세트와 테스트 세트로 나누는 함수이다. 여러 개의 배열을 전달할 수 있다. 테스트 세트로 나눌 비율은 test_size 매개변수에서 지정할 수 있으며 기본값은 0.25이다.
  • shuffle 매개변수로 훈련 세트와 테스트 세트로 나누기 전에 무작위로 섞을지 여부를 결정할 수 있다. 기본값은 True.
  • stratify 매개변수에 클래스 레이블이 담긴 배열(일반적으로 타깃 데이터)을 전달하면 클래스 비율에 맞게 훈련 세트와 테스트 세트를 나눈다.
1
from sklearn.model_selection import train_test_split
  • train_test_split() 함수에는 자체적으로 랜덤 시드를 지정할 수 있는 random_stat 매개변수가 있다.
1
2
3
# fish_data와 fish_target을 나눈다.
train_input, test_input, train_target, test_target = train_test_split(
fish_data, fish_target, random_state = 42)
  • 2개의 배열을 전달했으므로 2개씩 나뉘어 총 4개의 배열이 반환된다.
1
2
3
# 잘 나누었는지 확인
print(train_input.shape, test_input.shape)
print(train_target.shape, test_target.shape)
(36, 2) (13, 2)
(36,) (13,)
  • 훈련 데이터와 테스트 데이터를 36개와 13개로 나누었다.
  • 입력 데이터는 2개의 열이 있는 2차원 배열이고 타깃 데이터는 1차원 배열이다.
  • 도미와 빙어가 잘 섞였는지 테스트 데이터를 출력해본다.
1
print(test_target)
[1. 0. 0. 0. 1. 1. 1. 1. 1. 1. 1. 1. 1.]
  • 13개의 테스트 세트 중에 10개가 도미(1)이고, 3개가 빙어(0)이다.
  • 빙어의 비율이 조금 모자르다.
  • 원래 2.5:1 비율이었는데, 테스트 세트의 비율은 3.3:1으로 샘플링 편향이 나타났다.

stratify 매개변수

  • 이것을 활용해 비율을 맞출 수 있다.
  • 해당 매개변수에 타깃 데이터를 전달하면 된다.
1
2
train_input, test_input, train_target, test_target = train_test_split(
fish_data, fish_target, stratify = fish_target, random_state = 42)
1
print(test_target)
[0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]
  • 빙어가 한 마리 늘어나면서 테스트 세트의 비율도 2.25:1이 되었다.

수상한 도미 한 마리

  • 앞서 준비한 데이터로 k-최근접 이웃 모델로 훈련해보자.
1
2
3
4
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
1.0
  • 이 모델에 김 팀장이 알려준 도미 데이터를 넣고 결과를 확인하면 당연히 도미로 예측할까?
1
print(kn.predict([[25, 150]]))
[0.]
  • 당황스러운 결과가 도출.
  • 시각화로 확인해보자.
1
2
3
4
5
6
7
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.scatter(train_input[:, 0], train_input[:, 1])
ax.scatter(25, 150, marker='^')
ax.set_xlabel('length')
ax.set_ylabel('weight')
plt.show()

png

  • 분명히 도미쪽에 가까운 것 같은데… 이상하다.
  • 이 샘플의 주변 샘플을 알아보자.
  • KNeighborsClassifier 클래스의 kneighbors() 메서드를 활용한다.
  • 기본값은 5로, 5개의 이웃이 반환된다.
1
distances, indexes = kn.kneighbors([[25, 150]])
  • 시각화로 표현해보자.
1
2
3
4
5
6
7
fig, ax = plt.subplots()
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') # marker='D' 는 마름모
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

png

  • 이웃한 샘플이 빙어가 4개, 도미가 1개구나!
1
print(train_input[indexes])
[[[ 25.4 242. ]
  [ 15.   19.9]
  [ 14.3  19.7]
  [ 13.   12.2]
  [ 12.2  12.2]]]
1
print(train_target[indexes])
[[1. 0. 0. 0. 0.]]
  • 왜 가장 가까운 이웃을 빙어라고 생각한 걸까?
  • 실마리를 찾기 위해 distances 배열을 출력해보자. 이 배열에는 이웃 샘플까지의 거리가 담겨 있다.
1
print(distances)
[[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]

기준을 맞춰라

  • x축은 범위가 좁고(1040), y축은 범위가 넓다(01000).
  • 이를 명확히 확인하기 위해 x축의 범위를 동일하게 0~1000으로 맞추어 보자.
  • xlim() 함수를 사용한다.
1
2
3
4
5
6
7
8
fig, ax = plt.subplots()
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') # marker='D' 는 마름모
plt.xlim((0, 1000))
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

png

  • 산점도가 거의 일직선으로 나타난다.
  • 이런 데이터라면 생선의 길이(x축)는 가장 가까운 이웃을 찾는 데 크게 영향을 미치지 못한다.
  • 오로지 생선의 무게(y축)만 고려 대상이 된다.

스케일(scale)

  • 두 특성의 값이 놓인 범위가 다르다 = 두 특성의 스케일이 다르다
  • 스케일을 같게 만들어 주는 것이 데이터 전처리(data preprocessing)이다.

데이터 전처리

  • 가장 널리 사용하는 전처리 방법은 표준점수(standard score)이다.
1
2
3
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]
  • axis=0은 행을 따라(아래방향), axis=1은 열을 따라(오른쪽방향) 통계값을 계산한다.
  • 각 특성마다 평균과 표준편차가 구해졌다.
1
2
# 표준점수
train_scaled = (train_input-mean) / std

브로드캐스팅(broadcasting)

  • 넘파이는 train_input의 모든 행에서 mean에 있는 두 평균값을 빼준다.
  • 그다음 std에 있는 두 표준편차를 모든 행에 적용한다.
  • 이러한 넘파이의 기능을 브로드캐스팅이라고 한다.

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

1
2
3
4
5
plt.scatter(train_scaled[:,0], train_scaled[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

png

  • 주황색 세모가 동떨어져 있는 것은 당연하다.
  • 표준화해주지 않았기 때문이다.
  • 표준화를 해주고 다시 시각화해보자.
1
2
3
4
5
6
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()

png

  • x축과 y축의 범위가 -1.5~1.5 사이로 바뀌었다.
  • 이제 이 데이터셋으로 k-최근접 이웃 모델을 다시 훈련해 보자.
1
kn.fit(train_scaled, train_target)
KNeighborsClassifier()
1
test_scaled = (test_input - mean) /std
1
2
# 평가
kn.score(test_scaled, test_target)
1.0
  • 앞서 골칫거리를 도미로 예측하는지 확인해보자
1
print(kn.predict([new]))
[1.]
  • 제대로 예측했다.
  • 그림으로 다시 확인해보자
  • 아까와는 달리 가장 가까운 이웃에 변화가 생겼을 것이다.
1
2
3
4
5
6
7
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()

png

  • 주황색 세모가 가장 가까운 샘플은 모두 도미임을 확인했다.
Author

HS

Posted on

2022-04-05

Updated on

2022-04-05

Licensed under

You need to set install_url to use ShareThis. Please set it in _config.yml.
You forgot to set the business or currency_code for Paypal. Please set it in _config.yml.

Comments

You forgot to set the shortname for Disqus. Please set it in _config.yml.
You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.