[머신러닝] K-최근접 이웃 회귀

2 분 소요

지도학습 알고리즘은 크게 분류와 회귀(regression)로 나뉜다. 회귀는 임의의 어떤 숫자를 예측하는 문제이다.

예를 들면 내년도 경제 성장률 예측, 배달 도착 시간 예측 등이 회귀 문제이다.

k-최근접 이웃 분류 알고리즘은 예측하려는 샘플에 가장 가까운 샘플 k개를 선택하고 이 샘플들의 클래스를 확인해 다수 클래스를 새로운 샘플의 클래스로 예측한다.

k-최근접 이웃 회귀는 샘플에 가장 가까운 샘플 k개를 선택한다. 이때 이웃한 샘플의 타깃은 어떤 클래스가 아니라 임의의 수치이다. 이러한 임의의 수치의 평균값을 구해 예측 타깃값을 정한다.

데이터 준비

import numpy as np

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]
     )

농어의 길이가 특성이고 무게가 타깃이다.

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_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)

print(train_input.shape, test_input.shape)
(42, 1) (14, 1)

사이킷런에 사용할 훈련 세트는 2차원 배열이어야 한다. 이전에는 특성이 2개라 열이 2개인 2차원 배열을 사용했다. 이번에는 특성을 1개만 사용하므로 수동으로 2차원 배열로 만들어야 한다.

넘파이에서 -1을 지정하면 나머지 원소 개수로 모두 채우라는 의미이다.



k-최근접 이웃 회귀 알고리즘

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()
# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)

knr.score(test_input, test_target)
0.9928094061010639

사이킷런에서 k-최근접 이웃 회귀 알고리즘을 구현한 클래스는 KNegihborsRegressor이다.


회귀의 경우 결정계수(coefficient of determination)로 평가한다. 결정계수는 각 샘플의 타깃과 예측한 값의 차이를 제곱하여 더한다. 그 다음 타깃과 타깃평균의 차이를 제곱하여 더한 값으로 나눈다.

만약 타깃이 평균 정도를 예측하는 수준이라면(즉 분자와 분모가 비슷) R^2는 0에 가까워지고, 예측이 타깃에 아주 가까워지면(분자가 0에 가까워지기 때문에) 1에 가까운 값이 된다.

from sklearn.metrics import mean_absolute_error

# 테스트 세트에 대한 예측을 만듭니다
test_prediction = knr.predict(test_input)
# 테스트 세트에 대한 평균 절댓값 오차를 계산합니다
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
19.157142857142862

사이킷런은 sklearn.metrics 패키지 아래 여러 가지 측정 도구를 제공한다. 이 중에서 mean_absolute_error는 타깃과 예측의 절댓값 오차를 평균해 반환한다.



과대적합 vs 과소적합

print(knr.score(train_input, train_target))
0.9698823289099255

보통 훈련세트와 테스트세트로 평가하면 두 값중 훈련세트 점수가 더 높게 나온다.

만약 훈련 세트에서 점수가 굉장히 좋았는데 테스트 세트에서 점수가 굉장히 나쁘다면 모델이 훈련세트에 과대적합(overfitting)되었다고 말한다. 즉 훈련 세트에만 잘 맞는 모델이라 테스트 세트와 나중에 실전에 투입해 새로운 샘플에 대한 예측을 만들 때 잘 동작하지 않을 것이다.

반대로 훈련세트보다 테스트세트의 점수가 높거나 두 점수가 모두 너무 낮은 경우 모델이 훈련세트에 과소적합(underfitting)되었다고 말한다. 즉 모델이 너무 단순해 훈련세트에 적절히 훈련되지 않은 경우이다. 일반적으로 과소적합은 훈련 세트와 테스트 세트의 크기가 매우 작아서 일어난다.

훈련세트가 전체 데이터를 대표한다고 가정하기 때문에 훈련 세트를 잘 학습하는 것이 중요하다.

# 이웃의 갯수를 3으로 설정합니다
knr.n_neighbors = 3
# 모델을 다시 훈련합니다
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
0.9804899950518966

현재 발생한 과소적합 문제를 해결하기 위해 모델을 조금 더 복잡하게 만들면 된다. 그럼 테스트 세트의 점수는 조금 낮아질 것이다. k-최근접 이웃 알고리즘으로 모델을 더 복잡하게 만드는 방법은 이웃의 개수 k를 줄이는 것이다.

이웃의 개수를 줄이면 훈련 세트에 있는 국지적인 패턴에 민감해지고, 이웃의 개수를 늘리면 데이터 전반에 있는 일반적인 패턴을 따를 것이기 때문이다.

사이킷런의 k-최근접 이웃 알고리즘의 기본 k값은 5이다. 이를 3으로 낮춘다.

print(knr.score(test_input, test_target))
0.974645996398761

이렇게 과소적합 문제를 해결했다.