[Python] 데이터 사이언스 스쿨 - 5.2 교차검증

Updated:

데이터 사이언스 스쿨 자료를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

5.2 교차검증

회귀분석 모형을 주요 목적 중 하나는 종속 변수의 값을 모르고 독립변수만 알 때 종속변수를 예측하는 것이다.

보통은 학습 데이터(train)로 모델을 학습 시킨 후 검증 데이터(test)로 모델의 성능을 평가한다.

이를 교차검증이라 한다.

5.2.1 statsmodels 패키지의 교차검증

statsmodels 패키지에는 교차검증을 위한 기능이 별도로 준비되어 있지 않고 사용자가 직접 코드를 작성해야 한다.

from sklearn.datasets import load_boston

boston = load_boston()
dfX = pd.DataFrame(boston.data, columns=boston.feature_names)
dfy = pd.DataFrame(boston.target, columns=["MEDV"])
df = pd.concat([dfX, dfy], axis=1)

# 학습/검증 데이터 분리
N = len(df) # boston row
ratio = 0.7

np.random.seed(0)
idx_train = np.random.choice(np.arange(N), np.int(ratio * N)) # 원 데이터의 70% train set
idx_test = list(set(np.arange(N)).difference(idx_train)) # 30% test set

df_train = df.iloc[idx_train]
df_test = df.iloc[idx_test]
import statsmodels.api as sm

model = sm.OLS.from_formula("MEDV ~ " + "+".join(boston.feature_names), data=df_train)
result = model.fit()
print(f"train set 결정계수: {result.rsquared:0.3f}")
train set 결정계수: 0.757
pred = result.predict(df_test)

rss = ((df_test.MEDV - pred) ** 2).sum()
tss = ((df_test.MEDV - df_test.MEDV.mean())** 2).sum()
rsquared = 1 - rss / tss
print(f"test set 결정계수: {rsquared:0.3f}")
test set 결정계수: 0.688

5.2.2 scikit-learn 패키지의 교차검증

sklearn.model_selectiontrain_test_split() 명령어로 데이터 분리가 가능하다.

train_test_split(X_data, y_data, test_size, train_size, random_state)

  • X_data: 독립 변수 데이터 배열 또는 pandas 데이터프레임

  • y_data: 종속 변수 데이터. data 인수에 종속 변수 데이터가 같이 있으면 생략할 수 있다.

  • test_size: 검증용 데이터 개수. 1보다 작은 실수이면 비율을 나타낸다.

  • train_size: 학습용 데이터의 개수. 1보다 작은 실수이면 비율을 나타낸다. test_size와 train_size 중 하나만 있어도 된다.

  • random_state: 난수 시드

# 데이터 프레임에 독립변수, 종속변수가 같이 있는 경우
from sklearn.model_selection import train_test_split

df_train, df_test = train_test_split(df, test_size=0.3, random_state=0)
df_train.shape, df_test.shape
((354, 14), (152, 14))
  • train 70%, test 30%의 비율로 데이터가 잘 분리되었다.

  • 독립변수, 종속변수 데이터가 같이 있으면 train 데이터 프레임과 test 데이터 프레임을 반환한다.

# 독립변수, 종속변수 데이터가 따로 있는 경우
dfX_train, dfX_test, dfy_train, dfy_test = train_test_split(dfX, dfy, test_size=0.3, random_state=0)
dfX_train.shape, dfy_train.shape, dfX_test.shape, dfy_test.shape
((354, 13), (354, 1), (152, 13), (152, 1))
  • 독립변수, 종속변수 데이터가 따로 있으면 순서대로 X_train, X_test, y_train, y_test를 반환한다.

5.2.3 K-폴드 교차검증

sklearn.model_selectionKFold()를 이용해 K-폴드 교차검증을 진행할 수 있다.

sklearn.metrics에는 예측성능을 평가하기 위한 다양한 함수가 내제되어있다.

from sklearn.model_selection import KFold
from sklearn.metrics import r2_score

scores = np.zeros(5)
cv = KFold(5, shuffle=True, random_state=0) # k=5

# .split으로 train, test 인덱스 반환
for i, (idx_train, idx_test) in enumerate(cv.split(df)):
    df_train = df.iloc[idx_train]
    df_test = df.iloc[idx_test]
    
    model = sm.OLS.from_formula("MEDV ~ " + "+".join(boston.feature_names), data=df_train)
    result = model.fit()
    
    pred = result.predict(df_test)
    rsquared = r2_score(df_test.MEDV, pred)
    
    scores[i] = rsquared
    print("학습 R2 = {:.8f}, 검증 R2 = {:.8f}".format(result.rsquared, rsquared))
학습 R2 = 0.77301356, 검증 R2 = 0.58922238
학습 R2 = 0.72917058, 검증 R2 = 0.77799144
학습 R2 = 0.74897081, 검증 R2 = 0.66791979
학습 R2 = 0.75658611, 검증 R2 = 0.66801630
학습 R2 = 0.70497483, 검증 R2 = 0.83953317

5.2.4 교차검증 반복

sklearn.model_selectioncross_val_score()를 이용해 교차검증 반복을 쉽게 진행할 수 있다.

cross_val_score()sklearn의 모형만 사용가능하다.

cross_val_score(model, X, y, scoring=None, cv=None)

  • model: 회귀 분석 모형

  • X: 독립 변수 데이터

  • y: 종속 변수 데이터

  • scoring: 성능 검증에 사용할 함수 이름

  • cv: 교차검증 생성기 객체 또는 숫자로 None이면 KFold(3) 숫자 k이면 KFold(k)

statsmodels 모형을 사용할 경우

cross_val_score()에 statsmodels의 모형을 사용하려면 다음과 같이 scikit-learn의 RegressorMixin으로 래퍼 클래스를 만들어주어야 한다.

from sklearn.base import BaseEstimator, RegressorMixin
import statsmodels.formula.api as smf
import statsmodels.api as sm

class StatsmodelsOLS(BaseEstimator, RegressorMixin):
    def __init__(self, formula):
        self.formula = formula
        self.model = None
        self.data = None
        self.result = None
        
    def fit(self, dfX, dfy):
        self.data = pd.concat([dfX, dfy], axis=1)
        self.model = smf.ols(self.formula, data=self.data)
        self.result = self.model.fit()
        
    def predict(self, new_data):
        return self.result.predict(new_data)
from sklearn.model_selection import cross_val_score

model = StatsmodelsOLS("MEDV ~ " + "+".join(boston.feature_names))
cv = KFold(5, shuffle=True, random_state=0)

r2_scores = cross_val_score(model, dfX, dfy, scoring="r2", cv=cv)
r2_score = np.mean(r2_scores)

print(f"각 fold별 결정계수: {np.round(r2_scores,3)}")
print(f"평균 결정계수: {r2_score:.3f}")
각 fold별 결정계수: [0.589 0.778 0.668 0.668 0.84 ]
평균 결정계수: 0.709

sklearn 모형을 사용할 경우

from sklearn.linear_model import LinearRegression

model = LinearRegression(fit_intercept=True).fit(dfX,dfy)

r2_scores = cross_val_score(model, dfX, dfy, scoring="r2", cv=cv)
r2_score = np.mean(r2_scores)

print(f"각 fold별 결정계수: {np.round(r2_scores,3)}")
print(f"평균 결정계수: {r2_score:.3f}")
각 fold별 결정계수: [0.589 0.778 0.668 0.668 0.84 ]
평균 결정계수: 0.709
  • 결과는 동일하며 여기선 평균 결정계수로 모델의 성능을 평가한다.

Leave a comment