본문 바로가기

AI/기계학습(Machine Learning)

[기계학습/ML]5. 데이터 전처리 - 표준점수, 브로드캐스팅

728x90
반응형

Pupbani는 개발한 모델을 보고하고 실전에 투입해도 된다는 승낙을 받았다.

그런데 며칠 뒤 박 팀장이 Pupbani를 불러서 말했다.

  • "길이가 25cm이고 무게가 150g이면 도미인데 모델은 빙어라고 예측하네요?"

Pupbani는 믿을 수 없어 박 팀장이 말한 데이터로 예측을 해봤다.

 

예측하기

넘파이로 데이터 준비하기

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]
  • 생선 데이터를 만들어보자.
  • 넘파이의 column_stack() 함수를 사용한다.
    • 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결할 수 있다.
    • 연결한 리스트는 튜플(tuple)로 전달한다. Ex) column_stack((리스트1, 리스트2))
import numpy as np
fish_data = np.column_stack((fish_length, fish_weight))
print(fish_data[:5])

  • 이제 타깃 데이터를 만들어야 한다.
  • numpy의 zeros()와 ones()를 사용한다.
    • zeros() : 매개변수로 전달한 숫자만큼 0으로 채운 배열을 반환한다.
    • onses() : 매개변수로 전달한 숫자만큼 1로 채운 배열을 반환한다. 
  • 이렇게 만들어진 배열을 한 줄로 합치는 것은 concatenate() 함수를 사용한다.
    • 배열은 한 줄로 연결한다.
    • 전달할 매개변수들은 튜플로 묶어서 전달해야 한다.
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target)

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

  • 이전에 배열의 인덱스를 새로 만들어 섞은 다음 훈련 세트와 테스트 세트로 나누는 방법은 번거롭다.
  • 사이킷런의 train_test_split()라는 함수를 사용한다.
train_input, test_input, train_target, test_target = train_test_split(input_data, target_data, random_state = seed)
  • 전달되는 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트로 나누어 준다.
  • 랜덤으로 섞어 주기도 한다. - 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,train_target.shape,test_target.shape)

print(test_target)

예측

  • 이제 데이터를 가지고 훈련을 한다.
from sklearn.neighbors import KNeighborsClassifier

kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
print(f"정확도 : {kn.score(test_input, test_target).round(2) * 100}%")

  • 박 팀장이 이야기했던 도미 데이터를 넣고 예측해 보겠다. 
kind = {0:"빙어",1:"도미"}
print(kind[kn.predict([[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 : 이웃들의 인덱스들
distances, indexes = kn.kneighbors([[25, 150]])
  • indexes 값을 사용하여 이웃들을 그래프에 표시해 본다.
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^') 
// Array Indexing, indexes 부분만 선택
plt.scatter(train_input[indexes,0], train_input[indexes,1], marker='D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

  • 가까운 이웃 중에 도미가 2개, 빙어가 3개로 빙어로 예측되었다.
  • 직접 데이터를 확인해보았다.
kind = {0:"빙어",1:"도미"}
print(f"이웃 종류 : {[kind[i] for i in train_target[indexes][0]]}")

print(f"[25,150]으로 부터 이웃간의 거리 : {distances[0]}")

기준을 맞춰라

산점도를 살펴보면 이상한 점이 있다.

샘플에 가까운 거리를 볼 때 92, 130의 거리가 그림 상에서 거리 비율이 차이 나게 그려져 있다.

  • 가로와 세로 축의 범위가 다르기 때문에 이러한 현상이 일어났다.
  • 가로 세로의 범위를 맞추기 위해 0~1000 사이로 맞춰 보겠다.
    • x축 범위를 수정하는 함수 :  xlim() (y축 : ylim())
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축 데이터인 길이는 가까운 이웃을 찾는데 도움을 주지 못할 것 같다.
  • 생선의 무게만이 고려 대상이 된다.
  • 이렇게 두 특성의 값이 범위가 매우 다르다는 말을 스케일(Scale)이 다르다고도 이야기한다.
  • 이렇게 두 특성의 스케일이 다를 때 특성 값을 일정한 기준으로 맞춰 주어야 한다.
  • 맞춰주는 작업을 데이터 전처리(Data Preprocessing)라고 한다.

데이터 전처리

  • 가장 많이 사용하는 데이터 전 치리 방법은 표준점수(Standard Score)이다. (혹은 z점수)
    • 각 특성 값이 0에서 표준편차의 몇 배만큼 떨어져 있는지를 나타낸다.
    • 이를 통해 실제 특성 값의 크기와 상관 없이 동일한 조건으로 비교할 수 있다.
  • 표준점수 계산 방법 
    • 특성값 - 평균 / 표준편차
  • Numpy는 평균과 표준 편차를 구하는 함수를 지원한다.
    • mean() : 평균 구하기, axis 값에 따라 계산 방향 설정 (axis 매개변수 : 0(세로), 1(가로))
    • std() : 표준 편차 구하기, axis 값에 따라 계산 방향 설정 (axis 매개변수 : 0(세로), 1(가로)) 
mean = np.mean(train_input, axis=0)
std = np.std(train_input, axis=0)
print(f"길이 평균 : {mean[0].round(2)}")
print(f"무게 평균 : {mean[1].round(2)}")
print(f"길이 표준편차 : {std[0].round(2)}")
print(f"길이 표준편차 : {std[1].round(2)}")

  • 이렇게 구한 평균과 표준 편차로 표준점수를 구해보자
  • Numpy는 Numpy Array끼리 사칙연산 시 행렬 연산을 적용한다. 
    • 차원이 1인 경우
    • 차원에 대하여 축의 길이가 동일할 때 연산 가능하다. ex) 1x3와 3x3
train_scaled = (train_input - mean) / std
  • 이러한 연산을 브로드캐스팅(Broadcasting)이라고 부른다.

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

  • 전처리된 데이터를 산점도 그래프로 그려본다.
    • [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
print(f"정확도 : {kn.score(test_scaled,test_target).round(2) * 100}%")

  • 이제 데이터 예측을 한번 해보겠다.
kind = {0:"빙어",1:"도미"}
print(f"예측 결과 : {kind[kn.predict([new])[0]]}")

  • 도미로 정확하게 예측되었다.
  • 마지막으로 산점도 그래프를 그려 주변 이웃을 확인해보자
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()

  • 눈으로 봐도 가까운 거리에 있는 이웃이 전부 도미쪽에 있으므로 도미로 예측되는게 맞다.

문제를 해결했으니 이제 다시 보고하러 가보자.

728x90
반응형