이전 내용
규제 선형 모델
좋은 머신러닝 회귀 모델은 적절히 데이터에 적합하면서도 회귀 계수가 기하급수적으로 커지는 것을 제어할 수 있어야 한다.
이전까지 선형 모델의 비용 함수는 실제 값과 예측값의 차이 (RSS)를 최소화하는 것만 고려하다보니 학습 데이터에 지나치게 맞추게 되고, 회귀 계수가 쉽게 커졌다. 이럴 경우 변동성이 오히려 심해져서 테스트 데이터 세트에서는 예측 성능이 저하되기 쉽다.
▶ 비용 함수는 학습 데이터의 잔차 오류 값을 최소로하는 RSS 최소화 방법과 과적합을 방지하기 위해 회귀 계수 값이 커지지 않도록 하는 방법이 서로 균형을 이뤄야 한다.
◆ 규제(regularization)
비용 함수에 alpha 값으로 패널티를 부여해 회귀 계수 값의 크기를 감소시켜 과적합을 개선하는 방식.
규제에는 L2, L1 방식이 있으며,
- L2 규제는 W의 제곱에 대해 패널티를 부여하는 방식 - L2 규제 적용한 회귀는 릿지 회귀
- L1 규제는 W의 절댓값에 대해 패널티를 부여하는 방식. 영향력이 크지 않은 회귀 계수 값을 0으로 변환 - L1 규제 적용한 회귀 라쏘 회귀
릿지(Ridge) 회귀
사이킷런은 Ridge 클래스를 통해 릿지 회귀 구현. 주요 생성 파라미터는 alpha.
[보스턴 주택 가격 Ridge 클래스를 이용해 예측]
# 보스턴 주택 가격 Ridge 클래스를 이용해 예측
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score
import numpy as np
import pandas as pd
from scipy import stats
from sklearn import datasets
import warnings
warnings.filterwarnings('ignore')
# boston 데이터 세트 로드
boston = datasets.fetch_openml('boston', return_X_y=True, as_frame=True)
# boston 데이터 세트 DataFrame 변환
bostonDF = pd.DataFrame(boston[0], columns=boston[0].columns)
# Categorical 데이터 타입을 numeric 타입으로 변환 (필요한 경우)
for col in bostonDF.columns:
if pd.api.types.is_categorical_dtype(bostonDF[col]):
bostonDF[col] = bostonDF[col].astype(float)
# boston 데이터 세트의 target 배열은 주택 가격임. 이를 PRICE 칼럼으로 DF에 추가함.
bostonDF['PRICE'] = boston[1]
# boston 데이터 세트의 target 배열은
y_target = bostonDF['PRICE']
X_data = bostonDF.drop(['PRICE'], axis=1, inplace=False)
# alpha = 10으로 설정해 릿지 회귀 수행
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개 폴드 세트의 평균 RMSE가 5.518로, 규제가 없는 LinearRegression의 RMSE 평균인 5.829 보다 더 뛰어난 예측 성능을 보여준다.
[릿지의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크]
alpha 값을 0, 0.1, 1, 10, 100으로 변화시켜먼서 RMSE 값과 각 피처의 회귀 계수를 시각화하고 DataFrame에 저장
※ 릿지 회귀는 alpha 값이 커질수록 회귀 계수 값을 작게 만든다.
# 릿지의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크
alphas = [0, 0.1, 1, 10, 100]
# alphas list 값을 반복하면서 alpha 에 따른 평균 rmse 구함
for alpha in alphas:
ridge = Ridge(alpha = alpha)
# cross_val_score 를 이용해 5 폴드의 평균 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가 100일 때 평균 RMSE가 5.330으로 가장 좋다.
[alpha 값의 변화에 따른 피처의 회귀 계수 값 시각화]
회귀 계수를 Ridge 객체의 coef_ 속성에서 추출한 뒤에 Series 객체로 만들어서 시본 가로 막대 차트로 표시하고, DataFrame에 alpha 값별 회귀 계수로 저장
# alpha 값의 변화에 따른 피처의 회귀 계수 값 시각화
import matplotlib.pyplot as plt
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()
▶ alpha 값을 계속 증가시킬수록 회귀 계수 값은 지속적으로 작아지며, 특히 NOX 피처의 경우 alpha 값을 계속 증가시킴에 따라 회귀 계수가 크게 작아지고 있다.
[DataFrame에 저장된 alpha 값의 변화에 따른 릿지 회귀 계수 값 확인]
# DataFrame에 저장된 alpha 값의 변화에 따른 릿지 회귀 계수 값 확인
ridge_alphas = [0, 0.1, 1, 10, 100]
sort_columns = 'alpha:'+str(ridge_alphas[0])
coeff_df.sort_values(by=sort_columns, ascending=False)
▶ alpha 값이 증가하면서 회귀 계수가 지속적으로 작아지고 있으나, 릿지 회귀의 경우에는 회귀 계수를 0으로 만들지는 않는다.
라쏘(Lasso) 회귀
L1 규제는 불필요한 회귀 계수를 급격하게 감소시켜 0으로 만들고 제거한다. L1 규제는 적절한 피처만 회귀에 포함시키는 피처 선택의 특성을 가지고 있다.
사이킷런은 Lasso 클래스를 통해 라쏘 회귀를 구현하였으며, 주요 생성 파라미터 역시 alpha 이다.
Lasso 클래스를 이용해 라쏘의 alpha 값을 변화시키면서 RMSE와 각 피처의 회귀 계수를 출력.
[라쏘의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크]
get_linear_reg_eval() 함수는 alpha 값을 변화시키면서 결과를 출력하는 수행을 위한 함수이며 인자로 회귀 모델의 이름, alpha 값들의 리스트, 피처 데이터 세트와 타깃 데이터 세트를 입력받아서 alpha 값에 따른 폴드 평균 RMSE를 출력하고 회귀 계수값들을 DataFrame으로 반환한다.
# 라쏘의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크
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, return_coeff=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 folds의 평균 RMSE: {1:.3f}'.format(param, avg_rmse))
# cross_val_score는 evaluation metric만 반환하므로 모델을 다시 학습하여 회귀 계수 추출
model.fit(X_data_n, y_target_n)
if return_coeff:
# alpha 에 따른 피처별 회귀 계수를 Series로 변환하고 이를 DataFrame의 칼럼으로 추가
coeff = pd.Series(data=model.coef_, index=X_data_n.columns)
colname = 'alpha:'+str(param)
coeff_df[colname] = coeff
return coeff_df
# end of get_linear_reg_eval
[함수 생성 후 이를 이용해 alpha 값의 변화에 따른 RMSE와 그때의 회귀계수들 출력]
alpha 값은 [0.07, 0.1, 0.5, 1, 3]
# 라쏘에 사용될 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)
▶ alpha 가 0.07일 대 5.612로 가장 좋은 평균 RMSE를 보여줌.
[alpha 값에 따른 피처별 회귀 계수]
# 반환된 coeff_lasso_df를 첫 번째 칼럼순으로 내림차순 정렬해 회귀 계수 DataFrame 출력
sort_column = 'alpha:'+str(lasso_alphas[0])
coeff_lasso_df.sort_values(by=sort_column, ascending=False)
▶ alpha의 크기가 증가함에 따라 일부 피처의 회귀계수는 아예 0으로 변한다. 회귀 계수가 0인 피처는 회귀 식에서 제외되면서 피처 선택의 효과를 얻을 수 있다.
엘라스틱넷(Elastic Net) 회귀
엘라스틱넷 회귀는 L2 규제와 L1 규제를 결합한 회귀이며, 라쏘 회귀가 alpha 값에 따라 회귀 계수의 값이 급격히 변동하는 것을 완화하기 위해 L2 규제를 라쏘 회귀에 추가한 것이다.
엘라스틱넷 회귀의 단점은 L1과 L2 규제가 결합된 규제로 인해 수행시간이 상대적으로 오래 걸린다는 것이다.
사이킷런은 ElasticNet 클래스를 통해 엘라스틱넷 회귀를 구현하며, 주요 생성 파라미터는 alpha와 l1_ratio 이다.
단, 이 클래스의 alpha는 앞서 진행한 Ridge와 Lasso 클래스의 alpha 값과는 다르다.
엘라스틱넷의 규제는 a * L1 + b * L2 로 정의될 수 있는데, 여기서 a는 L1 규제의 alpha 값, b는 L2 규제의 alpha 값이다. 따라서 엘라스틱넷의 alpha 파라미터 값은 a+b 이다.
또한, l1_ratio 파라미터 값은 a / (a + b) 이며, 이 값이 0이면 a 가 0이므로 L2 규제와 동일하고, 1이면 b가 0이므로 L1 규제와 동일하다.
[엘라스틱넷의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크]
앞서 생성한 get_linear_reg_eval() 함수 이용
# 엘라스틱넷의 alpha 값을 변화시키면서 RMSE와 회귀 계수 값의 변화 체크
# 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)
▶ alpha 0.5 일 때 RMSE가 5.467로 가장 좋은 예측 성능을 보인다.
# 반환된 coeff_elstic_df를 첫 번째 칼럼순으로 내림차순 정렬해 회귀 계수 DataFrame 출력
sort_column = 'alpha:'+str(elastic_alphas[0])
coeff_elastic_df.sort_values(by=sort_column, ascending=False)
▶ alpha 값에 따른 피처들의 회귀 계수들 값이 라쏘보다는 상대적으로 0이 되는 값이 적다.
선형 회귀 모델을 위한 데이터 변환
선형 회귀의 경우 최적의 하이퍼 파라미터를 찾아내는 것 못지않게 먼저 데이터 분포도의 정규화와 인코딩 방법이 매우 중요하다.
선형 모델은 일반적으로 피처와 타깃값 간에 선형의 관계가 있다고 가정하고, 이 둘의 분포가 정규 분포 형태를 매우 선호한다.
타깃값의 경우 특정값의 분포가 치우친 왜곡된 형태일 경우 예측 성능에 부정적인 영향을 미칠 가능성이 높고, 피처값 역시 왜곡된 분포도로 인해 예측 성능에 부정적인 영향을 미칠 수 있다.
▶ 선형 회귀 모델 적용 전 데이터에 대한 스케일링/정규화 작업 수행 (특히 중요 피처들이나 타깃값의 분포도가 심하게 왜곡됐을 경우)
[사이킷런을 이용해 피처 데이터 세트에 적용하는 변환 작업]
- StandardScaler 클래스 이용: 평균이 0, 분산이 1인 표준 정규분포를 가진 데이터 세트로 변환
- MinMaxScaler 클래스 이용: 최솟값이 0, 최댓값이 1인 값으로 정규화 수행
- 스케일링/정규화를 수행한 데이터 세트에 다시 다항 특성을 적용하여 변환 (위의 두 가지 방법을 통해서도 예측 성능의 향상이 없을 경우)
- 로그 변환: 위의 방법의 경우 피처의 개수가 많을 때에는 다항 볗놘으로 생성되는 피처의 개수가 기하급수로 늘어나서 과적합의 이슈가 발생할 수 있음. 따라서 원래 값에 log 함수를 적용하여 보다 정규 분포에 가까운 형태로 값을 분포시킴
[보스턴 주택가격 피처 데이터 세트에 데이터 변환을 차례로 적용하여 RMSE 예측 성능 측정]
위에서 언급한 표준 정규 분포 변환, 최댓값/최솟값 정규화, 로그 변환 차례로 적용
◆ get_scaled_data() 함수 생성:
- method 인자로 변환 방법을 결정하며 위의 방법 중 하나를 선택.
- p_degree: 다항식 특성을 추가할 때 다항식 차수 입력 (차수는 2를 넘기지 않음)
- 로그 변환의 경우 np.log() 가 아닌 np.log1p() 이용
# 보스턴 주택가격 피처 데이터 세트에 데이터 변환을 차례로 적용하여 RMSE 예측 성능 측정
from sklearn.preprocessing import StandardScaler, MinMaxScaler, PolynomialFeatures
# method는 표준 정규 분포 변환(Standard), 최댓값/최솟값(MinMax), 로그변환(log) 결정
# p_degree는 다항식 특성 추가시 적용 (2 이상 부여 X)
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 값 변화 체크]
get_linear_reg_eval() 함수를 이용, Ridge 클래스의 alpha 값을 변화시키면서 피처 데이터 세트를 여러 가지 방법으로 변환한 데이터 세트를 입력받을 경우에 RMSE 값이 어떻게 변하는지 체크해본다.
# Ridge의 alpha 값을 변화시키면서 RMSE 값 변화 체크
alphas = [0.1, 1, 10, 100]
# 5개 방식으로 변환. 원본, 표준 + 다항식, 최대/최소, 최대/최소+디힝식, 로그
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, return_coeff=False)
이를 표로 정리해보면
변환 유형 | alpha 값 | |||
0.1 | 1 | 10 | 100 | |
원본 | 5.788 | 5.653 | 5.518 | 5.330 |
표준 | 5.826 | 5.803 | 5.637 | 5.421 |
표준 + 2차 다항식 | 8.827 | 6.871 | 5.485 | 4.634 |
최솟값/최댓값 정규화 | 5.764 | 5.465 | 5.754 | 7.635 |
최솟값/최댓값 정규화 + 2차 다항식 | 5.298 | 4.323 | 5.185 | 6.538 |
로그 변환 | 4.770 | 4.676 | 4.836 | 6.241 |
▶ 일반적으로 선형 회귀를 적용하려는 데이터 세트에 데이터 값의 분포가 심하게 왜곡되어 있을 경우에 로그 변환을 적용하는 것이 좋은 결과를 기대할 수 있다.
다음 내용
[출처]
파이썬 머신러닝 완벽 가이드
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 회귀 트리 (5) | 2024.10.23 |
---|---|
[머신러닝] 로지스틱 회귀 (3) | 2024.10.23 |
[머신러닝] 회귀 - 다항 회귀와 과대(과소) 적합 (3) | 2024.10.22 |
[머신러닝] 회귀 - LinearRegression 클래스 (2) | 2024.10.21 |
[머신러닝] 경사 하강법(GD, gradient descent) (3) | 2024.09.22 |