
회귀하면 또 니체죠…
근데 회귀는 돌아온다는 뜻 아님?

회귀는 돌아오는 거야!
‘프랜시스 콜턴’이라는 통계 / 사회 학자가 키 큰 부모의 아이가 부모보다 더 크지 않다는 사실을 관찰하고 이를
평균으로 회귀한다
라고 표현했답니다. 그 후로 이렇게 굳어졌다는…
그래서 정의하면
이제 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]
)
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)
train_test_split 함수로 훈련/테스트 세트를 나눴다
결과 유지를 위해 시드는 역시 42로 고정
사이킷런에서 훈련 세트는 2차원 배열이여야 함
[1, 2, 3] → [[1], [2], [3]]
크기: (3, ) | (3, 1)
이렇게
💡하나하나 변환하기 귀찮아하는 게으른 나를 위한 딸깍이 있다.
그것이 파이썬 쓰는 이유니까…
test_array = np.array([1,2,3,4])
print(test_array.shape)
test_array = test_array.reshape(2,2)
print(test_array.shape)
# 결과
(4,)
(2, 2)
!주의
test_array = test_array.reshape(2, 3)
# 결과
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-9-4d70e0624945> in <cell line: 0>()
----> 1 test_array = test_array.reshape(2, 3)
ValueError: cannot reshape array of size 4 into shape (2,3)
이제 reshape를 활용하여 train_input 과 test_input 을 2차원 배열로 만들자
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)
이제 준비한 훈련 세트로 k-최근접 이웃 알고리즘을 훈련시켜보자
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor()
knr.fit(train_input, train_target)
print(knr.score(test_input, test_target))
# 결과
**0.992809406101064**
분류의 경우 테스트 세트에 있는 샘플을 정확하게 분류한 개수의 비율(정확도, 정답 맞힌 개수 비율)
회귀에서는 정확한 숫자를 맞히는 것은 거의 불가능
회귀에서는 다르게 평가하는데 이 점수를 결정 계수라고 함()
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정도 타깃값과 다르다.
위 결과는 훈련 세트로 훈련하고 테스트 세트로 평가했다.
평가도 훈련세트로 하면 어떻게 될까?
훈련 세트의 점수를 확인하자
print(knr.score(train_input, train_target))
# 결과
0.9698823289099254
모델을 좀 더 복잡하게 만들면 이 문제를 해결할 수 있다.
knr.n_neighbors = 3
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
print(knr.score(test_input, test_target))
# 결과
0.9804899950518966
0.9746459963987609
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)
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor(n_neighbors = 3)
knr.fit(train_input, train_target)
print(knr.predict([[50]]))
# 결과
[1033.33333333]
어째서…?
import matplotlib.pyplot as plt
distances, indexes = knr.kneighbors([[50]])
plt.scatter(train_input, train_target)
# 이웃 샘플 강조
plt.scatter(train_input[indexes], train_target[indexes], marker = 'D')
# 길이 50 농어 데이터 강조
plt.scatter(50, 1033, marker = 'D')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()


빙고
1033.33333333 만 나오게 된다.이 문제를 해결하는 다른 알고리즘이 있답니다.

그럼 이 직선을 인식하는 것도 구현해야 함?
선형 회귀 알고리즘을 구현한 사이킷런 제공 클래스
한마디로 ‘딱 봐도 이거야!’ 하는 직선을 이 깡통이 찾을 수 있게 해주는 클래스
마찬가지로 fit(훈련), score(평가), predict(예측) 메서드 제공
훈련 모델 만들고 예측해보자
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_input, train_target)
print(lr.predict([[50]]))
# 결과
[1241.83860323]
LinearRegression은 어떻게 선을 그려서 예측할까?

x: 농어 길이
y: 농어 무게
coef_ 와 intercept_ 에 저장한다.print(lr.coef_, lr.intercept_)
# 결과
[39.01714496] -709.0186449535477
*머신러닝에선 기울기를 coefficient(계수) 또는 weight(가중치) 라고 하기도 함
lr.coef_, lr.intercept_ 를 머신러닝 알고리즘이 찾은 값 이라는 의마로 모델 파라미터 라고 부름기울기와 절편이 있으니 직선을 그려보자
plt.scatter(train_input, train_target)
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
plt.scatter(50, 1241.8, marker = '^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()


성공
print(lr.score(train_input, train_target))
print(lr.score(test_input, test_target))
# 결과
0.939846333997604
0.8247503123313558
근데 직선 그래프를 좀 더 자세히 들여다 보자

이질적인 느낌이 들지 않는가?
직선이 우상향 이다.
이 직선대로 예측하면 농어의 무게가 0 이하로 내려가게 된다.
0g인 반물질 물고기가 현실에 존재 할 리 없는데 말이다.
최선의 직선 말고 곡선을 찾으면 더 좋을 것 같다.
곡선이 있는 그래프는 2차 방정식이 필요하겠지?

그럼 2차 방정식에 맞게 길이를 또 제곱 해줘야 함
2장에서 썼던 column_stack 함수를 쓰면 간단하게 할수있다.
농어의 길이를 제곱해서 원래 데이터 앞에 붙여보자

train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
print(train_poly.shape, test_poly.shape)
# 결과
(42, 2) (14, 2)
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.predict([[50**2, 50]]))
# 결과
[1573.98423528]
좀 더 정답에 가까운 예측 결과다 나왔다.
이 모델이 훈련한 계수와 절편을 출력해 보자
print(lr.coef_, lr.intercept_)
# 결과
[ 1.01433211 -21.55792498] 116.0502107827827
이제 산점도에 그래프를 그려보자
point = np.arange(15, 50)
#산점도
plt.scatter(train_input, train_target)
# 2차 방정식 그래프
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)
# 50 농어 데이터
plt.scatter([50], [1574], marker = '^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

단순 선형 회귀보다 다항 회귀가 더 좋은 그래프가 그려졌다.
역시나 훈련 세트와 테스트세트의 R스퀘어 점수를 살펴보자
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
# 결과
0.9706807451768623
0.9775935108325122