아래 내용은 "혼자 공부하는 머신러닝+딥러닝" 을 공부하며 간략하게 정리한 내용입니다. 쉬운 사례로 어려운 수식 없이 설명하므로 입문자에게 강추하는 책입니다!
1. 시작하기 전에
1장의 결과를 가지고 담당자는 호기롭게 김팀장에게 가서 보고합니다.
그런데 김팀장이 학습데이터로 다시 테스트하면 맞추는게 당연하지 않냐고 질문하였습니다. 담당자는 다시 생각해보겠다며 자리에 돌아왔는데요........
2. 훈련 데이터와 테스트 데이터
예를들어 중간고사를 보기 전에 출제될 시험 문제와 정담을 미리 알려주고 시험을 본다면 외워서 100점을 맞을 수 있을 것 입니다. 머신러닝도 이와 마찬가지 입니다. 그래서 학습에 사용하지 않은 데이터를 준비하거나 이미 준비된 데이터 중에서 일부를 떼어 내어 활용합니다. 평가에 사용하는 데이터를 테스트 데이터(또는 테스트 세트, test set), 훈련에 사용되는 데이터를 훈련 데이터(또는 훈련 세트, train set)라고 합니다.
1장에서 합친 생선의 길이와 무게 데이터를 가져와 보겠습니다.
print(fish_data[:5])
print(fish_target[:5])
→ [[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0]]
→ [1, 1, 1, 1, 1]
이때 하나의 생선 데이터를 샘플(sample)이라고 부릅니다. 도미와 빙어는 각각 35, 14마리로 전체 데이터는 49개의 샘플을 갖고있습니다. 이 데이터를 처음 35개를 훈련 세트로 나머지 14개를 테스트 세트로 사용해서 모델을 생성하고 평가해보겠습니다.
# 훈련 세트
train_input = fish_data[:35]
train_target = fish_target[:35]
# 테스트 세트
test_input = fish_data[35:]
test_target = fish_target[35:]
# 모델 생성 및 학습
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
# 모델 평가
kn.score(test_input, test_target)
→ 0.0
정확도가 0%인 사실을 보고 담당자는 멘붕에 빠져있을 때 지나가던 선배에게 잘 못된 점을 지적받습니다. 바로 샘플이 골고루 섞여 있지 않고 한쪽으로 치우쳐져 있는 샘플링 편향(sampling bias) 때문이었습니다. 골고루 샘플을 뽑아주기 위해 간편한 파이썬 넘파이(numpy) 라이브러리를 사용하도록 하겠습니다.
import numpy as np
input_arr = np.array(fish_data)
target_arr = np.array(fish_target)
print(input_arr.shape)
→ (49,2)
넘파이 배열의 크기를 알려주는 shape 속성으로 49개의 샘플과 2개의 특성이 있는 것을 알았습니다. 이제 무작위로 샘플을 고르는 방법을 사용하도록 하겠습니다. 전체 샘플이 49개이므로 0~48번까지 1씩 증가하는 숫자를 만들고 shuffle() 함수로 배열을 무작위로 섞어주었습니다.
np.random.seed(42)
index = np.arange(49)
np.random.shuffle(index)
print(index)
→ [13 45 47 44 17 27 26 25 31 19 12 4 34 8 3 6 40 41 46 15 9 16 24 33
30 0 43 32 5 29 11 36 1 21 2 37 35 23 39 10 22 18 48 20 7 42 14 28
38]
# 학습 세트
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
# 테스트 세트
test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]]
# 산점도 비교
plt.scatter(train_input[:, 0], train_input[:, 1])
plt.scatter(test_input[:, 0], test_input[:, 1])
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
3. 두 번째 머신러닝 프로그램
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
kn.score(test_input, test_target)
→ 1.0
인덱스를 섞어 만든 train_input과 train_target으로 모델을 훈련시키고 test_input고 test_target으로 이 모델을 평가해보니 정확도 100%가 나왔습니다. 담당자가 좋아할 것 같습니다!!
4. 데이터 전처리
담당자는 김팀장에 이 소식을 보고 하고 모델을 실전에 투입해도 좋다는 승낙을 받았습니다. 그런데 며칠 뒤 김팀장이 담당자를 다시 불러 분류 모델이 이상하다고 합니다! (길이25cm, 무게 150g인 도미를 빙어로 예측한다고.....)
문제를 확인해보기 전에 1장과 다른 방법으로 전처리를 해보도록 하겠습니다. 먼저 numpy 라이브러리를 이용하여 데이터를 가공하는 것입니다.
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))
print(fish_data[:5])
→ [[ 25.4 242. ]
[ 26.3 290. ]
[ 26.5 340. ]
[ 29. 363. ]
[ 29. 430. ]]
np.column_stack() 함수를 이용하며 리스트처럼 한줄로 길게 출력되지 않고 행과 열을 맞추어 가지런히 정리된 모습으로 보여줍니다. 타겟 데이터도 [1], [0] 리스트를 여러번 곱해서 얻는 것이 아니라 np.ones()와 np.zeros()를 이용하여 배열을 만들어줍니다.
fish_target = np.concatenate((np.ones(35), np.zeros(14)))
print(fish_target[:5])
→ [1. 1. 1. 1. 1.]
사이킷런은 머신러닝 모델 알고리즘뿐만 아니라 다양한 유틸리티 도구도 제공하는 그중 훈련 세트와 테스트 세트를 나누는 train_test_split() 함수입니다. 이 함수는 전달되는 리스트나 배열을 비율에 맞게 나누어 주는데 샘플링의 편향이 생기지 않도록 클래스 비율에 맞게 데이터를 나눠주는 stratify 매개변수를 적용해보도록 하겠습니다.
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)
print(test_target)
→ [0. 0. 1. 0. 1. 0. 1. 1. 1. 1. 1. 1. 1.]
도미 35마리, 빙어 14마리의 비율은 2.5:1로 테스트 세트의 비율 역시 2.25:1로 비슷한 비율로 나뉘어졌습니다. 그럼 이제 김팀장이 제기한 문제를 확인해볼까요? 분명 두번째 프로그램에서 정확도 100%인 모델을 만들었는데 도미(1)로 예측하지 못하고 빙어(0)로 예측하고 있네요....
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()
kn.fit(train_input, train_target)
# 모델 평가
print(kn.score(test_input, test_target))
→ 1.0
# 특정 예제로 테스트
print(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() 메서드를 제공합니다. 샘플 데이터와 가장 가까운 이웃을 확인해보도록 하겠습니다. 이웃 5개 중 1개만 도미이고 나머지 4개는 모두 빙어라서 모델이 샘플을 빙어로 예측한 것 같습니다.
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()
kneighbors() 메서드에서 반환한 distances 배열을 보면 이웃 샘플까지의 거리가 담겨 있습니다. 이 값을 보면 조금 이상한 것을 알 수 있습니다. 첫번째 샘플(도미)과의 거리가 92, 나머지 샘플들(빙어)과 거리가 130~138정도 되는데 그림3에서 보면 몇 배는 될 것 같은 거리입니다.
시각적으로 봤을때 이렇게 차이가 나는 이유는 x축과 y축의 범위가 서로 다르기 때문입니다.
print(distances)
→ [[ 92.00086956 130.48375378 130.73859415 138.32150953 138.39320793]]
두 특성(길이와 무게)의 값이 다르면 스케일(scale)이 다르다고 하고, 이렇게 기준이 다르면 알고리즘이 올바르게 예측할 수 없습니다. 알고리즘이 거리 기반일 경우 특히 그렇습니다. k-최근접 이웃이 거리 기반 알고리즘 중 하나인데 이런 알고리즘들은 샘플 간의 거리에 영향을 많이 받으므로 일정한 기준으로 맞춰 주어야 합니다. 이런 작업을 데이터 전처리(data preprocessing)이라고 부릅니다.
5. 전처리 데이터로 모델 훈련하기
데이터를 표준점수(standard score)를 이용하여 전처리하고 산점도로 표현해보겠습니다.
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]
train_scaled = (train_input-mean)/std
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()
그래프를 보면 변환하기 전의 산점도와 비슷하지만 축 값을 보면 -1.5~1.5 사이의 값으로 바뀌어있습니다. 이 데이터로 다시 모델을 만들어보겠습니다.
kn.fit(train_scaled, train_target)
test_scaled = (test_input-mean)/std
print(kn.score(test_scaled, test_target))
→ 1.0
print(kn.predict([new]))
→[1.]
드디어 도미(1)로 올바르게 예측했습니다!!
담당자는 기뻐하며 김팀장에게 보고 하였고 조금 더 어려운 문제를 부탁하였습니다. (다음장에서 계속 ...)
감사합니다 :)
참고자료
혼자 공부하는 머신러닝+딥러닝
'머신러닝&딥러닝 > 책요약및리뷰' 카테고리의 다른 글
[혼자공부하는머신러닝+딥러닝] 4. 다양한 분류 알고리즘 (0) | 2022.06.17 |
---|---|
[혼자공부하는머신러닝+딥러닝] 3. 회귀 알고리즘과 모델 규제 (0) | 2022.06.15 |
[혼자공부하는머신러닝+딥러닝] 1. 마켓과 머신러닝 (0) | 2022.06.10 |
[XAI 설명가능한 인공지능] 3. 모델 튜닝하기-Xgboost (0) | 2022.05.17 |
[XAI 설명가능한 인공지능] 2. 모델 생성 및 해석 실습-Xgboost (0) | 2022.05.16 |