머신러닝

사이킷런 (Scikit-Learn) : 파이썬의 대표적인 머신러닝 패키지

jeongpil 2021. 3. 12. 12:15

*해당 포스팅은 파이썬 머신러닝 완벽 가이드(권철민 지음) 교재를 공부하며 작성한 글입니다.

 

 

오늘은 파이썬 머신러닝 라이브러리 중 가장 많이 사용되는 사이킷런의 주요 모듈을 알아보고

 

그 중 model_selection 모듈과 preprocessing 모듈에 대해 자세히 알아보겠습니다.

 

데이터 세트는 사이킷런에 내장된 데이터 세트를 사용하였고

 

알고리즘도 사이킷런의 머신러닝 알고리즘을 사용했습니다. 

 

 

1. 사이킷런의 주요 모듈

2. model_selection 모듈

  2-1. train_test_split()

  2-2. 교차 검증

  2-3. GridSearchCV

3. preprocessing 모듈

  3-1. 데이터 인코딩

  3-2. 피처 스케일링과 정규화

 

 

 

1. 사이킷런의 주요 모듈

 

 

 

 

2. Model_selection 모듈

 

사이킷런의 model_selection 모듈은 학습 데이터와 테스트 데이터 세트를 분리하거나

 

교차 검증 분할 및 평가, 그리고 하이퍼 파라미터를 튜닝하기 위한 다양한 함수와 클래스를 제공합니다.

 

2-1. train_test_split()

 

데이터 세트를 학습 데이터와 테스트 데이터로 나누지 않고 동일한 데이터셋으로 학습과 예측을 하면 정확도가 100%가 나옵니다.

 

이미 학습한 학습 데이터 세트를 기반으로 예측을 했기 때문입니다.

 

이미 답을 아는 상태에서 예측을 한 것과 마찬가지이기 때문에 예측이 의미가 없습니다.

 

따라서 데이터 세트를 학습용과 테스트용으로 나눠야 할 필요가 있습니다.

 

이는 사이킷런의 train_test_split()을 이용하면 됩니다. 첫 번째 파라미터로 피처 데이터 세트, 두 번째 파라미터로 레이블 데이터 세트를 입력받습니다.

 

 

from sklearn.model_selection import train_test_split
from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from sklearn.linear_model import LogisticRegression
from sklearn.tree import DecisionTreeClassifier

iris=load_iris() # 사이킷런에 내재된 붓꽃 데이터
lr_clf=LogisticRegression() # 로지스틱 회귀
dt_clf=DecisionTreeClassifier() # 결정 트리

#학습용 데이터와 테스트 데이터로 분리
X_train, X_test, y_train, y_test=train_test_split(iris.data,iris.target,test_size=0.35,random_state=0)

lr_clf.fit(X_train,y_train) # 로지스틱 회귀 모델 훈련
lr_pred=lr_clf.predict(X_test) # 예측
print('Logistic Regression model accuracy:{:.4f}'.format(accuracy_score(y_test,lr_pred))) # 정확도

dt_clf.fit(X_train,y_train) # 결정 트리 모델 훈련
dt_pred=dt_clf.predict(X_test) # 예측
print('Decsion Tree model accuracy:{:.4f}'.format(accuracy_score(y_test,dt_pred))) # 정확도

Logistic Regression model accuracy:0.9811

Decsion Tree model accuracy:0.9623

 

 

 

로지스틱 회귀와 결정 트리 알고리즘으로 붓꽃 데이터를 학습용과 테스트용 데이터로 나눠서 예측을 했을 때 정확도가 100%가 나오지 않는 것을 확인할 수 있습니다.

 

붓꽃 데이터는 데이터의 양이 크지 않아 이를 통해 알고리즘의 예측 성능을 파악하기에는 적절하지 않습니다.

 

하지만 학습을 위한 데이터의 양을 일정 수준 이상으로 보장하는 것도 중요하지만, 

 

학습된 모델에 대해 다양한 데이터를 기반으로 예측 성능을 평가해 보는 것도 매우 중요합니다.

 

그래야 새로운 데이터에 대한 예측이 정확해질 수 있습니다.

 

 

2-2. 교차 검증

 

위에서 처럼 학습 데이터와 테스트 데이터로 데이터를 분리해도 학습 데이터와 테스트 데이터가 고정되어 있기 때문에 오버피팅이 발생할 수도 있습니다.

 

오버피팅이 발생할 경우 새로운 데이터로 예측을 수행할 경우 성능이 과도하게 떨어질 수 있습니다.

 

이를 해결하기 위해 교차 검증을 이용하여 더욱 다양한 학습과 평가를 수행합니다.

 

 

 

① K 폴드 교차 검증

 

K 폴드 교차 검증은 가장 보편적으로 사용되는 교차 검증 기법입니다.

 

먼저 K개의 데이터 폴드 세트를 만들어 K번만큼 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법입니다.

 

 

예를 들어 학습용 데이터 세트를 5 폴드 교차 검증을 수행하면

 

데이터를 5 등분해서 4개는 학습 데이터, 1개는 검증 데이터로 사용하여 학습과 검증을 진행합니다.

 

각 등분된 것이 검증 데이터로 한 번씩 사용되어야 하므로 5번의 예측 평가가 이루어집니다.

 

이 5개의 예측 평가를 평균한 것이 K 폴드 평가 결과입니다.

 

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
from sklearn.datasets import load_iris
import numpy as np

iris=load_iris()
features=iris.data
label=iris.target
dt_clf=DecisionTreeClassifier()

kfold=KFold(n_splits=5) # 5개의 폴드
cv_accuracy=[] #평과 결과를 담을 배열
n_iter=0

# kfold의 split()을 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
    X_train, X_test=features[train_index], features[test_index]
    y_train, y_test=label[train_index], label[test_index]
    dt_clf.fit(X_train,y_train) # 학습
    dt_pred=dt_clf.predict(X_test)
    n_iter+=1
    accuracy=np.round(accuracy_score(y_test,dt_pred),4)
    train_size=X_train.shape[0]
    test_size=X_test.shape[0]
    cv_accuracy.append(accuracy) # 예측 결과 담기
    
# 5개의 예측 결과 평균하여 정확도 계산
kfold_accuracy=np.mean(cv_accuracy)

print('평균 검증 정확도:{:.2f}'.format(kfold_accuracy))

평균 검증 정확도:0.92

 

 

② Stratified K 폴드 교차 검증

 

Stratified K 폴드는 불균형한 분포도를 가진 레이블 데이터 집합을 위한 K 폴드 방식입니다.

 

특정 레이블 값이 특이하게 많거나 적어서 값의 분포가 한쪽으로 치우친 데이터 집합에 사용됩니다.

 

특정 레이블 값이 특이하게 적은데 이 레이블 값이 중요한 데이터일 경우,

 

원본 데이터와 유사한 분포로 학습 데이터와 테스트 데이터에도 분포해 있어야 훈련이 잘 되고 예측 또한 잘 될 수 있습니다.

 

 

그냥 랜덤하게 데이터를 나눌 경우 특이하게 적은 레이블 데이터들이 적절하게 학습 데이터와 테스트 데이터에 나눠지기 어렵지만 이를 Stratified K 폴드가 해결해 줍니다.

 

먼저 데이터의 분포를 알아야 합니다.

 

from sklearn.datasets import load_iris
import pandas as pd

iris=load_iris() # 붓꽃 데이터
iris_df=pd.DataFrame(data=iris.data,columns=iris.feature_names)
iris_df['label']=iris.target # label 컬럼 생성
iris_df['label'].value_counts()

2 50

1 50

0 50

Name: label, dtype: int64

 

 

라벨 0이 50개 라벨 1이 50개 라벨 2가 50개로 고르게 분포되어 있는 것을 알 수 있습니다. 

 

이 데이터의 경우 특이하게 많거나 적은 레이블은 없지만 이 데이터를 K 폴드로 학습 데이터와 검증 데이터로 나눠 보겠습니다.

 

 

from sklearn.model_selection import KFold

kfold=KFold(n_splits=3) # 3폴드
n_iter=0
for train_index, test_index in kfold.split(iris_df):
    n_iter+=1
    label_train=iris_df['label'].iloc[train_index]
    label_test=iris_df['label'].iloc[test_index]
    print('교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터\n',label_train.value_counts())
    print('검증 레이블 데이터\n',label_test.value_counts())
    print('\n')

 

 

데이터가 나눠진 것을 보면 첫 번째 교차 검증에서는 레이블이 0인 데이터가 학습 데이터에 없는 상태로 학습되기 때문에 예측 모델은 0을 절대 예측할 수 없습니다.

 

다른 교차 검증들도 마찬가지입니다. Stratified K 폴드를 통해 데이터를 나눠보겠습니다.

 

 

from sklearn.model_selection import StratifiedKFold

skfold=StratifiedKFold(n_splits=3) # 3 폴드
n_iter=0
for train_index, test_index in skfold.split(iris_df,iris_df['label']):#피처데이터세트, 레이블데이터세트
    n_iter+=1
    label_train=iris_df['label'].iloc[train_index]
    label_test=iris_df['label'].iloc[test_index]
    print('교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터\n',label_train.value_counts())
    print('검증 레이블 데이터\n',label_test.value_counts())
    print('\n')

 

 

데이터가 학습 데이터와 검증 데이터에 동일하게 분포된 것을 확인할 수 있습니다.

 

사실 일반적으로 분류에서의 교차 검증은 K 폴드가 아니라 Stratified K 폴드로 분할해야 합니다.

 

하지만 회귀에서는 Stratified K 폴드가 지원되지 않습니다.

 

회귀의 target 값은 이산값 형태가 아니라 연속값이기 때문에 target값별로 분포를 정하는 것이 의미가 없기 때문입니다.

 

 

※ 사실 사이킷런에서는 교차 검증을 좀 더 편리하게 할 수 있게 해주는 API를 제공합니다.

 

바로 cross_val_score()입니다.

 

위에서 한 것을 cross_val_score()를 이용해서 하면 다음과 같습니다.

 

from sklearn.model_selection import cross_val_score

scores=cross_val_score(알고리즘, data, label, scoring='accuracy', cv=3) # 3 폴드
# scores에 3개의 정확도가 저장된다

 

알고리즘은 DecisionTreeClassifier()나 LogisticRegression같은 알고리즘을 넣어주면 됩니다.

 

cross_val_score()는 내부적으로 StratifiedKFold를 이용합니다.

 

 

2-3. GridSerachCV

 

사이킷런은 GridSearchCV API를 이용해 분류나 회귀와 같은 알고리즘에 사용되는 하이퍼 파라미터의 최적 값을 도출할 수 있게 해 줍니다.

 

하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 요소이고

 

이 값을 조정해 알고리즘의 예측 성능을 개선할 수 있습니다.

 

하지만 순차적으로 파라미터를 테스트해서 최적의 파라미터를 구하기 때문에 수행 시간이 오래 걸립니다.

 

import pandas as pd
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV

iris=load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris.data,iris.target,test_size=0.2,random_state=0)
dt=DecisionTreeClassifier()

params={'max_depth':[1,2,3],'min_samples_split':[2,3]}

grid_dt=GridSearchCV(dt,param_grid=params,cv=3,refit=True)
grid_dt.fit(X_train,y_train)# 순차적 학습

scores_df=pd.DataFrame(data=grid_dt.cv_results_)
scores_df[['params','mean_test_score','rank_test_score',
          'split0_test_score','split1_test_score','split2_test_score']]

 

 

GridSearchCV로 찾은 최적의 파라미터 값은 max_depth:3, min_sample_split:3 이고

 

이때 정확도는 0.95로 최고입니다.

 

 

estimator=grid_dt.best_estimator_

pred=estimator.predict(X_test)
print('테스트 데이터 세트 정확도:{:.4f}'.format(accuracy_score(y_test,pred)))

테스트 데이터 세트 정확도:0.9667

 

 

별도의 테스트 데이터 세트로 정확도를 측정한 결과 96.67%의 정확도가 나왔습니다.

 

일반적으로 학습 데이터를 GridSearchCV를 이용해 최적 하이퍼 파라미터 튜닝을 한 뒤에 별도의 테스트 세트에서 이를 평가하는 것이 일반적인 머신러닝 모델 적용 방법입니다.

 

 

3. preprocessing 모듈

 

사이킷런의 preprocessing 모듈은 데이터 전처리에 필요한 다양한 가공 기능을 제공합니다.

 

3-1. 데이터 인코딩

 

사이킷런의 머신러닝 알고리즘은 문자열 값을 입력 값으로 허용하지 않기 때문에 모든 문자열 값은 인코딩 돼서 숫자 형으로 변환해야 합니다.

 

대표적인 인코딩 방식으로는 레이블 인코딩과 원-핫 인코딩 방식이 있습니다.

 

 

① 레이블 인코딩

 

레이블 인코딩은 카테고리 피처를 코드형 숫자 값으로 변환하는 것입니다.

 

from sklearn.preprocessing import LabelEncoder

brands=['페라리','포르쉐','벤츠','BMW','아우디','현대','현대','기아','벤츠']

encoder=LabelEncoder()
encoder.fit(brands)
labels=encoder.transform(brands)
print('인코딩 변환값: ',labels)
print('디코딩 원본값: ',encoder.inverse_transform([4, 5, 2, 0, 3, 6, 6, 1, 2]))

 

레이블 인코딩은 숫자 값으로 변환이 되면서 숫자 값의 크고 작음에 대한 특성이 작용해서  예측 성능이 떨어지는 경우 발생할 수 있습니다.

 

따라서, 레이블 인코딩은 선형 회귀에는 적용되면 안 됩니다.

 

 

② 원-핫 인코딩

 

레이블 인코딩의 문제점을 해결해주는 것이 원-핫 인코딩입니다. 

 

원-핫 인코딩은 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 추가하고 나머지 칼럼에는 0을 표시하는 방식입니다. 

 

원-핫 인코딩에서 주의할 점은 변환하기 전에 모든 문자열 값이 숫자형 값으로 변환돼야 한다는 점과,

 

입력 값으로 2차원 데이터가 필요하다는 점이 있습니다.

 

from sklearn.preprocessing import OneHotEncoder
from sklearn.preprocessing import LabelEncoder
import numpy as np

brands=['페라리','포르쉐','벤츠','BMW','아우디','현대','현대','기아','벤츠']

# LabelEncoder로 숫자형 값으로 변환
encoder=LabelEncoder()
encoder.fit(brands)
labels=encoder.transform(brands)
# 2차원 데이터로 변환
labels=labels.reshape(-1,1)

#원-핫 인코딩
oh_encoder=OneHotEncoder()
oh_encoder.fit(labels)
oh_labels=oh_encoder.transform(labels)
print('원-핫 인코딩 데이터')
print(encoder.classes_)
print(oh_labels.toarray())

 

 

판다스에는 get_dummies()라는 원-핫 인코딩을 쉽게 지원하는 API가 있습니다.

 

사이킷런의 OneHotEncoder와 다르게 문자열 카테고리 값을 숫자 형으로 변환할 필요 없이 바로 변환할 수 있습니다.

 

import pandas as pd

df=pd.DataFrame({'brand':['페라리','포르쉐','벤츠','BMW','아우디','현대','현대','기아','벤츠']})
pd.get_dummies(df)

 

 

3-2. 피처 스케일링과 정규화

 

서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업을 피처 스케일링이라고 하며 대표적인 방법으로 표준화와 정규화가 있습니다.

 

표준화는 데이터의 피처 각각이 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환하는 것입니다.

 

표준화 식은 다음과 같습니다.

 

 

 

정규화는 서로 다른 피처의 크리를 통일하기 위해 값을 모두 0 ~ 1의 값으로 변환해주는 것입니다.

 

정규화 식은 다음과 같습니다.

 

 

① StandardScaler

 

StandardScaler는 표준화를 쉽게 지원하기 위한 클래스입니다.

 

from sklearn.datasets import load_iris
import pandas as pd

iris=load_iris() # 붓꽃 데이터
iris_data=iris.data
iris_df=pd.DataFrame(data=iris.data,columns=iris.feature_names)

print('feature들의 평균 값')
print(iris_df.mean())
print('feature들의 분산 값')
print(iris_df.var())

 

 

from sklearn.preprocessing import StandardScaler

scaler=StandardScaler()
scaler.fit(iris_df)
iris_scaled=scaler.transform(iris_df)

iris_scaled_df=pd.DataFrame(data=iris_scaled,columns=iris.feature_names)
print('feature들의 평균 값')
print(iris_scaled_df.mean())
print('feature들의 분산 값')
print(iris_scaled_df.var())

 

 

StandardScaler로 피처를 스케일링한 결과 평균값은 0에 가깝게 변환되었고 분산 값은 1에 가깝게 변환되었습니다.

 

 

② MinMaxScaler

 

MinMaxScaler는 정규화를 쉽게 지원하기 위한 클래스입니다.

 

from sklearn.datasets import load_iris
import pandas as pd

iris=load_iris()
iris_data=iris.data
iris_df=pd.DataFrame(data=iris.data,columns=iris.feature_names)

print('feature들의 최솟값')
print(iris_df.min())
print('feature들의 최댓값')
print(iris_df.max())

 

from sklearn.preprocessing import MinMaxScaler

scaler=MinMaxScaler()
scaler.fit(iris_df)
iris_scaled=scaler.transform(iris_df)

iris_scaled_df=pd.DataFrame(data=iris_scaled,columns=iris.feature_names)
print('feature들의 최솟값')
print(iris_scaled_df.min())
print('feature들의 최댓값')
print(iris_scaled_df.max())

 

피처들의 최솟값이 0, 최댓값이 1인걸 확인할 수 있고 정규화가 잘 되었음을 알 수 있습니다.

 

 

※ 피처 스케일링 시 주의사항

 

학습 데이터로 fit(), transform()을 적용하고 테스트 데이터에서 다시 fit()을 수행하면 안 되고 학습 데이터로 fit()한 결과를 이용해서 transform()을 적용해야 합니다.

 

테스트 데이터로 새로운 스케일링 기준을 만들게 되면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 달라지기 때문에 학습 데이터의 스케일링 기준에 따라야 합니다.

 

그렇기 때문에, 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습 데이터와 테스트 데이터로 분리하는 것이 좋습니다.

 

 

 

공부한 내용이 모두 중요하다고 생각되어 자세하게 적고 직접 코딩을 해보면서 코드와 결과들을 글에 같이 적다 보니 글의 길이가 매우 길어졌습니다.

 

그래도 직접 생각하고 코딩해 보니 사이킷런에 대해 많이 알게 된 기분이 들고 앞으로 잘 사용할 수 있을 것 같다는 생각이 듭니다 :)  

 

앞으로 공부한 넘파이, 판다스, 사이킷런을 사용해서 많은 데이터 분석 작업을 해봐야겠습니다!