혼자 공부하는 머신러닝 + 딥러닝 03-1 K-최근접 이웃 회귀

손지호·2023년 7월 13일
0

k-최근접 이웃 회귀

회귀(regression) : 클래스 중 하나로 임의의 어떤 숫자를 예측하는 문제.
예를 들면 내년도 경제 성쟝률 예측이나 배달 도착 시간 예측. 여기서는 농어의 무게 예측하는 것도 회귀에 속함!

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

k-최근접 이웃 회귀
1. 예측하려는 샘플에 가장 가까운 샘플 k개 선택.
2. 여기서 회귀니까 이웃한 샘플의 타깃은 임의의 수치.


데이터 준비

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

훈련 데이터 준비. 어떤 형태 띠고 있는지 산점도 그려보기!

plt.scatter(perch_length, perch_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()


농어 길이 커짐에 따라 무게도 늘어남!!

농어 데이터를 머신러닝 모델에 사용하기 전에 훈련 세트와 테스트 세트로 나눈다.

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)

사이킷런에 사용할 훈련 세트는 2차원 배열이어야 한다. perch_lenght가 1차원 배열이기 때문에 이를 나눈 train_input과 test_input도 1차원 배열. 이를 1개의 열이 있는 2차원 배열로 바꿔야 한다.

이를 위해 reshape() 메서드를 사용하여 2차원 배열로 바꾼다.

# 배열 확인
test_array = np.array([1,2,3,4])
print(test_array.shape)
>>> (4,)

#(2,2) 크기로 변경
test_array = test_array.reshape(2, 2)
print(test_array.shape)
>>> (2,2)

reshape() 메서드는 바꾸려는 배열의 크기 지정할 수 있음!
다만, 크기가 바뀐 새로운 배열을 반환할 때 지정한 크기가 원본 배열에 있는 원소의 개수와 다르면 에러 발생!
예를 들어, (4,) 크기의 배열을 (2,3)으로 바꾸려고 하면 에러 발생. 원본 배열의 원소는 4개인데 2 x 3 = 6개로 바꾸려고 하기 때문!

train_input의 크기는 (42,). 이를 2차원 배열인 (42,1)로 바꾸려면 train_input.reshape(42,1)과 같이 사용 가능.
하지만 넘파이에는 배열의 크기를 자동으로 지정하는 기능을 제공한다! 크기에 -1을 지정하면 나머지 원소 개수로 모두 채우라는 의미.

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차원 배열로 성공적으로 변환 완료!!

결정계수(R^2)

사이킷런에서 k-최근접 이웃 알고리즘 구현한 클래스는 KNeighborsRegressor.
사용법은 KNeighborsClassifier와 매우 비슷!

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()
# k-최근접 이웃 회귀 모델을 훈련합니다
knr.fit(train_input, train_target)
#테스트 세트 점수 확인
print(knr.score(test_input, test_target))
>>> 0.992809406101064

좋은 점수. 근데 이건 무슨 점수일까?!!
분류의 경우 테스트 세트에 있는 샘플을 정확하게 분류한 개수의 비율. (정확도라고 불렀음!) 간단하게 정답 맞힌 개수의 비율.
회귀에서는 정확한 숫자 맞힌다는 것은 거의 불가능. 예측하는 값이나 타깃이 모두 임의의 수치이기 때문!

회귀의 경에는 조금 다른 값으로 평가. 이 점수를 결정계수(coefficient of determination), R^2 라 부른다.

각 샘플의 타깃과 예측한 값의 차이를 제곱하여 더한다. 그다음 타깃과 타깃 평균의 차이를 제곱하여 더한 값으로 나눈다. 만약 타깃의 평균 정도를 예측하는 수준이라면(분자와 분모가 비슷해져) R^2는 0에 가까워지고, 예측이 타깃에 아주 가까워지면(분자가 0에 가까워지니까) 1에 가까운 값이 된다!

사이킷런의 score() 메서드가 출력하는 값은 높을수록 좋은 것. 정확도나 결정계수가 그렇다. 만약 score() 메서드가 에러율을 반환한다면 이를 음수로 만들어 실제로는 낮은 에러가 score() 메서드로 반환될 때는 높은 값이 되도록 바꾼다.

0.99면 아주 좋은 값. 하지만 정확도처럼 R^2가 직감적으로 얼마나 좋은지 이해하기 어렵다. 타깃과 예측한 값 사이의 차이를 구해보면 어느 정도 예측이 벗어났는지 가늠하기 좋음. 사이킷런은 sklearn.metrics 패키지 아래 여러 가지 측정 도구를 제공한다. 이 중 mean_absolute_error는 타깃과 예측의 절댓값 오차를 평균하여 반환한다.

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

결과에서 예측이 평균적으로 19g 정도 타깃값과 다르다는 것 알 수 있다.
지금까지는 훈련 세트를 사용해 모델을 훈련하고 테스트 세트로 모델 평가했다.
but, 훈련 세트를 사용해 평가해 본다면??? 즉, score() 메서드에 훈련 세트를 전달하여 점수 출력해보는 것!! 테스트 세트의 점수와 다를 것이다!!

과대적합 vs 과소적합

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

모델을 훈련 세트에 훈련하면 훈련 세트에 잘 맞는 모델이 만들어진다. 훈련 세트에서 모델을 훈련했으므로 훈련 세트의 점수가 조금 더 높게 나온다.
과대적합(overfitting) : 훈련 세트에서 점수가 굉장히 좋았는데 테스트 세트에서 점수가 나쁠 때 훈련 세트에 붙이는 말.
즉, 훈련 세트에만 잘 맞는 모델이라 테스트 세트와 나중에 실전에 투입하여 새로운 샘플에 대한 예측을 만들 때 잘 동작하지 않을 것.
과소적합(underfitting) : 훈련 세트보다 테스트 세트의 점수가 높거나 두 점수가 모두 낮은 경우 훈련 세트에 붙이는 말.
즉, 모델이 너무 단순하여 훈련 세트에 적절히 훈련되지 않은 경우.

훈련 세트와 테스트 세트의 점수를 비교했을 때 훈련 세트가 너무 높으면 과대적합, 그 반대이거나 두 점수가 모두 낮으면 과소적합.
과소 적합 일어나는 이유는?? : 훈련 세트와 테스트 세트의 크기가 매우 작아서.

앞서 k-최근접 이웃 회귀로 평가한 훈련 세트와 테스트 세트의 점수는 --> 훈련 세트보다 테스트 세트의 점수가 높으니 과소적합. 이 문제를 어떻게 해결할까??

모델을 조금 더 복잡하게 만들면 된다. 즉, 훈련 세트에 더 잘 맞게 만들면 테스트 세트의 점수는 조금 낮아질 것. k-최근접 이웃 알고리즘으로 모델을 더 복잡하게 만드는 방법은 이웃의 개수 k를 줄이는 것!
이웃의 개수를 줄이면 훈련 세트에 잇는 국지적인 패턴에 민감해지고, 이웃의 개수 늘리면 데이터 전반에 있는 일반적인 패턴을 따를 것이다.
여기서 사이킷런의 k-최근접 이웃 알고리즘의 기본 k 값은 5!!

# 이웃의 개수를 3으로 설정
# n_neighbors 속성값 바꾸면 된다!
knr.n_neighbors = 3

# 모델을 다시 훈련한다.
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
>>> 0.9804899950518966

k값 줄이니 훈련 세트의 R^2 점수가 높아짐!!

# 테스트 세트의 점수 확인
print(knr.score(test_input, test_target))
>>> 0.974645996398761

테스트 세트의 점수는 훈련 세트보다 낮아졌으므로 과소적합 문제 해결. 그리고 두 점수의 차이가 크지 않으므로 과대적합도 아님.
성공적으로 회귀 모델 훈련함!!!

전체 코드 (출처 : https://bit.ly/hg-03-1)

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

import matplotlib.pyplot as plt
plt.scatter(perch_length, perch_weight)
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

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)
    
test_array = np.array([1,2,3,4])
print(test_array.shape)

test_array = test_array.reshape(2, 2)
print(test_array.shape)

train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
print(train_input.shape, test_input.shape)

from sklearn.neighbors import KNeighborsRegressor

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

knr.score(test_input, test_target)

from sklearn.metrics import mean_absolute_error

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

print(knr.score(train_input, train_target))

# 이웃의 개수를 3으로 설정합니다
knr.n_neighbors = 3

# 모델을 다시 훈련합니다
knr.fit(train_input, test_target)
print(knr.score(train_input, test_target))

print(knr.score(test_input, test_target))

키워드로 끝내는 핵심 포인트

  • 회귀는 임의의 수치를 예측하는 문제. 따라서 타깃값도 임의의 수치가 된다.
  • k-최근접 이웃 회귀는 k-최근접 이웃 알고리즘을 사용해 회귀 문제를 푼다. 가장 가까운 이웃 샘플 찾고 이 샘플들의 타깃값을 평균하여 예측으로 삼는다.
  • 결정계수(R^2) 는 대표적인회귀 문제의 성능 측정 도구. 1에 가까울수록 좋고, 0에 가까울 수록 성능 나쁜 모델.
  • 과대적합은 모델의 훈련 세트 성능이 테스트 세트 성능보다 훨씬 높을 때 일어남. 모델이 훈련 세트에 너무 집착해서 데이터에 내재된 거시적인 패턴 감지 못함.
  • 과소적합은 훈련 세트와 테스트 세트 성능이 모두 동일하게 낮거나 테스트 세트 성능이 오히려 더 높을 때 일어난다. 이런 경우 더 복잡한 모델을 사용해 훈련 세트에 잘 맞는 모델 만들어야 한다.

핵심 패키지와 함수

scikit-learn

  • KNeighborsRegressor는 k-최근접 이웃 회귀 모델 만드는 사이킷런 클래스. n_neighbors 매개변수로 이웃의 개수를 지정. 기본값은 5. 다른 매개변수는 KNeighborsClassifier 클래스와 거의 동일
  • mean_absolute_error() 는 회귀 모델의 평균 절댓값 오차를 계산. 첫 번째 매개변수는 타깃, 두 번째 매개변수는 예측값을 전달한다. 이와 비슷한 함수로는 평균 제곱 오차를 계산하는 mean_squared_error()가 있다. 이 함수는 타깃과 예측을 뺸 값을 제곱한 다음 전체 샘플에 대해 평균한 값을 반환한다.

numpy

  • reshape() 는 배열의 크기를 바꾸는 메서드. 바꾸고자 하는 배열의 크기를 매개변수로 전달. 바꾸기 전후의 배열 원소 개는 동일해야 한다. 넘파이는 종종 배열의 메서드와 동일한 함수를 별도로 제공. 이때 함수의 첫 번째 매개변수는 바꾸고자 하는 배열.
profile
초보 중의 초보. 열심히 하고자 하는 햄스터!

0개의 댓글