다중 회귀(multiple regression) : 여러 개의 특성을 사용한 선형 회귀
하지만 우리는 3차원 공간 이상을 그리거나 상상할 수 없다. 선형 회귀를 단순한 직선이나 평면으로 생각하여 성능이 무조건 낮다고 오해해선 안된다!! 특성 많은 고차원에서는 선형 회귀가 매우 복잡한 모델을 표현할 수 있다.
특성 공학(feature engineering) : 기존의 특성을 사용해 새로운 특성을 뽑아내는 작업. 여기서는 '농어 길이 x 농어 높이'를 새로운 특성으로 만드는 예시.
판다스(pandas) : 유명한 데이터 라이브러리.
데이터프레임(dataframe) : 넘파이 배열처럼 다차원 배열 다룰 수 있지만 훨씬 더 많은 기능 제공, 넘파이 배열로도 쉽게 바꿀 수 있음.
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]
...
[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)
# 이 데이터 사용해 새로운 특성 만들기.
사이킷런은 특성을 만들거나 정처리하기 위한 다양한 클래스를 제공한다. 사이킷런에서는 이런 클래스를 변환기(transfomer) 라고 부른다. 변환기 클래스는 fit(), transform() 메서드를 제공한다.
앞서 배운 LinearRegression 같은 사이킷런의 모델 클래스는 추정기(estimator)리고도 부른다.
우리가 사용할 변환기는 PolynomialFeatures 클래스. 이 클래스는 sklearnpreprocessing 패키지에 포함되어 있다.
from sklearn.preprocessing import PolynomialFEatures
poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transofrm([[2, 3]]))
>>> [[1. 2. 3. 4. 6. 9.]]
+ 꼭 훈련(fit)해야 변환(transform)이 가능함!!
fit() 메서드는 새롭게 만들 특성 조합을 찾고 transform() 메서드는 실제 데이터를 변환. 변환기는 입력 데이터를 변환하는 데 타깃 데이터 필요 X.
PolynomialFeatures 클래스는 기ㅗㄴ적으로 각 특성을 제곱한 항 추가하고 특성끼리 서로 곱한 항 추가한다. 근데 1은 왜 추가 되었을까??
선형 방정식의 절편을 항상 값이 1인 특성과 곱해지는 계수라고 볼 수 있다. 사이킷런의 선형 모델은 자동으로 철현 추가하므로 굳이 이렇게 특성 만들 필요 없음!
# include_bias = False로 하면 절편 위한 항 제거되고 특성의 제곱과 특성끼리 ㅗㅂ한 항만 추가.
poly = PolynomialFeatures(include_bias = False)
poly.fit([[2,3]])
print(poly.transform([[2,3]])
>>> [[2. 3. 4. 6. 9.]]
이 방식을 train_input에 적용.
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
>>> (42, 9)
# get_feature_name_out() 메서드 호출하면 9개의 특성이 각각 어떤 입력의 조합으로 만들어졌는지 알려줌.
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)
다중 회귀 모델을 훈련하는 것은 선형 회귀 모델을 훈련하는 것과 같다. 다만 여러 개의 특서을 사용하여 선형 회귀를 수행하는 것뿐. 먼저 사이킷런의 LinearRegression 클래스 임포트하고 앞에서 만든 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
테스트 세트에 대한 점수는 높아지지 않았지만 농어의 길이만 사ㅛㅇ했을 때 있던 과소적합 문제는 나타나지 않음.
특성 더 많이 추가하면?? POlynomialFeatures 클래스의 degree 매개변수를 사용하여 필요한 과항의 최대 차수를 지정할 수 있다.
# 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)
train_poly 배열의 열의 개수가 특성의 개수. 이 데이터를 사용해 선형 회귀 모델 다시 훈련.
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
>>> 0.9999999999996433
거의 완벽한 점수!! 테스트 세트에 대한 점수 출력
print(lr.score(test_poly, test_target))
>>> -144.40579436844948
너무나도 큰 음수!!???
→ 특성의 개수 크게 늘리면 선형 모델은 아주 강력해진다. 훈련 세트에 대해 거의 완벽하게 학습할 수 있지만, 이런 모델은 훈련 세트에 너무 과대적합되므로 테스트 세트에서는 형편없는 점수 만든다.
이 문제를 해결하기 위해선 특성을 줄여야 한다. 과대적합 줄이는 또 다른 방법 배워보자!!
규제(regularization) : 머신러닝 모델이 훈련 세트를 너무 과도하게 학습하지 못하도록 훼방하는 것. 즉, 모델이 훈련 세트에 과대적합되지 않도록 만드는 것.선형 회귀 모델의 경우 특성에 곱해지는 계수(또는 기울기)의 크기를 작게 만드는 일.
특성의 스케일이 정규화되지 않으면 곱해지는 계수 값도 차이 나게 된다. 일반적으로 선형 회귀 모델에 규제를 적용할 때 계수 값의 크기가 서로 많이 다르면 공정하게 제어되지 않을 것이다. 그래서 규제 적용하기 전에 먼저 정규화를 해야한다!! 사이킷런에서는 StandardScaler 클래스를 사용한다. (변환기의 하나.)
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
먼저 StandardScale 클래스의 객체 ss를 초기화한 후 PolynomialFeatures 클래스로 만든 trainpoly 사용해 객체 훈련한다. 강조하지만, 꼭 훈련 세트로 학습한 변호나기 사용해 테스트세트까지 변환해야 한다._
이제 표준점수 변환한 train_scaled와 test_scaled가 준비 되었다.
선형 회귀 모델에 규제를 추가한 모델을 릿지(ridge)와 라쏘(lasso) 라 부른다.
릿지는 계수를 제곱한 값을 기준으로 규제를 적용, 라쏘는 계수의 절댓값을 기준으로 규제를 적용한다. 일반적으로 릿지 조금 더 선호! 두 알고리즘 모두 계수의 크기를 줄이지만, 라쏘는 0으로 만들어버릴 수도 있음!
릿지, 라쏘 모두 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
선형 회귀에서 거의 완벽했던 점수가 조금 낮아짐. 테스트 세트 점수 확인.
print(ridge.score(test_scaled, test_target))
>>> 0.9790693977615387
테스트 세트 점수가 정상으로 돌아왔다!
릿지와 라쏘 모델 사용할 때 규제의 양을 임의로 조절할 수 있다. 모델 개체 만들 때 alpha 매개변수로 규제의 강도 조절.
alpha 값이 크면 규제 강도 세지므로 계수 값더 줄이고 조금 더 과소적합되도록 유도한다.
반대로 alpha 값이 작으면 계수를 줄이는 역할이 줄어들고 선형 회귀 모델과 유사해지므로 과대적합될 가능성이 크다.
+ 사람이 직접 지정해야 하는 매개변수
하이퍼파라미터(hyperparameter) : 머신러닝 모델이 학습할 수 없고 사람이 알려줘야 하는 파라미터.
적절한 alpha 값찾는 한 가지 방법은 alpha 값에 대한 R^2 값의 그래프 그려보는 것.훈련 세트와 테스트 세트의 점수가 가장 가까운 지점이 최적의 alpha 값이 된다.
# 맷플롯립 임포트하고 alpha 값을 바꿀 때마다 score() 매서드 결과 저장할 리스트 생성
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))
# 그래프 작성
# 0.001 부터 10배씩 했기 때문에 너무 촘촘해짐 → alpha_list에 있는 6개의 값 동일한 간격으로 나타내기 위해 로그 함수로 바꾸어 지수로 표현.
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^-1 = 0.1일 때. alpha 값을 0.1로 하여 최종 모델 훈련해보자!
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
>>> 0.9903815817570367
0.9827976465386928
훈련 세트, 테스트 세트 점수 비슷하고 과대적합과 과소적합에서 균형 이루고 있다!
라쏘 모델 훈련하는 것은 릿지와 매우 비슷. Ridge 클래스를 Lasso 클래스로 바꾸는 것이 전부!
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
테스트 세트의 점수도 릿지만큼 아주 좋음!! 라쏘 모델도 alpha 매개변수로 규제의 강도 조절 가능.
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_target)
# 훈련 점수와 테스트 점수를 저장합니다
train_score.append(lasso.score(train_scaled, train_target))
test_score.append(lasso.score(test_scaled, test_target))
# train_score와 test_score 리스트 사용해 그래프 작성. x축은 로그 스케일로 변환.
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()
파란색이 훈련 세트 그래프. 주황색이 테스트 세트 그래프. 이것도 왼쪽은 과대적합. 오른쪽으로 갈수록 훈련 세트와 테스트 세트의 점수가 좁혀지고 있다. 가장 오른쪽은 아주 크게 점수가 떨어진다. 이 지점은 분명 과소적합되는 모델일 것. 라쏘 모델에서 최적의 aplha 값은 1, 즉 10^1 = 10이다. 이 값으로 모델 다시 훈련.
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
>>> 0.9888067471131867
0.9824470598706695
모델 잘 훈련됨!!! 특성 많이 사용했지만, 릿지와 마찬가지로 라쏘 모델이 과대적합을 잘 억제하고 테스트 세트의 성능 크게 높였다. 앞에서 라쏘 모델은 계수 값을 아예 0으로 만들 수 있었다. 라쏘 모델의 계수는 coef_ 속성에 저장되어 있다.
# 0인 것 헤아려보기
print(np.sum(lasso.coef_ == 0))
>>> 40
55개의 특성을 사용했지만, 라쏘 모델이 사용한 특성은 15개 뿐. 이런 특징 때문에 라쏘 모델을 유용한 특성을 골라내는 용도로 사용할 수 있다.
선형 회귀 알고리즘 사용해 농어 무게 예측하는 모델 훈련. 훈련 세트에 과소적합되는 문제 발생. 이를 위해 농어의 길이뿐만 아니라 높이와 두께도 사용하여 다중 회귀 모델 훈련 시킴.
또한 다항 특성을 많이 추가하여 훈련 세트에서 거의 완벽에 가까운 점수 얻는 모델 훈련. 특성 많이 추가하면 선형 회귀는 매우 강력한 성능 낸다. 하지만 특성이 너무 많으면 선형 회귀 모델을 제약하기 위한 도구가 필요.
이 도구가 릿지와 라쏘. 사이킷런 사요해 다중 회귀 모델과 릿지, 라쏘 모델을 훈련. 규제 양 조절하기 위해 최적의 alpha 값 찾는 방법 알아봤다!
# 데이터 준비
import pandas as pd
df = pd.read_csv('https://bit.ly/perch_csv_data')
perch_full = df.to_numpy()
print(perch_full)
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)
# 사이킷런의 변환기
from sklearn.preprocessing import PolynomialFeatures
poly = PolynomialFeatures()
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))
poly = PolynomialFeatures(include_bias=False)
poly.fit([[2, 3]])
print(poly.transform([[2, 3]]))
poly = PolynomialFeatures(include_bias=False)
poly.fit(train_input)
train_poly = poly.transform(train_input)
print(train_poly.shape)
poly.get_feature_names_out()
test_poly = poly.transform(test_input)
# 다중 회귀 모델 훈련하기
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
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)
lr.fit(train_poly, train_target)
print(lr.score(train_poly, train_target))
print(lr.score(test_poly, test_target))
#규제
from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_poly)
train_scaled = ss.transform(train_poly)
test_scaled = ss.transform(test_poly)
# 릿지
from sklearn.linear_model import Ridge
ridge = Ridge()
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
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_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()
ridge = Ridge(alpha=0.1)
ridge.fit(train_scaled, train_target)
print(ridge.score(train_scaled, train_target))
print(ridge.score(test_scaled, test_target))
# 라쏘
from sklearn.linear_model import Lasso
lasso = Lasso()
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
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_target)
# 훈련 점수와 테스트 점수를 저장합니다
train_score.append(lasso.score(train_scaled, train_target))
test_score.append(lasso.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()
lasso = Lasso(alpha=10)
lasso.fit(train_scaled, train_target)
print(lasso.score(train_scaled, train_target))
print(lasso.score(test_scaled, test_target))
print(np.sum(lasso.coef_ == 0))
pandas
scikit-learn