이전 글: 선형 회귀 및 다항 회귀(1)
이전에는 학습용 데이터셋으로 모델 회귀모델 학습 후, 테스트용 데이터셋으로 해당 모델을 평가하였다.

이번에는 테스트용 데이터셋이 아닌 새로운 데이터셋을 모델에 넣어 무게를 예측해보자. 길이가 50cm, 무게가 1500g인 농어가 있다. 이 농어의 무게를 길이 데이터를 이용하여 예측해보자.

k-최근접 이웃의 한계

print(knr.predict([[50]]))  #50cm인 농어의 무게 예측하기
#출력값: [1033.333]

해당 농어의 원래 무게는 1500g이다. 하지만 해당 모델은 이보다 많이 낮은 1033g으로 예측을 하였다. 이 모델은 왜 이렇게 예측을 하는 것일까?

직관적으로 확인하기 위하여 가장 가까운 이웃의 거리와 인덱스를 반환하는 kneighbors() 메서드와 산점도(Scatter)를 이용하자.

import matplotlib.pyplot as plt

distances, indexes = knr.kneighbors([[50]])

plt.scatter(train_X, train_y)
plt.scatter(train_X[indexes], train_y[indexes], marker='D')     #길이가 50cm인 농어와 가장 근접한 점 3개

plt.scatter(50, 1033, marker='^')
plt.xlabel('length')
plt.ylabel('weight')

plt.show()

이 산점도를 보면 농어의 길이가 커질수록 무게도 증가하는 경향이 있다. 하지만 갑자기 이런 엉뚱한 값이 나오는 이유는 바로 k-최근접 이웃 모델이 타깃을 구하는 방법에 있다.

k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 평균값을 구하여 예측한다. 하지만 50cm 광어와 가장 가까운 샘플 3개는 모두 50cm보다 작은, 45cm근방의 점들의 평균값으로 정하기 때문에 우리의 예상과는 다르게 엉뚱한 값으로 예측할 수 밖에 없다.

예를 들어, 길이가 100cm인 농어와 가장 가까운 점 역시 45cm 근방의 점들이기 때문에 여전히 1033g으로 예측할 수 밖에 없다.

#길이를 100cm까지 더 늘려보자.
new = knr.predict([[100]])      #길이가 100cm인 농어의 무게를 예측한 값
#new의 값: 1033

distances, indexes = knr.kneighbors([[100]])

plt.scatter(train_X, train_y)
plt.scatter(train_X[indexes], train_y[indexes], marker='D')     #길이가 100cm인 농어와 가장 근접한 점 3개

plt.scatter(100, new, marker='^')
plt.xlabel('length')
plt.ylabel('weight')

plt.show()

이런 식이면 농어가 아무리 커도 무게가 더 이상 늘어나지 않는다.

k-최근접 이웃 알고리즘의 한계점
새로운 샘플이 학습용 데이터셋의 범위를 벗어나면 예측값은 더 이상 변하지 않는다.

선형 회귀(Linear Regression)

선형 회귀는 가장 대표적인 회귀 알고리즘이다. 비교적 간단하고 성능이 뛰어나다. 선형이라는 말에서 짐작할 수 있듯이 특성이 하나인 경우, 어떤 직선을 학습하는 알고리즘이다.

사이킷런의 LinearRegression 클래스를 이용하여 선형 회귀 모델을 학습시켜보자.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()

lr.fit(train_X, train_y)
print(lr.predict([[50]]))
#출력값: [1241.83860323]

농어의 길이가 50cm일 때 k-최근접 이웃 회귀를 사용했을 때는 1033g이 나왔다. 그에 비해 선형 회귀는 1241g으로 k-최근접 이웃 회귀 모델과 비교하였을 때 높은 무게로 예측하였다.

선형 회귀는 어떻게 예측할까?

y = a*x + b 라는 방정식으로 쓸 수 있다. 여기에서 y는 우리가 예측하고자 하는 값(종속변수)인 농어의 무게, x는 우리가 예측할 때 사용하는 데이터(독립변수) 농어의 길이를 의미한다.

그렇다면 해당 모델은 데이터와 잘 맞는 a와 b의 값을 찾았을까요?

print(lr.coef_, lr.intercept_)
# 출력값: [39.01714496], -709.018644953547

이제 학습용 데이터셋과 그에 알맞는 방정식을 산점도로 그려보자.

# 훈련 세트의 산점도를 그립니다
x = np.array(range(15,51))

plt.scatter(train_X, train_y)
plt.plot(x, lr.coef_*x + lr.intercept_)  # 15에서 50까지 1차 방정식 그래프를 그립니다
plt.scatter(50, 1241.8, marker='^') # 50cm 농어 데이터

plt.xlabel('length')
plt.ylabel('weight')

plt.show()

보이는 직선이 해당 데이터셋에서 찾은 최적의 직선이다.

이제 k-최근접 이웃 알고리즘과는 다르게 학습용 데이터셋의 범위에 벗어난 농어의 무게도 위의 방정식을 이용하여 예측할 수 있게 된다.
그러면 이 전과 같이 학습용 데이터셋과 테스트용 데이터셋에 대한 결정계수(R2R^2)를 확인해보자.

print(lr.score(train_X, train_y))
#출력값: 0.9398463339976041
print(lr.score(test_X, test_y))
#츌력값: 0.8247503123313562

테스트 데이터셋의 점수가 학습용 데이터셋의 점수에 비해 상당히 낮다. 이는 즉 과대적합(Overfitting)이다.

또한 산점도를 본다면, 해당 방정식은 아래로 쭉 뻗어있다. 이 직선대로 예측한다면 농어의 무게는 0g으로 예측할텐데, 이는 현실에서 있을 수 없는 일이다.

산점도를 다시 본다면 학습용 데이터셋을 나타낸 파란 점들이 일직선이라기보다는 구부러진 곡선에 가깝다. 그렇다면 최적의 직선보다는, 최적의 곡선을 찾아야 한다.

다항 회귀(Polynomial Regression)

이런 2차 방정식의 그래프를 그리면 최적의 곡선을 찾을 수 있다. 2차 방정식의 그래프를 그리려면 길이를 제곱한 데이터가 학습용 데이터셋에 추가되어야 한다. 넘파이의 column_stack을 이용하여 추가하자.

train_X_poly = np.column_stack((train_X ** 2, train_X))
test_X_poly = np.column_stack((test_X ** 2, test_X))

이제 제곱한 값을 추가한 새로운 데이터로 다시 한번 회귀 모델을 학습시키자.

lr = LinearRegression()
lr.fit(train_X_poly, train_y)

new = lr.predict([[50**2, 50]])  #독립변수인 X를 제곱시켰으므로, 타깃도 제곱하자.
print(new)    #농어의 길이가 50cm인 데이터
#출력값: [1573.98423528]

원래 50m의 농어의 무게는 1500g이다. 예측을 얼추 정확하게 하는 것으로 보인다.

이제 이 모델이 훈련한 계수(coef)와 절편(intercept)를 출력해보자.

print(lr.coef_, lr.intercept_)
#출력값: [  1.01433211, -21.55792498] , 116.05021078278293

즉 이 모델의 방정식은 아래와 같다.

무게 = 1.01 무게^2 - 21.6 길이 + 116.05

이런 방정식을 다항식(polynimial)이라 부르며, 다항식을 사용한 선형 회귀를 다항 회귀(polynomial regression)이라 한다.

이제 학습용 데이터셋과 이 다항식을 산점도(scatter)로 그려보자.

# 훈련 세트의 산점도를 그립니다
x = np.array(range(15,51))

plt.scatter(train_X, train_y)
plt.plot(x, (lr.coef_[0] * (x ** 2)) + (lr.coef_[1] * x) + lr.intercept_)  # 15에서 50까지 1차 방정식 그래프를 그립니다
plt.scatter(50, new, marker='^') # 50cm 농어 데이터

plt.xlabel('length')
plt.ylabel('weight')

plt.show()


앞선 단순 선형 회귀 모델보다는 훨씬 나은 그래프가 그려졌다. 2차원 곡선이라 무게가 음수로 나올 일도 없다.
이제 이 모델의 결정계수(R-Squared)를 구해보자.

print(lr.score(test_X_poly, test_y))
#출력값: 0.9775935108325123
print(lr.score(train_X_poly, train_y))
#출력값: 0.9706807451768623

요약

  • k-최근점 이웃 회귀는 이웃한 점들의 평균을 구하여 예측한다.
  • 그러기 때문에 학습용 데이터셋과 아무 멀리 떨어진 점이 있다 하더라도 무조건 가까운 점들의 평균을 구하기 때문에 엉뚱한 값이 나올 수 있다.
  • k-최근접 이웃 회귀의 단점을 보완한 것이 선형 회귀이다.
  • 하지만 선형 회귀 또한 모델 자체가 너무 단순하여 엉뚱한 값이 있을 수 있다.
  • 이 선형 회귀를 보완한 것이 다항 회귀이다.
profile
노력하는 개발자

0개의 댓글