이전 학습에서 무게의 예측은 할 수 있었지만 과소적합인 경우를 해결하지 못했다. 이 문제를 해결하기 위해서는 조금 더 고차항을 넣어야한다.
여러 개의 특성을 사용한 선형 회귀를 다중 회귀라고 한다.
데이터에 길이 뿐만 아니라 높이, 두께도 함께 사용한다.
3개의 특성을 각각 제곱하여 추가하고 각 특성을 서로 곱해서 또 다른 특성을 만든다.
이렇게 기존의 특성을 사용해 새로운 특성을 만드는 작업을 특성 공학이라고 한다.
판다스는 유명한 데이터 분석 라이브러리로 데이터프레임이라는 핵심 데이터 구조를 가지고있다.
넘파이 배열과 비슷하게 다차원 배열을 다룰 수 있다.
# 데이터 준비
import pandas as 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. ]
# ...
# [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])
# 훈련 세트와 테스트 세트 나누기
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)
사이킷런은 특성을 만들거나 전처리하기 위한 다양한 클래스를 제공하는데 이러한 클래스를 변환기라고 한다.
변환기는 모두 fit(), transform() 메서드를 제공한다.
# 사이킷런의 변환기
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures()
# 2와 3으로 이루어진 2개의 특성으로 샘플만들기
poly.fit([[2, 3]])
print(poly.transform([[2,3]]))
# 출력 [[1. 2. 3. 4. 6. 9.]]
각 특성을 제곱한 항(4, 9)을 추가하고 특성끼리 서로 곱한 항(6)을 추가한다.
이므로 1이 추가되게된다.
# 절편 특성에서 제외
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2,3]]))
# 출력 [[2. 3. 4. 6. 9.]]
사이킷런 모델은 특성에 추가된 절편항을 자동으로 무시한다. (명시적으로만 존재함)
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
# 훈련 세트 변환
train_poly = poly.transform(train_input)
print(train_poly.shape)
# (42, 9)
# 테스트 세트 변환
test_poly = poly.transform(test_input)
총 9개의 특성이 만들어 졌다.
해당 메서드를 호출하면 특성이 각각 어떤 입력의 조합으로 만들어졌는지 알 수 있다.
print(poly.get_feature_names_out())
# ['x0' 'x1' 'x2' 'x0^2' 'x0 x1' 'x0 x2' 'x1^2' 'x1 x2' 'x2^2']
다중 회귀 모델 훈련은 선형 회귀 모델을 훈련하는 것과 동일하다. 다른점은 여러 개의 특성을 사용하여 선형 회귀를 수행한다는 것이다.
# 다중 회귀 모델 훈련
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
과소적합의 문제는 해결
polynomialFeatures 클래스의 degree 매개변수를 사용하여 필요한 고차항의 최대 차수를 저장할 수 있다.
55개의 특성을 가지게 된다.
이걸 학습 시킬경우
# 고차항 (5제곱)
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)
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
# 출력 0.9999999999996433
print(lr.score(test_poly, test_target))
# 출력 -144.40579436844948
특성의 개수를 크게 늘리면 선형 모델이 아주 강력해지고, 훈련 세트에 대해 거의 완벽한 학습이 가능하다.
이렇게 되면 훈련세트에 너무 과대적합되므로 테스트 세트의 점수는 터무니없이 낮게 나오게 된다.
규제는 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하는 것을 막는 것이다. (과대적합 예방)
평균과 표준편차를 구해 특성을 표준점수로 바꿔준다.
✔️ 직접 구했던 방법
import numpy as np ... mean = np.mean(train_input, axis=0) # 평균 std = np.std(train_input, axis=0) # 표준편차 # 표준점수 train_scaled = (train_input - mean) / std
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
선형 회귀 모델에 규제를 추가한 모델을 릿시(ridge)와 라쏘(lasso)라고 부른다.
alpha 매개변수를 사용하여 규제의 강도를 조절할 수 있다.
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
# 출력 0.9896101671037343
print(ridge.score(test_scaled, test_target))
# 출력 0.9790693977615387
# 과대적합 해결
값의 그래프를 확인하여 훈련과 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha값이 된다.
import matplotlib.pyplot as plt
train_score = []
test_score = []
# alpha 범위 0.001 ~ 100 10배식 증가하여 점수 측정
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_target)
train_score.append(ridge.score(train_scaled, train_target))
test_score.append(ridge.score(test_scaled, test_target))
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()

0.1()에서 가장 가까우므로 가장 적절한 alpha로 선정
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
# 출력 0.9903815817570367
print(ridge.score(test_scaled, test_target))
# 출력 0.9827976465386928
적절한 점수가 잘 나왔다.
# 라쏘 회귀
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
# 출력 0.989789897208096
print(lasso.score(test_scaled, test_target))
# 출력 0.9800593698421883
train_score = []
test_score = []
alpha_list = [0.001, 0.01, 0.1, 1, 10, 100]
for alpha in alpha_list:
ridge = Lasso(alpha=alpha, max_iter=10000)
ridge.fit(train_scaled, train_target)
train_score.append(ridge.score(train_scaled, train_target))
test_score.append(ridge.score(test_scaled, test_target))
plt.plot(np.log10(alpha_list), train_score)
plt.plot(np.log10(alpha_list), test_score)
plt.xlabel('alpha')
plt.ylabel('R^2')
plt.show()

왼쪽 과대적합, 오른쪽으로 갈수록 점수차이가 줄어들지만 alpha=1(10)을 지나는 시점에서 점수가 크게 떨어진다.
10()에서 가장 가까우며 점수가 낮지않아 가장 적절한 alpha로 선정
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
# 출력 0.9888067471131867
print(lasso.score(test_scaled, test_target))
# 출력 0.9824470598706695
적절한 점수가 잘 나왔다.