파이썬 머신러닝 완벽 가이드 (권철민 저)을 요약정리했습니다.
다항회귀
회귀가 독립변수의 단항식이 아닌 2차. 3차방정식과 같은 다항식으로 표현되는 것을 다항 회귀 라고한다.
한 가지 주의 할 것은 다항 회귀를 비선형 회귀로 혼동하기 쉽지만, 다항 회귀는 선형회귀라는 점이다.
아쉽게도 사이킷런에는 다항 회귀를 위한 클래스는 존재 하지않고, 대신 다항 회귀 역시 선형회귀 이기 때문에 비선형 함수를 선형 모델에 적용시키는 방법을 사용해 구현 할 수 있다.
사이킷런의 Polynomial Features 클래스를 통해 피처를 Polynomial(다항식) 피처로 변환시킨다.
단항값 [x_1,x_2]를 2차 다항값 으로 변환 시켜 보자.
from sklearn.preprocessing import PolynomialFeatures
import numpy as np
# 다항식으로 변환한 단항식 생성, [[0,1],[2,3]]의 2X2 행렬 생성
X = np.arange(4).reshape(2,2)
print('일차 단항식 계수 feature:\n',X )
# degree = 2 인 2차 다항식으로 변환하기 위해 PolynomialFeatures를 이용하여 변환
poly = PolynomialFeatures(degree=2)
poly.fit(X)
poly_ftr = poly.transform(X)
print('변환된 2차 다항식 계수 feature:\n', poly_ftr)
일차 단항식 계수 feature:
[[0 1]
[2 3]]
변환된 2차 다항식 계수 feature:
[[1. 0. 1. 0. 0. 1.]
[1. 2. 3. 4. 6. 9.]]
단항 계수 피처 [0 1] -> 변환된 2차 다항식 계수 피처 [1 0 1 0 0 1]
단항 계수 피처 [2 3] -> 변환된 2차 다학식 계수 피처 [1 2 3 4 6 9]
피처 변환과 선형 회귀 적용을 각각 별도로 하는 것보다는 사이킷런의 Pipeline 객체를 이용해 한 번에 다항 회귀를 구현하는 것이 코드를 더 명료하게 작성하는 방법이다.
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
import numpy as np
def polynomial_func(X):
y = 1 + 2*X[:,0] + 3*X[:,0]**2 + 4*X[:,1]**3
return y
# Pipeline 객체로 Streamline 하게 Polynomial Feature변환과 Linear Regression을 연결
model = Pipeline([('poly', PolynomialFeatures(degree=3)),
('linear', LinearRegression())])
X = np.arange(4).reshape(2,2)
y = polynomial_func(X)
model = model.fit(X, y)
print('Polynomial 회귀 계수\n', np.round(model.named_steps['linear'].coef_, 2))
Polynomial 회귀 계수
[0. 0.18 0.18 0.36 0.54 0.72 0.72 1.08 1.62 2.34]
다항 회귀를 이용한 과소적합 및 과적합 이해
다항 회귀는 피처의 직선적 관계가 아닌 복잡한 다항 관계를 모델링할 수 있다.
다항식의 차수가 높아질수록 매우 복잡한 피처 간의 관계까지 모델링이 가능하다.
하지만 다항 회귀의 차수가 높을수록 학습 데이터에만 너무 맞춘 학습이이뤄져서 정작 테스트 데이터 환경에서는 오히려 예측의 정확도가 떨어진다.
즉 , 차수고 높아 질 수록 과적합의 문제가 크게 발생한다.
다음은 사이킷런 홈페이지의 다항회귀를 이용한 과소적합과 과적합의 문제를 잘 보여주는 예시이다.
학습 데이터는 30개의 임의의 데이터인 X, 그리고 X의 코사인 값에 약간의 noise 를 더한 target인 y로 구성된다.
import numpy as np
import matplotlib.pyplot as plt
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import cross_val_score
%matplotlib inline
# random 값으로 구성된 X값에 대해 Cosine 변환값을 반환.
def true_fun(X):
return np.cos(1.5 * np.pi * X)
# X는 0 부터 1까지 30개의 random 값을 순서대로 sampling 한 데이타 입니다.
np.random.seed(0)
n_samples = 30
X = np.sort(np.random.rand(n_samples))
# y 값은 cosine 기반의 true_fun() 에서 약간의 Noise 변동값을 더한 값입니다.
y = true_fun(X) + np.random.randn(n_samples) * 0.1
이제 예측 결과를 비교 할 다항식 차수를 각각 1, 4, 15 로 변경하면서 예측 결과를 비교해보자.
다항식 차수별로 학습을 수행한 뒤 cross_val_score()로 MSE 값을 구해 차수별 예측 성능을 평가한다.
그리고 0부터 1까지 균일하게 구성된 100개의 테스트용 데이터 셋을 이용해 차수별 회귀 예측 곡선을 시각화 시켜보자.
plt.figure(figsize=(14, 5))
degrees = [1, 4, 15]
# 다항 회귀의 차수(degree)를 1, 4, 15로 각각 변화시키면서 비교합니다.
for i in range(len(degrees)):
ax = plt.subplot(1, len(degrees), i + 1)
plt.setp(ax, xticks=(), yticks=())
# 개별 degree별로 Polynomial 변환합니다.
polynomial_features = PolynomialFeatures(degree=degrees[i], include_bias=False)
linear_regression = LinearRegression()
pipeline = Pipeline([("polynomial_features", polynomial_features),
("linear_regression", linear_regression)])
pipeline.fit(X.reshape(-1, 1), y)
# 교차 검증으로 다항 회귀를 평가합니다.
scores = cross_val_score(pipeline, X.reshape(-1,1), y,scoring="neg_mean_squared_error", cv=10)
coefficients = pipeline.named_steps['linear_regression'].coef_
print('\nDegree {0} 회귀 계수는 {1} 입니다.'.format(degrees[i], np.round(coefficients),2))
print('Degree {0} MSE 는 {1:.2f} 입니다.'.format(degrees[i] , -1*np.mean(scores)))
# 0 부터 1까지 테스트 데이터 세트를 100개로 나눠 예측을 수행합니다.
# 테스트 데이터 세트에 회귀 예측을 수행하고 예측 곡선과 실제 곡선을 그려서 비교합니다.
X_test = np.linspace(0, 1, 100)
# 예측값 곡선
plt.plot(X_test, pipeline.predict(X_test[:, np.newaxis]), label="Model")
# 실제 값 곡선
plt.plot(X_test, true_fun(X_test), '--', label="True function")
plt.scatter(X, y, edgecolor='b', s=20, label="Samples")
plt.xlabel("x"); plt.ylabel("y"); plt.xlim((0, 1)); plt.ylim((-2, 2)); plt.legend(loc="best")
plt.title("Degree {}\nMSE = {:.2e}(+/- {:.2e})".format(degrees[i], -scores.mean(), scores.std()))
plt.show()
Degree 1 회귀 계수는 [-2.] 입니다.
Degree 1 MSE 는 0.41 입니다.
Degree 4 회귀 계수는 [ 0. -18. 24. -7.] 입니다.
Degree 4 MSE 는 0.04 입니다.
Degree 15 회귀 계수는 [-2.98300000e+03 1.03900000e+05 -1.87417300e+06 2.03717520e+07
-1.44874234e+08 7.09320168e+08 -2.47067524e+09 6.24565587e+09
-1.15677381e+10 1.56896159e+10 -1.54007266e+10 1.06458152e+10
-4.91381762e+09 1.35920853e+09 -1.70382347e+08] 입니다.
Degree 15 MSE 는 181777900.40 입니다.
- 맨 왼쪽은 Degree1 예측 곡선은 단순한 직선으로서 선형 회귀와 똑같다. 실제 데이터 셋을 직선으로 예측하기에는 너무 단순해 보인다. 예측 곡선이 학습 데이터의 패턴을 제대로 반영하지 못하고 있는 과소 적합 모델이 되었다. MSE는 약 0.407이다.
- 가운데 Degree 4 예측 곡선은 실제 데이터 셋과 유사한 모습이다. 변동하는 잡음까지 예측하지는 못했지만, 학습 데이터 셋을 비교적 잘 반영해 코사인 곡선 기반으로 테스트 데이터를 잘 예측한 곡선을 가진 모델이 되었다. MSE는 약 0.043 으로 가장 뛰어난 예측 성능을 나타내고 있다.
- 맨 오른쪽 Degree 15 예측 곡선은 MSE 값이 18177900.4인 오류 값이 발생했다. 예측 곡선을 보면 데이터 셋의 변동 잡음 값 까지 지나치게 반영한 결과 예측 곡선이 학습 데이터 셋만 정확히 예측하고 테스트 갑스이 실제 곡선과는 완전히 다른 형태의 예측 곡선이 만들어 졌다. 결과적으로 학습 데이터에너무 충실하게 맞춘 과적합이 심한 모델이 되었다.
결국 좋은 예측 모델은 학습 데이터의 패턴을 잘 반영 하면서도 복잡하지 않은 균형 잡힌 모델을 의미히 한다.
편향 – 분산 트레이드 오프(Bias-Variance Trade off)
편향 - 분산 트레이드 오프는 머신러닝이 극복해야항 가장 중요한 이슈 중 하나이다.
앞의 Degree 1 과 같은 모델은 매우 단순화된 모델로서 지나치게 한 방향성으로 치우친 경향이 있는데,
이런 모델을 고편향(High Bias)성 을 가졌다고 표현한다.
반대로 Degree 15와 같은 모델은 학습 데이터 하나하나의 특성을 반영하면서 매우 복잡한 모델이 되었고 지나치게 높은 변동성을 가지게 되었다.
이러한 모델을 고분산(High Variance)성을 가졌다고 표현한다.
다음 그림의 ‘양궁 과녘’ 그래프는 편향과 분산의 고/저의 의미를 직관적으로 잘 표현 하고있다.
그림 상단 왼쪽의 저편향/저분산은 예측 결과과 실제 결과에 매우 잘 근접 하면서도 예측 변동이 크지 않고 특정 부분에 집중돼 있는 아주 뛰어난 성능을 보여준다.
상단 오른쪽의 저편향/고분산은 예측 결과과 실제 결과에 비교적 근접하지만, 예측 결과과 실제 결과를 중심으로 꽤 넓은 부분에 분포돼 있다.
하단 왼쪽의 고편향/저분산은 정확한 결과에서 벗어나면서도 예측이 특정 부분에 집중돼 있다.
마지막으로 하단 오른쪽의 고편향/ 고분산은 정확한 예측 결과를 벗어나면서도 넓은 부분에 분포돼 있다.
일반적으로 편향과 분산은 반비례 적인 경향이 있다.
다음 그림은 편향과 분산의 관계에 따른 전체 오류 값(Total Error)의 변화를 잘 보여준다.
편향이 너무 높으면 전체 오류가 높다. 편향을 점점 낮추면 동시에 분산이 높아지고 전체 오류도 낮아지게 된다.
편향을 낮추고 분산을 높이면서 전체 오류가 가장 낮아지는 '골디락스' 지점을 통과하면서 분산을 지속적으로 높이면 전체 오류 값이 오히려 증가하면서 예측 성능이 다시 저하 된다 .
높은 편향/ 낮은 분산에서 과소적합 되기 쉽고, 낮은 평향/ 높은 분산에서 과적합 되기 쉽다.
편향과 분산이 서로 트레이드 오프를 이루면서 오류 Cost 값이 최소가 되는 모델을 구축하는 것이 가장 효율적인 머신러닝 예측 모델을 만드는 방법이다.
규제 선형 모델 - 릿지, 라쏘, 엘라스틱넷
이전 까지의 선형모델의 비용 함수는 RSS를 최소화하는, 즉 실제 값과 예측값의 차이를 최소화하는 것만 고려했다.
그러다 보니 학습 데이터에 지나치게 맞추게 되고, 회귀 계수가 쉽게 커졌는데 이럴 경우 변동성이 오히려 심해져서 테스트 데이터 셋에서는 예측 성능이 저하되기 쉽다.
이를 반영해 비용 함수는 학습 데이터의 잔차 오류 값을 최소로 하는 RSS 최소화 방법과
과적합을 방지하기 위해 회귀 계수 값이 커지지 않도록 하는 방법이 서로 균형을 이뤄야 한다.
이렇게 회귀 계수의 크기를 제어해 과적합을 개선하려면 비용(Cost) 함수의 목표가 다음 식을 최소화 하는 것으로 변경 될 수 있다.
비용함수에 alpha 값으로 페널티를 부여해 회귀 계수 값의 크기를 감소시켜 과적합을 개선하는 방식을 규제 라고 부른다.
규제는 크게 L2 방식과 L1 방식으로 구분되는데 , L2 규제는 W의 제곱에 대해 페널티를 부여하는 방식을 말하고,
L2 규제를 적용한 회귀를 릿지 회귀 라고한다.
라쏘 회귀는 L1 규제를 적용한 회귀이고, L1 규제는 W의 절댓값에 대해 페널티를 부여한다.
L1 규제를 적용하면 영향력이 크지 않은 회귀 계수 값을 0 으로 변환한다.
릿지 회귀
사이킷런은 Ridge 클래스를 통해 릿지 회귀를 구현한다.
Ridge 클래스의 주요 생성 파라미터는 alpha이며, 이는 릿지 회귀의 alpha L2 규제 개수에 해당한다.
보스턴 주택 가격을 Ridge 클래스를 이용해 다시 예측 해보고, cross_val_score()로 평가해 보자.
# 앞의 LinearRegression예제에서 분할한 feature 데이터 셋인 X_data과 Target 데이터 셋인 Y_target 데이터셋을 그대로 이용
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_boston
import pandas as pd
%matplotlib inline
# boston 데이타셋 로드
boston = load_boston()
# boston 데이타셋 DataFrame 변환
bostonDF = pd.DataFrame(boston.data , columns = boston.feature_names)
# boston dataset의 target array는 주택 가격임. 이를 PRICE 컬럼으로 DataFrame에 추가함.
bostonDF['PRICE'] = boston.target
y_target = bostonDF['PRICE']
X_data = bostonDF.drop('PRICE',axis=1,inplace = False)
ridge = Ridge(alpha = 10)
neg_mse_scores = cross_val_score(ridge, X_data, y_target, scoring="neg_mean_squared_error", cv = 5)
rmse_scores = np.sqrt(-1 * neg_mse_scores)
avg_rmse = np.mean(rmse_scores)
print(' 5 folds 의 개별 Negative MSE scores: ', np.round(neg_mse_scores, 3))
print(' 5 folds 의 개별 RMSE scores : ', np.round(rmse_scores,3))
print(' 5 folds 의 평균 RMSE : {0:.3f} '.format(avg_rmse))
5 folds 의 개별 Negative MSE scores: [-11.422 -24.294 -28.144 -74.599 -28.517]
5 folds 의 개별 RMSE scores : [3.38 4.929 5.305 8.637 5.34 ]
5 folds 의 평균 RMSE : 5.518
릿지의 5개 폴드 셋의 평균 RMSe는 5.524 입니다.
이번에는 릿지의 alpha 값을 0, 0.1, 1, 10, 100 으로 변화 시키면서 RMSE와 회귀 계수 값의 변화를 살펴 보자.
# Ridge에 사용될 alpha 파라미터의 값들을 정의
alphas = [0 , 0.1 , 1 , 10 , 100]
# alphas list 값을 iteration하면서 alpha에 따른 평균 rmse 구함.
for alpha in alphas :
ridge = Ridge(alpha = alpha)
#cross_val_score를 이용하여 5 fold의 평균 RMSE 계산
neg_mse_scores = cross_val_score(ridge, X_data, y_target, scoring="neg_mean_squared_error", cv = 5)
avg_rmse = np.mean(np.sqrt(-1 * neg_mse_scores))
print('alpha {0} 일 때 5 folds 의 평균 RMSE : {1:.3f} '.format(alpha,avg_rmse))
alpha 0 일 때 5 folds 의 평균 RMSE : 5.829
alpha 0.1 일 때 5 folds 의 평균 RMSE : 5.788
alpha 1 일 때 5 folds 의 평균 RMSE : 5.653
alpha 10 일 때 5 folds 의 평균 RMSE : 5.518
alpha 100 일 때 5 folds 의 평균 RMSE : 5.330
alpha = 100 일때 평균 RMSe가 5.330으로 가장 좋다.
이번에는 alpha 값의 변화에 따른 피처의 회귀 계수 값을 가로 막대그래프로 시각화 해보자.
import seaborn as sns
# 각 alpha에 따른 회귀 계수 값을 시각화하기 위해 5개의 열로 된 맷플롯립 축 생성
fig , axs = plt.subplots(figsize=(18,6) , nrows=1 , ncols=5)
# 각 alpha에 따른 회귀 계수 값을 데이터로 저장하기 위한 DataFrame 생성
coeff_df = pd.DataFrame()
# alphas 리스트 값을 차례로 입력해 회귀 계수 값 시각화 및 데이터 저장. pos는 axis의 위치 지정
for pos , alpha in enumerate(alphas) :
ridge = Ridge(alpha = alpha)
ridge.fit(X_data , y_target)
# alpha에 따른 피처별 회귀 계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가.
coeff = pd.Series(data=ridge.coef_ , index=X_data.columns )
colname='alpha:'+str(alpha)
coeff_df[colname] = coeff
# 막대 그래프로 각 alpha 값에서의 회귀 계수를 시각화. 회귀 계수값이 높은 순으로 표현
coeff = coeff.sort_values(ascending=False)
axs[pos].set_title(colname)
axs[pos].set_xlim(-3,6)
sns.barplot(x=coeff.values , y=coeff.index, ax=axs[pos])
# for 문 바깥에서 맷플롯립의 show 호출 및 alpha에 따른 피처별 회귀 계수를 DataFrame으로 표시
plt.show()
alpah 값을 계속 증가 시킬수록 회귀 계수 값은 지속적으로 작아짐을 알 수 있다.
DataFrame에 저장된 alpha 값의 변화에 따른 릿지 회귀 계수 값을 구현해보면은 alpha가 증가 할 수록 , 회귀 계수는 감소한다.
하지만 릿지 회귀의 경우 회귀 계수를 0으로 만들지는 않는다 .
ridge_alphas = [0 , 0.1 , 1 , 10 , 100]
sort_column = 'alpha:'+str(ridge_alphas[0])
coeff_df.sort_values(by=sort_column, ascending=False)
alpha:0 | alpha:0.1 | alpha:1 | alpha:10 | alpha:100 | |
---|---|---|---|---|---|
RM | 3.804752 | 3.813177 | 3.849256 | 3.698132 | 2.331966 |
CHAS | 2.688561 | 2.671849 | 2.554221 | 1.953452 | 0.638647 |
RAD | 0.305655 | 0.303105 | 0.289650 | 0.279016 | 0.314915 |
ZN | 0.046395 | 0.046546 | 0.047414 | 0.049547 | 0.054470 |
INDUS | 0.020860 | 0.016293 | -0.008547 | -0.042745 | -0.052626 |
B | 0.009393 | 0.009449 | 0.009754 | 0.010117 | 0.009471 |
AGE | 0.000751 | -0.000212 | -0.005368 | -0.010674 | 0.001230 |
TAX | -0.012329 | -0.012415 | -0.012907 | -0.013989 | -0.015852 |
CRIM | -0.107171 | -0.106612 | -0.103622 | -0.100352 | -0.101451 |
LSTAT | -0.525467 | -0.526678 | -0.534072 | -0.560097 | -0.661312 |
PTRATIO | -0.953464 | -0.941449 | -0.876633 | -0.798335 | -0.829503 |
DIS | -1.475759 | -1.459773 | -1.372570 | -1.248455 | -1.153157 |
NOX | -17.795759 | -16.711712 | -10.793436 | -2.374959 | -0.263245 |
라쏘 회귀
사이킷런은 Lasso 클래스를 통해 라쏘 회귀를 구현 하였다. Lasso 클래스의 주요 생성 파라미터는 alpha 이며, 이는 라쏘 회귀의 alpha L1 규제 계수에 해당한다.
라쏘의 alpha 값을 변화시키면서 RMSE와 각 피처의 회귀 계수를 출력해 보자.
get_linear_reg_eval()는 인자로 회귀 모델의 이름, alpha 값들의 리스트, 피처 데이터 셋과 타깃 데이터 셋을 입력 받아서 alpha 값에 따른 폴드 평균 RMSE를 출력하고 회귀 계수값들을 DataFrame으로 반환하는 함수이다.
from sklearn.linear_model import Lasso, ElasticNet
# alpha값에 따른 회귀 모델의 폴드 평균 RMSE를 출력하고 회귀 계수값들을 DataFrame으로 반환
def get_linear_reg_eval(model_name, params=None, X_data_n=None, y_target_n=None, verbose=True):
coeff_df = pd.DataFrame()
if verbose : print('####### ', model_name , '#######')
for param in params:
if model_name =='Ridge': model = Ridge(alpha=param)
elif model_name =='Lasso': model = Lasso(alpha=param)
elif model_name =='ElasticNet': model = ElasticNet(alpha=param, l1_ratio=0.7)
neg_mse_scores = cross_val_score(model, X_data_n,
y_target_n, scoring="neg_mean_squared_error", cv = 5)
avg_rmse = np.mean(np.sqrt(-1 * neg_mse_scores))
print('alpha {0}일 때 5 폴드 세트의 평균 RMSE: {1:.3f} '.format(param, avg_rmse))
# cross_val_score는 evaluation metric만 반환하므로 모델을 다시 학습하여 회귀 계수 추출
model.fit(X_data , y_target)
# alpha에 따른 피처별 회귀 계수를 Series로 변환하고 이를 DataFrame의 컬럼으로 추가.
coeff = pd.Series(data=model.coef_ , index=X_data.columns )
colname='alpha:'+str(param)
coeff_df[colname] = coeff
return coeff_df
# end of get_linear_regre_eval
함수를 생성했으면 이를 이용해 alpha값의 변화에 따른 RMSE와 그때의 회귀 계수들을 출력해 보자.
get_linear_reg_eval()에 모델명을 ‘Lasso’로 입력하면 라쏘 모델 기반으로 수행합니다.
# 라쏘에 사용될 alpha 파라미터의 값들을 정의하고 get_linear_reg_eval() 함수 호출
lasso_alphas = [ 0.07, 0.1, 0.5, 1, 3]
coeff_lasso_df =get_linear_reg_eval('Lasso', params=lasso_alphas, X_data_n=X_data, y_target_n=y_target)
####### Lasso #######
alpha 0.07일 때 5 폴드 세트의 평균 RMSE: 5.612
alpha 0.1일 때 5 폴드 세트의 평균 RMSE: 5.615
alpha 0.5일 때 5 폴드 세트의 평균 RMSE: 5.669
alpha 1일 때 5 폴드 세트의 평균 RMSE: 5.776
alpha 3일 때 5 폴드 세트의 평균 RMSE: 6.189
alpha가 0.07 일 때 가장 좋은 평균 RMSE를 보여준다.
# 반환된 coeff_lasso_df를 첫번째 컬럼순으로 내림차순 정렬하여 회귀계수 DataFrame출력
sort_column = 'alpha:'+str(lasso_alphas[0])
coeff_lasso_df.sort_values(by=sort_column, ascending=False)
alpah의 크기가 증가함에 따라 일부 피처의 회귀 계수는 아예 0으로 바뀌고 있다.
NOX 속성은 alpha가 0.7일 때부터 회귀 계수가 0이며, alpha를 증가시키면서 INDUS, CHAS와 같은 속성의 회귀 계수가 0 으로 바뀝니다.
회귀 계수가 0인 피처는 회귀 식에서 제외되면서 피처 선택의 효과를 얻을 수 있습니다.
엘라스틱넷 회귀
엘라스틱넷 회귀는 L2 규제와 L1 규제를 결합한 회귀이다. 따라서 엘라스틱넷 회귀 비용 함수의 목표는 다음 식을 최소화하는 W를 찾는 것이다.
엘라스틱넷은 라쏘 회귀가 서로 상관관계가 높은 피처들의 경우에 이들 중에서 중요 피처만 셀렉션 하고 다른 피처들은 모두 회귀 계수를 0으로 만든는 성향이 강하다.
이러한 성향으로 인해 alpha 값에 따라 회귀 계수의 값이 급격히 변동할 수 있는데,
엘라스틱넷 회귀는 이를 완화하기 위해 L2 규제를 라쏘 회귀에 추가한 것이다.
엘라스틱넷 회귀의 단점으로는 수행시간이 오래걸린다는 것이다 .
사이킷 런은 ElasticNet 클래스를 통해서 엘라스틱넷 회귀를 구현합니다.
ElasticNet 클래스의 주요 생성 파라미터는 alpha와 l1_ratio입니다. ElasticNet 클래스의 alpha는 Ridge와 Lasso 클래스의 alpha와 다르다.
엘라스틱넷의 규제는 a * L1+ b * L2 로 정의될 수 있으며, 이때 a는 L1규제의 alpah 값, b 는 L2규제의 alpha 값이다.
따라서 ElasticNet 클래스의 alpha 파라미터 값은 a+b입니다. ElasticNet 클래스의 l1_ratio 파라미터 값은 a / (a + b)이다.
l1_ratio 가 0이면 a 가 0이므로 L2 규제와 동일 하다. l1_ratio가 1이면 b가 0이므로 L1 규제와 동일하다.
이제 엘라스틱넷 alpha 값을 변화시키면서 RMSE와 각 피처의 회귀 계수를 출력해 보자.
# 엘라스틱넷에 사용될 alpha 파라미터의 값들을 정의하고 get_linear_reg_eval() 함수 호출
# l1_ratio는 0.7로 고정
elastic_alphas = [ 0.07, 0.1, 0.5, 1, 3]
coeff_elastic_df =get_linear_reg_eval('ElasticNet', params=elastic_alphas,
X_data_n=X_data, y_target_n=y_target)
####### ElasticNet #######
alpha 0.07일 때 5 폴드 세트의 평균 RMSE: 5.542
alpha 0.1일 때 5 폴드 세트의 평균 RMSE: 5.526
alpha 0.5일 때 5 폴드 세트의 평균 RMSE: 5.467
alpha 1일 때 5 폴드 세트의 평균 RMSE: 5.597
alpha 3일 때 5 폴드 세트의 평균 RMSE: 6.068
# 반환된 coeff_elastic_df를 첫번째 컬럼순으로 내림차순 정렬하여 회귀계수 DataFrame출력
sort_column = 'alpha:'+str(elastic_alphas[0])
coeff_elastic_df.sort_values(by=sort_column, ascending=False)
alpha 0.5 일 때 RMSE 가 5.468로 가장 좋은 예측 성능을 보이고 있다.
그리고 alpha 값에 따른 피처들의 회귀 계수들 값이 라쏘보다는 상대적으로 0이 되는 값이 적음을 알 수 있습니다.
선형 회귀 모델을 위한 데이터 변환
선형 회귀 모델은 피처값과 타깃값의 분포가 정규 분포 형태가 아닌 경우에 예측 성능에 부정적인 영향을 미칠 가능성이 높다.
따라서 선형 회귀 모델을 적용하기 전에 먼저 데이터에 대한 스케일링/ 정규화 작업을 수행하는 것이 일반적이다.
일반적으로 중요 피처들이나 타깃값의 분포도가 심하게 왜곡됐을 경우에 이러한 변환 작업을 수행 한다 .
- dardScaler 클래스를 이용해 평균이 0, 분산이 1인 표준 정규 분포를 가진 데이터 셋으로 변환하거나
MinMaxScaler 클래스를 이용해 최솟값이 0이고 최댓값이 1인 값으로 정규화를 수행한다. - 스케일링/정규화를 수행한 데이터 셋에 다시 다항 특성을 적용하여 변환하는 방법이다.
보통 1번 방법을 통해 예측 성능에 향상이 없을 경우 이와 같은 방법을 적용한다.
- 원래 값이 log 함수를 적용하면 보다 정규 분포에 가까운 형태로 값이 분포되는데, 이러한 변환을 로그 변환 이라고 부른다.
로그 변환은 매우 유용한 변환이며, 실제로 선형 회귀에서는 앞에서 소개한 1,2번 방법보다0 로그 변환이 훨씬 많이 사용 되는 변환 방법이다.
왜냐하면 1번 방법의 경우 예측 성능 향상을 크게 기대하기 어려운 경우가 많으며 ,
2번 방법의 경우 피처의 개수가 매우 많을 경우에는 다항 변환으로 생성되는 피처의 개수가 기하급수로 늘어나서 과적합의이슈가 발생할 수 있기 때문입니다.
타깃값의 경우는 일반적으로 로그변환을 적용한다.
결정 값을 정규 분포나 다른 정규값으로 변환하면 변환된 값을 다시 원본 타깃값으로 원복하기 어려울 수 있기 때문이다.
보스턴 주택 가격 피처 데이터 셋에 위에서 언급한 표준 정규 분포 변환, 최댓값/ 최솟값 정규화, 로그 변환을 차례로 적용한 후에 RMSE로 각 경우별 예측 성능을 측정해 보자.
from sklearn.preprocessing import StandardScaler, MinMaxScaler, PolynomialFeatures
# method는 표준 정규 분포 변환(Standard), 최대값/최소값 정규화(MinMax), 로그변환(Log) 결정
# p_degree는 다향식 특성을 추가할 때 적용. p_degree는 2이상 부여하지 않음.
def get_scaled_data(method='None', p_degree=None, input_data=None):
if method == 'Standard':
scaled_data = StandardScaler().fit_transform(input_data)
elif method == 'MinMax':
scaled_data = MinMaxScaler().fit_transform(input_data)
elif method == 'Log':
scaled_data = np.log1p(input_data)
else:
scaled_data = input_data
if p_degree != None:
scaled_data = PolynomialFeatures(degree=p_degree,
include_bias=False).fit_transform(scaled_data)
return scaled_data
# Ridge의 alpha값을 다르게 적용하고 다양한 데이터 변환방법에 따른 RMSE 추출.
alphas = [0.1, 1, 10, 100]
#변환 방법은 모두 6개, 원본 그대로, 표준정규분포, 표준정규분포+다항식 특성
# 최대/최소 정규화, 최대/최소 정규화+다항식 특성, 로그변환
scale_methods=[(None, None), ('Standard', None), ('Standard', 2),
('MinMax', None), ('MinMax', 2), ('Log', None)]
for scale_method in scale_methods:
X_data_scaled = get_scaled_data(method=scale_method[0], p_degree=scale_method[1],
input_data=X_data)
print('\n## 변환 유형:{0}, Polynomial Degree:{1}'.format(scale_method[0], scale_method[1]))
get_linear_reg_eval('Ridge', params=alphas, X_data_n=X_data_scaled,
y_target_n=y_target, verbose=False)
출력 결과를 좀 더 이해하기 쉽게 표 형태로 정리하겠습니다.
일반적으로 선형 회귀를 적용하려는 데이터 셋에 데이터 값의 분포가 심하게 왜곡 되어있을 경우에 이처럼 로그 변환을 적용하는 것이 좋은 결과를 기대할 수 있다.