[혼공머신] 1주차 02-1 훈련 세트와 테스트 세트
본 게시글은 한빛미디어의 혼자 공부하는 머신러닝+딥러닝을 바탕으로 작성되었습니다.
ML 알고리즘의 종류
지난번에 도미와 빙어의 classification에 대해서 알아보았다. 그런데, 거기서 중간에 잠깐 언급했던 것이 있다. 열심히 학습한 연습문제로 그대로 시험을 보게 된다면 100점을 맞게 되는 것은 당연하다는 것이다. 그럼 시험문제를 따로 만들어야 제대로 컴퓨터가 학습을 했는지 못했는지 알 수 있을 것이다.
그 전에 뜬금없지만, ML 알고리즘의 분류에 대해서 교재는 다루고 있기에 그것만 잠깐 언급하고 가겠다.
ML 알고리즘은 크게 지도 학습(指導學習, Supervised Learning)과 비지도 학습(非指導學習, Unsupervised Learning)으로 나뉜다. 여기서 지도는 길을 찾는 지도(地圖)가 아니라 학생을 지도(指導)하다를 할 때의 지도이다.
Supervised learning은 우리가 친절하게 컴퓨터에게 어떤 문제가 있을 때 친절하게 답을 알려주고 학습을 시키는 것이다. 그와 반면 unsupervised learning은 그냥 데이터만 주고 컴퓨터에게 너가 어디 한번 이거 가지고 해봐라 하면서 답도 없이 그냥 주는 것이다. supervised learning은 우리가 답이라는 명확한 기준을 주었기에 컴퓨터도 우리에게 명확한 답변을 준다. 지난번에 살펴보았던 도미, 빙어 classification과 같이 어떤 것이 도미인지, 빙어인지 명확하게 답을 준다. 그러나 unsupervised learning은 우리가 답을 알려주지 않았기 때문에 컴퓨터도 우리에게 답을 알려주지 않은 상응한 대가를 치르게 하기 위해 명확한 답을 내어주지는 않는다. 대신 그룹화와 같이 그 데이터의 특성을 알 수 있게 해준다.
학습 데이터와 평가 데이터
그럼 방금 supervised learning에서 문제와 답을 이야기 했었는데, 이 문제와 답을 일컽는 말이 따로 있다. 도미와 빙어의 길이, 무게를 가지고 class를 구별하라고 했듯이 우리는 길이, 무게와 같은 것을 입력값(input)이라고 하고, 도미, 빙어라는 class를 컴퓨터가 내놓기를 희망하는데 이러한 값을 타깃(target)이라고 한다. 그리고 데이터를 가지고 컴퓨터를 학습시켰기 때문에 이러한 데이터를 훈련 데이터(Training Data)라고 한다. 그러면 시험에 쓰는 데이터는 당연히 평가 데이터(Test Data)가 된다.
그런데 test data가 뜬금없이 나와서 당황할 것이다. 지금까지 training data에 대한 것은 봤는데 test data는 어디서 얻는 것인가? 그런 것은 걱정하지 않아도 된다. 우리는 지난 번에 사용했던 training data를 training data와 test data로 나눌 것이다. 이렇게 하면 추가로 test data를 얻을 필요 없이 기존의 data로 학습과 평가를 모두 할 수 있다.
지난번에 썼던 도미와 빙어 길이, 무게 data를 가져와보자.
1
2
3
4
5
6
7
8
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]
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]
그리고 지난번에 한 것과 같이 각 생선의 길이와 무게를 하나의 리스트로 만들어 물고기 개체들을 만들어준다. 이제 이렇게 만들어진 하나의 물고기, 즉 길이와 무게가 조합된 엄청 작은 리스트 하나를 샘플(Sample)이라고 할 것이다. 통계학을 공부한다면 처음부터 끝까지 계속 보게 되는 단어이나 혹시 모르는 사람을 위해 친절하게 적도록 하였다.
1
2
fish_data = [[l, w] for l, w in zip(length, weight)]
fish_target = [1]*35 + [0]*14
그리고 이렇게 만든 것 중에서 처음 35개의 sample을 training data, 나머지 14개를 test data로 설정하여 KNN 알고리즘을 통해 학습, 평가를 진행해볼 것이다.
KNN 알고리즘과 관련한 대략적인 이야기는 지난번에 이미 했기에 넘어가고, 슬라이싱을 통해서 간단하게 데이터를 나눠볼 것이다.
1
2
3
4
5
6
7
8
9
10
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
X_train = fish_data[:35]
X_test = fish_target[:35]
y_train = fish_data[35:]
y_test = fish_data[35:]
knn.fit(X_train, y_train)
knn.score(X_test, y_test)
0.0
샘플링 편향
이런, 0점이 나와버렸다. 마치 교수님은 이번 시험이 쉽다고 하셨는데 시험 문제를 봤을 때 교수님이 우리를 배신하시는 것과 같이 우리가 컴퓨터를 배신했기 때문일까? 그건 아닌 것 같다. 지난번에는 잘 학습을 해서 예측을 했던 기억이 있다. 우리가 컴퓨터를 과대평가한 것일까? 그것도 아닐 것이다.
그럼 문제가 무엇일까? 우리는 우리가 만든 data에 대해서 명확하게 파악하고 있어야 할 필요가 있다. 먼저, training data와 test data를 35개와 14개라는 어중간하고 이상한 숫자로 설정한 것에는 다 이유가 있다. 우리는 도미의 data 35개, 빙어의 data 14개를 차례로 설정하였고, fish_data에는 도미의 데이터 35개가 나오고 그 다음에 빙어의 data 14개가 나오게 된다.
근데 우리는 training data를 설정할 때, 처음 35개의 data만 학습하게 하였다. 그렇다, 우리는 도미만 공부하라고 해놓고 시험 범위에도 없는 빙어를 물어본 천인공노할 일을 저질러 버린 것이다. 이렇게 한 쪽에 치우치게 학습을 시키는 것을 샘플링 편향(Sampling Bias)이라고 한다. 통계에서도 sampling을 할 때 unbiased하게, 대표성을 지닐 수 있게 해야 하는 것과 같이 머신러닝에서도 마찬가지이다. 그러면 우리는 어떻게 해야 할까? 도미랑 빙어를 골고루 섞어서 공부하게 할 수는 없을까?
솔직히 여기서 train_test_split을 알려줄 것이라고 예상하고 그 내용을 적으려고 했는데 array를 통해서 랜덤하게 data를 섞고 training data와 test data를 뽑는 것을 보고 솔직히 많이 귀찮다는 생각이 들었다. 하지만 어쩔 수 없다. 뭐든지 쉬운 길로만 갈 수는 없는 법이다. 기초를 제대로 해야 편하게 가는 법도 배울 수 있는 것이다. 마치 수학에서 공식을 유도하는 것을 굳이 안 해도 수식만 외워서 문제는 풀 수 있지만, 그런 방법으로는 험난한 대학 수학에서 버티지 못하는 것처럼.
넘파이를 이용한 랜덤 샘플링
나는 친절하기에 또 넘파이 설치를 위한 코드도 적어준다. Gogole Colab에는 이미 설치가 되어 있겠지만, 난 Colab보다는 오프라인에서도 쓸 수 있고, 내 PC에 설치되어 있어서 마음이 편안한 VS Code가 더 좋기에 하나하나 설정하는 것이 더 좋다.
1
pip3 install numpy
그리고 우리의 생선들의 리스트를 넘파이의 랜덤 기능을 활용해서 뽑을 것이기에, 넘파이에서 사용하는 데이터 형태인 배열(Array)로 바꿔준다. 즉, 행렬로 만들어 준다고 생각하면 편하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import numpy as np
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]
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 = [[l, w] for l, w in zip(length, weight)]
fish_target = [1]*35 + [0]*14
X = np.array(fish_data)
y = target_arr = np.array(fish_target)
이제 넘파이 배열로 바꾸었으니, 무작위로 데이터를 뽑아볼 것이다. 보통 이럴 때 사용하는 random seed나 random state는 42로 쓰는 경향이 있으나, 난 그렇게 하지 않을 것이다. 남들이 하는 것을 똑같이 한다면 차별화될 수 없다. 그렇기에 난 반골 기질이 아닌, 차별화를 위해서 다른 것으로 설정하기로 했다.
1
2
3
np.random.seed(0)
index = np.arange(49)
np.random.shuffle(index)
이제 인덱스는 비빔밥 섞듯이 마구 섞여 버렸다. 이제 도미와 빙어가 있는 상자에서 물고기를 뽑아도 처음에 항상 도미만 나오는 게 아니라 빙어가 나올 수도 있는 것이다. 이제 마구 섞여버린 상자에서 training data와 test data로 쓸 물고기들을 선택해보자.
1
2
3
4
5
X_train = X[index[:35]]
y_train = y[index[:35]]
X_test = X[index[35:]]
y_test = y[index[35:]]
그런데 우리는 정확하게 한 것 같더라도 실수를 하기 마련이다. 통계 분석을 하다가 뭔가 이상하다 싶으면 human error를 의심하라고 말씀하신 교수님의 말씀처럼, 우리는 항상 나 자신을 의심해야 한다. 또, 교수님께서 말씀하신 분석을 하기 전 항상 visualization을 해보라고 하신 것을 생각해보며 scatter plot을 다시 한번 그려보도록 하자.
1
2
3
4
5
6
7
import matplotlib.pyplot as plt
plt.scatter(X_train[:,0], X_train[:,1])
plt.scatter(X_test[:,0], X_test[:,1])
plt.xlabel('length')
plt.ylabel('weight')
plt.show
드디어 골고루 data가 뽑힌 것을 볼 수 있다. 파란색 점들이 training data로 뽑힌 물고기들이고, 주황색 점이 test data로 뽑힌 물고기들이다. 이제 우리의 컴퓨터는 파란색 점들로 공부를 하고 주황색 점으로 시험을 보는 것이다. 이제 컴퓨터의 학습 능력 수준을 확인해보러 가자.
모델 평가
1
2
3
4
5
6
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier()
knn.fit(X_train, y_train)
knn.score(X_test, y_test)
1.0
이런, 아쉽게도 100점이 나와버렸다. 이건 말이 안 된다. 직접 답안지를 확인해서 제대로 컴퓨터가 맞게 쓴 것이 맞는지 확인해보아야겠다. 우선, 컴퓨터가 내놓은 정답은 아래와 같다.
1
knn.predict(X_test)
array([0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0])
이제 기대되는 정답 확인 시간이다. 과연 정말로 컴퓨터가 100점이 맞는지 확인할 시간이다.
1
y_test
array([0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0])
아쉽게도 정답과 동일하다… 역시 컴퓨터의 학습 능력은 이길 수가 없어 보인다. 이렇게 지금까지 training data와 test data를 나누는 것에 대해서 알아보았다. 너무 유익한 시간이었다.
추가 숙제
그리고 한빛미디어가 우리에게 준 숙제도 해볼 시간이다. 물론, 필수는 아니지만 이런 건 굳이 다 해줘야 좋기에 해보기로 하였다. 그런데 문듯 든 생각이 있다. 문제를 풀고, 풀이과정을 작성하는 숙제인데, 내 기억으로는 문제집의 경우, 내가 푼 풀이는 몰라도 문제에 대한 저작권은 출판사에 있다고 들었던 것 같다. 그렇기에 문제는 번호만 작성하고, 정답과 해설만 여기에 적어보도록 하겠다.
확인문제
①
우리는 지난 시간에 supervised learning과 unsupervised learning에 대해서 이미 한 번 확인해본 적이 있다. supervised learning은 우리가 model 학습을 할 때 문제와 정답을 모두 주는 것으로, sample의 input과 target을 모두 알고 있을 때 사용할 수 있는 방법이다.④
training data와 test data가 잘못 만들어져서 전체 data를 대표하지 못하는 현상은 우리가 방금 도미와 빙어를 통해 확인해 보았다. 우리는 이것이 biased해 있는데, sampling에 대해서 biased해 있다고 하여 sampling bias라고 하기로 했었다.②
우리는 이미 scikit-learn을 KNN 알고리즘을 사용하기 위해 경험해 보았다. 방금까지 했던 것을 다시 되새겨보면, 먼저 도미와 빙어의 길이와 무게를 하나의 리스트로 만들어 하나의 sample로 만들어 주었다. 이후, 이것을 numpy를 통해 array로 만들었다. 이러한 경우, 하나의 행(Row), 즉 가로축으로 있는 것은 하나의 sample이 되고, 하나의 열(Column), 즉 세로축으로 되어 있는 것은 하나의 feature이 될 것이다.