Go together

If you want to go fast, go alone. If you want to go far, go together.

파이썬 머신 러닝 완벽 가이드

사이킷런 scikit-learn

NowChan 2021. 10. 15. 16:22

배운 내용

  1. train_test_split()
  2. fit()
  3. predict()
  4. accuracy_score()
  5. 사이킷런의 주요 모듈
  6. Dictionary.keys()
  7. train_test_split() - 상세한 설명
  8. KFold
  9. Stratified K 폴드
  10. cross_val_score()
  11. GridSearchCV
  12. StandardScaler
  13. MinMaxScaler
  14. 학습 데이터와 테스트 데이터 스케일링 변환 시 유의할 점

Scikit-learn

사이킷런은 파이썬 머신러닝 라이브러리 중 가장 많이 사용되는 라이브러리입니다.

 

 

붓꽃 품종 예측하기

책에서 첫 번째로 만들어볼 머신러닝 모델은 붓꽃 품종을 분류(Classification)하는 것입니다. 붓꽃 데이터 세트의 피쳐는 꽃잎의 길이, 너비, 꽃받침의 길이와 너비이고, 이를 기반으로 품종을 예측합니다.

 

분류는 대표적인 지도학습(Supervised Learning) 방법 중 하나입니다. 지도학습은 다양한 피처분류 결정값인 레이블(Lable) 데이터로 모델을 학습한 뒤, 별도의 테스트 데이터 세트에서 미지의 레이블을 예측합니다.

 

사이킷런에서 사용할 모듈을 임포트합니다.

sklearn.model_selection의 경우 가장 적합한 모델을 위해 최적의 하이퍼 파라미터를 평가합니다. 하이퍼 파라미터란 머신러닝 알고리즘별로 최적의 학습을 위해 직접 입력하는 파라미터들을 통칭하며, 하이퍼 파라미터를 통해 머신러닝 알고리즘의 성능을 튜닝할 수 있습니다. 

 

load_iris()로 데이터 세트를 생성하고, ML 알고리즘은 의사 결정 트리(Decision Tree) 알고리즘으로, 이를 구현한 DecisionTreeClassifier를 적용합니다. 데이터를 학습 데이터테스트 데이터로 분리하는 train_test_split()함수를 사용합니다.

# sklearn.datasets: 사이킷런에서 제공하는 데이터 세트를 생성하는 모듈
from sklearn.datasets import load_iris

# sklearn.tree: 트리 기반 ML 알고리즘을 구현한 클래스 모임
from sklearn.tree import DecisionTreeClassifier

# sklearn.model_selection: 학습·검증·예측 데이터로 데이터를 분리하거나 최적의 평가를 위한 모듈
from sklearn.model_selection import train_test_split

import pandas as pd

# 붗꽃 데이터 세트 로딩
iris = load_iris()

# iris.data는 Iris 데이터 세트에서 피쳐 데이터를 numpy형태로 가지고 있습니다.
iris_data = iris.data
# iris.target은 붗꽃 데이터 세트에서 레이블(결정 값) 데이터를 numpy형태로 가지고 있습니다.
iris_label = iris.target
print('iris target값:', iris_label)
'''
iris target값: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
'''
print('iris target명:', iris.target_names)
'''
iris target명: ['setosa' 'versicolor' 'virginica']
'''
iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names)
iris_df['label'] = iris.target
iris_df.head(3)
'''결과1'''

결과1

label은 0이 setosa, 1이 versicolor, 2가 virginica입니다.

 

train_test_split()

train_test_split을 통해 학습데이터와 테스트 데이터를 분리합니다.

첫 번째 파라미터 iris_data는 피처 데이터 세트입니다. 두 번째 파라미터 iris_label은 레이블(Label) 데이터 세트입니다.

 

test_size 파라미터를 0.2로 주면, 테스트 데이터 비율이 20%입니다. random_state는 호출할 때마다 같은 학습 · 테스트 용 데이터 세트를 생성하기 위해 주어지는 난수 발생 값입니다.

 

random_state를 지정하지 않으면, train_test_split()가 호출 시 무작위로 데이터를 분리해서 수행할 때마다 다른 학습 · 테스트용 데이터를 만들 수 있습니다. 즉, 실행할 때마다 결과가 같도록 하기 위해 지정할 뿐입니다. random을 만드는 seed를 지정하는 것과 같은 의미이므로 어떤 값으로 지정해도 상관없습니다.

X_train, X_test, Y_train, Y_test = train_test_split(iris_data, iris_label, 
                                                    test_size=0.2, random_state=11)

학습용 피처 데이터 세트를 X_train로 , 테스트용 피처 데이터 세트를 X_test로,

학습용 레이블 데이터 세트를 Y_train로, 테스트용 레이블 데이터 세트를 Y_test로 반환합니다.

 

fit()

사이킷런의 의사 결정 트리 클래스인 DecisionTreeClassifier를 객체로 생성합니다. 여기서의 random_State도 train_test_split과 의미가 같습니다. 생성된 DecisionTreeClassifier 객체의 fit() 메서드에 학습용 데이터 세트들을 입력해 학습을 수행합니다.

# DecisionTreeClassifier 객체 생성
dt_clf = DecisionTreeClassifier(random_state=11)

# DecisionTreeClassifier.fit()로 학습용 데이터로 학습 수행
dt_clf.fit(X_train, Y_train)

 

predict()

# DecisionTreeClassifier.predict()로 테스트 피쳐 데이터에 대한 예측값 출력
pred = dt_clf.predict(X_test)

 

accuracy_score()

# .metrics 모듈에서 accuacy_score함수를 import한다.
from sklearn.metrics import accuracy_score

# accuracy_score()함수에 테스트 레이블 데이터셋과 예측한 값을 인자로 넣습니다.
print('예측 정확도: {0:.4f}'.format(accuracy_score(Y_test, pred)))
'''
예측 정확도: 0.9333
'''

 

붓꽃 예제에서 분류를 예측한 과정

  1. 데이터 세트 분리: 데이터를 학습데이터와 테스트 데이터로 분리합니다.
  2. 모델 학습: 학습 데이터를 기반으로 ML 알고리즘을 적용해 모델을 학습시킵니다.
  3. 예측 수행: 학습된 ML 모델을 이용해 테스트 데이터의 분류(즉, 붓꽃 종류)를 예측합니다.
  4. 평가: 예측값과 테스트 데이터의 실제 결과값을 비교해 ML 모델 성능을 평가합니다.

일반적으로 머신러닝 모델을 구축하는 주요 프로세스는 아래 과정을 반복적으로 수행하는 것입니다.

  1. 피처 처리(feature processing) : 피처의 가공, 변경, 추출을 수행
  2. ML 알고리즘 학습 · 예측 수행
  3. 모델 평가

 

사이킷런의 기반 프레임워크 익히기

사이킷런은 ML 모델 학습을 위해서 fit()을, 학습된 모델의 예측을 위해 predict() 메서드를 제공합니다. 지도 학습의 주요 두 축인 분류(Classification)과 회귀(Regression)의 사이킷런 클래스는 fit()과 predict()만을 이용해 간단하게 학습과 예측 결과를 반환합니다.

 

분류 알고리즘을 구현한 클래스를 Classifier 클래스로, 회귀 알고리즘을 구현한 클래스를 Regressor 클래스로 지칭합니다. 이 둘을 포함해 지도학습의 모든 알고리즘을 Estimator 클래스라고 부르기도 합니다. Estimator 클래스는 fit()과 predict()를 내부에서 구현하고 있습니다.

 

분류를 구현한 클래스 DecisionTreeClassifier
RandomForestClassifier
GradientBoostingClassifier
GaussianNB
SVC
회귀를 구현한 클래스 LinearRegression
Ridge
Lasso
RandomForestRegressor
GradientBoostingRegressor

 

evalutation 함수, 하이퍼 파라미터 튜닝을 지원하는 클래스의 경우 Estimator를 인자로 받습니다. 인자로 받은 Estimator의 fit()과 predict()를 호출해 평가하거나 하이퍼 파라미터 튜닝을 수행하는 것입니다.

 

사이킷런의 주요 모듈

분류 모듈명 설명
예제 데이터 sklearn.datasets 사이킷런에 내장된 예제 데이터 세트
피처처리 sklearn.preprocessing 데이터 전처리에 필요한 가공 기능
(문자열 → 숫자 인코딩, 정규화, 스케일링)
sklearn.feature_selection 알고리즘에 큰 영향을 미치는 순으로 피처의  셀렉션 작업을 수행하는 기능 제공 등
sklearn.feature_extraction 텍스트 데이터(.text)나 이미지 데이터(.image)의 벡터화된 피처를 추출함
피처처리 & 차원 축소 sklearn.decomposition 차원 축소(PCA, NMF, Truncated SVD)
데이터 분리, 검증 & 파라미터 튜닝 sklearn.model_selection 교차 검증을 위한 학습/테스트용 분리, Grid Search로 최적 파라미터 추출 등
평가 sklearn.metrics 분류, 회귀, 클러스터링, Pairwise에 대한 성능 측정 방법 제공(Accuracy, Precision, Recall, ROC-AUC, RMSE)
ML 알고리즘 sklearn.ensemble 앙상블 알고리즘 제공 (랜덤 포레스트, 에이다 부스트, 그래디언트 부스팅 등을 제공)
sklearn.linear_model 선형회귀, Ridge, Lasso 및 로지스틱 회귀 등 회귀 관련 알고리즘, SGD 관련 알고리즘
sklearn.naive_bayes 나이브 베이즈 알고리즘, 가우시안 NB, 다항 분포 NB 등
sklearn.neighbors 최근접 이웃 알고리즘 제공, K-NN 등
sklearn.svm 서포트 벡터 머신 알고리즘 제공
sklearn.tree 의사 결정 트리 알고리즘 제공
sklearn.cluster 비지도 클러스터링 알고리즘 제공
(K-평균, 계층형, DBSCAN 등)
유틸리티 sklearn.pipeline 피처 처리 등의 변환과 ML 알고리즘 학습, 예측을 함께 묶어 실행할 수 있는 유틸리티 

 

내장된 예제 데이터 세트

datasets 모듈에는 분류회귀를 연습하기 위한 예제 용도의 데이터 세트와 분류클러스터링을 위해 표본 데이터로 생성될 수 있는 데이터 세트로 나뉘어집니다.

 

분류나 회귀 연습용 예제 데이터 세트

API 명 설명
datasets.load_boston() 회귀 용도, 미국 보스턴의 집 피처들과 가격에 대한 데이터 셋
datasets.load_breast_cancer() 분류 용도, 위스콘신 유방암 피처들과 악성 · 음성 레이블 데이터 셋
datasets.load_diabetes() 회귀 용도, 당뇨 데이터 셋
datasets.load_digits() 분류 용도, 0~9까지 숫자의 이미지 픽셀 데이터 셋
datasets.load_iris() 분류 용도, 붓꽃에 대한 피처를 가진 데이터셋

 

fetch 계열 명령은 데이터 크기가 커서 저장돼 있지 않고, 인터넷에서 내려받아 홈 디렉토리 아래의 scikit_learn_data에 저장하고 추후 불러들이는 데이터입니다.

  • fetch_covtype(): 회귀 분석용 토지 조사 자료
  • fetch_20newsgroups(): 뉴스 그룹 텍스트 자료
  • fetch_olivetti_faces(): 얼굴 이미지 자료
  • fetch_lfw_people(): 얼굴 이미지 자료
  • fetch_lfw_pairs(): 얼굴 이미지 자료
  • fetch_rcv1(): 로이터 뉴스 말뭉치
  • fetch_mldata(): ML 웹사이트에서 다운로드

 

분류와 클러스터링을 위한 표본 데이터 생성기

API 명 설명
datasets.make_classifications() 분류를 위한 데이터 세트를 만듭니다. 특히 높은 상관도, 불필요한 속성 등의 노이즈 효과를 위한 데이터를 무작위로 생성해줍니다.
datasets.make_blobs() 클러스터링을 위한 데이터 세트를 무작위로 생성해줍니다. 군집 지정 개수에 따라 여러가지 클러스터링을 위한 데이터 세트를 만들어줍니다.

 

분류나 회귀를 위한 연습용 예제 데이터 구성

사이킷런에 내장된 이 데이터 세트는 일반적으로 딕셔너리 형태로 돼있습니다.

키는 보통 data, target, target_name, feature_names, DESCR로 구성돼있습니다. 개별 키가 가리키는 데이터 세트의 의미는 다음과 같습니다.

  • data는 피처의 데이터 세트를 가리킵니다.
  • target은 분류 시 레이블 값, 회귀일 때는 숫자 결괏값 데이터 세트입니다.
  • target_names는 개별 레이블의 이름을 나타냅니다.
  • feature_names는 피처의 이름을 나타냅니다.
  • DESCR은 데이터 세트에 대한 설명과 각 피처의 설명을 나타냅니다.

data와 target은 ndarray 타입이고, target_names와 feature_names는 ndarray 또는 list 타입입니다. DESCR은 string 타입입니다. 피처의 데이터 값을 반환받기 위해서는 내장 데이터 세트 API를 호출하고 그 Key값을 지정하면 됩니다.

 

# 붓꽃 데이터를 로딩한 후 데이터 세트의 type을 출력합니다.
from sklearn.datasets import load_iris

iris_data = load_iris()
print(type(iris_data))
'''
<class 'sklearn.utils.Bunch'>
'''

출력이 sklearn.utils.Bunch 클래스인데, 이는 파이썬의 딕셔너리 자료형과 유사합니다. 데이터 세트에 내장돼있는 대부분의 데이터 세트는 이와 같은 딕셔너리 형태의 값을 반환합니다.

 

Dictionary.keys()

keys = iris_data.keys()
print(keys)
'''
dict_keys(['data', 'target', 'target_names', 'DESCR', 'feature_names', 'filename'])
'''

 

딕셔너리의(데이터 세트)의 key값을 반환합니다. 데이터 세트딕셔너리 형태이기 때문에 데이터세트.data(또는 데이터 세트['data'])를 이용하면 됩니다. target, target_names, DESCR ... 도 동일하게 접근 가능합니다.

 

load_iris()가 반환하는 객체의 키들을 출력해보겠습니다. list형의 경우 len(list)로 shape를 출력합니다.

from sklearn.datasets import load_iris
iris_data = load_iris()


#.feature_names
print(type(iris_data.feature_names))
'''<class 'list'>'''
print(len(iris_data.feature_names))
'''4'''
print(iris_data.feature_names)
'''
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']
'''


#.target_names
print(type(iris_data.target_names))
'''<class 'numpy.ndarray'>'''
print(len(iris_data.target_names))
'''3'''
print(iris_data.target_names)
'''
['setosa' 'versicolor' 'virginica']
'''


#.data
print(type(iris_data.data))
'''<class 'numpy.ndarray'>'''
print(iris_data.data.shape)
'''(150, 4)'''
print(iris_data.data)
'''
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
        ....
 [6.5 3.  5.2 2. ]
 [6.2 3.4 5.4 2.3]
 [5.9 3.  5.1 1.8]]
'''


#.target
print(type(iris_data.target))
'''<class 'numpy.ndarray'>'''
print(iris_data.target.shape)
'''(150,)'''
print(iris_data.target)
'''
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
'''

 

Model Selection 모듈 소개

model_selection 모듈은 학습 데이터와 테스트 데이터 세트를 분리하거나 교차 검증 분할 및 평가, Estimator의 하이퍼 파라미터를 튜닝하기 위한 다양한 함수와 클래스를 제공합니다.

 

train_test_split()

train_test_split()의 반환값은 리스트형입니다. 순차적으로 학습용 피처 데이터, 테스트용 피처 데이터, 학습용 레이블 데이터, 테스트용 레이블 데이터가 반환됩니다.

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
# 모델 학습
dt_clf.fit(train_data, train_label)

# 모델의 예측값 추출
pred = dt_clf.predict(train_data)
# 정확도 출력
print(accuracy_score(train_label, pred))
''' 1.0 '''

학습한 데이터로 예측한 값을 테스트 하면 1.0이라는 정확도가 나옵니다. train_test_split()을 이용해 쉽게 train data와 test data를 분리할 수 있습니다.

 

train_test_split()은 첫 번째 파라미터로 피처 데이터 세트, 두 번째 파라미터로 레이블 데이터 세트를 필수로 입력 받습니다.

선택적으로 다음 파라미터를 입력받습니다.

  • test_size: 전체 데이터에서 테스트 데이터 세트 크기를 결정합니다. default값은 0.25 즉, 25%입니다.
  • train_size: 전체 데이터에서 학습용 데이터 세트 크기를 결정합니다. 보통 대신에 test_size를 씁니다.
  • shuffle: 데이터를 분리하기 전 미리 섞을 지를 결정합니다. default는 True입니다. 데이터를 분산시켜 효율적인 학습 및 테스트 데이터를 만듭니다.
  • random_state: random_state는 함수를 호출할 때마다 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수 값입니다.  
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
X_train, X_test, y_train, y_test = train_test_split(train_data, train_label, \
                                                    test_size=0.3, random_state=121)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print(accuracy_score(y_test, pred))
''' 0.9555555555555556 '''

데이터 양이 작아서, 예측 성능을 판단하기엔 적절하지 않습니다. 다양한 데이터로 테스트를 해보는 것은 매우 중요합니다.

 

교차 검증

학습용 데이터와 테스트용 데이터를 분리해 평가해도 과적합(Overfitting)에 취약한 약점을 가질 수 있습니다. 고정된 학습 데이터와 테스트 데이터로 평가하다보면, 테스트 데이터에만 과적합되어 다른 테스트용 데이터가 들어올 경우에 성능이 저하됩니다. 이를 개선하기 위해 교차검증을 이용해 다양한 학습과 평가를 수행합니다.

 

교차 검증은 학습용 데이터와 테스트 데이터를 나누고, 다시 학습용 데이터를 학습용 데이터검증 데이터로 나눕니다. 학습용과 검증 데이터 세트에서 수행한 평가 결과에 따라 파라미터 튜닝 등의 모델 최적화를 손쉽게 할 수 있습니다. 1차적으로 검증 데이터로 ML 모델을 평가하고, 최종적으로 테스트 데이터를 통해 ML 모델의 성능을 평가합니다.

 

K 폴드 교차 검증

데이터를 k개로 나눴을 때, 1개는 검증 데이터로 나머지는 학습용 데이터로 할당한다. 검증 데이터를 바꿔가며 k번 검증 평가를 반복 하고, 예측 평가들의 평균을 k 폴드 평가 결과로 반영합니다.

 

사이킷런에서는 K 폴드 교차 검증 프로세스를 구현하기 위해 KFold와 StratifiedKFold 클래스를 제공합니다.

 

KFold

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

# 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 시트르 객체 생성/
kfold = KFold(n_splits=5)
cv_accuracy = []

# 붓꽃 데이터 크기 출력
print(features.shape[0])
''' 150 '''

n_iter = 0

# KFold 객체의 split()을 호출하면 폴드 별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
  # kfold.split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
  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)
  pred = dt_clf.predict(X_test)
  n_iter +=1
  # 반복 시마다 정확도 측정
  accuracy = np.round(accuracy_score(y_test, pred), 4)
  train_size = X_train.shape[0]
  test_size = X_test.shape[0]
  print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'.format(n_iter,\
                                                              accuracy, train_size, test_size))
  print('#{0} 검증 세트 인덱스:{1}'.format(n_iter, test_index))
  cv_accuracy.append(accuracy)

# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))

'''
#1 교차 검증 정확도 :1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#1 검증 세트 인덱스:[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
 24 25 26 27 28 29]

#2 교차 검증 정확도 :0.9667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#2 검증 세트 인덱스:[30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
 54 55 56 57 58 59]

#3 교차 검증 정확도 :0.8667, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#3 검증 세트 인덱스:[60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
 84 85 86 87 88 89]

#4 교차 검증 정확도 :0.9333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#4 검증 세트 인덱스:[ 90  91  92  93  94  95  96  97  98  99 100 101 102 103 104 105 106 107
 108 109 110 111 112 113 114 115 116 117 118 119]

#5 교차 검증 정확도 :0.7333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#5 검증 세트 인덱스:[120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
 138 139 140 141 142 143 144 145 146 147 148 149]

## 평균 검증 정확도: 0.9
'''

n_splits=5를 할당했으므로, kfold.split(features)에는 list들의 묶음 5개가 있는데, train_index에는 학습용 index list 첫 번째 묶음을, test_index에는 검증용 index list 첫 번째 묶음을 할당한다. 이를 5번째 묶음까지 할당하면 for 문을 종료한다. 검증용 index list는 30개씩 할당되므로 [0~29], [30~59] ... , [120~149]의 인덱스 list들이 할당 된다.

 

kford.split(X)에서 X는 data면 되는 듯 하다. ndarray든 dataFrame이든

 

Stratified K 폴드

Stratified K 폴드는 불균형한(imbalanced) 분포도를 가진 레이블(결정 클래스) 데이터 집합을 위한 K 폴드 방식입니다. imbalanced한 분포도를 가진다는 건 특정 레이블 값이 특이하게 많거나 적어서 분포가 한 쪽으로 치우치는 것입니다.

 

이를 KFold로 검증할 경우 특정 학습 · 검증 데이터 세트에 갯수가 적은 레이블 값이 몰리는 문제가 발생합니다. Case 1을 보면, 학습 레이블은 0과 1만 주어졌는데, 테스트 데이터는 2로만 구성돼있습니다. 저렇게 분포될 경우 정확도는 0이 나오게 됩니다.

iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label'] = iris.target
iris_df['label'].value_counts()
'''
2    50
1    50
0    50
Name: label, dtype: int64
'''

# Case 1
kfold = KFold(n_splits=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())
'''
## 교차 검증: 1
학습 레이블 데이터 분포:
 2    50
1    50

검증 레이블 데이터 분포:
 0    50

## 교차 검증: 2
학습 레이블 데이터 분포:
 2    50
0    50

검증 레이블 데이터 분포:
 1    50

## 교차 검증: 3
학습 레이블 데이터 분포:
 1    50
0    50

검증 레이블 데이터 분포:
 2    50
'''

 

이 문제를 Stratified K 폴드가 해결합니다. Stratified K 폴드는 원본 데이터의 분포 비율을 맞춰 학습 · 검증 데이터 세트를 구성해줍니다. Stratified K 폴드는 KFold와 사용법이 같습니다. 단지, 레이블의 분포를 알기 위해 .split()의 인자로 레이블 데이터도 입력해주어야 합니다.

from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=3)
n_iter = 0

for train_index, test_index in skf.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())
  
  '''
  ## 교차 검증: 1
학습 레이블 데이터 분포:
 2    34
1    33
0    33

검증 레이블 데이터 분포:
 1    17
0    17
2    16

## 교차 검증: 2
학습 레이블 데이터 분포:
 1    34
2    33
0    33

검증 레이블 데이터 분포:
 2    17
0    17
1    16

## 교차 검증: 3
학습 레이블 데이터 분포:
 0    34
2    33
1    33

검증 레이블 데이터 분포:
 2    17
1    17
0    16
  '''

 

Stratified K 폴드로 붓꽃 예제 교차 검증 하기

일반적으로 분류(Classification)에서는 Stratified K 폴드로 데이터가 분할돼야 합니다.  왜곡된 레이블 데이터 세트를 가지고 있을 수 있기 때문입니다.

 

참고로, 회귀(Regression)에서는 Stratified K 폴드가 지원되지 않습니다. 회귀의 결정값은 이산값 형태가 아니라 연속된 숫자값이기 때문에 결정값별로 분포를 정하는 의미가 없기 때문입니다.

df_clf = DecisionTreeClassifier(random_state=156)
iris = load_iris()
feature = iris.data
label = iris.target

skf = StratifiedKFold(n_splits=3)
n_iter = 0
cv_accuracy=[]

for train_index, test_index in skf.split(feature, label):
  # split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
  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)
  pred = dt_clf.predict(X_test)

  # 반복 시마다 정확도 측정
  n_iter += 1
  accuracy = np.round(accuracy_score(y_test, pred), 4)
  train_size = X_train.shape[0]
  test_size = X_test.shape[0]
  print('\n#{0} 교차 검증 정확도: {1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'\
        .format(n_iter, accuracy, train_size, test_size))
  print('#{0} 검증 세트 인덱스: {1}'.format(n_iter, test_index))
  cv_accuracy.append(accuracy)

# 교차 검증별 정확도 및 평균 정확도 계산
print('\n## 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('## 평균 검증 정확도:', np.mean(cv_accuracy))

'''
#1 교차 검증 정확도: 0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#1 검증 세트 인덱스: [  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  50
  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66 100 101
 102 103 104 105 106 107 108 109 110 111 112 113 114 115]

#2 교차 검증 정확도: 0.94, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#2 검증 세트 인덱스: [ 17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  67
  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82 116 117 118
 119 120 121 122 123 124 125 126 127 128 129 130 131 132]

#3 교차 검증 정확도: 0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#3 검증 세트 인덱스: [ 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  83  84
  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 133 134 135
 136 137 138 139 140 141 142 143 144 145 146 147 148 149]

## 교차 검증별 정확도: [0.98 0.94 0.98]
## 평균 검증 정확도: 0.9666666666666667
'''

 

cross_val_score()

교차검증을 편리하게 수행할 수 있게 해주는 API입니다. KFold의 경우는 아래의 과정을 수행해야했습니다.

  1. 폴드 세트를 설정한다
  2. for 루프에서 반복으로 학습 및 테스트 데이터의 인덱스를 추출한다
  3. 반복적으로 학습과 예측을 수행하고 예측 성능을 반환한다.

cross_val_score()는 이 과정을 한 번에 수행해줍니다.

cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, verbose=0, fit_params=None, pre_dispatch='2*n_jobs'). 이 중 estimator, X, y, scoring, cv가 주요 파라미터입니다.

 

estimator는 사이킷런의 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor를 의미합니다.

X는 피처 데이터 세트, y는 레이블 데이터 세트, scoring은 예측 성능 평가 지표를 기술하며, cv는 교차 검증 폴드 수를 의미합니다. 반환 값은 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환합니다. estimatorclassifier가 입력되면 Stratified K 폴드 방식으로 레이블값의 분포에 따라 학습/테스트 세트를 분할합니다. (회귀인 경우는 K폴드로 분할)

 

교차 검증 폴드 수는 3, 성능 평가 지표는 정확도인 accuracy로 하겠습니다.

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score, cross_validate
from sklearn.datasets import load_iris
import numpy as np

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능 지표는 정확도(accuracy), 교차 검증 세트는 3개
scores = cross_val_score(dt_clf, data, label, scoring='accuracy', cv=3)
print('교차 검증별 정확도:', np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))
'''
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9667
'''

비슷한 API로는 cross_validate()가 있고, cross_val_score()는 단 하나의 평가 지표만 가능하지만, cross_validate()는 여러개의 평가 지표를 반환할 수 있습니다. 또한, 학습 데이터에 대한 성능 평가 지표와 수행 시간도 같이 제공합니다.

 

GridSearchCV - 교차 검증과 최적 하이퍼 파라미터 튜닝을 한 번에

하이퍼 파라미터는 머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정해 알고리즘의 예측 성능을 개선할 수 있습니다. GridSearchCV API를 이용해 Classifier나 Regressor의 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면 최적의 파라미터를 도출할 수 있는 방안을 제공합니다.

 

GridSearchCV는 cross-validation을 위한 학습/테스트 세트로 자동으로 분할한 뒤, 하이퍼 파라미터 그리드에 기술된 모든 파라미터를 순차적으로 적용해 최적의 파라미터를 찾을 수 있게 해줍니다. 순차적으로 파라미터를 테스트하므로 수행시간이 상대적으로 오래 걸립니다.

 

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

위의 경우 순차적으로 6회에 걸쳐 하이퍼 파리미터를 변경하며 교차 검증을 해야합니다. CV가 3회라면, 3개의 폴딩 세트를 6회씩 교차 검증해야하므로 18번 학습/평가가 이루어집니다.

 

GridSearchCV 클래스의 생성자로 들어가는 주요 파라미터는 다음과 같습니다.

  • estimator: classifier, regressor, pipeline이 사용될 수 있습니다.
  • param_grid: key + list값을 가지는 딕셔너리형입니다. estimator의 튜닝할 여러 파라미터를 지정합니다.
  • scoring: 예측 성능을 평가할 평가 방법을 지정합니다. 보통 평가 지표를 지정하는 문자열을 쓰나, 별도의 평가 지표 함수를 지정할 수도 있음
  • cv: 교차 검증을 위해 분할되는 학습/테스트 세트 갯수를 지정합니다.
  • refit: default는 True이며, True로 생성시 가장 최적의 파라미터를 찾은후, estimator 객체를 해당 하이퍼 파라미터로 재학습 시킵니다.

 

GridSearchCV로 붓꽃 예제의 최적의 하이퍼 파라미터 찾기

결정 트리 알고리즘을 구현한 DecisionTreeClassifier의 중요 하이퍼 파라미터인 max_depth와 min_samples_split을 변화시키며 최적화를 진행하겠습니다.

 

학습 데이터 세트는 GridSearchCV 객체의 fit 메서드 인자로 입력합니다. fit 메서드를 수행하면, 학습 데이터를 cv에 기술된 폴딩 세트로 분할해 param_grid에 있는 하이퍼 파라미터를 순차적으로 변경하며 학습/평가를 수행합니다.

 

그 결과를 cv_results_ 속성에 기록합니다. cv_results_는 gridsearchcv의 결과 세트로서 딕셔너리 형태입니다. 

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

iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target,\
                                                    test_size=0.2, random_state=121)
dtree = DecisionTreeClassifier()

## 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth':[1, 2, 3], 'min_samples_split':[2, 3]}

# param_grid의 하이퍼 파라미터를 3개의 train, test set fold로 나누어 테스트 수행 설정
### refit=True가 default임. True이면 가장 좋은 파라미터 설정으로 재학습시킴
grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True)

# 붓꽃 학습데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습/평가
grid_dtree.fit(X_train, y_train)

# GridSearchCV 결과를 추출해 DataFrame으로 변환
scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score', 'split0_test_score',\
           'split1_test_score', 'split2_test_score']]
''' 결과1 '''

결과1

params 칼럼에는 수행할 때마다 적용된 하이퍼 파라미터값을 가지고 있습니다. rank_test_score는 해당 하이퍼 파라미터 조합의 순위를 나타냅니다. index 4번은 예측 성능 순위가 1위임을 나타냅니다. mean_test_score는 split0, split1, split2_test_score의 평균입니다. split_test_score는 CV가 3인 즉, 3개의 폴딩 세트에서 각각 테스트한 성능 수치입니다.

 

GridSearchCV 객체의 fit()을 수행하면, 최고 성능을 나타낸 하이퍼 파라미터 값과 그때의 평가 결과 값best_params_, best_score_속성에 기록됩니다.( 즉, cv_results_의 rank_test_score가 1일 때의 값입니다.)

print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
print('GridSearchCV 최고 정확도:{0:.4f}'.format(grid_dtree.best_score_))
''' 
GridSearchCV 최적 파라미터: {'max_depth': 3, 'min_samples_split': 2}
GridSearchCV 최고 정확도:0.9750
'''

 

refit=True가 디폴트이므로, 최적 성능을 나타내는 하이퍼 파라미터로 Estimator를 학습해 best_estimator_로 저장합니다.

# GridSearchCV의 refit으로 이미 학습된 estimator 반환
estimator = grid_dtree.best_estimator_

# GridSearchCV의 best_estimator_는 이미 최적 학습이 됐으므로 별도 학습이 필요 없음
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도:{0:.4f}'.format(accuracy_score(y_test, pred)))
'''
테스트 데이터 세트 정확도:0.9667
'''

일반적으로 GridSearchCV를 이용해 학습데이터(X_train)로 최적 하이퍼 파라미터 튜닝을 수행한 뒤에 학습에 영향을 주지 않은 테스트 세트(X_test)로 이를 평가하는 것이 일반적인 머신러닝 모델 적용 방법입니다.

 

데이터 전처리

ML 알고리즘은 결손값, 즉 NaN, Null 값은 허용되지 않습니다. 따라서 Null 값은 고정된 다른 값으로 변환해야 합니다. Null 값의 비중이 작다면, 피처의 평균값 등으로 간단히 대체할 수 있습니다. Null 값이 일정 수준 이상이고, 중요도가 높은 피처라면 예측값이 많이 변경될 수 있어 평균값으로 대체될 수 없습니다. 인덱스, 주민번호, ID와 같은 단순 식별자 피처는 예측에 중요하지 않으므로 가급적 삭제하는게 좋습니다.

 

사이킷런의 머신러닝 알고리즘은 입력 값으로 문자열 값을 허용하지 않습니다. 그래서 모든 문자열은 인코딩돼서 숫자 형으로 변환해야합니다. 카테코리형 피처는 코드값으로 표현하는게 더 쉽고, 텍스트형 피처는 피처 벡터화(feature vectorization)으로 벡터화 할 수 있습니다.

 

데이터 인코딩

대표적인 인코딩 방식은 레이블 인코딩(Label encoding)과 원-핫 인코딩(One Hot encoding)이 있습니다.

 

LabelEncoding

Label encoding은 카테고리 피처를 코드형 숫자값으로 변환하는 것입니다. LabelEncoder 클래스로 구현합니다. LabelEncoder를 객체로 생성한 후 fit()과 transform()을 호출해 레이블 인코딩을 수행합니다.

from sklearn.preprocessing import LabelEncoder

items=['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '믹서', '믹서']

#LabelEncoder를 객체로 생성한 후, fit()과 transform()으로 레이블 인코딩 수행.
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
print('인코딩 변환값:', labels)
'''
인코딩 변환값: [0 1 4 5 3 2 2]
'''

TV는 0, 냉장고는 1, 전자레인지는 4, ... 믹서는 2로 인코딩 됐습니다. 데이터가 많아질 경우 어떤 문자열이 어떤 숫자 값으로 인코딩됐는지 직관적으로 알 수 없습니다. LabelEncoder 객체의 classes_ 속성값으로 확인하면 됩니다.

print('인코딩 클래스:', encoder.classes_)
'''
인코딩 클래스: ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터']
'''

inverse_transform()을 통해 인코딩된 값을 다시 디코딩할 수 있습니다.

print('디코딩 원본값:', encoder.inverse_transform([4, 5, 2, 1, 1, 3, 3]))
'''
디코딩 원본값: ['전자레인지' '컴퓨터' '믹서' '냉장고' '냉장고' '선풍기' '선풍기']
'''

트리 계열의 ML 알고리즘은 인코딩된 숫자에 가중치를 부여하지 않아 사용해도 되지만, 선형 회귀와 같은 ML 알고리즘은 단순 코드 이자 숫자 값이 큰 걸 더 중요하게 인식할 수 있습니다. 이러한 문제점을 개선하기 위해 One-Hot Encoding 방식이 생겼습니다.

 

원-핫 인코딩(One-Hot Encoding)

One-Hot Encoding은 원본 데이터들 중 고유한 값들을 고유 값 피처로 변환하고, 해당되는 레코드1을 입력하고 나머지0으로 채웁니다. 여러 개의 속성 중 단 한개의 속성만 1로 표시하므로 원-핫 인코딩으로 불립니다.

 

OneHotEncoder 클래스로 원-핫 인코딩을 구현할 수 있습니다. 원-핫 인코더를 쓸 때 주의할 점2가지 있습니다.

  1. OneHotEncoder를 사용하기 전에 모든 문자열 값이 숫자형 값으로 변환돼야 합니다.
  2. 입력 값으로 2차원 데이터가 필요합니다.
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
import numpy as np

items=['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

# 먼저 숫자 값으로 변환을 위해 LabelEncoder로 변환합니다.
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)

# 2차원 데이터로 변환합니다.
labels = labels.reshape(-1, 1)

# 원-핫 인코딩을 적용합니다.
oh_encoder = OneHotEncoder()
oh_encoder.fit(labels)
oh_labels = oh_encoder.transform(labels)
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)
'''
원-핫 인코딩 데이터
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]]
 
원-핫 인코딩 데이터 차원
(8, 6)
'''

8개의 레코드와 1개의 칼럼을 가진 원본 데이터가 8개의 레코드와 6개의 칼럼을 가진 데이터로 변환됐습니다. TV 0, 냉장고 1, 믹서 2, 선풍기 3, 전자레인지 4, 컴퓨터 5로 인코딩됐습니다.

 

pd.get_dummies(DataFrame)

판다스에서는 One-Hot Encoding을 더 쉽게 지원하는 get_dummies()가 있습니다. get_dummies()를 이용하면 숫자형 값으로 변환 없이도 바로 변환이 가능합니다.

import pandas as pd
df = pd.DataFrame({'item':['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']})
pd.get_dummies(df)
'''결과1'''

결과1

 

피처 스케일링과 정규화

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

 

표준화는 데이터의 피처 각각이 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환하는 것을 의미합니다. 표준화를 통해 변환될 피처(칼럼) x 의 새로운 i 번째 데이터를 X_inew 라고 한다면, 그 값은 아래와 같다. stdev(x)는 표준편차다.

표준화 공식

정규화는 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념입니다. 거리를 나타내는 피처 A의 값이 0 ~ 100KM 이고, 금액을 나타내는 피처 B의 값이 0 ~ 1억으로 주어진다면, 두 값을 모두 최소 0 ~ 최대 1의 값으로 변환하는 것입니다. 새로운 데이터 X_inew라고 한다면, 그 값은 아래와 같다.

정규화 공식

사이킷런의 Normalizer 모듈과 일반적인 정규화는 약간의 차이가 있습니다. Normalizer 모듈은 선형대수에서의 정규화 개념이 적용됐으며, 개별 벡터의 크기를 맞추기 위한 변환입니다. 개별 벡터를 모든 피처 벡터의 크기로 나눠줍니다. 세 개의 피처 x, y, z 가 있다고 하면 X_inew의 값은 아래와 같습니다.

Normalizer 공식

혼선을 방지하기 위해 표준화, 정규화를 피처 스케일링이라 통칭하고 선형대수 개념의 정규화를 벡터 정규화라 지칭하겠습니다.

피처 스케일링의 대표적인 클래스인 StandardScalerMinMaxScaler를 알아보겠습니다.

 

StandardScaler

RBF 커널을 이용하는 SVM이나 Linear Regression, Logistic Regression데이터가우시안 분포를 가지고 있다고 가정하고 구현됐기 때문에 표준화를 적용하는 것은 예측 성능 향상에 중요한 요소가 될 수 있습니다.

 

StandardScaler를 사용하기 전 데이터의 평균과 분산

from sklearn.datasets import load_iris
import pandas as pd

# 붓꽃 데이터 세트를 로딩하고 DataFrame으로 변환합니다.
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('\nfeature들의 분산값')
print(iris_df.var())
'''
feature들의 평균값
sepal length (cm)    5.843333
sepal width (cm)     3.057333
petal length (cm)    3.758000
petal width (cm)     1.199333
dtype: float64

feature들의 분산값
sepal length (cm)    0.685694
sepal width (cm)     0.189979
petal length (cm)    3.116278
petal width (cm)     0.581006
dtype: float64
'''

 

StandardScaler를 사용한 후 데이터의 평균과 분산

StandardScaler.fit(DataFrame)을 쓴 후 StandardScaler.transform(DataFrame)을 쓰며, transform의 반환값은 ndarray이다.

from sklearn.preprocessing import StandardScaler

# StandardScaler객체 생성
scaler = StandardScaler()
# StandardScaler로 데이터 세트 변환. fit()과 transform()호출.
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터 세트가 NumPy ndarray로 반환돼 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names)
print('feature들의 평균값')
print(iris_df_scaled.mean())
print('\nfeature들의 분산값')
print(iris_df_scaled.var())
'''
feature들의 평균값
sepal length (cm)   -1.690315e-15
sepal width (cm)    -1.842970e-15
petal length (cm)   -1.698641e-15
petal width (cm)    -1.409243e-15
dtype: float64

feature들의 분산값
sepal length (cm)    1.006711
sepal width (cm)     1.006711
petal length (cm)    1.006711
petal width (cm)     1.006711
dtype: float64
'''

평균이 0에 아주 가깝게, 분산이 1에 아주 가깝게 변환됐다.

 

MinMaxScaler

MinMaxScaler는 데이터값을 0 ~ 1 사이의 범위값으로 변환합니다. 음수가 있으면 -1에서 1값으로 변환합니다. 데이터의 분포가 가우시안 분포가 아닐 경우 MinMaxScale을 적용해 볼 수 있습니다.

from sklearn.preprocessing import MinMaxScaler
# MinMaxScaler 객체 생성
scaler = MinMaxScaler()
# MinMaxScaler로 데이터 세트 변환. fit()과 transform() 호출.
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform()시 스케일 변환된 데이터 세트가 ndarray로 반환돼 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(data=iris_scaled, columns = iris.feature_names)
print('feature들의 최솟값')
print(iris_df_scaled.min())
print('\nfeature들의 최댓값')
print(iris_df_scaled.max())
'''
feature들의 최솟값
sepal length (cm)    0.0
sepal width (cm)     0.0
petal length (cm)    0.0
petal width (cm)     0.0
dtype: float64

feature들의 최댓값
sepal length (cm)    1.0
sepal width (cm)     1.0
petal length (cm)    1.0
petal width (cm)     1.0
dtype: float64
'''

모든 피처에 데이터 값들이 0 ~ 1 사이값으로 변환되는 스케일링이 적용됐음을 알 수 있습니다.

 

학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점

StandardScaler나 MinMaxScaler와 같은 Scaler 객체를 이용해 데이터의 스케일링 변환 시 fit(), transform(), fit_transform() 메소드를 이용합니다. 

 

fit()은 데이터 변환을 위한 기준 정보 설정(예를 들면 데이터 세트의 최대/최솟값 설정 등)을 적용합니다. transform()은 이렇게 설정된 정보를 이용해 데이터를 변환합니다. fit_transform()fit()transform()을 한 번에 적용하는 기능을 수행합니다.

 

학습 데이터 세트와 테스트 데이터 세트에 fit()과 transform()을 적용할 때 주의가 필요합니다. Scaler 객체를 이용해 학습 데이터 세트fit()transform()을 적용하면, 테스트 데이터 세트로는 다시 fit()을 수행하지 않고 학습 데이터 세트로 fit()을 수행한 결과를 이용해 transform() 변환을 적용해야 한다는 것입니다. 그렇지 않으면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 서로 달라져 예측 정확도가 떨어질 수 있습니다.

 

테스트 데이터에 fit()을 적용할 때 어떠한 문제가 발생하는지 알아보겠습니다.

from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 학습 데이터는 0부터 10까지, 테스트 데이터는 0부터 5까지 값을 가지는 데이터 세트로 생성
# Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하므로 reshape(-1, 1)로 차원 변경
train_array = np.arange(0, 11).reshape(-1, 1)
test_array = np.arange(0, 6).reshape(-1, 1)

# MinMaxScaler 객체에 별도의 feature_range 파라미터 값을 지정하지 않으면 0~1 값으로 변환
scaler = MinMaxScaler()

# fit()하게 되면 train_array 데이터의 최솟값이 0, 최댓값이 10으로 설정.
scaler.fit(train_array)

# 1/10 scale로 train_array 데이터 변환함. 원본 10-> 1로 변환됨.
train_scaled = scaler.transform(train_array)

print('원본 train_array 데이터:', np.round(train_array.reshape(-1), 2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1), 2))
'''
원본 train_array 데이터: [ 0  1  2  3  4  5  6  7  8  9 10]
Scale된 train_array 데이터: [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]
'''

test_array로 다시 scaler를 fit하게 되면 1/5 스케일링됩니다. 학습데이터와 비교해볼 때, 1이라는 데이터를 학습데이터0.1로 받아들이지만 테스트 데이터0.2로 받아들입니다. 즉, 서로 같은 원본값을 다른 값으로 인식해버린 것입니다. 머신러닝 모델은 학습데이터를 기반으로 학습되기 때문에 반드시 학습 데이터의 스케일링 기준을 따라야 합니다.

# MinMaxScaler에 test_array를 fit()하게 되면 원본 데이터의 최솟값이 0, 최댓값이 5로 설정됨
scaler.fit(test_array)

# 1/5 scale로 test_array 데이터 변환함. 원본 5->1로 변환.
test_scaled = scaler.transform(test_array)

# test_array의 scale 변환 출력.
print('원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))
'''
원본 test_array 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0.  0.2 0.4 0.6 0.8 1. ]
'''

fit_transform()을 적용할 때도 마찬가지로 학습 데이터에서는 상관없지만, 테스트 데이터에서는 transform()만 적용해야 합니다.

fit_transform()은 fit()과 transform()을 순차적으로 실행하기 때문입니다. 

 

그래서 가급적 전체 데이터의 스케일링 변환을 적용한 뒤 학습 데이터와 테스트 데이터를 분리해야합니다.

 

 

출처: 파이썬 머신러닝 완벽 가이드 (권민철)

'파이썬 머신 러닝 완벽 가이드' 카테고리의 다른 글

피마 인디언 당뇨병 예측  (0) 2021.11.05
평가  (0) 2021.10.27
타이타닉 생존자 예측  (0) 2021.10.20
Pandas  (0) 2021.10.10
Numpy  (0) 2021.10.09