[혼자공부하는 머신러닝 + 딥러닝] 6_선형회귀
5. 선형 회귀
1. 들어가며
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsRegressor
from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt
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])
- 필요한 라이브러리를 임포트하고, 농어의 데이터를 불러온다.
- 이후 데이터를 훈련 세트와 테스트 세트로 분리한다.
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)
- 각 셋의 입력값을 2차원배열로 만들어줍니다.
- 이후 이웃 개수를 3개로 하는 모델을 훈련합니다.
knr = KNeighborsRegressor(n_neighbors=3)
knr.fit(train_input, train_target)
print(knr.predict([[50]]))
[1033.33333333]
- perdict()를 사용해 길이가 50센치인 농어의 무게가 1033kg정도로 예측되었다. 하지만 실제로는 이보다 클 수도 있다.
- 산점도를 이용해 근처에 있는 이웃을 표시해서 확인해 보겠다.
- kneighbors()
distance, indexes = knr.kneighbors([[50]])
plt.scatter(train_input, train_target)
plt.scatter(50, 1033, marker='^')
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
<matplotlib.collections.PathCollection at 0x1b814785790>
- 해당 산점도는 길이가 커질수록 무게가 증가하는 경향이 있다. 하지만 길이가 50cm인 농어는 45cm 근방이기 때문에 k-최근접 이웃 알고리즘의 근처 농어의 무게 평균인 1033을 50cm의 농어의 무게로 예측했다.(45cm의 길이가 넘어가는 농어는 1033kg으로 나타날 것이다.)
distance, indexes = knr.kneighbors([[100]])
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D')
plt.scatter(100, 1033, marker='^')
<matplotlib.collections.PathCollection at 0x1b81470a040>
- 100cm 짜리 농어 역시 같은 결과를 가진다. 위와 같은 문제가 계속되면 농어가 아무리 커져도 무게가 더 늘어나지는 않는다. 이를 해결하기 위해서는 선형회귀를 사용해야 한다.
2. 선형회귀
- 선형 회귀는 널리 사용되는 대표적인 회귀알고리즘이다. 비교적 간단하고 성능이 뛰어나기 때문이다.
- 선형이라는 말에서 짐작할 수 있듯이 특성이 하나인 경우 어떤 직선을 학습하는 알고리즘을 말한다.
- 사이킷런은 sklearn.liner_model 패키지 아래에 LinearRegression 클래스로 선형 회귀 알고리즘을 구현해 놓았다.
- 사이킷런의 모델 클래스들은 훈련, 평가, 예측하는 메서드 이름이 모두 동일하다. 이말인 즉슨 여기서도 fit(), score(), predict()메서드를 다 사용이 가능하다는 것이다.
lr = LinearRegression()
lr.fit(train_input, train_target)
LinearRegression()
print(lr.predict([[50]]))
[1241.83860323]
- 50cm 의 농어 무게를 이전에 K-최근접 이웃회귀를 사용했을떄보다 높게 예측되었다.
- 하나의 직선을 그리려면 기울기와 절편이 있어야 한다. y= a * x+b 에서 x는 농어의 길이 y는 농어의 무게로 볼 수 있다. 여기서 LinearRegression은 적절한 a, b를 찾는다. 이는 lr객체에 coef_와 intercept_속성에 저장되어 있습니다.
- coef_와 intercept_를 머신러닝 알고리즘이 찾은 값이라는 의미로 모델 파라미터라고 부른다. 이 모델 파라미터를 가지는 모델들은 모델 기반 학습이라 한다.
- 모델파라미터가 없는 k-최근접 이웃 같은 모델은 사례기반 학습이라고 한다.
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('wight')
plt.show()
- 위의 그래프가 선형 회귀 알고리즘이 해당 데이터셋에서 찾은 최적의 직선이다. 그렇다면 훈련세트와 테스트세트에 대한 결정계수값을 찾아보자.
print(lr.score(train_input, train_target))
print(lr.score(test_input, test_target))
0.9398463339976041
0.824750312331356
- 훈련세트와 테스트 세트는 점수차가 난다. 이는 훈련 세트 또한 점수가 낮기 때문에 과소적합의 문제가 있어보인다. 과소적합 뿐 아니라 다른 문제도 있다.
3. 다항회귀
- 위의 그래프에서 왼쪽 하단의 모양을 살펴보면 그래프가 직선이기 때문에 무게가 0인 농어도 존재한다고 볼 수 있지만 현실적으로 불가능하다.
- 농어 길이와 무게에 대한 그래프를 보면 완만하게 구부러진 선형을 보이고 있다.
- 최적의 직선보다는 최적의 곡선을 찾는것이 좀 더 바람직해 보인다.(2차 방정식의 그래프를 그리기 위해 제곱한 항을 넘파이를 이용해 추가해준다.)
train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
print(train_poly[:5])
[[ 384.16 19.6 ]
[ 484. 22. ]
[ 349.69 18.7 ]
[ 302.76 17.4 ]
[1296. 36. ]]
print(train_poly.shape)
print(test_poly.shape)
(42, 2)
(14, 2)
- 원래 특성인 길이를 제곱해 왼쪽열에 추가했기 때문에 훈련 세트와 테스트 세트 모두 열이 2개로 늘어난 것을 확인할 수 있다.(넘파이 브로드캐스팅이 적용되어 모든 원소를 제곱해 넣어주었다. ~간편하다~)
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
- 위의 모델은 2차 방정식이라 비선형으로 볼 수도 있다. 제곱의 길이를 다른 변수로 치환해 사용하면 선형이라고 볼 수 있다. 이런 방정식을 다항식이라 부르며, 다항식을 사용한 선형 회귀를 다항회귀라고 부른다.
- 이를 이용해 다시 산점도를 그려보자.
point = np.arange(15, 50)
plt.scatter(train_input, train_target)
plt.plot(point, 1.01*point**2 - 21.6*point + 116.05)
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.9775935108325122
- 테스트세트와 훈련세트의 점수가 이전보다 상승했다. 하지만 여전히 테스트 세트의 점수가 더 높은 것으로 볼 때, 과소적합문제가 아직 남아있다.는 것을 알 수 있다.
4. 마치며
- 이번 시간에는 선형회귀와 다항회귀에 대해 알아보는 시간이였다. 이번 장에서 과소적합 문제를 완전하게 해결하지 못했다. 얼른 다음장을 이어서 공부해야겠다.
Leave a comment