티스토리 뷰
03-2 선형 회귀¶
- 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]
)
이번 데이터를 훈련세트와 테스트 세트로 나누자. 특성 데이터는 2차원 배열로 변환하자.
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차원 배열로 바꾸자.
train_input = train_input.reshape(-1, 1)
test_input = test_input.reshape(-1, 1)
최근접 이웃 개수를 3으로 하는 모델을 훈련하자.
from sklearn.neighbors import KNeighborsRegressor
knr = KNeighborsRegressor(n_neighbors=3)
# k-최근접 이웃 회귀 모델을 훈련한다.
knr.fit(train_input, train_target)
KNeighborsRegressor(n_neighbors=3)
print(knr.predict([[50]]))
[1033.33333333]
위 모델은 50cm 농어의 무게를 1033g으로 예측했다. 하지만 이 농어의 무게는 실제 더 많이 나간다고 한다. 무엇이 문제일까?
훈련 세트와 50cm 농어 그리고 이 농어의 최근접 이웃을 산점도에 표시해보자.
앞서 배운 kneighbors() 메서드를 사용하면 가장 가까운 이웃까지의 거리와 이웃 샘플의 인덱스를 얻을 수 있다.
import matplotlib.pyplot as plt
# 50cm 농어의 이웃을 구한다.
distances, indexes = knr.kneighbors([[50]])
# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)
# 훈련 세트 중에서 이웃 샘플만 다시 그린다.
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
# 50cm 농어 데이터
plt.scatter(50, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
길이가 50이고 무게가 1033인 농어는 세모로 표시되고 그 주변의 샘플은 마름모이다. 이 산점도를 보면 길이가 커질수록 농어의 무게가 증가하는 경향이 있다. 하지만 50cm 농어에서 가장 가까운 것은 45cm 근방이기 때문에 k-최근접 이웃 알고리즘은 이 샘플들의 무게를 평균한다. 이웃 샘플의 타깃의 평균을 구하자.
print(np.mean(train_target[indexes]))
1033.3333333333333
모델이 예측했던 값과 정확히 일치한다. k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 타깃을 평균한다. 따라서 새로운 샘플이 훈련 세트의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다. 예를 들어 길이가 100cm인 농어도 여전히 1033g으로 예측한다.
print(knr.predict([[100]]))
[1033.33333333]
그래프를 한번 더 그려보자.
# 100cm 농어의 이웃을 구한다.
distances, indexes = knr.kneighbors([[100]])
# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)
# 훈련 세트 중에서 이웃 샘플만 다시 그린다.
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
# 100cm 농어 데이터
plt.scatter(100, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
이런 식이면 농어가 아무리 커도 무게가 더 늘어나지 않는다. k-최근접 이웃을 사용해 이 문제를 해결하려면 가장 큰 농어가 포함되도록 훈련 세트를 다시 만들어야 한다.
- 선형 회귀¶
선형 회귀는 널리 사용되는 대표적인 회귀 알고리즘이다. 비교적 간단하고 성능이 뛰어나기 때문에 맨 처음 배우는 머신러닝 알고리즘 중 하나이다. 선형이라는 말에서 짐작할 수 있듯 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘이다.
사이킷런은 sklearn.linear_model 패키지 아래에 LinearRegression 클래스로 선형 회귀 알고리즘을 구현해 놓았다. 이 클래스의 객체를 만들어 훈련해보자. LinearRegression 클래스에도 fit(), score(), predict() 메서드가 있다.
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
# 선형 회귀 모델을 훈련한다.
lr.fit(train_input, train_target)
# 50cm 농어에 대해 예측한다.
print(lr.predict([[50]]))
[1241.83860323]
k-최근접 이웃 회귀를 사용했을 때와 달리 선형 회귀는 50cm 농어의 무게를 아주 높게 예측했다. 왜 이런 값이 나왔을까? 이 선형 회귀가 학습한 직선을 그려 보고 어떻게 이런 값이 나왔는지 알아보자. 하나의 직선을 그리려면 기울기와 절편이 있어야 한다. y = a * x + b 처럼 쓸 수 있다. 여기에서 x를 농어의 길이, y를 농어의 무게로 바꾸면 학교에서 배웠던 간단한 직선 방정식이다. 그렇다면 LinearRegression 클래스가 이 데이터에 가장 잘 맞는 a와 b를 찾았을까? LinearRegression 클래스가 찾은 a와 b느니 lr 객체의 coef와 intercept 속성에 저장되어 있다.
print(lr.coef_, lr.intercept_)
[39.01714496] -709.0186449535474
농어의 길이를 15에서 50까지 직선으로 그려보자. 앞서 구한 기울기와 절편을 이용하자. 그리고 훈련 세트의 산점도와 함께 그려보자.
# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)
# 15에서 50까지 1차 방정식 그래프를 그린다.
plt.plot([15, 50], [15*lr.coef_+lr.intercept_, 50*lr.coef_+lr.intercept_])
# 50cm 농어 데이터
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
위의 직선이 바로 선형 회귀 알고리즘이 이 데이터셋에서 찾은 최적의 직선이다. 길이가 50cm인 농어에 대한 예측은 이 직선의 연장선에 있다. 이제 훈련 세트 범위를 벗어난 농어의 무게도 예측할 수 있다. 이제 훈련 세트와 테스트 세트에 대한 결정계수를 구해보자.
print(lr.score(train_input, train_target)) # 훈련세트
print(lr.score(test_input, test_target)) # 테스트 세트
0.9398463339976041 0.824750312331356
훈련 세트와 테스트 세트의 점수가 조금 차이가 난다. 이 모델이 훈련 세트에 과대적합되었다고 말할 수 있을까? 사실 훈련 세트의 점수도 높지 않다. 오히려 과소적합 되었다고 볼 수 있다. 하지만 다른 문제가 있다. 그래프 왼쪽 아래를 보면 뭔가 이상하다.
- 다항 회귀¶
농어의 길이와 무게에 대한 산점도를 자세히 보면 일직선이라기보다 왼쪽 위로 조금 구부러진 곡선에 가깝다. 그렇다면 최적의 직선을 찾기보단 최적의 곡선을 찾아야한다. 이런 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)
원래 특성인 길이를 제곱하여 왼쪽 열에 추가했기 때문에 훈련 세트와 테스트 세트 모두 열이 2개로 늘어났다.
이제 train_ploy를 사용해 선형 회귀 모델을 다시 훈련하자. 여기서 주목할 점은 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.05021078278276
이런 방정식을 다항식이라 부르며 다항식을 사용한 선형회귀를 다항회귀라고 부른다. 이제 2차 방정식의 계수와 절편 a,b,c를 알았으니 이전과 동일하게 산점도에 그래프로 그려보자. 짧은 직선을 이어서 그리면 곡선처럼 그릴 수 있다.
# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만든다.
point = np.arange(15,50)
# 훈련 세트의 산점도를 그린다.
plt.scatter(train_input, train_target)
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)
# 50cm 농어 데이터
plt.scatter([50], [1574], marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()
앞서 단순 선형 회귀 모델보다 훨씬 나은 그래프가 그려졌다. 이제 훈련 세트와 테스트 세트의 결정계수를 평가하자.
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
0.9706807451768623 0.9775935108325121
점수가 크게 좋아졌다. 하지만 여전히 테스트 점수가 높아 과소적합이 남아있는듯 하다.
출처 : 혼자 공부하는 머신러닝 + 딥러닝
'혼자공부하는 머신러닝+딥러닝' 카테고리의 다른 글
혼자 공부하는 머신러닝+딥러닝(ch4-1 로지스틱 회귀) (0) | 2021.04.30 |
---|---|
혼자 공부하는 머신러닝+딥러닝(ch3-3 특성 공학과 규제) (1) | 2021.04.29 |
혼자 공부하는 머신러닝+딥러닝(ch.3-1 k-최근접 이웃 회귀) (0) | 2021.04.27 |
혼자 공부하는 머신러닝+딥러닝(ch.2) (0) | 2021.04.26 |
혼자 공부하는 머신러닝+딥러닝(ch.1) (0) | 2021.04.25 |