[ch02-1] 혼자 공부하는 머신러닝 + 딥러닝

02-1 훈련 세트와 테스트 세트

지도 학습과 비지도 학습

머신러닝 알고리즘은 크게 지도 학습비지도 학습으로 나뉜다.

강화 학습도 있는데 이 책에서는 다루지 않는다.

1장에서 도미와 빙어의 데이터를 가지고 한 알고리즘이 지도 학습이고, 지도 학습 알고리즘은 훈련하기 위한 데이터와 정답이 필요하다.
지도 학습에서 데이터를 입력(input), 정답을 타깃(target)이라고 하고 이 둘을 합쳐 훈련 데이터(training data)라고 한다.
지도 학습은 정답(타깃)이 있어서 알고리즘이 정답을 맞히는 것을 학습하지만 비지도 학습은 타깃 없이 입력 데이터만 사용한다.
정답을 사용하지 않으므로 무언가를 맞힐 수는 없지만 데이터를 파악하거나 변형하는 데는 좋다.
👉 입력 데이터만 있을 때 비지도 학습 알고리즘을 사용하기 좋다!

k-최근접 이웃 알고리즘을 이용한 모델이 도미와 빙어를 완벽하게 판별했는데 과연 완벽한 모델일까 ?

훈련 세트와 테스트 세트

1장에서는 지도 학습 알고리즘을 이용했으므로 2장에서는 비지도 학습 알고리즘을 이용한다.
앞에서는 입력과 타깃을 주고 훈련하고 훈련에 쓰인 데이터로 테스트를 했기 때문에 도미와 빙어를 완벽하게 판별했다.
(어찌보면 당연한 얘기)

비지도 학습 알고리즘에서는 훈련한 데이터와 테스트하는 데이터를 다르게 하고 알고리즘의 성능을 평가한다.
그러므로 정확한 평가를 위해서는 훈련 세트테스트 세트가 미리 준비되어야 한다.

훈련 세트(train set)
훈련에 사용되는 데이터

테스트 세트(test set)
평가에 사용하는 데이터

1장에서처럼 도미와 빙어의 길이와 무게 데이터를 가져오는데 둘의 데이터를 합쳐 하나의 파이썬 리스트로 준비한다.
👉 도미와 빙어 리스트

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]

길이와 무게를 하나의 리스트로 담은 2차원 리스트로 만든다.

1
2
fish_data = [[l, w] for l, w in zip(fish_length, fish_weight)]
fish_target = [1]*35 + [0]*14

이때 만들어지는 fish_data는 아래와 같은 2차원 리스트 형태인데

1
fish_data = [[25.4, 242.0], [26.3, 290.0], [26.5, 340.0], [29.0, 363.0], [29.0, 430.0], [29.7, 450.0], [29.7, 500.0], [30.0, 390.0], [30.0, 450.0], [30.7, 500.0], [31.0, 475.0], [31.0, 500.0], [31.5, 500.0], [32.0, 340.0], [32.0, 600.0], [32.0, 600.0], [33.0, 700.0], [33.0, 700.0], [33.5, 610.0], [33.5, 650.0], [34.0, 575.0], [34.0, 685.0], [34.5, 620.0], [35.0, 680.0], [35.0, 700.0], [35.0, 725.0], [35.0, 720.0], [36.0, 714.0], [36.0, 850.0], [37.0, 1000.0], [38.5, 920.0], [38.5, 955.0], [39.5, 925.0], [41.0, 975.0], [41.0, 950.0], [9.8, 6.7], [10.5, 7.5], [10.6, 7.0], [11.0, 9.7], [11.2, 9.8], [11.3, 8.7], [11.8, 10.0], [11.8, 9.9], [12.0, 9.8], [12.2, 12.2], [12.4, 13.4], [13.0, 12.2], [14.3, 19.7], [15.0, 19.9]]

여기서 [25.4, 242.0], [26.3, 290.0]과 같은 데이터를 샘플(sample)이라고 부른다.
도미와 빙어가 각각 35, 14마리 있으므로 전체 데이터는 49개의 샘플이 있고 사용하는 특성은 길이와 무게로 2개이다.
이 데이터의 35개를 훈련 세트, 14개를 테스트 세트로 사용해보자.

먼저 사이킷런의 KNeighborsClassifier 클래스를 import하고 모델 객체를 만든다.

1
2
from sklearn.neighbors import KNeighborsClassifier
kn = KNeighborsClassifier()

그리고 전체 데이터에서 처음 35개를 선택한다.
파이썬에서 리스트의 요소를 선택할 때 인덱스를 사용하는데 여기서도 인덱스와 슬라이싱연산을 통해 첫 번째부터 다섯 번째까지의 샘플을 선택해보자.

1
print(fish_data[0:5])

슬라이싱

이런식으로 훈련 세트와 테스트 세트 값을 지정해보자.

1
2
3
4
train_input = fish_data[:35] #훈련 세트의 데이터(입력)을 fish_data의 1~35번 데이터로 지정
train_target = fish_target[:35] #훈련 세트의 정답(타깃)을 fish_target의 1~35번 값으로 지정
test_input = fish_data[35:] #테스트 세트의 데이터(입력)을 fish_data의 36번~마지막 데이터까지 지정
test_target = fish_target[35:] #테스트 세트의 정답(타깃)을 fish_target의 36번~마지막 값으로 지정

슬라이싱 연산으로 인덱스 0~34까지 처음 35개 샘플을 훈련 세트로 지정하고, 인덱스 35~48까지 나머지 14개 샘플을 테스트 세트로 지정했다.

데이터를 준비했으니 훈련 세트로 fit() 메서드를 호출해 모델을 훈련하고, 테스트 세트로 score() 메서드를 호출해 평가해보자.

1
2
kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)

결과

정확도가 0.0이 나왔는데 대충 내려오면서 이상함을 눈치채지 않았나요 ?
앞 35개 데이터는 도미이고 뒷 14개 데이터는 빙어인데 도미로 훈련하고 빙어로 테스트해서다.
이걸 어떻게 해결하면 될까?😮

샘플링 편향

원래 프로그램의 목적처럼 했더라면 sample 이런 훈련 세트와 테스트 세트로 훈련하고 테스트했어야 하는데

앞에서 한 훈련과 테스트는 sampling_bias 이런식으로 편향된 데이터로 했다.

이렇게 훈련 세트와 테스트 세트에 샘플이 골고루 섞여 있지 않고 샘플링이 한쪽으로 치우친 걸 샘플링 편향(sampling bias)라고 한다.
샘플링 편향때문에 정확도가 0.0이 나왔던 거다.

그렇다면 원래의 목적처럼 잘 섞인 훈련 세트를 만들어보자.

넘파이

넘파이란 ?
파이썬의 대표적인 배열(array) 라이브러리

앞에서 설정한 데이터 세트는 파이썬 리스트로 2차원 리스트를 표현했는데 이걸로 고차원 리스트를 표현하기는 번거롭다.
넘파이는 고차원 배열을 손쉽게 만들고 조작할 수 있는 간편한 도구를 많이 제공해 넘파이를 사용하겠다.

먼저 넘파이 라이브러리를 import 한다.

1
import numpy as np

그 다음에 파이썬 리스트를 넘파이 배열로 넘겨준다.

1
2
input_arr = np.array(fish_data)
target_arr = np.array(fish_target) 

input_arr를 확인해보면 input_arr 앞에서의 fish_data와 같은 2차원 배열이지만 넘파이에서는 좀 더 보기좋게 행과 열을 나눠서 출력해준다.

배열의 크기를 알려주는 shape 속성을 사용해 49개의 샘플과 2개의 특성이 있는 것을 확인해보자. input_arr_shape

이제 무작위로 샘플을 고르는 방법을 사용해 훈련 세트와 테스트 세트를 만들어보자.

한 가지 주의할 점은 input_arr와 target_arr에서 같은 위치는 함께 선택되어야 한다는 점이다.
input_arr의 두 번째 값이 훈련 세트로 가면 target_arr의 두 번째 값도 훈련 세트로 가야한다.
example

함께 잘 선택됐는지 확인하려면 인덱스값을 기억해야겠지만 매번 이럴 순 없으니까 아예 인덱스를 섞은 다음 input_arr와 target_arr에서 샘플을 선택한다.
그러면 무작위로 훈련 세트를 나누는 거랑 같게 된다.

넘파이 arange()함수를 사용해 0에서 48까지 1씩 증가하는 인덱스를 만들고 랜덤하게 섞는다.
일정한 결과를 얻기위해 랜덤 시드를 42로 지정하자. (다른 값을 넣어도 됨)

arange(N) 함수는 0 ~ (N-1)까지의 순차배열을 반환하는 함수. ex) arange(3)이면 [0,1,2] 반환
시드 값이 일정해야 동일한 난수를 발생시킨다. 시드값 42에 정해진 값이 있고 그걸 불러오는 느낌

1
2
3
np.random.seed(42)
index = np.arange(49)  # 0 ~ 48까지 1씩 증가하는 index 배열 생성
np.random.shuffle(index)    # index 배열 무작위로 섞음

인덱스(배열)를 확인해보면 index 0부터 48까지의 정수가 잘 섞인 걸 알 수 있다.

이제 이 인덱스를 가지고 훈련 세트와 테스트 세트를 만들어보자.

1
2
3
4
train_input = input_arr[index[:35]]
train_target = target_arr[index[:35]]
test_input = input_arr[index[35:]]
test_target = target_arr[index[35:]]

샘플들이 잘 섞인 훈련 세트와 테스트 세트를 만들었는지 확인해보자.

1
2
3
4
5
6
import matplotlib.pyplot as plt 
plt.scatter(train_input[:,0], train_input[:,1]) #0은 길이 1은 무게
plt.scatter(test_input[:,0], test_input[:,1])
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

plt_index

파란색이 훈련 세트고 주황색이 테스트 세트인데 양쪽에 도미와 빙어가 잘 섞여있는 것을 확인할 수 있다.
이제 모델을 다시 훈련시켜 보자.

두 번째 머신러닝 프로그램

앞에서 만든 KNeighborsClassifier 클래스의 객체는 이전에 학습한 모든 것을 잃어버린다.
이전에 만든 kn 객체를 사용해 fit() 메서드를 실행하고 score() 메서드로 테스트 해본다.

1
2
kn = kn.fit(train_input, train_target)
kn.score(test_input, test_target)    

score2

1.0으로 100%의 정확도로 테스트 세트에 있는 모든 생선을 맞혔다.

predict() 메서드로 테스트 세트의 예측 결과와 실제 타깃을 확인해보자.

1
kn.predict(test_input)

predict2

테스트 세트에 대한 예측이 맞는지 확인해보자.

1
test_target

test_target

코랩은 셀의 마지막 코드 결과를 자동으로 출력해 주기 때문에 print() 함수를 사용하지 않아도 된다.

둘 다 같은 배열을 출력하는 것을 통해 예측을 정확하게 했음을 알 수 있다.

두 행의 코드 결과가 array() 로 감싸져 있는데 이 값은 넘파이 배열을 의미한다.
즉 predict() 메서드가 반환하는 값은 단순한 파이썬 리스트가 아니라 넘파이 배열이고
사이킷런 모델의 입출력은 모두 넘파이 배열이다.