[혼공머신] 3장 회귀 알고리즘과 모델 규제

Changh2·2024년 10월 7일
0

[혼자 공부하는 머신러닝+딥러닝] 교재 3장을 기반으로 작성되었습니다.


3-1 k-최근접 이웃 회귀

지도 학습 알고리즘은 크게 분류회귀로 나뉜다.
분류는 말 그대로 샘플을 몇개의 클래스 중 하나로 분류하는 문제이고,
회귀는 임의의 어떤 숫자를 예측하는 문제이다.
ex) 농어의 무게를 예측한다.

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

하나의 특성을 사용하기 때문에 특성 데이터를 x축에, 타깃 데이터를 y축에 두고 산점도를 그려본다.

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)   # 교재와 같은 결과를 위해 랜덤 42 지정

사이킷런에 사용할 입력 데이터는 2차원 배열이어야 한다!
perch_length 는 1차원 배열이기 때문에, 억지로 2차원 배열로 바꿔주어야 한다.

reshape( )

넘파이에서 제공하는 배열의 크기를 바꿀 수 있는 메서드

test_array = np.array([1,2,3,4])
print(test_array.shape)   # shpae: 배열의 형상을 리턴해줌
print(test_array)
>>> (4,)
	[1 2 3 4]
test_array = test_array.reshape(2,2)   # reshape 메서드로 2x2 형상으로 바꿔준다.
print(test_array.shape)
print(test_array)
>>> (2, 2)
    [[1 2]
     [3 4]]

위의 방법을 사용해 train_input과 test_input을 2차원 배열로 바꿔주자.
+) 배열의 크기를 자동으로 지정하는 기능도 제공하는데, 크기에 -1을 지정하면 나머지 원소 개수로 모두 채우라는 의미이다.

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)

결정계수(R^2)

k-최근접 이웃 회귀 알고리즘을 구현한 클래스는 KNeighborsRegressor이다.
객체를 생성하고 fit() 메서드로 회귀 모델을 훈련해보자.

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor()

knr.fit(train_input, train_target)
print(knr.score(test_input, test_target))
>>> 0.992809406101064

좋은 점수다. 이 점수의 의미는 결정계수(R^2)이고, 이는 회귀 모델을 평가하는 점수이다.
아래와 같이 정의되고, 1 에 가까울수록 성능이 좋은 것이다.

0.99면 좋은 값이라는건 알겠지만, 정확히 어느정도로 예측한건진 모르겠다.
타깃과 예측한 값 사이의 차이를 구해서 예측이 어느정도 벗어났는지 확인해보자.

from sklearn.metrics import mean_absolute_error

# 테스트 세트에 대한 예측을 만든다
test_prediction = knr.predict(test_input)

# 테스트 세트에 대한 평균 절댓값 오차를 계산한다
# mean_absolute_error는 타깃과 예측의 절댓값 오차를 평균하여 반환한다.
mae = mean_absolute_error(test_target, test_prediction)
print(mae)
>>> 19.157142857142862
# 평균적으로 예측이 타깃값과 19g 정도 다르다고 한다.

과대적합 vs 과소적합

훈련세트를 사용해 모델을 훈련하고 테스트세트로 평가한 점수와,
훈련세트를 사용해 모델을 훈련하고 훈련세트로 평가한 점수는 다를 것이다.
여기서 우린 무엇을 배울 수 있을까?

print(knr.score(train_input, train_target))
>>> 0.9698823289099254

훈련 세트에서 모델을 훈련했으므로, 훈련세트로 평가하면 당연히 더 좋은 점수가 나올 것이라고 예상되지만, 오히려 테스트 세트를 사용한 점수보다 낮은 점수가 나온 것을 볼 수 있다.

훈련 세트 점수와 테스트 세트 점수의 차이가 크면 좋지 않다!

훈련 세트에서 점수가 굉장히 좋았는데 테스트 세트에서는 점수가 굉장히 나쁜 경우엔 모델이 훈련 세트에 과대적합(overfitting)되었다고 말한다.

반대로,
(1)훈련 세트보다 테스트 세트의 점수가 높거나, (2)두 점수가 모두 너무 낮은 경우엔 모델이 훈련 세트에 과소적합(underfitting) 되었다고 말한다.

우리는 현재 과소적합이 일어난 상황인데, 모델을 더 복잡하게 만들면 된다.
복잡하게 만든다는 것은 훈련 세트에 더 잘 맞게 만든다는 뜻이다. 그렇게 되면 테스트 점수는 낮아진다.

k-최근집 이웃 알고리즘으로 모델을 복잡하게 만드는 방법은 이웃의 개수 k를 줄이는 것이다.
이웃의 개수를 줄이면 훈련 세트에 있는 국지적인 패턴에 민감해지고, 늘리면 데이터 전반에 있는 일반적인 패턴을 따른다.(과대적합일 경우에 k값을 늘려 사용)

# 이웃의 갯수를 3으로 설정한다 (기본 k값은 5이다) 
knr.n_neighbors = 3

# 모델을 다시 훈련한다
knr.fit(train_input, train_target)
print(knr.score(train_input, train_target))
>>> 0.9804899950518966

k값을 줄였더니 훈련 세트의 점수가 높아졌다.

print(knr.score(test_input, test_target))
>>> 0.9746459963987609

예상대로 테스트 세트의 점수는 낮아졌다. 이로써 과소적합 문제를 해결했다.
두 점수의 차이가 크지 않으므로 이 모델이 과대적합 된 것 같지도 않다!

이웃 n 값에 따른 모델의 변화

# k-최근접 이웃 회귀 객체를 만든다
knr = KNeighborsRegressor()
# 5에서 45까지 x 좌표를 만든다
x = np.arange(5, 45).reshape(-1, 1)
# n = 1, 5, 10일 때 예측 결과를 그래프로 그린다
for n in [1, 5, 10]:
    # 모델 훈련
    knr.n_neighbors = n
    knr.fit(train_input, train_target)
    # 지정한 범위 x에 대한 예측 구하기
    prediction = knr.predict(x)
    # 훈련 세트와 예측 결과 그래프 그리기
    plt.scatter(train_input, train_target)
    plt.plot(x, prediction)
    plt.title('n_neighbors = {}'.format(n))
    plt.xlabel('length')
    plt.ylabel('weight')
    plt.show()

n이 커짐에 따라 모델이 단순해지는 것을 볼 수 있다.


3-2 선형 회귀

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

from sklearn.neighbors import KNeighborsRegressor

knr = KNeighborsRegressor(n_neighbors=3)

# k-최근접 이웃 회귀 모델을 훈련한다
knr.fit(train_input, train_target)

print(knr.predict([[50]]))  # 길이 50의 농어의 무게를 예측한다
>>> [1033.33333333]

모델은 50cm 농어의 무게를 1033 g 정도로 예측했는데, 실제 농어의 무게는 1500g 이라고한다.

문제를 찾기 위해 Kneighbors() 메서드를 사용하여 산점도를 표시해보자.

+) Kneighbors() 메서드는 가장 가까운 이웃까지의 거리와 이웃 샘플의 인덱스를 반환해준다.
# 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()

길이가 커질수록 무게가 증가해야 하는데, k-최근접 이웃 알고리즘은 주변 이웃 샘플들의 무게를 평균하여 예측하기 떄문에 예상되는 값보다 작게 측정된것이다.

print(np.mean(train_target[indexes]))
>>> 1033.3333333333333
# 이웃 샘플의 타깃의 평균을 구해보니 모델의 예측값과 동일하다

k-최근접 이웃 알고리즘새로운 샘플이 훈련세트의 범위를 벗어나면 엉뚱한 값을 예측할 수 있다.

print(knr.predict([[100]]))
>>> [1033.33333333]
# 길이가 100인 농어도 여전히 1033g으로 예측하는 모습이다.

산점도를 그려 확인해보자.

# 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 클래스로 선형 회귀 알고리즘을 구현해 놓았고, 모델을 훈련, 평가, 예측하는 메서드는 지금까지와 모두 동일하다.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()

# 선형 회귀 모델 훈련
lr.fit(train_input, train_target)

# 50cm 농어에 대한 예측
print(lr.predict([[50]]))
>>> [1241.83860323]
# k-최근접 이웃 회귀와 달리 무게를 높게 예측한 모습

하나의 직선을 그리려면 y = a*x + b 에서 기울기 a와 절편 b가 있어야 하는데,
(x = 농어의 길이, y = 농어의 무게)로 설정하면 아래와 같다.

여기서 기울기 a와 절편 b는 LinearRegression 클래스가 적절한 값을 찾아두었고,
lr 객체의 coef_intercept_ 속성에 각각 저장되어 있다.

print(lr.coef_, lr.intercept_)
>>> [39.01714496] -709.0186449535477

coef_와 intercept_를 머신러닝 알고리즘이 찾은 값이라는 의미로 모델 파라미터라고 하고,
대부분의 머신러닝 알고리즘의 훈련 과정은 이와같은 "최적의 모델 파라미터"를 찾는 것과 같다.
이를 모델 기반 학습이라 부르고, k-최근접 이웃과 같이 모델 파라미터가 없는 것은 사례 기반 학습이라 부른다.

농어의 길이 15에서 50까지 직선으로 그려보자. (coef_ 부분 교재 코드 오류!)

# 훈련 세트의 산점도를 그린다
plt.scatter(train_input, train_target)

# 15에서 50까지 1차 방정식 그래프를 그린다
plt.plot([15, 50], [15*lr.coef_[0]+lr.intercept_, 50*lr.coef_[0]+lr.intercept_])
						# coef_는 리스트 타입이므로 0 인덱스 지정해서 값 반환 
# 50cm 농어 데이터
plt.scatter(50, 1241.8, marker='^')
plt.xlabel('length')
plt.ylabel('weight')
plt.show()

이제 훈련 세트와 테스트 세트에 대한 R^2 점수를 확인해보자.

print(lr.score(train_input, train_target))
print(lr.score(test_input, test_target))
>>> 0.939846333997604
	0.8247503123313558

훈련 세트와 테스트 세트의 점수가 차이가 나는데, 훈련 세트 점수 자체가 낮은걸 보니 과소적합이다.
그에 더해서, 그래프 왼쪽 아래를 보니 뭔가 이상하다.


다항 회귀

위 그래프의 직선대로면, 농어의 무게가 0g 이하로도 내려갈텐데 이는 현실에서는 있을 수 없다.
그에 더해 산점도를 보면 일직선보다는 아래의 곡선에 가까운 것 같다.

이런 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)

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

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

print(lr.predict([[50**2, 50]]))
>>> [1573.98423528]

실제 무게인 1500g 에 가까운 값을 예측했다. 이 모델이 훈련한 파라미터를 출력해보자.

print(lr.coef_, lr.intercept_)
>>> [  1.01433211 -21.55792498] 116.0502107827827

이 모델은 아래와 같은 다항식을 학습했다는 것을 확인할 수 있다.

이렇게 다항식을 사용한 선형 회귀다항 회귀라고 부른다.

산점도와 곡선을 그려보자.

# 구간별 직선을 그리기 위해 15에서 49까지 정수 배열을 만든다
point = np.arange(15, 50)

# 훈련 세트의 산점도를 그린다
plt.scatter(train_input, train_target)

# 15에서 49까지 2차 방정식 그래프를 그린다
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()

훈련 세트와 테스트 세트의 R^2 점수를 평가해보자.

print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
>>> 0.9706807451768623
	0.9775935108325122

좋은 성능이다! 하지만 약간의 과소적합이 남아있는 것 같다.



3-3 특성 공학과 규제

다중 회귀

위에서는 하나의 특성을 사용하여 선형 회귀 모델을 훈련시켰는데,
여러 개의 특성을 사용한 선형 회귀다중 회귀라고 부른다.

1개의 특성을 사용했을때 선형 회귀 모델은 "직선"을 학습했고,
2개의 특성을 사용하면 선형 회귀 모델은 "평면"을 학습한다.

이렇게, 선형 회귀라도 특성이 많은 고차원에서는 매우 복잡한 모델을 표현할 수 있다.

이제부턴 '농어의 길이' 뿐만 아니라 '높이'와 '두께'도 함께 사용할 것이다.
또한 위에서 처럼 3개의 특성을 각각 제곱하여 추가할 것이고, 추가로 '길이x높이'와 같은 새로운 특성을 만들 것이다. 이렇게 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업특성공학이라 한다.


데이터 준비

판다스는 유명한 데이터 분석 라이브러리이고, 데이터프레임은 판다스의 핵심 데이터 구조이다.
판다스 데이터프레임을 만들기 위해 많이 사용하는 파일은 CSV 파일인데, 구조는 아래와 같다.

판다스를 사용해 데이터를 인터넷에서 내려받아 데이터프레임에 저장하고, 넘파이 배열로 바꿔주자.

read_csv( )

CSV 파일을 로컬 컴퓨터나 인터넷에서 읽어 판다스 데이터프레임으로 변환하는 판다스의 함수

to_numpy()

넘파이 배열로 바꾸어주는 판다스의 함수

import pandas as pd  # pd는 관례적으로 사용하는 판다스의 별칭이다

df = pd.read_csv('https://bit.ly/perch_csv_data')

perch_full = df.to_numpy()
print(perch_full)
>>> [[ 8.4   2.11  1.41]
     [13.7   3.53  2.  ]
     [15.    3.82  2.43]
     [16.2   4.59  2.63]
     [17.4   4.59  2.94]
     [18.    5.22  3.32]
     [18.7   5.2   3.12]
     [19.    5.64  3.05]
     [19.6   5.14  3.04]
     [20.    5.08  2.77]
     [21.    5.69  3.56]
     [21.    5.92  3.31]
     [21.    5.69  3.67]
     [21.3   6.38  3.53]
     [22.    6.11  3.41]
     [22.    5.64  3.52]
     [22.    6.11  3.52]
     [22.    5.88  3.52]
     [22.    5.52  4.  ]
     [22.5   5.86  3.62]
     [22.5   6.79  3.62]
     [22.7   5.95  3.63]
     [23.    5.22  3.63]
     [23.5   6.28  3.72]
     [24.    7.29  3.72]
     [24.    6.38  3.82]
     [24.6   6.73  4.17]
     [25.    6.44  3.68]
     [25.6   6.56  4.24]
     [26.5   7.17  4.14]
     [27.3   8.32  5.14]
     [27.5   7.17  4.34]
     [27.5   7.05  4.34]
     [27.5   7.28  4.57]
     [28.    7.82  4.2 ]
     [28.7   7.59  4.64]
     [30.    7.62  4.77]
     [32.8  10.03  6.02]
     [34.5  10.26  6.39]
     [35.   11.49  7.8 ]
     [36.5  10.88  6.86]
     [36.   10.61  6.74]
     [37.   10.84  6.26]
     [37.   10.57  6.37]
     [39.   11.14  7.49]
     [39.   11.14  6.  ]
     [39.   12.43  7.35]
     [40.   11.93  7.11]
     [40.   11.73  7.22]
     [40.   12.38  7.46]
     [40.   11.14  6.63]
     [42.   12.8   6.87]
     [43.   11.93  7.28]
     [43.   12.51  7.42]
     [43.5  12.6   8.14]
     [44.   12.49  7.6 ]]

타깃 데이터는 이전과 동일하게 준비해주자.

import numpy as np

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

perch_full 과 perch_weight를 훈련 세트와 테스트 세트로 나눠주자.

from sklearn.model_selection import train_test_split

train_input, test_input, train_target, test_target = train_test_split(perch_full, perch_weight, random_state=42)

이 데이터를 사용해 새로운 특성을 만들어보자.


사이킷런의 변환기

사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공하는데, 변환기라고 불린다.

우리가 쓸 변환기는 sklearn.preprocessing 패키지 내의 PolynomialFeatures 클래스이다.

transform()

fit() 메서드로 훈련시킨뒤, transform() 메서드로 데이터를 변환한다.

from sklearn.preprocessing import PolynomialFeatures

poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))
>>> [[1. 2. 3. 4. 6. 9.]]
	# 특성이 아주 많아졌다!

선형 방적식에서 절편은 어떻게 보면 "항상 값이 1인 특성과 곱해주는 계수"라고 볼 수 있는데, 
그렇기에 1이 특성으로 출력된 것이다. 하지만 사이킷런의 선형 모델은 자동으로 절편을 추가하므로 
이를 제외하고 특성을 다시 반환해보자.
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))
>> [[2. 3. 4. 6. 9.]]

사실 include_bias=False 지정을 하지 않아도 사이킷런 모델은 자동으로 절편 항을 무시한다.
여기서는 명시적으로 보여주기 위해 지정한것.

위의 방법을 사용하여 train_input에 적용해보자.

poly = PolynomialFeatures(include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
>>> (42, 9)

각 9개의 특성이 어떻게 만들어진건지 확인하는 함수가 있는데,get_feature_names_out() 함수이다

poly.get_feature_names_out()
>>> array(['x0', 'x1', 'x2', 'x0^2', 'x0 x1', 'x0 x2', 'x1^2', 'x1 x2',
       'x2^2'], dtype=object)

이제 테스트 세트도 변환하자.

test_poly = poly.transform(test_input)

다중 회귀 모델 훈련하기

다중 회귀 모델을 훈련하는 것은 선형 회귀 모델을 훈련하는 것과 같다.
train_poly를 사용해 모델을 훈련시켜보자.

from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
>>> 0.9903183436982125

아주 좋은 점수가 나온다!
테스트 세트에 대한 점수도 확인해보자.

print(lr.score(test_poly, test_target))
>>> 0.9714559911594111

테스트 세트에 대한 점수는 높아지지 않았다.
특성을 더 추가해보자. PolynomialFeatrues 클래스의 degree 매개변수를 사용하여 필요한 고차항의 최대 차수를 지정할 수 있다.

PolynomialFeatures

주어진 특성을 조합하여 새로운 특성을 만든다.
degree 는 최고 차수를 지정한다. (기본값=2)
include_bias 가 Flase이면 절편을 위한 특성을 추가하지 않는다. (기본값=True)
interaction_only 가 True이면 거듭제곱 항은 제외되고 특성간의 곱셈항만 추가된다.
(기본값=False)

poly = PolynomialFeatures(degree=5, include_bias=False)

poly.fit(train_input)
train_poly = poly.transform(train_input)
test_poly = poly.transform(test_input)

print(train_poly.shape)
>>> (42, 55)

train_poly 배열의 열의 개수가 특성의 개수인데, 특성의 개수가 55개나 된걸 볼 수 있다.
이를 이용해 모델을 다시 훈련해보자.

lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
>>> 0.9999999999996433

아주 높은 점수다! 이제 테스트 세트에 대한 점수를 확인해보자.

print(lr.score(test_poly, test_target))
>>> -144.40579436844948

음수가 나왔다..? 모델에 뭔가 이상이 있다.
특성의 개수를 크게 늘리면, 훈련 세트에 대해 거의 완벽하게 학습할 수 있지만
훈련 세트에 너무 과대적합(Overfitting)되므로 테스트 세트에서는 형편없는 점수를 만든다.


규제

규제란 머신러닝 모델이 훈련 세트를 과대적합하게 학습하지 못하도록 훼방하는 것을 말한다.
선형 회귀 모델의 경우엔 특성에 곱해지는 계수(기울기)의 크기를 작게 만드는 일이다.

왼쪽 = 과대적합, 오른쪽 = 규제를 통해 기울기를 줄임

위의 55개의 특성으로 훈련한 모델에 규제를 가해볼텐데, 그 전에 사이킷런에서 제공하는 StandardScaler 클래스를 사용하여 스케일을 정규화 하겠다.
(규제를 적용하기 전엔 항상 스케일 정규화를 해주어야 한다!)

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)

# 스케일 정규화된 데이터들
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)

선형 회귀 모델에 규제를 추가한 모델을 릿지라쏘라고 부른다.
두 모델은 규제를 가하는 방법이 다른데,
릿지는 계수를 제곱한 값을 기준으로 규제를 적용하고,
라쏘는 계수의 절대값을 기준으로 규제를 적용한다.


릿지 회귀

릿지와 라쏘 모두 sklearn.linear_model 패키지 안에 있다.
앞서 준비한 train_scaled 데이터로 릿지 모델을 훈련해보자.

from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)

print(ridge.score(train_scaled, train_target))
>>> 0.9896101671037343

선형 회귀에서 완벽에 가까웠던 점수에 비해 낮아졌다. (Good)
이제 테스트 세트에 대한 점수를 확인해보자.

print(ridge.score(test_scaled, test_target))
>>> 0.9790693977615387

많은 특성을 사용했음에도 테스트 세트도 높은 점수로 돌아왔다!
규제를 가하여 과대적합되지 않도록 한 덕분이다.

릿지와 라쏘 모델을 사용할 때 규제의 정도를 임의로 조절할 수 있는데,
모델 객체를 만들 때 alpha 매개변수로 규제의 강도를 조절한다. (기본값 = 1)

alpha 값이 크면 규제 강도가 강해져 계수를 더 줄이고 과소적합되도록 유도되며,
alpha 값이 작으면 계수가 조금만 줄여져 과대적합될 가능성이 크게 된다.

이와 같이 사람이 직접 지정해야 되는 매개변수하이퍼파라미터라고 부른다.

적절한 alpha 값을

라쏘 회귀

profile
Shoot for the moon!

0개의 댓글