🔥알림🔥
① 테디노트 유튜브 -
구경하러 가기!
② LangChain 한국어 튜토리얼
바로가기 👀
③ 랭체인 노트 무료 전자책(wikidocs)
바로가기 🙌
④ RAG 비법노트 LangChain 강의오픈
바로가기 🙌
⑤ 서울대 PyTorch 딥러닝 강의
바로가기 🙌
Lasso, Ridge, ElasticNet-L1, L2규제를 적용한 선형 알고리즘
선형 알고리즘에 기반한 알고리즘에 대해서 알아보고 회귀 (Regression) 문제를 다뤄보도록 하겠습니다. 이번 선형 알고리즘에 기반한 회귀 문제를 다룰 때 L1, L2 규제의 개념이 등장합니다. L1, L2 규제는 딥러닝까지 쭉 이어지는 개념이고, 앞으로 머신러닝/딥러닝 학습시 과대 적합을 최대한 방지하기 위한 용도로 규제를 적용합니다.
최소제곱법에 근거한 선형 알고리즘인 Linear Regression에 L1, L2 규제를 적용한 알고리즘을 어떻게 scikit-learn
패키지에서 가져다 사용할 수 있는지놔 규제 계수인 alpha
값의 변화에 따라 어떻게 모델의 성능이 달라지는지도 모니터링 해보도록 하겠습니다.
실습의 끝자락에는 Pipeline
의 개념도 등장하며, PolynomialFeature를 활용한 feature 생성 그리고 StandardScaler와 MinMaxScaler를 활용한 전처리까지 바로 Pipeline
을 활용하여 쉽게 적용해볼 수 있는 방법까지 알아보도록 하겠습니다.
코드
# 튜토리얼 진행을 위한 모듈 import
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from IPython.display import Image
np.set_printoptions(suppress=True, precision=3)
회귀 (regression)
회귀 분석(regression analysis)은 관찰된 연속형 변수들에 대해 두 변수 사이의 모형을 구한뒤 적합도를 측정해 내는 분석 방법입니다.
하나의 종속변수와 하나의 독립변수 사이의 관계를 분석할 경우를 단순회귀분석(simple regression analysis), 하나의 종속변수와 여러 독립변수 사이의 관계를 규명하고자 할 경우를 다중회귀분석(multiple regression analysis)이라고 합니다.
예시
- 주택 가격 예측
- 매출액 예측
- 주가 예측
- 온도 예측
대표적인 회귀 모델로는
- 최소제곱법(Ordinary Least Squares)을 활용한 LinearRegression
- 경사하강법(Gradient Descent)을 활용한 SGDRegressor
- 선형 회귀 모델에 L1, L2 규제를 더한 Lasso, Ridge, ElasticNet
등이 있습니다.
회귀 모델을 위한 평가 지표
def make_linear(w=0.5, b=0.8, size=50, noise=1.0):
x = np.arange(size)
y = w * x + b
noise = np.random.uniform(-abs(noise), abs(noise), size=y.shape)
yy = y + noise
plt.figure(figsize=(10, 7))
plt.plot(x, y, color='r', label=f'y = {w}*x + {b}')
plt.scatter(x, yy, label='data')
plt.legend(fontsize=20)
plt.show()
print(f'w: {w}, b: {b}')
return x, y, yy
x, y_true, y_pred = make_linear(size=50, w=1.5, b=0.8, noise=5.5)
R2 Score (결정계수)
- 통계학 회귀분석에서 자주 쓰이는 회귀 평가 지표.
- 실제 값의 분산 대비 예측 값의 분산 비율을 나타냅니다.
- 1에 가까울 수록 좋은 모델, 0에 가까울 수록 나쁨, 음수가 나오면 잘못 평가 되었음을 의미합니다.
Image(url='https://miro.medium.com/max/2812/1*_HbrAW-tMRBli6ASD5Bttw.png', width=350)
Python 코드로 위의 수식을 그대로 구현합니다.
r2 = 1 - ((y_true - y_pred)**2).sum() / ((y_true - y_true.mean())**2).sum()
print('r2 score = {:.3f}'.format(r2))
sklearn.metrics
패키지에 r2_score
로 구현
from sklearn.metrics import r2_score
r2_ = r2_score(y_true, y_pred)
print('r2 score = {:.3f}'.format(r2_))
MSE(Mean Squared Error)
- 예측 값과 실제 값의 차이에 대한 제곱에 대하여 평균을 낸 값
- MSE 오차가 작으면 작을수록 좋지만, 과대적합이 될 수 있음에 주의합니다.
- 예측 값과 실제 값보다 크게 예측이 되는지 작게 예측되는지 알 수 없습니다.
Image(url='https://blog.kakaocdn.net/dn/qJowI/btqBBUCMNDv/L7bfq2lu0hfsZzCSDx0E5k/img.png', width=300)
Python 코드로 위의 수식을 그대로 구현합니다.
mse = ((y_true - y_pred)**2).mean()
print('mse = {:.3f}'.format(mse))
sklearn.metrics
패키지에 mean_squared_error
를 활용합니다.
from sklearn.metrics import mean_squared_error
mse_ = mean_squared_error(y_true, y_pred)
print('mse = {:.3f}'.format(mse_))
MAE (Mean Absolute Error)
- 예측값과 실제값의 차이에 대한 절대값에 대하여 평균을 낸 값
- 실제 값과 예측 값 차이를 절대 값으로 변환해 평균을 계산합니다. 작을수록 좋지만, 과대적합이 될 수 있음에 주의합니다.
- 스케일에 의존적입니다.
예를 들어, 아파트 집값은 10억, 20억으로 구성되어 있고, 과일 가격은 5000원, 10000원으로 구성되어 있을때,
예측하는 각각 모델의 MSE 가 똑같이 100 이 나왔다고 가정한다며,동일한 오차율이 아님에도 불구하고 동일하게 평가되어 지는 현상이 발생합니다. 이는 MSE 오차에서도 마찬가지 입니다.
Image(url='https://miro.medium.com/max/630/1*OVlFLnMwHDx08PHzqlBDag.gif')
Python 코드로 위의 수식을 그대로 구현합니다.
mae = (np.abs(y_true - y_pred)).mean()
print('mae = {:.3f}'.format(mae))
sklearn.metrics
패키지에 mean_absolute_error
를 활용합니다.
from sklearn.metrics import mean_absolute_error
mae_ = mean_absolute_error(y_true, y_pred)
print('mae = {:.3f}'.format(mae_))
RMSE (Root Mean Squared Error)
Image(url='https://miro.medium.com/max/966/1*lqDsPkfXPGen32Uem1PTNg.png', width=350)
- 예측값과 실제값의 차이에 대한 제곱에 대하여 평균을 낸 뒤 루트를 씌운 값
- MSE의 장단점을 거의 그대로 따라갑니다.
- 제곱 오차에 대한 왜곡을 줄여줍니다.
Python 코드로 위의 수식을 그대로 구현합니다.
rmse = np.sqrt(mse)
print('mse = {:.3f}, rmse = {:.3f}'.format(mse, rmse))
sklearn.metrics
패키지에는 별도로 RMSE 평가지표는 없습니다.
회귀 모델 (Regression Models)
모델별 성능 확인을 위한 함수
import matplotlib.pyplot as plt
import seaborn as sns
my_predictions = {}
my_pred = None
my_actual = None
my_name = None
colors = ['r', 'c', 'm', 'y', 'k', 'khaki', 'teal', 'orchid', 'sandybrown',
'greenyellow', 'dodgerblue', 'deepskyblue', 'rosybrown', 'firebrick',
'deeppink', 'crimson', 'salmon', 'darkred', 'olivedrab', 'olive',
'forestgreen', 'royalblue', 'indigo', 'navy', 'mediumpurple', 'chocolate',
'gold', 'darkorange', 'seagreen', 'turquoise', 'steelblue', 'slategray',
'peru', 'midnightblue', 'slateblue', 'dimgray', 'cadetblue', 'tomato'
]
def plot_predictions(name_, pred, actual):
df = pd.DataFrame({'prediction': pred, 'actual': y_test})
df = df.sort_values(by='actual').reset_index(drop=True)
plt.figure(figsize=(11, 8))
plt.scatter(df.index, df['prediction'], marker='x', color='r')
plt.scatter(df.index, df['actual'], alpha=0.7, marker='o', color='black')
plt.title(name_, fontsize=15)
plt.legend(['prediction', 'actual'], fontsize=12)
plt.show()
def mse_eval(name_, pred, actual):
global my_predictions, colors, my_pred, my_actual, my_name
my_name = name_
my_pred = pred
my_actual = actual
plot_predictions(name_, pred, actual)
mse = mean_squared_error(pred, actual)
my_predictions[name_] = mse
y_value = sorted(my_predictions.items(), key=lambda x: x[1], reverse=True)
df = pd.DataFrame(y_value, columns=['model', 'mse'])
print(df)
min_ = df['mse'].min() - 10
max_ = df['mse'].max() + 10
length = len(df) / 2
plt.figure(figsize=(9, length))
ax = plt.subplot()
ax.set_yticks(np.arange(len(df)))
ax.set_yticklabels(df['model'], fontsize=12)
bars = ax.barh(np.arange(len(df)), df['mse'], height=0.3)
for i, v in enumerate(df['mse']):
idx = np.random.choice(len(colors))
bars[i].set_color(colors[idx])
ax.text(v + 2, i, str(round(v, 3)), color='k', fontsize=12, fontweight='bold', verticalalignment='center')
plt.title('MSE Error', fontsize=16)
plt.xlim(min_, max_)
plt.show()
def add_model(name_, pred, actual):
global my_predictions, my_pred, my_actual, my_name
my_name = name_
my_pred = pred
my_actual = actual
mse = mean_squared_error(pred, actual)
my_predictions[name_] = mse
def remove_model(name_):
global my_predictions
try:
del my_predictions[name_]
except KeyError:
return False
return True
def plot_all():
global my_predictions, my_pred, my_actual, my_name
plot_predictions(my_name, my_pred, my_actual)
y_value = sorted(my_predictions.items(), key=lambda x: x[1], reverse=True)
df = pd.DataFrame(y_value, columns=['model', 'mse'])
print(df)
min_ = df['mse'].min() - 10
max_ = df['mse'].max() + 10
length = len(df) / 2
plt.figure(figsize=(9, length))
ax = plt.subplot()
ax.set_yticks(np.arange(len(df)))
ax.set_yticklabels(df['model'], fontsize=12)
bars = ax.barh(np.arange(len(df)), df['mse'], height=0.3)
for i, v in enumerate(df['mse']):
idx = np.random.choice(len(colors))
bars[i].set_color(colors[idx])
ax.text(v + 2, i, str(round(v, 3)), color='k', fontsize=12, fontweight='bold', verticalalignment='center')
plt.title('MSE Error', fontsize=16)
plt.xlim(min_, max_)
plt.show()
보스턴 집 값 데이터
데이터 로드 (load_boston)
from sklearn.datasets import load_boston
data = load_boston()
data['data']에는 X 데이터, data['feature_names']에는 컬럼 명입니다.
df = pd.DataFrame(data['data'], columns=data['feature_names'])
target 데이터도 Column 에 추가 합니다.
df['target'] = data['target']
df.head()
컬럼 소개
속성 수 : 13
- CRIM: 범죄율
- ZN: 25,000 평방 피트 당 주거용 토지의 비율
- INDUS: 비소매(non-retail) 비즈니스 면적 비율
- CHAS: 찰스 강 더미 변수 (통로가 하천을 향하면 1; 그렇지 않으면 0)
- NOX: 산화 질소 농도 (천만 분의 1)
- RM:주거 당 평균 객실 수
- AGE: 1940 년 이전에 건축된 자가 소유 점유 비율
- DIS: 5 개의 보스턴 고용 센터까지의 가중 거리
- RAD: 고속도로 접근성 지수
- TAX: 10,000 달러 당 전체 가치 재산 세율
- PTRATIO 도시 별 학생-교사 비율
- B: 1000 (Bk-0.63) ^ 2 여기서 Bk는 도시 별 검정 비율입니다.
- LSTAT: 인구의 낮은 지위
- target: 자가 주택의 중앙값 (1,000 달러 단위)
학습(train) / 테스트(test) 용 데이터를 분할 합니다.
from sklearn.model_selection import train_test_split
SEED=30
x_train, x_test, y_train, y_test = train_test_split(df.drop('target', 1), df['target'], random_state=SEED)
x_train.shape, x_test.shape
x_train.head()
LinearRegression
from sklearn.linear_model import LinearRegression
model = LinearRegression(n_jobs=-1)
- n_jobs: CPU코어의 사용
model.fit(x_train, y_train)
pred = model.predict(x_test)
mse_eval('LinearRegression', pred, y_test)
규제 (Regularization)
학습이 과대적합 되는 것을 방지하고자 일종의 penalty를 부여하는 것
L2 규제 (L2 Regularization)
- 각 가중치 제곱의 합에 규제 강도(Regularization Strength) λ를 곱한다.
- λ를 크게 하면 가중치가 더 많이 감소되고(규제를 중요시함), λ를 작게 하면 가중치가 증가한다(규제를 중요시하지 않음).
L1 규제 (L1 Regularization)
- 가중치의 제곱의 합이 아닌 가중치의 합을 더한 값에 규제 강도(Regularization Strength) λ를 곱하여 오차에 더한다.
- 어떤 가중치(w)는 실제로 0이 된다. 즉, 모델에서 완전히 제외되는 특성이 생기는 것이다.
L2 규제가 L1 규제에 비해 더 안정적이라 일반적으로는 L2규제가 더 많이 사용된다
Ridge (L2 Regularization)
- L2 규제 계수를 적용합니다.
- 선형회귀에 가중치 (weight)들의 제곱합에 대한 최소화를 추가합니다.
주요 hyperparameter
alpha
: 규제 계수
수식
$Error=MSE+αw^2$
from sklearn.linear_model import Ridge
규제 계수(alpha)를 정의합니다.
# 값이 커질 수록 큰 규제입니다.
alphas = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001]
for alpha in alphas:
ridge = Ridge(alpha=alpha, random_state=SEED)
ridge.fit(x_train, y_train)
pred = ridge.predict(x_test)
add_model('Ridge(alpha={})'.format(alpha), pred, y_test)
plot_all()
coef_는 feature의 가중치를 보여줍니다.
가중치(weight)를 토대로 회귀 예측시 어떤 feature가 주요하게 영향을 미쳤는지 보여 줍니다.
x_train.columns
ridge.coef_
DataFrame으로 feature별 가중치를 시각화 합니다.
pd.DataFrame(list(zip(x_train.columns, ridge.coef_)), columns=['features', 'importances']).sort_values('importances')
def plot_coef(columns, coef):
coef_df = pd.DataFrame(list(zip(columns, coef)))
coef_df.columns=['feature', 'coef']
coef_df = coef_df.sort_values('coef', ascending=False).reset_index(drop=True)
fig, ax = plt.subplots(figsize=(9, 7))
ax.barh(np.arange(len(coef_df)), coef_df['coef'])
idx = np.arange(len(coef_df))
ax.set_yticks(idx)
ax.set_yticklabels(coef_df['feature'])
fig.tight_layout()
plt.show()
plot_coef(x_train.columns, ridge.coef_)
이번에는, alpha 값에 따른 coef 의 차이를 확인해 봅시다
ridge_100 = Ridge(alpha=100)
ridge_100.fit(x_train, y_train)
ridge_pred_100 = ridge_100.predict(x_test)
ridge_001 = Ridge(alpha=0.001)
ridge_001.fit(x_train, y_train)
ridge_pred_001 = ridge_001.predict(x_test)
plot_coef(x_train.columns, ridge_100.coef_)
plot_coef(x_train.columns, ridge_001.coef_)
Lasso (L1 Regularization)
Lasso(Least Absolute Shrinkage and Selection Operator)
- 선형 회귀에 L1 규제 계수를 적용합니다.
- 가중치(weight)의 절대 값의 합을 최소화 하는 계수를 추가 합니다.
- 불필요한 회귀 계수를 급격히 감소, 0으로 만들어 제거합니다.
- 특성(Feature) 선택에 유리합니다.
주요 hyperparameter
alpha
: L1 규제 계수
수식
$Error=MSE+α|w|$
from sklearn.linear_model import Lasso
# 값이 커질 수록 큰 규제입니다.
alphas = [100, 10, 1, 0.1, 0.01, 0.001, 0.0001]
for alpha in alphas:
lasso = Lasso(alpha=alpha)
lasso.fit(x_train, y_train)
pred = lasso.predict(x_test)
add_model('Lasso(alpha={})'.format(alpha), pred, y_test)
plot_all()
lasso_100 = Lasso(alpha=100)
lasso_100.fit(x_train, y_train)
lasso_pred_100 = lasso_100.predict(x_test)
lasso_001 = Lasso(alpha=0.001)
lasso_001.fit(x_train, y_train)
lasso_pred_001 = lasso_001.predict(x_test)
plot_coef(x_train.columns, lasso_001.coef_)
lasso_001.coef_
Lasso 모델에 너무 큰 alpha 계수를 적용하면 대부분의 feature들의 가중치가 0으로 수렴합니다.
plot_coef(x_train.columns, lasso_100.coef_)
lasso_100.coef_
ElasticNet
Elastic Net 회귀모형은 가중치의 절대값의 합(L1)과 제곱합(L2)을 동시에 제약 조건으로 가지는 모형입니다.
Image(url='https://miro.medium.com/max/1312/1*j_DDK7LbVrejTq0tfmavAA.png', width=500)
주요 hyperparameter
alpha
: 규제 계수
l1_ratio (default=0.5)
- l1_ratio = 0 (L2 규제만 사용).
- l1_ratio = 1 (L1 규제만 사용).
- 0 < l1_ratio < 1 (L1 and L2 규제의 혼합사용)
from sklearn.linear_model import ElasticNet
alpha=0.01
ratios = [0.2, 0.5, 0.8]
for ratio in ratios:
elasticnet = ElasticNet(alpha=alpha, l1_ratio=ratio, random_state=SEED)
elasticnet.fit(x_train, y_train)
pred = elasticnet.predict(x_test)
add_model('ElasticNet(l1_ratio={})'.format(ratio), pred, y_test)
plot_all()
elsticnet_20 = ElasticNet(alpha=5, l1_ratio=0.2)
elsticnet_20.fit(x_train, y_train)
elasticnet_pred_20 = elsticnet_20.predict(x_test)
elsticnet_80 = ElasticNet(alpha=5, l1_ratio=0.8)
elsticnet_80.fit(x_train, y_train)
elasticnet_pred_80 = elsticnet_80.predict(x_test)
plot_coef(x_train.columns, elsticnet_20.coef_)
plot_coef(x_train.columns, elsticnet_80.coef_)
Scaler 적용
from sklearn.preprocessing import MinMaxScaler, StandardScaler
MinMaxScaler (정규화)
정규화 (Normalization)도 표준화와 마찬가지로 데이터의 스케일을 조정합니다.
정규화가 표준화와 다른 가장 큰 특징은 모든 데이터가 0 ~ 1 사이의 값을 가집니다.
즉, 최대값은 1, 최소값은 0으로 데이터의 범위를 조정합니다.
minmax_scaler = MinMaxScaler()
minmax_scaled = minmax_scaler.fit_transform(x_train)
round(pd.DataFrame(minmax_scaled).describe(), 2)
min값과 max값을 0~1사이로 정규화
minmax_scaler = MinMaxScaler()
minmax_scaled = minmax_scaler.fit_transform(x_train)
round(pd.DataFrame(minmax_scaled).describe(), 2)
StandardScaler (표준화)
표준화는 데이터의 평균을 0 분산 및 표준편차를 1로 만들어 줍니다.
표준화를 하는 이유
- 서로 다른 통계 데이터들을 비교하기 용이하기 때문입니다.
- 표준화를 하면 평균은 0, 분산과 표준편차는 1로 만들어 데이터의 분포를 단순화 시키고, 비교를 용이하게 합니다.
Image(url='https://t1.daumcdn.net/cfile/tistory/999EC6335CDE8D8131', width=100)
std_scaler = StandardScaler()
std_scaled = std_scaler.fit_transform(x_train)
round(pd.DataFrame(std_scaled).describe(), 2)
파이프라인 (pipeline)
scikit-learn의 전처리(pre-processing)용 모듈과 모델의 학습 기능을 파이프라인으로 합칠 수 있습니다.
- 파이프라인으로 결합된 모형은 원래의 모형이 가지는
fit
,predict
함수를 가집니다. - 파이프라인에 정의된 순서에 따라 전처리 모듈이 먼저 호출되어 전처리 과정을 거친 후 모델이 학습하게 됩니다.
from sklearn.pipeline import make_pipeline
pipeline = make_pipeline(
MinMaxScaler(),
ElasticNet(alpha=0.1, l1_ratio=0.2)
)
pipeline.fit(x_train, y_train)
pipeline_pred = pipeline.predict(x_test)
mse_eval('MinMax ElasticNet', pipeline_pred, y_test)
pipeline = make_pipeline(
StandardScaler(),
ElasticNet(alpha=0.1, l1_ratio=0.2)
)
pipeline.fit(x_train, y_train)
pipeline_pred = pipeline.predict(x_test)
mse_eval('Standard ElasticNet', pipeline_pred, y_test)
Polynomial Features
다항식의 계수간 상호작용을 통해 새로운 feature를 생성합니다.
예를들면, [a, b] 2개의 feature가 존재한다고 가정하고,
degree=2로 설정한다면, polynomial features 는 [1, a, b, a^2, ab, b^2] 가 됩니다.
주의
degree
를 올리면, 기하급수적으로 많은 feature 들이 생겨나며, 학습 데이터에 지나치게 과대적합 될 수 있습니다.
주요 hyperparameter
degree
: 차수include_bias
: 1로 채운 컬럼 추가 여부
from sklearn.preprocessing import PolynomialFeatures
x = np.arange(5).reshape(-1, 1)
x
degree=2
, include_bias=False
인 경우
poly = PolynomialFeatures(degree=2, include_bias=False)
x_poly = poly.fit_transform(x)
x_poly
degree=2
, include_bias=True
인 경우
poly = PolynomialFeatures(degree=2, include_bias=True)
x_poly = poly.fit_transform(x)
x_poly
degree=3
, include_bias=True
인 경우
poly = PolynomialFeatures(degree=3, include_bias=True)
x_poly = poly.fit_transform(x)
x_poly
보스톤 집 값 데이터의 features에 PolynomialFeatures
를 적용합니다.
poly = PolynomialFeatures(degree=2, include_bias=False)
poly_features = poly.fit_transform(x_train)[0]
poly_features
PolynomialFeature
도 파이프라인(pipeline)을 활용하여 전처리 해준다면, 손쉽게 구현 및 적용이 가능합니다.
poly_pipeline = make_pipeline(
PolynomialFeatures(degree=2, include_bias=False),
ElasticNet(alpha=0.1, l1_ratio=0.2)
)
poly_pipeline.fit(x_train, y_train)
poly_pred = poly_pipeline.predict(x_test)
mse_eval('Poly ElasticNet', poly_pred, y_test)
댓글남기기