이전 글: 선형 회귀 및 다항 회귀(2)
이전 글에서는 농어의 길이와 무게를 회귀 모델에 학습시킨 후, 새로운 농어의 길이 데이터를 입력한 후, 해당 농어의 무게를 예측하는 작업을 하였다.
하지만 학습시키는 데이터가 다양할 수록 더욱 더 정확하지 않을까?
이번에는 농어의 길이뿐만 아니라 농어의 두께와 높이를 포함한 데이터를 이용해볼 것이다.
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(len(perch_full))
#출력결과: 56
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은 농어의 길이, 높이, 두께 총 3가지의 데이터가 있는 3 * 56의 데이터이다.
perch_weight는 56마리의 농어의 무게가 있는 종속변수(y) 데이터이다.
이제 이 전과 같이 56개의 데이터를 학습용 데이터셋과 테스트용 데이터셋으로 나눈다.
from sklearn.model_selection import train_test_split
train_X, test_X, train_y, test_y = train_test_split(perch_full, perch_weight, random_state=42)
사이킷런은 특성(feature)를 만들거나, 전처리하기 위한 클래스가 있다. 이러한 클래스를 변환기(transform)이라 부른다. 변환기는 fit(), transform() 메서드를 제공한다.
앞서 배운 LinearRegression, k-최근접 이웃 회귀 등의 모델은 추정기(estimator)이라 한다.
우리사 사용할 변환기는 PolynimialFeatures 이다. 이 변환기를 학습용 데이터의 독립변수(train_X)를 학습(fit)시킨 후 이 변환기를 이용하여 학습용 데이터셋의 독립변수(train_X)와 테스트용 데이터셋의 독립변수(test_X)를 변환(transform)시킨다.
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_X)
train_poly = poly.transform(train_X)
test_poly = poly.transform(test_X)
print(train_poly.shape)
변환기로 학습시킨 데이터를 이용하여 LinearRegression 모델을 학습시킨다. 후에 이 모델에 학습용 데이터셋과 테스트용 데이터셋의 결정계수(R-Squared)를 확인한다.
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_y)
print(lr.score(train_poly, train_y))
#출력값: 0.9903183436982125
print(lr.score(test_poly, test_y))
#출력값: 0.9714559911594223
처음에 언급했다시피, 더 많은 특성을 사용하면 정확도가 높아질 수 있다고 하였다. PolynimialFeatures의 degree 매개변수를 사용하여 필요한 고차항의 최대 차수를 지정할 수 있다. 이것을 이용하여 더 많은 특성을 추가해보자.
poly = PolynomialFeatures(degree=5, include_bias=False)
poly.fit(train_X)
train_poly = poly.transform(train_X)
test_poly = poly.transform(test_X)
print(train_poly.shape)
#출력값: (42, 55) => 만들어진 특성의 개수가 55개나 된다는 뜻이다.
이제 모델을 학습시킨 후 결정계수를 확인해보자.
lr = LinearRegression()
lr.fit(train_poly, train_y)
print(lr.score(train_poly, train_y))
#0.9999999999957028
lr.score에 학습용 데이터셋을 넣었더니 점수가 무려 0.9999나 나왔다. 완벽한 점수이다. 그렇다면 테스트용 데이터셋의 점수는 어떻게 될까?
print(lr.score(test_poly, test_y))
#출력값: -144.40508211356158
결정계수의 값이 굉장히 큰 음수가 나왔다.
특성의 개수를 크게 늘리면 선형 모델은 아주 강력해진다. 학습용 데이터셋에 대해 완벽하게 학습할 수 있다. 하지만 이 때문에 모델은 학습용 데이터셋에 과대적합이 되므로 테스트 데이터셋에서는 최악의 점수를 반환한다.
이런 과적합을 방지하는 방법이 규제이다. 선형 모델에 규제를 추가한 모델은 릿지(Ridge)와 라쏘(Lasso)가 있다. 두 모델의 규제를 가하는 방법은 다르다.
- 릿지는 계수를 제곱한 값을 기준으로 규제를 적용함.
- 라쏘는 계수의 절대값을 기준으로 규제를 적용함.
일반적으로 릿지를 조금 더 선호하는 편이다.
규제를 추가한 모델을 학습시키기 전에 우선 특성의 표준화부터 진행한다. 그 이유는 특성의 스케일이 정규화되지 않으면 모델에 규제를 적용할 때 계수 값을 사용하는데, 계수 값의 크기가 서로 다르면 공정하게 제어되지 않기 때문이다.
PolynimialFeatures 변환기를 이용하여 제곱 등의 값을 추가한 데이터를 다시 한 번 StandardScaler 변환기를 이용하여 데이터를 변환해주자.
from sklearn.preprocessing import StandardScaler
ss = StandardScaler() #StandardScaler 또한 변환기(tranform) 중 하나이다.
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
위에서 정규화가 된 데이터를 사용하여 회귀 모델을 학습시키자.
#릿지 화귀(ridge)
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_y)
print(ridge.score(train_scaled, train_y))
#0.9896101671037343
print(ridge.score(test_scaled, test_y))
#0.9790693977615393
많은 특성을 사용했음에도 불구하고 모델이 학습용 데이터셋을 과대적합으로 학습하지 않아 학습용 데이터셋 뿐만 아니라 테스트용 데이터셋에서도 좋은 성능을 내고 있다.
규제가 추가된 선형 모델 릿지(Ridge)와 라쏘(Lasso)의 모델을 사용할 때 규제의 양을 임의로 조절할 수 있다. alpha 매개변수를 사용한다.
alpha 값이 크면 규제의 강도가 강해지므로 계수 값을 더 줄이고, 조금 더 과소적합(underfitting)하도록 유도한다.
반대로 alpha 값이 작으면 계수를 줄이는 역할이 줄어들어, 원래의 선형 회귀 모델과 유사해지므로 과대적합(overfitting)하도록 유도한다.
여기에서의 중점은 모델에 맞는 적절한 alpha값을 찾는 것이다. 적절한 alpha값을 찾기 위한 방법 중 하나는 결정계수(R-Squared)를 그래프로 그려 직관적으로 확인하는 것이다.
import matplotlib.pyplot as plt
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
ridge = Ridge(alpha=alpha)
ridge.fit(train_scaled, train_y)
train_score.append(ridge.score(train_scaled, train_y))
test_score.append(ridge.score(test_scaled, test_y))
plt.plot(np.log10(alpha_list), train_score, label='train')
plt.plot(np.log10(alpha_list), test_score, label='test')
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.legend()
plt.show()
그래프를 그릴 때 np.log10(alpha_list)로 변환하는 이유는 만약 그대로 그래프를 그린다면 alpha값은 0.001부터 10배씩 늘려가기 때문에 그래프 왼쪽이 너무 촘촘해진다. 이것을 방지하기 위해서 로그 함수를 이용하여 동일한 간격으로 나타낸다.
적절한 alpha값은 학습용 데이터, 테스트용 데이터가 가장 가까운, 즉 alpha가 -1일때가 가장 적절한 alpha값이다. 하지만 alpha값은 그 전에 log10으로 변환하였으니, alpha값은 10^-1 = 0.1이 된다.
라쏘 모델 또한 릿지와 굉장히 유사하다. 이전에 코딩했던 것에 Ridge클래스를 Lasso 클래스로 바꾸는 것이 전부이다.
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_y)
print(lasso.score(train_scaled, train_y))
#출력값: 0.989789897208096
print(lasso.score(test_scaled, test_y))
#출력값: 0.9800593698421884
라쏘 또한 릿지와 마찬가지로 학습용 데이터셋, 테스트용 데이터셋 둘다 좋은 성능을 보여주고 있다.
이제 릿지에서 했던 것처럼 적절한 alpha값을 찾아보자.
#라쏘회귀(Lasso)
from sklearn.linear_model import Lasso
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
lasso = Lasso(alpha=alpha, max_iter=10000)
lasso.fit(train_scaled, train_y)
train_score.append(lasso.score(train_scaled, train_y))
test_score.append(lasso.score(test_scaled, test_y))
plt.plot(np.log10(alpha_list), train_score, label='train')
plt.plot(np.log10(alpha_list), test_score, label='test')
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.legend()
plt.show()
라쏘 모델 또한 테스트용 데이터와 학습용 데이터가 가장 가까운 점이 적절한 alpha값이며, 그 값은 1이 된다. 그 전에 log10으로 변환하였으니, alpha값은 10^1 = 10이 된다.
요약
- 다양한 특성을 추가하면 강력한 모델을 학습시킬 수 있다.
- 하지만 너무 많은 특성을 추가하면 과대적합(overfitting)이 나타날 수 있다.
- 과적합을 방지하기 위하여 규제가 추가된 선형 모델을 사용한다.
- 규제가 추가된 선형모델에는 라쏘(Lasso)와 릿지(Ridge)가 있다.
- 규제 양을 조절하기 위하여는 적절한 alpha 값을 찾아야 한다.