[목차]
1. K-NN Regression
2. KNeighborsRegressor Class
3. K-NN Regression 실습
4. 과대적합 vs 과소적합
5. K-NN Regression의 한계
1. K-NN Regression
이전에는 생선이 도미에 속하는지 빙어에 속하는지 '분류'하는 문제를 다루었다. (https://rebro.kr/183)
다른 문제로 생선의 크기나 길이를 보고 생선의 무게를 예측하는 문제도 다룰 수 있다. 이처럼 클래스 중 하나로 분류하는 것이 아니라 임의의 어떤 '숫자'를 예측하는 문제를 회귀(Regression)라고 한다.
그중 K-NN Regression(K-최근접 이웃 회귀) 알고리즘은 주변의 가장 가까운 K개의 샘플을 통해 값을 예측하는 방식이다. 예를 들어 가장 간단한 방식으로는 K개 샘플의 평균을 이용할 수 있다.
2. KNeighborsRegressor Class
실습에 앞서, 먼저 K-NN 회귀 알고리즘이 구현된 클래스인 KNeighborsRegressor에 대해 알아보자.
https://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsRegressor.html
KNeighborsRegressor 클래스는 다음과 같이 구성되어 있다.
class sklearn.neighbors.KNeighborsRegressor(n_neighbors=5, *, weights='uniform', algorithm='auto',
leaf_size=30, p=2, metric='minkowski', metric_params=None, n_jobs=None)
▶ n_neighbors : int
이웃의 수인 K를 결정한다. default는 5다.
▶ weights : {'uniform', 'distance'} or callable
예측에 사용되는 가중 방법을 결정한다. default는 uniform이다.
'uniform' : 각각의 이웃이 모두 동일한 가중치를 갖는다.
'distance' : 거리가 가까울수록 더 높은 가중치를 가져 더 큰 영향을 미치게 된다.
callable : 사용자가 직접 정의한 함수를 사용할 수도 있다. 거리가 저장된 배열을 입력으로 받고 가중치가 저장된 배열을 반환하는 함수가 되어야 한다.
▶ algorithm : {'auto', 'ball_tree', 'kd_tree', 'brute'}
가장 가까운 이웃들을 계산하는 데 사용하는 알고리즘을 결정한다. default는 auto이다.
'auto' : 입력된 훈련 데이터에 기반하여 가장 적절한 알고리즘을 사용한다.
'ball_tree' : Ball-Tree 구조를 사용한다. (Ball-Tree 설명 : https://nobilitycat.tistory.com/entry/ball-tree)
'kd_tree' : KD-Tree 구조를 사용한다.
'brute' : Brute-Force 탐색을 사용한다.
▶ leaf_size : int
Ball-Tree나 KD-Tree의 leaf size를 결정한다. default값은 30이다.
이는 트리를 저장하기 위한 메모리뿐만 아니라, 트리의 구성과 쿼리 처리의 속도에도 영향을 미친다.
▶ p : int
민코프스키 미터법(Minkowski)의 차수를 결정한다. 예를 들어 p = 1이면 맨해튼 거리(Manhatten distance), p = 2이면 유클리드 거리(Euclidean distance)이다.
3. K-NN Regression 실습
농어의 무게를 예측하는 프로그램을 작성해보자.
실습 내용은 '혼자 공부하는 머신러닝+딥러닝' 책을 이용하였다.
먼저 훈련 데이터를 준비하자. 데이터는 data.py 모듈에 따로 저장하였다.
perch_length = np.array([8.4, 13.7, 15.0, 16.2, 17.4, 18.0, 18.7, 19.0, 19.6, 20.0, 21.0,
21.0, 21.0, 21.3, 22.0, 22.0, 22.0, 22.0, 22.0, 22.5, 22.5, 22.7,
23.0, 23.5, 24.0, 24.0, 24.6, 25.0, 25.6, 26.5, 27.3, 27.5, 27.5,
27.5, 28.0, 28.7, 30.0, 32.8, 34.5, 35.0, 36.5, 36.0, 37.0, 37.0,
39.0, 39.0, 39.0, 40.0, 40.0, 40.0, 40.0, 42.0, 43.0, 43.0, 43.5,
44.0])
perch_weight = np.array([5.9, 32.0, 40.0, 51.5, 70.0, 100.0, 78.0, 80.0, 85.0, 85.0, 110.0,
115.0, 125.0, 130.0, 120.0, 120.0, 130.0, 135.0, 110.0, 130.0,
150.0, 145.0, 150.0, 170.0, 225.0, 145.0, 188.0, 180.0, 197.0,
218.0, 300.0, 260.0, 265.0, 250.0, 250.0, 300.0, 320.0, 514.0,
556.0, 840.0, 685.0, 700.0, 700.0, 690.0, 900.0, 650.0, 820.0,
850.0, 900.0, 1015.0, 820.0, 1100.0, 1000.0, 1100.0, 1000.0,
1000.0])
농어의 길이만으로 무게를 예측할 것이다. 따라서 농어의 길이가 특성이고 무게가 타깃이 된다.
산점도를 그려보면 당연하게 농어의 길이가 커짐에 따라 무게가 늘어나는 것을 볼 수 있다.
이제 농어 데이터를 훈련 셋과 테스트 셋으로 나누어보자. 이를 위해 사이킷런(scikit-learn)의 train_test_split() 함수를 사용한다.
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(perch_length, perch_weight, random_state = 42)
이전 게시글에서는 직접 랜덤과 인덱스를 이용하여 셋을 분리했지만, 위 함수를 사용하면 간편하게 분리해낼 수 있다.
train_test_split 함수는 다음과 같이 구성되어 있다.
sklearn.model_selection.train_test_split(*arrays, test_size=None, train_size=None,
random_state=None, shuffle=True, stratify=None)
array에는 동일한 길이의 리스트, 넘파이 배열, 판다스 dataframe 등의 집합이 들어간다.
test_size는 0.0 ~ 1.0 사이의 실수 값이 들어가고, 이는 테스트 셋의 크기 비율을 설정한다. default는 0.25로, 전체 중 25프로의 샘플을 테스트 셋으로 뽑는다.
train_size도 test_size와 동일하게 훈련 셋의 비율을 설정하고, default는 테스트 셋을 제외한 나머지다.
사이킷런에 사용할 훈련 셋은 2차원 배열이어야 한다. 하지만 perch_length가 1차원 배열이기 때문에 train_input과 test_input도 1차원 배열이다. 따라서 이를 1개의 열로 구성된 2차원 배열로 바꿔주어야 한다.
넘파이 배열의 크기를 바꾸는 메서드는 reshape()가 있다.
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
a = a.reshape(2, 2)
print(a)
# [[1 2]
# [3 4]]
a = a.reshape(2, 3) # ERROR
다만 reshape 메서드는 지정한 크기가 원본 배열에 있는 원소의 개수와 다르면 에러가 발생한다.
배열의 크기를 자동으로 지정하는 기능도 제공한다. 크기를 -1로 지정하면 나머지 원소 개수로 모두 채워진다. 이를 사용하면 배열의 원소의 개수를 정확히 모르더라도 크기를 쉽게 변경할 수 있다.
a = np.array([1, 2, 3, 4])
print(a) # [1 2 3 4]
a = a.reshape(-1, 2)
print(a)
# [[1 2]
# [3 4]]
a = a.reshape(-1, 1)
print(a)
#[[1]
# [2]
# [3]
# [4]]
이를 이용해서 훈련 셋과 테스트 셋을 2차원으로 바꿔주자.
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
이제 앞에서 설명한 KNeighborsRegressor 클래스를 통해 회귀 모델을 훈련할 수 있다.
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
knr.fit(train_input, train_target)
print(knr.score(test_input, test_target)) # 0.992809406101064
훈련하는 방법은 분류와 비슷하다. 테스트 셋의 점수를 확인해보니 0.9928... 로 나온다.
분류에서는 이 점수가 맞은 샘플의 개수(비율)를 의미했는데, 회귀에서는 어떤 값을 의미할까?
회귀에서는 이 점수를 결정계수(Coefficient of determination)라고 부른다. 또는 간단하게 $R^2$라고도 부른다. 계산 식은 다음과 같다.
$R^2 = 1 - \frac{(타깃 - 예측)^2}{(타깃 - 평균)^2}$
만약 타깃의 평균 정도를 예측하는 수준이라면 $R^2$는 0에 가까워지고, 타깃이 예측에 아주 가까워지면 1에 가까운 값이 된다. 따라서 0.99면 매우 좋은 값이라고 할 수 있다.
4. 과대적합 vs 과소적합
앞에서 훈련한 모델에 테스트 셋의 점수를 계산해보았다. 그러면 훈련 셋의 점수는 어떻게 될까?
print(knr.score(train_input, train_target)) # 0.9698823289099254
훈련 셋으로 모델을 훈련했기 때문에 훈련 셋의 점수가 더 높아야 할 텐데 오히려 테스트 셋 보다 점수가 더 낮게 나온다.
이처럼 훈련 셋보다 테스트 셋의 점수가 높거나 두 점수가 모두 너무 낮은 경우는 모델이 훈련 셋에 '과소적합(Underfitting)'되었다고 말한다.
반대로 훈련 셋의 점수보다 테스트 셋의 점수가 지나치게 낮은 경우 모델이 훈련 셋에 '과대적합(Overfitting)'되었다고 말한다.
과소적합 문제는 모델을 조금 더 복잡하게 만들면 된다. 즉, 훈련 셋에 더 잘 맞게 만들면 테스트 점수가 조금 낮아질 것이다.
K-NN에서는 K 값을 줄임으로써 모델을 복잡하게 만들 수 있다. 이웃의 수를 줄이면 훈련 셋에 있는 국지적인 패턴에 민감해지고, 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따르게 되기 때문이다.
따라서 K의 값을 5에서 3으로 줄여보자.
knr.n_neighbors = 3
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target)) # 0.9804899950518966
print(knr.score(test_input, test_target)) # 0.9746459963987609
테스트 셋의 점수가 훈련 셋보다 낮아졌고, 둘의 차이도 크지 않기 때문에 모델이 적절하게 만들어졌음을 알 수 있다.
5. K-NN Regression의 한계
훈련된 모델을 이용하여 길이가 100인 농어의 무게를 예측해보자.
print(knr.predict([[100]])) # [1033.33333333]
무게를 약 1033g으로 예측하였다. 그런데, 데이터를 참고해보면 길이가 40~44되는 농어의 무게들이 1000 또는 1100인 것을 볼 수 있다. 산점도를 통해 확인해보자.
실제로는 길이가 100인 농어는 훨씬 무게가 많이 나와야 할 것이다. 하지만 가장 가까운 이웃의 길이가 45 근방이기 때문에 이 샘플들의 무게를 이용해 평균을 계산하게 된다. 과장해서 길이가 10000인 농어의 무게도 여전히 1033으로 예측할 것이다.
이처럼 테스트하고자 하는 샘플에 근접한 훈련 데이터가 없는 경우, 즉 훈련 셋의 범위를 많이 벗어나는 샘플인 경우 정확하게 예측하기 어려운 한계가 있다.
참고)
- 혼자 공부하는 머신러닝+딥러닝(박해선, 한빛미디어)
PC로 보시는 것을 권장합니다.
피드백은 언제나 환영입니다. 댓글로 달아주세요 ^-^
'머신러닝' 카테고리의 다른 글
[머신러닝] 다중 선형 회귀(Multiple Linear Regression) (0) | 2021.10.26 |
---|---|
[머신러닝] 단순 선형 회귀(Simple Linear Regression) (0) | 2021.10.20 |
[머신러닝] K-최근접 이웃(K-NN) 알고리즘 및 실습 (0) | 2021.10.11 |
[머신러닝] 머신러닝(Machine Learning) 정의 및 종류 (1) | 2021.10.07 |