Python/ML & DL 공부

[ML] 03-2 선형 회귀

dori_0 2022. 3. 6. 15:21

혼자 공부하는 머신러닝+딥러닝 책을 바탕으로 공부한 내용입니다.

 

CH3 회귀 알고리즘과 모델 규제 ②

선형 회귀로 훈련 세트 범위 밖의 샘플 예측하기

 


 

k-최근접 이웃 회귀와 선형 회귀 알고리즘의 차이 이해하기
사이킷런을 사용해 여러 가지 선형 회귀 모델을 만들어보자

 

농어 담담 직원이 길이가 50cm인 농어를 들고와 무게를 예측해달라고 하였다.
이전에 만든 모델을 사용해 농어의 무게를 예측해보니,
저울에 나온 농어의 무게와 모델이 예측한 무게가 너무 차이가 났다.

이 문제점을 해결해보자

 

 

▶ k-최근접 이웃의 한계

이전에 만들었던 모델을 가져와 길이가 50cm인 농어의 무게를 예측해보자

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])
       
# train set, test set으로 나누기
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
model = KNeighborsRegressor(n_neighbors = 3)
model.fit(train_input, train_target)

# 길이가 50cm인 농어의 무게 예측해보기
print(model.predict([[50]]))

  • 모델은 50cm 농어의 무게를 실제 무게보다 훨씬 적은 1033g으로 예측했다.

 

왜 이러한 문제가 생긴건지 50cm 농어와 최근접 이웃 산점도를 그려 확인해보자

# 50cm농어, 농어의 최근접 이웃 산점도
import matplotlib.pyplot as plt

# 농어의 이웃 구하기
distances, indexes = model.kneighbors([[50]])

# 산점도 그리기
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D') # 이웃샘플
plt.scatter(50, 1033, marker='^')  # 50cm 농어
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

  • k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 타깃을 평균한다.
  • 새로운 샘플이 train set의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다.

 

 

길이가 100cm인 농어의 무게도 확인해보자

model.predict([[100]])  #1033.33333333

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

# 산점도 그리기
plt.scatter(train_input, train_target)
plt.scatter(train_input[indexes], train_target[indexes], marker='D') # 이웃샘플
plt.scatter(100, 1033, marker='^')  # 50cm 농어
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

  • 100cm 농어의 무게도 1033g으로 예측하고 있다.

 

k-최근접 이웃 회귀는 가장 가까운 샘플을 찾아 타깃의 평균으로 예측을 하므로

이렇게 새로운 샘플이 훈련 세트 범위 밖에 있다면 엉뚱한 값을 예측하는 단점이 있다.

 

 

▶ 선형 회귀

선형 회귀는 널리 사용되는 대표적인 회귀 알고리즘이다.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()

# 선형 회귀 모델 훈련 후 예측
lr.fit(train_input, train_target)
lr.predict([[50]])  #array([1241.83860323])
  • 이전과 달리 50cm 농어의 무게를 높게 측정하였다.
lr.coef_, lr.intercept_  #(array([39.01714496]), -709.0186449535477)

이 모델은 다음과 같은 그래프를 학습했다.

 

 

plt.scatter(train_input, train_target)

# 15에서 50까지 일차방정식 그래프 그리기
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()

  • 이 직선이 선형 회귀 알고리즘이 데이터셋에서 찾은 최적의 직선이다.

 

하지만, 이 직선에서 길이가 15cm 이하인 농어에 대해서는 무게를 0g 이하로 예측하고 있다.

이를 해결하기 위해 이번에는 최적의 직선이 아닌 최적의 곡선을 찾아보자

 

 

▶ 다항 회귀

2차 방정식의 그래프를 그리려면 길이를 제곱한 항이 훈련 세트에 추가되어야 한다.

column_stack() 함수를 사용해 추가해보자

train_poly = np.column_stack((train_input ** 2, train_input))
test_poly = np.column_stack((test_input ** 2, test_input))
train_poly.shape, test_poly.shape  #((42, 2), (14, 2))

 

 

이제 train_poly를 사용해 선형 회귀 모델을 다시 훈련 후 50cm 농어의 무게를 예측해보자

lr = LinearRegression()
lr.fit(train_poly, train_target)

lr.predict([[50**2, 50]])

  • 이전보다 더 높은 값을 예측했다.
lr.coef_, lr.intercept_  #(array([  1.01433211, -21.55792498]), 116.05021078278259)

  • 이 모델은 다음과 같은 그래프를 학습했다.

 

 

이제 2차방정식의 계수와 절편들을 알았으니 곡선과 농어 데이터 산점도를 그려보자

# 구간별 직선을 그리기 위해 15-49 정수 배열 만들기
point = np.arange(15, 50)

# train set의 산점도
plt.scatter(train_input, train_target)

# 15-49까지 2차 방정식 그래프 그리기
plt.plot(point, lr.coef_[0]*point**2 + lr.coef_[1]*point + lr.intercept_)

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

  • 과소적합이 조금 있는 것으로 보아 더 복잡한 모델이 필요할 것 같다 (다음 장)

 

 

▶ 정리

  1. k-최근접 이웃 회귀를 사용해 농어의 무게를 예측할 때 발생하는 큰 문제는 훈련 세트 범위 밖의 샘플을 예측할 수 없다는 점
  2. k-최근접 이웃 회귀는 아무리 멀리 떨어져있어도 가장 가까운 샘플의 타깃을 평균하여 예측함
  3. 이를 해결할 수 있는 방법은 선형 회귀, 다항 회귀 등
  4. 선형 회귀 모델의 coef_, intercept_를 통해 최적의 기울기와 절편을 구할 수 있음

 

 

 

 

'Python > ML & DL 공부' 카테고리의 다른 글

[ML] 04-1 로지스틱 회귀  (0) 2022.03.07
[ML] 03-3 특성 공학과 규제  (0) 2022.03.07
[ML] 03-1 k-최근접 이웃 회귀  (0) 2022.03.06
[ML] 02-2 데이터 전처리  (0) 2022.03.05
[ML] 02-1 훈련 세트와 테스트 세트  (0) 2022.03.03