데이터 전처리; data processing
머신러닝 모델에 훈련 데이터를 주입하기 전에 가공하는 단계
특성값을 일정 기준에 맞춰 주기
데이터 전처리를 하지 않았을 시 발생하는 문제 예시
넘파이로 데이터 준비
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
fish_data = np.column_stack((fish_length, fish_weight))
# np.cloumn_stack(): 전달받은 리스트를 일렬로 세운 다음 차례대로 나란히 연결
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
# np.concatenate(): 첫 번째 차원을 따라 배열 연결
# np.ones(): 원하는 개수의 1을 채운 배열 만듦
# np.zeros(): 원하는 개수의 0을 채운 배열 만듦
# 출력 예시
print(fish_data[:5])
print(fish_target)
# [[ 25.4 242. ]
# [ 26.3 290. ]
# [ 26.5 340. ]
# [ 29. 363. ]
# [ 29. 430. ]]
# [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.]
사이킷런으로 훈련 세트와 테스트 세트 나누기
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(
fish_data, fish_target, stratify=fish_target, random_state=42)
# train_test_split(): 전달되는 리스트나 배열을 비율에 맞게 훈련 세트와 테스트 세트로 나누어 줌
# random_state 매개변수: 랜덤 시드 지정
# stratify 매개변수: 타깃 데이터를 전달하면 클래스 비율에 맞게 데이터를 나눔
앞에서 준비한 데이터로 k-최근접 이웃 훈련
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]]))
# [0.] 출력
🤔 [[25, 150]]이면 도미(1) 아닌가요? 왜 0이 출력되나요?
plt.scatter(train_input[:,0], train_input[:,1])
plt.scatter(25, 150, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
🤔 누가 봐도 도미에 더 가깝잖아요!
🐢 k-최근접 이웃은 주변의 샘플 중에서 다수인 클래스를 예측으로 사용하기 때문이에요.
문제 상황 확인
distances, indexes = kn.kneighbors([[25, 150]])
# kneighbors() 메서드: 이웃까지의 거리와 이웃 샘플의 인덱스 반환
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()
print(train_target[indexes])
# [[1. 0. 0. 0. 0.]] 출력
print(distances)
# [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]] 출력
🐢 길이가 25cm, 무게가 150g인 생선에 가장 가까운 이웃은 빙어가 압도적으로 많아요.
근데 삼각형 샘플에 가장 가까운 첫 번째 샘플까지의 거리가 92, 그 외 가장 가까운 샘플들은 모두 130, 138이라니 비율이 이상하지 않나요?
문제는 바로 x축은 범위가 10~40, y축은 범위가 0~1000이라는 점에 있어요!
y축으로 조금만 멀어져도 거리가 아주 큰 값으로 계산되는 거죠.
이를 해결하기 위해 x축 범위를 동일하게 0~1000으로 만들어볼까요?
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))
# xlim(): x축 범위 지정
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
🐢 그래도 문제는 해결되지 않죠. 단순히 x축의 범위를 0~1000으로 늘렸을 뿐, 여전히 두 특성의 값이 놓인 범위가 매우 다르기 때문이죠. (생선의 길이 데이터는 변경되지 않음)
이를 두 특성의 스케일(scale)이 다르다고 하는데요.
데이터를 표현하는 기준이 다르면 알고리즘이 올바르게 예측할 수 없어요.
알고리즘이 거리 기반이면 샘플 간의 거리에 영향을 많이 받으므로 제대로 사용하기 위해선 특성값을 일정한 기준으로 맞춰 주어야 되는데요.
이 작업을 데이터 전처리라고 불러요!
데이터 전처리 구현
가장 널리 사용하는 전처리 방법 중 하나는 표준점수(standard score, 혹은 z 점수)입니다.
표준점수는 각 특성값이 평균에서 표준편차의 몇 배만큼 떨어져 있는지를 나타내는데요.
이를 통해 실제 특성값의 크기와 상관없이 동일한 조건으로 비교할 수 있어요.
표준점수 계산
mean = np.mean(train_input, axis=0)
# np.mean(): 평균 계산, axis=0: 각 특성별로 계산(행을 따라 각 열의 통계 값 계산)
std = np.std(train_input, axis=0)
# np.std(): 표준편차 계산
print(mean, std)
# [ 27.29722222 454.09722222] [ 9.98244253 323.29893931] 출력
train_scaled = (train_input - mean) / std
# 원본 데이터에서 평균을 빼고 표준편차로 나누어 표준점수로 변환
# 브로드캐스팅(boadcasting)을 통해
전처리 데이터로 모델 훈련
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 출력
print(kn.predict([new])) # [1.] 출력
🐢 드디어 도미(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()
출처: 박해선 님의 '혼자 공부하는 머신러닝 + 딥러닝'
'AI' 카테고리의 다른 글
훈련 세트와 테스트 세트 (1) | 2025.05.01 |
---|---|
k-최근접 이웃 알고리즘(k-NN) (0) | 2025.03.14 |
시작하며 (0) | 2025.03.10 |