728x90

선형 판별 분석 LDA : Linear Discriminant Analysis

- PCA와 마찬가지로 차원 축소 기법

- 클래스간 분산을 최대화, 클래스 내부 분산을 최소화 하는 선형 결정식을 찾음

 

- 클래스 별 중심점과 공분산이 주어질떄 클래스간 분산과 클래스내 분산을 구할수 있음

- 클래스내 분산 행렬과 클래스간 분산 행렬로부터 고유 값과 고유벡터를 추출할수 있음.

- 고유 값은 비중, 고유 벡터는 클래스 내 분산을 최소, 클래스간 분산을 최대로 하는 선형 판별식의 계수

 

 

 

 

붓꽃 데이터에 선형 판별 분석 적용하기

 

PCA는 반응 변수가 필요없는 비지도 학습이지만

 

LDA는 반응 변수 종속 변수가 필요한 지도 학습 알고리즘

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_iris

iris = load_iris()
iris_scaled = StandardScaler().fit_transform(iris.data)
lda = LinearDiscriminantAnalysis(n_components=2)
lda.fit(X=iris.data, y=iris.target)
iris_lda = lda.transform(iris_scaled)
print(iris_lda.shape)

 

 

선형 판별 분석을 통해

 

차원수, 독립변수를 2개로 줄였음에도

 

클래스 간 분산이 최대, 클래스 내 분산이 최소가 되도록 변환 된 것을 볼수 있음

 

lda_cols = ["lda_1", "lda_2"]
df = pd.DataFrame(data=iris_lda, columns=lda_cols)
df["target"] = iris.target

markers=["^", "s", "o"]

for i, marker in enumerate(markers):
    x_data = df[df["target"]==i]["lda_1"]
    y_data = df[df["target"]==i]["lda_2"]
    plt.scatter(x_data, y_data, marker=marker, label=iris.target_names[i])

plt.legend(loc="upper right")
plt.xlabel("lda_1")
plt.ylabel("lda_2")
plt.show()

 

 

 

300x250
728x90

uci에서 제공하는 신용 카드 데이터에 PCA를 적용하여 연체 여부를 판단해보자

 

archive.ics.uci.edu/ml/datasets/default+of+credit+card+clients

 

 

이 데이터의 반응 변수로 연체 여부인데 1이면 연체, 0이면 연체 아님

 

 

데이터를 로드하긴 했는데

 

실제 컬럼 이름이 ID 행으로 빠져나와 있다.

 

ID 행의 컬럼 이름들을 다시 컬럼으로 바꿔주자

 

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt

#df = pd.read_csv("./res/credit-card-fraud-detection/creditcard.csv")
df = pd.read_excel("./credit_card.xls", sheet_name="Data")
df.head()

 

 

 

0번째 행들의 값으로 컬럼 명을 바꾸고,

 

인덱스 ID 행을 제거

 

 

cols = df.iloc[0,:].values
df.columns = cols
df.drop(index="ID", axis=0, inplace=True)
df.head()

 

 

 

다음달 연체 여부는 이름을

 

default로 줄여주고, pay0은 pay1로 바꾸자

 

df.rename(columns={"PAY_0":"PAY_1", "default payment next month":"default"}, inplace=True)
df.head()

 

PCA 에 앞서

 

상관 계수 행렬로 히트맵을 그리려고하는데

 

corr()이 반환이 안된다.

y = df["default"]
X = df.drop(columns="default", axis=1, inplace=False)

corr = X.corr()
sns.heatmap(corr, annot=True)

 

 

데이터 타입을 보니 다 문자열 처리가 된듯하다

 

가능한 데이터는 수로 바꾸어줘야겟다

 

 

 

float으로 바꿔주고

 

 

 

 

 

히트맵을 살펴보면

 

BILL_AMT1 ~ 6까지는 모두 상관관계가 0.8 이상으로 강한 선형적 관계를 보인다.

 

다중공선성 문제를 해결하기 위해

 

차원 축소를 해주자.

 

y = df["default"]
X = df.drop(columns="default", axis=1, inplace=False)

corr = X.corr()

plt.figure(figsize=(14,14))
sns.heatmap(corr, annot=True)

 

 

BILL_AMT1 ~ 6 6개 속성들을 추출하여

 

표준화 후 주성분 2개로 주성분 분석을 한 결과

 

2개의 주성분 요소 만으로도 95%정도의 설명력을 가짐

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

cols_bill = ["BILL_AMT" + str(i) for i in range(1, 7)]
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X[cols_bill])
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X_scaled)
print(pca.explained_variance_ratio_)

 

 

원본 데이터셋과 PCA 적용 데이터셋의 랜덤 포레스트 성능을 비교해보자

 

기존 데이터셋의 경우 81% 였으나 pca를 통해 6개의 주성분 만으로 79%의 성능을 보임

 

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import cross_val_score

rf = RandomForestRegressor()
scores = cross_val_score(rf, X, y, cv=5, scoring="accuracy", n_jobs=-1)
print("normal dataset : {0:.3f}".format(np.mean(scores)))
rf = RandomForestRegressor()
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
pca = PCA(n_components=6)
X_pca = pca.fit_transform(X_scaled)
scores = cross_val_score(rf, X_pca, y, scoring="accuracy", cv=5, n_jobs=-1)
print("pca dataset : {0:.3f}".format(np.mean(scores)))

 

 

300x250
728x90

머신 러닝과 차원 축소 dimension reduction

- 피처들이 늘어날수록 데이터간의 거리는 크게 증가

- 중요하지 않은 피처가 많은 데이터의 경우 그렇지 않은 경우보다 예측 정확도가 떨어짐

 + 피처 간의 상관 관계의 영향(다중 공선성)

=> 차원 축소 실시

 

차원 축소

- 특징 추출 feature exraction : 기존의 데이터셋을 개선된 형태로 축소된 공간으로 맵핑

- 특징 선택 feature selection : 기존의 데이터셋에서 중요한 특징들만 선정

=> PCA, SVD, NMF 등

- 이미지나 텍스트에서 차원 축소이 성능 향상에 도움이 됨.

 

 

 

주성분 분석 Principal Component Analysis

- 특징들의 공분산 행렬로부터 주성분을 추출하는 방법

- 공분산 행렬에서 직교화된 고유값과 고유 행렬을 구함. 고유값은 해당 번째 주성분의 중요도, 고유 벡터는 해당번째 주성분의 가중 계수벡터

- 서로 다른 고유 벡터는 직교하므로 두 주성분 사이 공분산은 0, 고유 벡터는 자기 자신의 전치한것과 내적은 1

 

 

 

붓꽃 데이터로 살펴보자

 

일단 데이터 준비

from sklearn.datasets import load_iris
import pandas as pd
import matplotlib.pyplot as plt

iris = load_iris()
cols = ["sepal_length", "sepal_width", "petal_length", "petal_width"]
df = pd.DataFrame(data=iris.data, columns=cols)
df["target"]=iris.target
df.head()

 

 

 

데이터를 간단하게 살펴보면

 

sepal_length와 sepal_width 만으로도 어느정도 붓꽃의 종류를 분류할수 있음을 알수있다.

markers = ["^", "s", "o"]
for i, marker in enumerate(markers):
    x_val = df[df["target"] ==i]["sepal_length"]
    y_val = df[df["target"]==i]["sepal_width"]
    plt.scatter(x_val, y_val, marker=marker,label=iris.target_names[i])
plt.legend()
plt.xlabel("sepal_length")
plt.ylabel("sepal_width")
plt.show()

 

 

주성분 분석은 여러 피처들을 같이 다루므로

 

사전에 표준화부터 시켜주어야 한다.

 

표준화 후 기존의 피처 4개를 2개의 주성분으로 축소시키자.

 

from sklearn.preprocessing import StandardScaler
from sklearn.decomposition import PCA

df_scaled = StandardScaler().fit_transform(df)
pca = PCA(n_components=2)
pca.fit(df_scaled)
df_pca = pca.transform(df_scaled)
print(df_pca.shape)

 

표준화나 pca 변환 후에는 넘파이 배열 형태로 되어있으므로

 

주성분 변환 결과를 데이터 프레임으로 변환시킨 후

 

주성분 1을 x축 주성분 2를 y축으로 할때 어떻게 분류되는지 시각화하여보자

 

두개의 주성분 만으로 대부분의 데이터를 올바르게 분류하고 있는걸 확인할 수 있다.

 

마지막에 pca.explained_variance_ratio_로

 

해당 주성분들이 데이터를 얼마나 잘 설명하고 있는지 나타내고 있으며

 

주성분요소 1은 76%, 주성분 요소 2는 18%정도로 전체 변동에서의 비율을 나타내고 있다.

cols = ["pc1", "pc2"]
df_pca = pd.DataFrame(data=df_pca, columns=cols)
df_pca["target"] = iris.target

markers = ["^", "s", "o"]
for i, marker in enumerate(markers):
    x_val = df_pca[df_pca["target"] ==i]["pc1"]
    y_val = df_pca[df_pca["target"]==i]["pc2"]
    plt.scatter(x_val, y_val, marker=marker,label=iris.target_names[i])
plt.legend()
plt.xlabel("pc1")
plt.ylabel("pc2")
plt.show()

print(pca.explained_variance_ratio_)

 

 

 

랜덤 포레스트로 

 

기존 데이터와 pca 적용 후 데이터 정확도를 살펴보았는데

 

pca 후가 다소 좋은 정확도를 얻음.

 

일반적으로 차원이 축소된 후(정보가 줄어들어) 예측 성능을 떨어짐

 

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
import numpy as np

rf = RandomForestClassifier()

scores = cross_val_score(rf, iris.data, iris.target, scoring="accuracy",cv=3)
print("normal accuracy : {0:.3f}".format(np.mean(scores)))
scores = cross_val_score(rf, df_pca.iloc[:,:-1], df_pca.iloc[:,-1], scoring="accuracy", cv=3)
print("pca accuracy : {0:.3f}".format(np.mean(scores)))

 

 

 

 

 

 

300x250
728x90

 

 

www.kaggle.com/c/house-prices-advanced-regression-techniques

 

 

 

이번에는 고급 회귀 기법을 이용한 주택 가격 예측을 해보고자 한다.

 

우선 대이터에 대해서 살펴보자

 

파일들은

 

훈련데이터, 테스트 데이터, 제출 샘플 csv와 데이터 설명을 위한 텍스트 파일이 있다.

 

데이터 속성들로는

 

SalePrice 주택 가격으로 타갯 변수

 

나머지는 우리들이 회귀 모델을 학습을 해야하는데 필요한 독립 변수들이 되겠다.

 

이제 우선 필요한 라이브러리들과 데이터부터 로드하자.

 

 

 

 

데이터는 1460개, 컬럼은 81개 정도가 있고

 

보면 Id 부터 시작한다.

 

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings

warnings.filterwarnings("ignore")
%load_ext autotime

train = pd.read_csv("./res/house-prices-advanced-regression-techniques/train.csv")
train.info()
train.describe()

 

 

결측치 체크해본 결과

 

 

몇개의 열에서는 결측치가 상당히 많이 존재한다.

 

이런 열들은 나중에 제거해주고

 

 

 

종속 변수를 한번

 

히스토그램으로 플로팅 시켜본 결과 좌측으로 치우쳐진 형태로 되어있다.

 

로그 변환을 한번 시켜보자

 

 

왜도가 0에 가까운 정규 분포의 형태가 되었다.

 

이 데이터를 반영해주고

 

널 처리를 해주자

 

 

 

 

타겟 값 로그 변환 형으로 변경

 

널 많은 열 삭제

 

정수형 널데이터 평균값 대치

 

이외 널 데이터 타입 확인

train["SalePrice"] = log_sale_price

# 널 많은 컬럼 삭제
drop_cols = ["Id", "PoolQC", "MiscFeature", "Alley", "Fence", "FireplaceQu"]
train.drop(columns=drop_cols, axis=1, inplace=True)

#정수형 데이터 평균 값 대치
train.fillna(train.mean(), inplace=True)

#정수형 이외 널이 존재하는 칼람 추출
#train.isnull().sum() 결과에서 0보다 큰 항들의 인덱스 추출
null_column = train.isnull().sum()[train.isnull().sum() > 0]

print(train.dtypes[null_column.index])

 

 

 

문자열 변수들을 원핫 인코딩으로 가변수화

 

이제 선형 회귀 모델 별 성능을 측정해보자

 

#문자열 속성 가변수로 카테고리화
#get_dummies는 컬럼 미지정시 문자열 속성들을 변환 수행
print("before get dummies train.shape : {}".format(train.shape))
train_oh = pd.get_dummies(train)
print("after get dummies train.shape : {}".format(train.shape))
null_column = train_oh.isnull().sum()[train.isnull().sum() > 0]

print(train.dtypes[null_column.index])

 

 

 

 

RMSE 결과를 살펴보면

 

라쏘 모델이 유독 RMSE가 커보인다

 

하이퍼파라미터를 튜닝해보자

 

 

 

from sklearn.linear_model import LinearRegression, Ridge, Lasso
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

def get_rmse(model, X_test, y_test):
    y_pred = model.predict(X_test)
    mse = mean_squared_error(y_test, y_pred)
    rmse = np.sqrt(mse)
    print("#### model : {0} ####\n MSE : {1:.3f}\n RMSE : {2:.3f}\n".format(model.__class__.__name__, mse, rmse))
    return rmse

def get_rmses(models, X_test, y_test):
    
    rmses = []
    for model in models:
        rmse = get_rmse(model, X_test, y_test)
        rmses.append(rmse)
    
    return rmses

y = train_oh["SalePrice"]
X = train_oh.drop(columns=["SalePrice"], axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

lr = LinearRegression()
ridge = Ridge()
lasso = Lasso()

lr.fit(X_train, y_train)
ridge.fit(X_train, y_train)
lasso.fit(X_train, y_train)

models = [lr, ridge, lasso]
get_rmses(models, X_test, y_test)

 

 

각 모델 별 가장큰 회귀계수와 작은 회귀계수들을 살펴보면

 

 

선형괴 릿지 모델은 비슷하나

 

라쏘 모델의 경우 YearBulit가 너무 크고 나머지는 작음.

 

릿지 모델은 회귀 벡터 W의 절대값 제곱에 패널티를 주는 L2 규제 

 

L2 규제의 경우 회귀 계수의 크기를 감소시켰었음

 

throwexception.tistory.com/1074

 

 

라쏘 모델은 회귀 벡터 W의 절대값에 패널티를 부여하는 L1 규제 적용

 

L1 규제는 중요하지 않은 회귀 계수를 크게 줄여 0으로 만들어 제거했었음

 

그래서인지 릿지 모델은 선형 모델보다 회귀 계수가 작아졌으며

 

라쏘 모델의 경우 중요한 변수 이외에는 크게 축소되거나 제거

def get_top_bottom_coef(model, X, n=10):
    coef = pd.Series(data=model.coef_, index=X.columns)
    coef_high = coef.sort_values(ascending=False).head(n)
    coef_low = coef.sort_values(ascending=False).tail(n)
    return coef_high, coef_low

def visualize_coeff(models, X, n=10):
    fig, axs = plt.subplots(figsize=(24, 10), ncols=3, nrows=1)
    fig.tight_layout()
    
    for idx, model in enumerate(models):
        coef_high, coef_low = get_top_bottom_coef(model, X, n=10)
        coef_concat = pd.concat([coef_high, coef_low])
        axs[idx].set_title(model.__class__.__name__+" Coefficient", size=24)
        axs[idx].tick_params(axis="y", direction="in", pad=-120)
        for label in (axs[idx].get_xticklabels() + axs[idx].get_yticklabels()):
            label.set_fontsize(22)
        sns.barplot(x=coef_concat.values, y=coef_concat.index, ax=axs[idx])

visualize_coeff(models, X, n=10)

 

 

 

릿지와 라쏘 모델의 하이퍼 파라미터를 조정시켜보자

 

 

from sklearn.model_selection import GridSearchCV

def print_best_params(model, params, X, y):
    
    gs = GridSearchCV(model, param_grid=params, n_jobs=-1, cv=5, scoring="neg_mean_squared_error")
    gs.fit(X, y)
    rmse = np.sqrt(-1 * gs.best_score_)
    print("### {0} ###\n mean RMSE : {1:.3f}\n best alpha : {2}\n".format(model.__class__.__name__, np.mean(rmse), gs.best_params_))
    

params = {
    "alpha" : [0.001, 0.05, 0.1, 1, 5, 8, 10, 15, 20]
}

print_best_params(ridge,params, X, y)
print_best_params(lasso,params, X, y)

 

 

최적 하이퍼 파라미터를 찾았으니 성능 지표와 시각화를 시켜보면

 

라쏘 모델의 RMSE가 타 모델들보다 가장 작아졌으며 

 

 

더 데이터 튜닝을 한다면 아직 이상치를 처리하지 않았으니

 

데이터 분포도를 살펴보자

 

이전에 잠깐 한 과정을 되살펴보면

 

 

ridge = Ridge(alpha=10)
lasso = Lasso(alpha=0.001)

ridge.fit(X_train, y_train)
lasso.fit(X_train, y_train)

models = [lr, ridge, lasso]
get_rmses(models, X_test, y_test)
visualize_coeff(models, X, n=10)

 

 

 

나는 이전에 지금까지 머신러닝 문제를 푸는 과정을

 

라이브러리 임포트

 

데이터 확인, 탐색

 

전처리

 

모델 설계

 

학습

 

성능평가

 

이 단계를 다 거치고 나서

 

해야된다고 생각은 했는데

 

 

 

내가 기억하고 있는 모든 과정을 다 하고나서 조금씩 수정하는 방식으로 갔었다

 

 

그러다 보니 임의로 필요하다고 생각하는데로 전처리를 한후 모델 조정하고, 다시 전처리하고 모델 조정하고

 

이 과정을 반복했는데

 

내 방식대로만 가면 여기서 몇가지 놓친 부분이 있었던것 같았다.

 

 

 

 

 

 

다시 생각해보자

 

데이터 로드하고,

 

간단하게 살펴본다음에

 

최소한의 데이터 처리

- 결측치 열 제거, 대치, 카테고리형 변환

 

학습 전에 타겟 분포를 살펴보고 왜곡된 형태였는지 살펴봤었다.

 

왜곡이 심하면 로그 변환을 수행해주고

 

 

그 다음 기본 모델들을 사용하여 성능 평가와

 

중요한 회귀 계수들 시각화해서 살펴보았었다.

 

거기서 라쏘 모델 L1 규제를 적용하는(중요한 피처만 남기고 나머지 크게 축소, 제거)으로 인해 문제점을 확인하였고

 

 

하이퍼 파라미터를 조정해서 타 모델보다 개선된 결과를 구한게 여기까지 한일

 

 

 

 

타깃 값과 마찬가지로 독립 변수들의 왜곡도들을 살펴보자

 

 

from scipy.stats import skew

ftr_idx = train.dtypes[train.dtypes != "object"].index
skew_ftrs = train[ftr_idx].apply(lambda x : skew(x))
skew_ftrs_top = skew_ftrs[skew_ftrs > 1]
print(skew_ftrs_top.sort_values(ascending=False))

 

 

왜곡도가 심한 변수들을 로그 변환하고

 

 

다시 최적 하이퍼파라미터를 탐색한 결과

 

왜도가 큰 데이터를 로그 변환하기 전

 

0.142였던 RMSE가 

 

 

 

0.128, 0.125로 줄어들었다.

train[skew_ftrs_top.index] = np.log1p(train[skew_ftrs_top.index])
train_oh = pd.get_dummies(train)
y = train_oh["SalePrice"]
X = train_oh.drop("SalePrice", axis=1, inplace=False)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

print_best_params(ridge,params, X, y)
print_best_params(lasso,params, X, y)

 

 

 

 

 

 

이렇게 구한 파라미터로 학습 결과를 살펴보면

 

성능이 크게 변화하지는 않았다.

 

내가 처음부터 random_state를 고정시켜서 했어야됬는데 ;;

 

 

아무튼 세 모델다 GrLiveArea가 판매 가격에 상당히 큰 영향을 미치는걸 그래프로 볼수 있다.

 

이 변수와 판매 가격 사이 관계를 살펴보자

 

 

lr = LinearRegression()
ridge = Ridge(alpha=0.10)
lasso = Lasso(alpha=0.001)
lr.fit(X_train, y_train)
ridge.fit(X_train, y_train)
lasso.fit(X_train, y_train)

models = [lr, ridge, lasso]
get_rmses(models, X_test, y_test)
visualize_coeff(models, X, n=10)

 

 

 

 

편하게 살펴보려 했는데 지금 train은 로그 변환을 해서인지 정규 분포 형태를 띄고 있다.

원본 데이터로 다시보면

 

GrLivArea의 4000너머로 크게 동떨어진 이상치가 보인다

train_org = pd.read_csv("./res/house-prices-advanced-regression-techniques/train.csv")
sns.regplot(x=train_org["GrLivArea"], y=train_org["SalePrice"])

 

이 아웃라이어 들의 인덱스를 다음과 같이 조건문으로 구하고 제거하자

* GrLivArea가 4000보다 크고, Saleprice가 500000이하인 데이터

 

그리고 다시 하이퍼 파라미터 재탐색

 

cond1 = train_oh["GrLivArea"] > np.log1p(4000)
cond2 = train_oh["SalePrice"] < np.log1p(500000)
outlier = train_oh[cond1 & cond2].index
train_oh.drop(outlier, axis=0, inplace=True)

y = train_oh["SalePrice"]
X = train_oh.drop("SalePrice", axis=1, inplace=False)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2)

print_best_params(ridge,params, X, y)
print_best_params(lasso,params, X, y)

 

 

 

 

다시 구한 하이퍼파라미터로 재 평가, 시각화 한 결과

 

릿지 모델과 라쏘 모델로 더 좋은 결과를 구하였다.

 

lr = LinearRegression()
ridge = Ridge(alpha=8)
lasso = Lasso(alpha=0.001)
lr.fit(X_train, y_train)
ridge.fit(X_train, y_train)
lasso.fit(X_train, y_train)

models = [lr, ridge, lasso]
get_rmses(models, X_test, y_test)
visualize_coeff(models, X, n=10)

 

 

스태킹 기법으로 풀어보자

 

 

스태킹 기법은

 

최종 모델과 최종 모델 학습 데이터용 모델들로 나눠지는데

 

학습 데이터용 모델은 기존의 데이터로 학습하여(학습 데이터 -> 학습/검증 용으로 분할) 추정 결과들을

 

최종 모델의 입력(학습 데이터)으로 사용하여 개선된 결과를 구하는 기법이었다.

 

 

 

다음 함수를 통해 최종 모델에 사용할 학습/ 테스트 데이터를 만들었다.

 

폴드를 나누고, 해당 폴드 데이터로 학습, 테스트하여 예측 결과를 저장. 테스트 값은 폴드별 열방향 추가 후 평균

 

 

from sklearn.model_selection import KFold
from sklearn.metrics import mean_absolute_error

def get_stacking_db(model, X_train, y_train, X_test, n_folds):
    kf = KFold(n_splits=n_folds, shuffle=False, random_state=0)
    # (X rows, 1)
    train_fold_pred = np.zeros((X_train.shape[0], 1))
    test_pred = np.zeros((X_test.shape[0], 1))
    print("## model : {} ##".format(model.__class__.__name__))
    for fold_cnt, (train_idx, valid_idx) in enumerate(kf.split(X_train)):
        print("fond set : {}".format(fold_cnt))
        X_fd = X_train[train_idx]
        y_fd = y_train[valid]
        model.fit(X_fd, y_fd)
        train_fold_pred[valid_idx, :] = model.predict(X_fd).reshape(-1, 1)
        test_pred[:, fold_cnt] = model.predict(X_test)
    test_pred_mean = np.mean(test_pred, axis = 1).reshape(-1, 1)
    return train_fold_pred, test_pred_mean

 

 

최종 모델을 라쏘 모델로 사용한 스태킹 모델의 성능은 0.960165로 기존의 방법보다 최적의 결과를 구할 수 있었슴.

from lightgbm import LGBMRegressor


ridge = Ridge(alpha=8)
lasso = Lasso(alpha=0.001)
lgbm = LGBMRegressor(n_estimators=1000)

ridge_train, ridge_test = get_stacking_db(ridge, X_train.values, y_train.values, X_test.values, 5)
lasso_train, lasso_test = get_stacking_db(lasso, X_train.values, y_train.values, X_test.values, 5)
lgbm_train, lgbm_test = get_stacking_db(lgbm, X_train.values, y_train.values, X_test.values, 5)


X_stack_train = np.concatenate((ridge_train, lasso_train, lgbm_train), axis=1)
X_stack_test = np.concatenate((ridge_test, lasso_test, lgbm_test), axis=1)

final_lasso = Lasso(alpha=0.001)
final_lasso.fit(X_stack_train, y_train)
final_pred = final_lasso.predict(X_stack_test)

get_rmse(final_lasso,X_stack_test, y_test)

 

 

 

 

300x250
728x90

 

 

캐글의 자전거 대여 수요 예측 문제

www.kaggle.com/c/bike-sharing-demand/data

 

 

 

 

 

 

일단 라이브러리 임포트

 

import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings("ignore")

 

 

 

 

 

관련 데이터셋 로드

samples = pd.read_csv("./res/bike-sharing-demand/sampleSubmission.csv")
train = pd.read_csv("./res/bike-sharing-demand/train.csv")
test = pd.read_csv("./res/bike-sharing-demand/test.csv")


train.head()

 

 

 

 

데이터 확인하기

 

 

"""
session : 계절
holiday : 공휴일 여부
workingday : 평일 여부
weather : 날씨
temp : 온도
atemp : 체감온도
humidity : 습도
windspeed : 풍속
casual : 대여 회수
registered : 등록자의 대여횟수
count : 대여횟수


10886 x 12

na는 없음
"""

train.info()

 

 

 

 

날짜의 경우 문자열로 되어있으므로 datetime으로 변환

 

+ 년월일시간으로 분리

"""
문자열 날짜 -> datetime으로 변환
"""
train["datetime"] = train.datetime.apply(pd.to_datetime)

train["year"] = train.datetime.apply(lambda x : x.year)
train["month"] = train.datetime.apply(lambda x : x.month)
train["day"] = train.datetime.apply(lambda x : x.day)
train["hour"] = train.datetime.apply(lambda x : x.hour)
train.head()

 

 

필요없는 열 제거

 

datetime + casual, registered(count에 포함되므로)

 

"""
casual + registered = count
-> casual, registered, datetime 열 삭제
"""

drop_col = ["casual", "registered", "datetime"]
train.drop(columns=drop_col, axis=1, inplace= True)

 

 

성능 평가 지표들 정의

 

* np.log1p(val) : val + 1 후 로그 변환 수행

 => val = 0인 경우 -inf 방지

 

from sklearn.metrics import mean_squared_error, mean_absolute_error

#get root mean sqaured log error
def get_rmsle(y, pred):
    log_y = np.log1p(y)
    log_pred = np.log1p(pred)
    mlse = np.sum((log_y - log_pred)**2)
    rmlse = np.sqrt(mlse)
    return rmlse

def get_rmse(y, pred):
    return np.sqrt(mean_squared_error(y, pred))

def eval_reg(y, pred):
    rmlse = get_rmsle(y, pred)
    rmse = get_rmse(y, pred)
    mae = mean_absolute_error(y, pred)
    print("rmlse : {0:.3f}\nrmse : {1:.3f}\nmae : {2:.3f}".format(rmlse, rmse, mae))

 

 

단순 선형 회귀 학습 후 평가

 

 

- 성능 평가 지표들 오차 값이 크게 나옴

from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.linear_model import LinearRegression, Ridge, Lasso

y = train["count"]
X = train.drop(columns=["count"], axis=1, inplace=False)

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

lr = LinearRegression()
lr.fit(X_train, y_train)

pred = lr.predict(X_test)

eval_reg(y_test, pred)

 

 

 

오차가 가장 큰 값들 비교

 

실제 값과 예측 값사이 상당히 큰 오차

-> 타겟 값의 분포가 왜곡될 가능성이 큼

"""
종속 변수 count 값을 생각하면 오차가 상당히 크게 나옴
오차가 가장 큰 경우를 비교해보자
"""
def get_top_error_data(y_test, pred, n_tops = 5):
    res = pd.DataFrame(data=y_test.values, columns=["real"])
    res["predicted"] = pred
    res["diff"] = np.abs(res["real"] - res["predicted"])
    print(res.sort_values("diff", ascending=False)[:n_tops])

get_top_error_data(y_test, pred, n_tops=5)

 

 

라벨 분포 히스토그램으로 시각화

 

로그 변환을 통해 정규분포에 가깝게 변환

"""
예측  오류가 매우 큰것을 보면 타깃이 왜곡된 분포를 따르는지 확인해야함
"""
y.hist()

 

 

 

이전에 0을 중심으로 하던 분포가

 

5~6을 중심으로 정규 분포와 유사해짐

"""
로그 변환을 통해 y를 정규분포 형태로 바꾸어 주자
"""
y = np.log1p(y)
y.hist()

 

 

 

변환된 데이터로 재학습 및 성능 평가

 

rmse, mae는 크게 줄어들었으나 rmlse는 여전히 큼

 

-> 계수 확인해보자

"""
로그 변환을 수행 후

오차 척도들이 크게 줄어들었다.
"""
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.3)

lr = LinearRegression()
lr.fit(X_train, y_train)

pred = lr.predict(X_test)

eval_reg(y_test, pred)
get_top_error_data(y_test, pred, n_tops=5)

 

 

 

트리 모델에서 feature_importances_로 보았으나

 

회귀 모델에서 coef_로 볼수있음

"""
회귀 모델에서 피처 중요도로 보았던것과는 달리

선형 회귀 모델에선 회귀 계수로 어떤 피처가 중요하게 판단되었는지 몰수 있음

"""
coeff = pd.Series(data=lr.coef_, index=X.columns)
coeff_sort = coeff.sort_values(ascending=False)
sns.barplot(x=coeff_sort.values, y=coeff_sort.index)

 

 

 

카테고리형 데이터들을

 

원핫 인코딩으로 변환 후

 

학습, 결과 출력

 

rmlse도 어느정도 줄어듦

 

"""
상대적으로 큰 값을 갖는 year가 높은 중요도를 가진것으로 나오고 있음

year 간에는 큰 의미가 없는데도 영향을 주고 있으니

카테고리형 변수들을 인코딩 필요
"""

X_ohe = pd.get_dummies(X, columns=["year", "month", "hour", "holiday", "workingday",
                                  "season", "weather"])


X_train, X_test, y_train, y_test = train_test_split(X_ohe, y, test_size = 0.3)
lr = LinearRegression()
lr.fit(X_train, y_train)
pred = lr.predict(X_test)
eval_reg(y_test, pred)
get_top_error_data(y_test, pred, n_tops=5)

 

 

300x250
728x90

회귀 트리

 

분류 트리의 경우 리프 노드를 통해 데이터들을 분할시키켰다면

 

회귀 트리는 해당 리프 노드에 속하는 데이터의 평균 값으로 추정값을 구함.

 

 

 

 

회귀 트리 모델로 보스턴 집값을 예측해보자

 

 

라이브러리 임포트

from sklearn.datasets import load_boston
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestRegressor
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import GradientBoostingRegressor
from lightgbm import LGBMRegressor
import pandas as pd
import numpy as np
import seaborn as sns

 

 

간단하게 랜덤 포레스트 모델로 성능 평가

 

boston = load_boston()
df = pd.DataFrame(data=boston.data, columns=boston.feature_names)
df["PRICE"] = boston.target

y = df["PRICE"]
X = df.iloc[:,:-1]

rf = RandomForestRegressor()
neg_mse_scores = cross_val_score(rf, X, y, scoring="neg_mean_squared_error", cv =5)
rmse_scores = np.sqrt(-1 * neg_mse_scores)
avg_rmse = np.mean(rmse_scores)

print("negative mse scores : ", np.round(neg_mse_scores, 2))
print("rmse scores : ",np.round(rmse_scores, 2))
print("avg socres : ", np.round(avg_rmse))

 

 

 

성능 평가 함수 구현 후

 

타 트리 회귀 모델간 성능 비교

 

def get_model_cv_prediction(model, X, y):
    neg_mse_scores = cross_val_score(model, X, y, scoring="neg_mean_squared_error", cv =5)
    rmse_scores = np.sqrt(-1 * neg_mse_scores)
    avg_rmse = np.mean(rmse_scores)
    print("### {} ###".format(model.__class__.__name__))
    print("negative mse scores : ", np.round(neg_mse_scores, 2))
    print("rmse scores : ",np.round(rmse_scores, 2))
    print("avg socres : ", np.round(avg_rmse))
    


dt = DecisionTreeRegressor()
rf = RandomForestRegressor()
gb = GradientBoostingRegressor()
lgb = LGBMRegressor()


models = [dt, rf, gb, lgb]
for model in models:
    get_model_cv_prediction(model, X, y )

 

 

 

랜덤 포레스트 모델에서 중요한 특징 시각화

rf.fit(X, y)

feature_series = pd.Series(data=rf.feature_importances_,index=X.columns)
feature_series = feature_series.sort_values(ascending=False)
sns.barplot(x = feature_series, y=feature_series.index)

 

 

 

 

가장 중요한 변수인 RM을 단일 독립변수로 사용하여 종속 변수 PRICE 간에

 

최대 깊이에 따라 어떻게 회귀 예측 결과가 달라지는지 살펴보고,

 

선형 회귀와도 비교해보자

 

 

 

 

 

 

 

기본적으로

 

RM이 5 ~ 8사이 분포하며, PRICE는 3 ~ 50까지 다양하게 분포

 

from sklearn.linear_model import LinearRegression
import matplotlib.pyplot as plt


df_sample = df[["RM","PRICE"]]
df_sample = df_sample.sample(n=100)

plt.scatter(df_sample.RM,df_sample.PRICE)

 

 

 

 

 

 

선형 회귀의 경우 데이터들을 직선 형태로 적합시키고 있으나

 

랜덤 포레스트 모델의 경우 트리의 깊이에 따라 속하는 분류의 평균 값으로 회귀

 

랜덤 포래스트 최대 깊이 2의 경우 값이 4개로 회귀

 

최대 깊이가 6인 경우 매우 복잡한 경우의 수들로 회귀하는 모습을 보임

 

 

lr = LinearRegression()
rf_dep2 = DecisionTreeRegressor(max_depth=2)
rf_dep6 = DecisionTreeRegressor(max_depth=6)


X_train = df["RM"].values.reshape(-1, 1)
y_train = df["PRICE"].values.reshape(-1, 1)

lr.fit(X_train, y_train)
rf_dep2.fit(X_train, y_train)
rf_dep6.fit(X_train, y_train)


X_test = np.linspace(5, 8, 100).reshape(-1, 1)

pred_lr = lr.predict(X_test)
pred_rf_dep2 = rf_dep2.predict(X_test)
pred_rf_dep6 = rf_dep6.predict(X_test)


fig, (ax1, ax2, ax3) = plt.subplots(figsize=(14, 4), ncols=3)

ax1.set_title("linear regression")
ax1.scatter(df_sample.RM,df_sample.PRICE)
ax1.plot(X_test, pred_lr)

ax2.set_title("RF depth 2")
ax2.scatter(df_sample.RM,df_sample.PRICE)
ax2.plot(X_test, pred_rf_dep2, color="red")

ax3.set_title("RF depth 6")
ax3.scatter(df_sample.RM,df_sample.PRICE)
ax3.plot(X_test, pred_rf_dep6, color="green")

 

 

300x250
728x90

로지스틱 회귀분석 logistic regression

 

연속 적인 값을 예측하는 일반적인 회귀 분석과는 달리

 

0 또는 1로 분류하는 분류 추정기로

 

 

 

타겟 값이 0, 1만 주어진 문제에서

 

선형 모델을 사용하는 경우 예측률이 많이 떨어진다.

 

이 선형 모델을 로짓 변환을 통해 시그모이드 함수 형태로 바꾸어

 

0, 1을 다루는 문제를 더 잘 분류 할수 있도록 만든 회귀 모델이 로지스틱 회귀 모델

 

 

 

 

 

 

간단하게 유방암 데이터로 다뤄보자

 

우선 라이브러리 임포트

from sklearn.linear_model import LogisticRegression
from sklearn.datasets import load_breast_cancer
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.metrics import accuracy_score, roc_auc_score
import pandas as pd
import numpy as np

 

 

 

데이터를 표준화 시킨 후

 

간단하게 정확도와 roc auc 스코어를 보면

cancer = load_breast_cancer()
df = pd.DataFrame(data=cancer.data, columns=cancer.feature_names)
scaled_df = StandardScaler().fit_transform(df)

X_train, X_test, y_train, y_test = train_test_split(scaled_df, cancer.target, test_size=0.2)

lr = LogisticRegression()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)

print("acc : {0:.3f}".format(accuracy_score(y_test, y_pred)))
print("roc auc : {0:.3f}".format(roc_auc_score(y_test, y_pred)))

 

 

로지스틱 회귀 모델도 L2, L1 규제를 파라미터로 받는데 이 규제 파라미터와

 

알파 값을 조절하는 파라미터 C = 1/alpha를 조절하여 최적 하이퍼파라미터 탐색

 

 

C = 1 -> alpha = 1이고, L2 규제를 했을때 0.980686으로 최적의 성능이 나옴. 

params = {
    "penalty" : ["l2", "l1"],
    "C" : [0.01, 0.1, 1, 5, 10]
}

lr = LogisticRegression()
gs = GridSearchCV(lr, param_grid=params, cv=5, n_jobs=-1, scoring="accuracy")
gs.fit(scaled_df, cancer.target)
print("best param : {}".format(gs.best_params_))
print("best acc : {}".format(gs.best_score_))
300x250
728x90

선형 회귀 모델은 기본적으로 오차항은 정규 분포를 따르고 있어

 

데이터의 분포가 정규분포를 따르는 것이 좋다.

 

 

하지만 분포가 한 방향으로 치우쳐진(왜도 != 0) 경우 예측 성능이 저하 될 수 있다.

 

-> 정규화 수행

 

1. 표준 정규 분포를 따르도록 정규화

- StandardScaler 사용

 

2. 최소 0, 최대 1이 되도록 정규화

- MinMaxScaler 사용

 

3. 로그 변환을 이용한 정규화

- 입력 데이터에 로그 변환 적용 -> 출력 값은 정규 분포를 따르게 됨.

 

 

 

 

 

데이터 변환 방법에 따른 성능을 비교하기 위해

 

 

이전의 선형 회귀 평가 함수에서 알파 값별 rmse를 반환하도록 약간 수정

 

def get_linear_reg_eval(model_name, params=None, X_data = None, y_target=None, visualize=False, columns=None):
    fig, axs = plt.subplots(figsize=(18, 6), nrows=1, ncols=5)
    coeff_df = pd.DataFrame()
    print("#### {} ####".format(model_name))
    params = params.copy()
    rmses = []
    for pos, param in enumerate(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, y, scoring="neg_mean_squared_error",cv = 5)
        rmse_scores = np.sqrt(-1 * neg_mse_scores)
        avg_rmse = np.mean(rmse_scores)
        rmses.append(avg_rmse)
        print("# alpha = {} #".format(param))
        print("neg_mse_socres : {}".format(neg_mse_scores))
        print("rmse_scores : {}".format(rmse_scores))
        print("avg_rmse : {}\n".format(avg_rmse))
        
        model.fit(X_data, y_target)
        coeff = pd.Series(model.coef_, index=columns)
        colname="alpha="+str(param)
        coeff_df[colname] = coeff

        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])
    
    if visualize == False:
        plt.close()
    return rmses

 

 

보스턴 집값 데이터를 사용하기 위해

 

보스턴 데이터 함수 정의

def get_boston_Xy():
    boston = load_boston()
    df = pd.DataFrame(data=boston.data, columns=boston.feature_names)
    df["PRICE"] = boston.target
    df.head()
    X = df.iloc[:,:-1]
    y = df.iloc[:, -1]
    return X, y

 

 

입력한 method에 따라 변환 하는 데이터 변환 함수 정의

 

def get_scaled_data(method=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
    return scaled_data

 

 

데이터 변환 방법과 alpha 값에 따른 rmse 비교 데이터 프레임 반환 함수 정의

 

def comparision_data_transform(model_name=None, X_data=None, y_target=None, alphas=None):
    scale_methods = ["Standard", "MinMax", "Log", None]
    df_columns = alphas.copy()
    df_columns.insert(0, "scaled_method")
    print("df_columns ", df_columns)
    df = pd.DataFrame(columns=df_columns)
    for scale_method in scale_methods:
        scaled_data = get_scaled_data(method=scale_method, input_data=X)
        print("# scale method = {}#".format(scale_method))
        print("----------------------")
        #print(scaled_data)
        rmses = get_linear_reg_eval(model_name, params=alphas, X_data=scaled_data, y_target=y, columns=X.columns)
        rmses.insert(0, scale_method)
        rmse_series = pd.Series(rmses, index=df_columns)
        df = df.append(rmse_series.to_frame().T)
    
    return df

 

 

 

 

실행 코드

X, y = get_boston_Xy()
alphas = [0.1, 1, 10, 100]
model_name = "Ridge"
df = comparision_data_transform(model_name=model_name, X_data=X, y_target=y, alphas=alphas)

 

 

결과

- 대부분 Log 변환을 하였을때 좋은 성능을 보였음.

- Log 변환을 수행하고, 알파 값이 1일때 rmse가 4.67623으로 최적의 결과를 얻음

 

 

 

300x250
728x90

회귀 모델에서 과소 적합과 과적합을 다루기 위한 최적의 비용 함수

- 잔차 RSS를 줄이는것 뿐만이 아니라 회귀 계수가 급격하게 커지는 것을 막아야 함.

 * 아래의 그림와 같이 과적합의 경우 회귀계수가 매우 크게 증가됨.

- 최적의 비용 함수 = 잔차 최소화 + 회귀 계수 크기 제어

 

 

 

L2 규제와 L1 규제

- 위 비용 함수에서 alpha가 0이라면 RSS가 최소가 되는 모델을 구하게 됨.

- alpha가 무한대라면 가중치 벡터(계수 벡터) W가 매우 작아져야 비용이 최소화됨,

=> alpha가 크면 회귀 계수를 줄여 과적합 개선, 작다면 RSS로 상쇄시킴

- 아래의 경우와 같이 가중치 벡터의 제곱에 패널티를 주는 경우를 L2 규제, L2 규제를 적용한 선형 회귀(릿지 회귀)

- 아래의 경우처럼 가중치 벡터의 절대값에 패널티를 주는 경우 L1 규제, L1규제를 적용한 선형 회귀(라쏘 회귀)

 

 

 

 

릿지 회귀를 통한 보스턴 주택 가격 예측하기

 

라이브러리 임포트하고 출력

from sklearn.linear_model import Ridge
from sklearn.linear_model import Lasso, ElasticNet
from sklearn.model_selection import cross_val_score
from sklearn.preprocessing import PolynomialFeatures
from sklearn.datasets import load_boston
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import warnings
import seaborn as sns

warnings.filterwarnings("ignore")
%load_ext autotime

 

 

특징과 라벨 데이터 준비

df = pd.DataFrame(data=boston.data, columns=boston.feature_names)
df["PRICE"] = boston.target
df.head()

X = df.iloc[:,:-1]
y = df.iloc[:, -1]

 

 

alpha = 10일때 릿지 회귀 교차 검증 결과

 

ridge = Ridge(alpha = 10)
neg_mse_scores = cross_val_score(pipeline, X, y, scoring="neg_mean_squared_error",cv = 5)
rmse_scores = np.sqrt(-1 * neg_mse_scores)
avg_rmse = np.mean(rmse_scores)

print("neg_mse_socres : {}".format(neg_mse_scores))
print("rmse_scores : {}".format(rmse_scores))
print("avg_rmse : {}".format(avg_rmse))

 

 

alpha 값에 변화를 줄때 릿지 회귀

- alpha = 0 인 경우(MSE만을 비용함수로 한 경우) avg rmse = 5.82865

- alpha = 100 인 경우(MSE와 L2 규제를 적용한 경우) avg rmse = 5.32959로 가장 작음

alphas = [0, 0.1, 1, 10, 100]
for alpha in alphas:
    ridge = Ridge(alpha = alpha)
    neg_mse_scores = cross_val_score(ridge, X, y, scoring="neg_mean_squared_error",cv = 5)
    rmse_scores = np.sqrt(-1 * neg_mse_scores)
    avg_rmse = np.mean(rmse_scores)
    print("### alpha = {} ###".format(alpha))
    print("neg_mse_socres : {}".format(neg_mse_scores))
    print("rmse_scores : {}".format(rmse_scores))
    print("avg_rmse : {}\n".format(avg_rmse))

 

 

 

 

알파 값에 따른 회귀 계수의 변화 시각화

 

 

alpha가 커지면서 회귀 계수 값들이 전체적으로 작아지는것을 확인할 수 있음.

* 회귀 계수를 0으로 만들지는 않음)

 

fig, axs = plt.subplots(figsize=(18, 6), nrows=1, ncols=5)

coeff_df = pd.DataFrame()

for pos, alpha in enumerate(alphas):
    ridge = Ridge(alpha=alpha)
    ridge.fit(X, y)
    coeff = pd.Series(ridge.coef_, index=X.columns)
    colname="alpha="+str(alpha)
    coeff_df[colname] = coeff
    
    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])
plt.show()

 

 

 

 

 

 

라쏘 회귀

- 가중치 벡터 W의 절대값에 패널티 부여하는 L1 규제를 선형 회귀에 적용

 

- alpha값이 커지면서 릿지 회귀떄와는 달리 회귀 계수가 0이 되는 특징이 생김

 -> 회귀 계수가 0인 특징은 제거 가능

alphas = [0.07, 0.1, 0.5, 1, 3]
def get_linear_reg_eval(model_name, params=None, X_data = None, y_target=None):
    fig, axs = plt.subplots(figsize=(18, 6), nrows=1, ncols=5)

    print("#### {} ####".format(model_name))
    for pos, param in enumerate(params):
        if model_name == "Ridge": model = Ridge(alpha=param)
        elif model_name == "Lasso": model= Lasso(alpha=param)
        elif model_name == "ElasticNet": model = ElasticJet(alpha=param, l1_ratio=0.7)
        
        neg_mse_scores = cross_val_score(model, X, y, scoring="neg_mean_squared_error",cv = 5)
        rmse_scores = np.sqrt(-1 * neg_mse_scores)
        avg_rmse = np.mean(rmse_scores)
        print("### alpha = {} ###".format(param))
        print("neg_mse_socres : {}".format(neg_mse_scores))
        print("rmse_scores : {}".format(rmse_scores))
        print("avg_rmse : {}\n".format(avg_rmse))
        
        model.fit(X_data, y_target)
        coeff = pd.Series(model.coef_, index=X_data.columns)
        colname="alpha="+str(param)
        coeff_df[colname] = coeff

        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])
    plt.show()
 get_linear_reg_eval("Lasso", params=alphas, X_data=X, y_target=y)

 

 

엘라스틱 회귀

- L2 규제와 L1 규제의 결합

- 라쏘 회귀에서 중요 피처들만 남기고, 나머지를 0으로 만드는 문제를 개선하기 위해 L2 규제를 추가한것

- l1_ratio = alpha1/(alpha1 + alpha2)

* l1_ratio = 0.7 = 7/(7 + 3)

get_linear_reg_eval("ElasticNet", params=alphas, X_data=X, y_target=y)

alpha = 0.5 -> avg_rmse = 5.466으로 가장 낮음.

 

라쏘 회귀보다 사라진 변수가 적음

 

300x250
728x90

다항 회귀의 문제에서

 

과소 적합과 과적합 문제를 다뤄보자

 

 

 

구간이 [0, 1]사이에

 

구하고자 하는 함수로 y = cos( 1.5 * pi * x)가 있다고 하자

 

노이즈가 추가 된 cos( 1.5 * pi * x) + N(0, 0.1)으로 부터 샘플 값을 얻을때

 

이 때 1차 다항식, 4차 다항식, 15차 다항식의 결과를 비교해보자

 

 

 

우선 필요한 라이브러리들을 가져오고

 

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
import warnings
warnings.filterwarnings("ignore")
%load_ext autotime

 

 

 

실제 함수와 샘플들, 노이즈를 추가한 y값들을 얻자

 

def true_func(X):
    return np.cos(1.5 * np.pi * X)

np.random.seed(0)
n_samples = 30
X = np.sort(np.random.rand(n_samples))
y = true_func(X) + np.random.randn(n_samples) * 0.1
print(y.shape)

 

 

차수가 1, 4, 15인 경우에 대해

 

pipeline으로 학습 과정을 수행하였고,

 

계수와 MSE를 출력해보면

 

fig = plt.figure(figsize=(12,4))
degrees = [1, 4, 15]

for i in range(len(degrees)):
    ax = fig.add_subplot(1, len(degrees), i+1)
    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)
    
    coeffs = pipeline.named_steps["linear_regression"].coef_
    print("\n\n degree {}의 regression coeffs\n{}".format(degrees[i], np.round(coeffs, 2)))
    print("\n degree {}의 MSE {}".format(degrees[i], -1 * np.mean(scores)))
    
    X_test = np.linspace(0, 1, 100)
    
    plt.plot(X_test, pipeline.predict(X_test[:,np.newaxis]), label="Model")
    plt.plot(X_test, true_func(X_test),"--", label="true")
    plt.scatter(X, y, edgecolors="b", s=20, label="samples")
    plt.title("degree {}".format(degrees[i]))
    plt.xlabel("x")
    plt.ylabel("y")
    plt.legend(loc="best")

 

 

 

4차 다항식의 경우 MSE가 가장 적으며,

 

주어진 샘플 데이터로 실제 다항 함수를 잘 적합한 것을 확인할 수 있다.

 

차수가 1인 경우 구한 선형 식에서는 너무 일반화되어 단순하며,

 

차수가 15에는 학습 데이터에 너무 치중한 나머지 실제와는 완전히 다른 곡선이 만들어짐.

 

 

300x250

+ Recent posts