by museonghwang

분류의 성능 평가 지표(Evaluation Metric)와 정확도(Accuracy)

|

머신러닝은 데이터 가공/변환, 모델 학습/예측, 그리고 평가(Evaluation)의 프로세스로 구성되며, 머신러닝 모델은 여러 가지 방법으로 예측 성능을 평가할 수 있습니다. 성능 평가 지표(Evaluation Metric) 는 일반적으로 모델이 분류냐 회귀냐에 따라 여러 종류로 나뉩니다. 회귀 의 경우 대부분 실제값과 예측값의 오차 평균값에 기반하며, 분류 의 평가방법도 일반적으로 실제 결과 데이터와 예측 결과 데이터가 얼마나 정확하고 오류가 적게 발생하는가에 기반하지만, 단순히 정확도만 가지고 판단했다가는 잘못된 평가 결과에 빠질 수 있습니다.

분류의 대표적인 성능 평가 지표를 살펴보면 다음과 같습니다.

  • 정확도(Accuracy)
  • 오차행렬(Confusion Matrix)
  • 정밀도(Precision)
  • 재현율(Recall)
  • F1 스코어
  • ROC AUC


분류는 결정 클래스 값 종류의 유형에 따라 긍정/부정과 같은 2개의 결과값만을 가지는 이진 분류 와 여러 개의 결정 클래스 값을 가지는 멀티 분류 로 나뉠 수 있습니다. 분류에 사용되는 성능 평가 지표에 대해 자세히 알아보겠습니다.


정확도(Accuracy)

정확도실제 데이터에서 예측 데이터가 얼마나 같은지를 판단하는 지표 입니다.

[정확도(Accuracy)=\frac{예측\ 결과가\ 동일한\ 데이터\ 건수}{전체\ 예측\ 데이터 건수}]


정확도는 직관적으로 모델 예측 성능을 나타내는 평가 지표 입니다. 하지만 이진 분류의 경우 데이터의 구성에 따라 ML 모델의 성능을 왜곡할 수 있기 때문에 정확도 수치 하나만 가지고 성능을 평가하지 않습니다. 정확도 지표가 어떻게 ML 모델의 성능을 왜곡하는지 예제로 살펴보겠습니다.


타이타닉 데이터 세트를 기준으로 탑승객이 남자인 경우보다 여자인 경우에 생존 확률이 높았기 때문에, 별다른 알고리즘의 적용 없이 무조건 성별이 여자인 경우 생존으로, 남자인 경우 사망으로 예측해도 예측 정확도의 결과가 약 80%가 나올 수 있습니다. 단지 성별 조건 하나만을 가지고 결정하는 별거 아닌 알고리즘도 높은 정확도를 나타내는 상황이 발생하는 것 입니다.

다음 예제에서는 사이킷런의 BaseEstimator 클래스를 상속받아 아무런 학습을 하지 않고, 성별에 따라 생존자를 예측하는 단순한 Classifier 를 생성합니다. 사이킷런은 BaseEstimator 를 상속받으면 Customized 형태의 Estimator 를 개발자가 생성할 수 있습니다. 생성할 MyDummyClassifier 클래스는 학습을 수행하는 fit() 메서드는 아무것도 수행하지 않으며 예측을 수행하는 predict() 메서드는 단순히 Sex 피처가 1이면 0, 그렇지 않으면 1로 예측하는 매우 단순한 Classifier 입니다.

import numpy as np
from sklearn.base import BaseEstimator

class MyDummyClassifier(BaseEstimator):
    # fit() 메소드는 아무것도 학습하지 않음. 
    def fit(self, X, y=None):
        pass
    
    # predict() 메소드는 단순히 Sex feature가 1이면 0, 그렇지 않으면 1로 예측함. 
    def predict(self, X):
        pred = np.zeros((X.shape[0], 1))
        for i in range(X.shape[0]):
            if X['Sex'].iloc[i] == 1:
                pred[i] = 0
            else:
                pred[i] = 1
        return pred


이제 생성된 MyDummyClassifier 를 이용해 타이타닉 생존자 예측을 수행해 보겠습니다. 데이터를 가공하고 나서 이 Classifier 를 이용해 학습/예측/평가를 적용해 보겠습니다.

from sklearn.preprocessing import LabelEncoder

# Null 처리 함수
def fillna(df):
    df['Age'].fillna(df['Age'].mean(), inplace=True)
    df['Cabin'].fillna('N', inplace=True)
    df['Embarked'].fillna('N', inplace=True)
    df['Fare'].fillna(0, inplace=True)
    return df

# 머신러닝 알고리즘에 불필요한 피처 제거
def drop_features(df):
    df.drop(['PassengerId', 'Name', 'Ticket'], axis=1, inplace=True)
    return df

# 레이블 인코딩 수행.
def format_features(df):
    df['Cabin'] = df['Cabin'].str[:1]
    features = ['Cabin', 'Sex', 'Embarked']
    for feature in features:
        le = LabelEncoder()
        le = le.fit(df[feature])
        df[feature] = le.transform(df[feature])
    return df

# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
    df = fillna(df)
    df = drop_features(df)
    df = format_features(df)
    return df
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

# 원본 데이터를 재로딩, 데이터 가공, 학습 데이터/테스트 데이터 분할.
titanic_df = pd.read_csv('./titanic/train.csv')
y_titanic_df = titanic_df['Survived']
X_titanic_df = titanic_df.drop('Survived', axis=1)
X_titanic_df = transform_features(X_titanic_df)

X_train, X_test, y_train, y_test = train_test_split(
    X_titanic_df,
    y_titanic_df,
    test_size=0.2,
    random_state=0
)

# 위에서 생성한 Dummy Classifier를 이용해 학습/예측/평가 수행.
myclf = MyDummyClassifier()
myclf.fit(X_train, y_train)

mypredictions = myclf.predict(X_test)
print('Dummy Classifier의 정확도는: {0:.4f}'.format(accuracy_score(y_test, mypredictions)))
[output]
Dummy Classifier의 정확도는: 0.7877


이렇게 단순한 알고리즘으로 예측을 하더라도 데이터의 구성에 따라 정확도 결과는 약 78.77%로 꽤 높은 수치가 나올 수 있기에 정확도를 평가 지표로 사용할 때는 매우 신중해야 합니다. 특히 정확도는 불균형한(imbalanced) 레이블 값 분포에서 ML 모델의 성능을 판단할 경우, 적합한 평가 지표가 아닙니다. 예를 들어 100개의 데이터가 있고 이 중에 90개의 데이터 레이블이 0, 단 10개의 데이터 레이블이 1이라고 한다면 무조건 0으로 예측 결과를 반환하는 ML 모델의 경우라도 정확도가 90%가 됩니다.


MNIST 데이터 세트(0부터 9까지의 숫자 이미지 픽셀 정보)를 변환해 불균형한 데이터 세트로 만든 뒤에 정확도 지표 적용 시 어떤 문제가 발생할 수 있는지 살펴보겠습니다. 사이킷런은 load_digits() API를 통해 MNIST 데이터 세트를 제공합니다. MNIST 데이터 세트는 레이블 값이 0부터 9까지 있는 멀티 레이블 분류 를 위한 것입니다. 이것을 레이블 값이 7 인 것만 True, 나머지 값 은 모두 False 로 변환해 이진 분류 문제 로 살짝 바꿔 보겠습니다.

즉, 전체 데이터의 10%만 True, 나머지 90%는 False인 불균형한 데이터 세트로 변형하는 것입니다.

image


이렇게 불균형한 데이터 세트에 모든 데이터를 False, 즉 0으로 예측하는 classifier를 이용해 정확도를 측정하면 약 90%에 가까운 예측 정확도를 나타냅니다. 아무것도 하지 않고 무조건 특정한 결과로 찍어도(?) 데이터 분포도가 균일하지 않은 경우 높은 수치가 나타날 수 있는 것이 정확도 평가 지표의 맹점입니다. 예제 코드로 확인하기위해, 불균형한 데이터 세트Dummy Classifier 를 생성합니다.

from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
from sklearn.base import BaseEstimator
from sklearn.metrics import accuracy_score
import numpy as np
import pandas as pd

class MyFakeClassifier(BaseEstimator):
    def fit(self, X, y):
        pass
    
    # 입력값으로 들어오는 X 데이터 셋의 크기만큼 모두 0값으로 만들어서 반환
    def predict(self, X):
        return np.zeros((len(X), 1), dtype=bool)

# 사이킷런의 내장 데이터 셋인 load_digits()를 이용하여 MNIST 데이터 로딩
digits = load_digits()

# digits번호가 7번이면 True이고 이를 astype(int)로 1로 변환, 7번이 아니면 False이고 0으로 변환. 
y = (digits.target == 7).astype(int)
X_train, X_test, y_train, y_test = train_test_split(digits.data, y, random_state=11)


다음으로 불균형한 데이터로 생성한 y_test 의 데이터 분포도를 확인하고 MyFakeClassifier 를 이용해 예측과 평가를 수행해 보겠습니다.

# 불균형한 레이블 데이터 분포도 확인. 
print('레이블 테스트 세트 크기 :', y_test.shape)
print('테스트 세트 레이블 0 과 1의 분포도')
print(pd.Series(y_test).value_counts())

# Dummy Classifier로 학습/예측/정확도 평가
fakeclf = MyFakeClassifier()
fakeclf.fit(X_train, y_train)
fakepred = fakeclf.predict(X_test)
print('\n모든 예측을 0으로 하여도 정확도는 : {:.3f}'.format(accuracy_score(y_test, fakepred)))
[output]
레이블 테스트 세트 크기 : (450,)
테스트 세트 레이블 0 과 1의 분포도
0    405
1     45
dtype: int64

모든 예측을 0으로 하여도 정확도는 : 0.900


단순히 predict() 의 결과를 np.zeros() 로 모두 0 값으로 반환함에도 불구하고 450개의 테스트 데이터 세트에 수행한 예측 정확도는 90%입니다. 단지 모든 것을 0으로만 예측해도 MyFakeClassifier 의 정확도가 90%로 유수의 ML 알고리즘과 어깨를 겨룰 수 있다는 것은 말도 안 되는 결과입니다.

이처럼 정확도 평가 지표는 불균형한 레이블 데이터 세트에서는 성능 수치로 사용돼서는 안 됩니다. 이러한 한계점을 극복하기 위해 여러 가지 분류 지표와 함께 적용하여 ML 모델 성능을 평가해야 합니다.

Read more

데이터 전처리(Data Preprocessing)

|

데이터 전처리(Data Preprocessing)ML 알고리즘만큼 중요 합니다. ML 알고리즘은 데이터에 기반하고 있기 때문에 어떤 데이터를 입력으로 가지느냐에 따라 결과도 크게 달라질 수 있습니다(GarbageIn, Garbage Out).

사이킷런의 ML 알고리즘을 적용하기 전에 데이터에 대해 미리 처리해야 할 기본사항이 있습니다.

  • 결손값, 즉 NaN, Null 값은 허용되지 않습니다.
    • 그러므로 Null 값은 고정된 다른 값으로 변환해야 합니다.
  • 사이킷런의 머신러닝 알고리즘은 문자열 값을 입력값으로 허용하지 않습니다.
    • 그래므로 모든 문자열 값은 인코딩돼서 숫자 형으로 변환해야 합니다.


데이터 인코딩

머신러닝을 위한 대표적인 인코딩 방식은 레이블 인코딩(Label encoding)원 핫 인코딩(One Hotencoding) 이 있습니다.


레이블 인코딩(Label encoding)

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

사이킷런의 레이블 인코딩은 LabelEncoder 클래스로 구현합니다. LabelEncoder 를 객체로 생성한 후 fit()transform() 을 호출해 레이블 인코딩을 수행합니다.

from sklearn.preprocessing import LabelEncoder

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

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


TV는 0, 냉장고는 1, 전자레인지는 4, 컴퓨터는 5, 선풍기는 3, 믹서는 2로 변환됐습니다. 위 예제는 데이터가 작아서 문자열 값이 어떤 숫자 값으로 인코딩됐는지 LabelEncoder 객체의 classes_ 속성값으로 확인할 수 있습니다.

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


classes_ 속성은 0번부터 순서대로 변환된 인코딩 값에 대한 원본값을 가지고 있습니다. 따라서 TV가 0, 냉장고 1, 믹서 2, 선풍기 3, 전자레인지 4, 컴퓨터가 5로 인코딩됐음을 알 수 있습니다. inverse_transform() 을 통해 인코딩된 값을 다시 디코딩할 수 있습니다.

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


상품 데이터가 상품 분류, 가격 두 개의 속성으로 돼 있을 때 상품 분류를 레이블 인코딩하면 다음과 같이 변환될 수 있습니다.

image


레이블 인코딩은 간단하게 문자열 값을 숫자형 카테고리 값으로 변환합니다. 하지만 레이블 인코딩이 일괄적인 숫자 값으로 변환이 되면서 몇몇 ML 알고리즘에는 이를 적용할 경우 예측 성능이 떨어지는 경우가 발생할 수 있습니다. 이는 숫자 값의 경우 크고 작음에 대한 특성이 작용하기 때문 입니다.

즉, 냉장고가 1, 믹서가 2로 변환되면, 1보다 2가 더 큰 값이므로 특정 ML 알고리즘에서 가중치가 더 부여되거나 더 중요하게 인식할 가능성이 발생합니다. 하지만 냉장고와 믹서의 숫자 변환 값은 단순 코드이지 숫자 값에 따른 순서나 중요도로 인식돼서는 안 됩니다. 이러한 특성 때문에 레이블 인코딩은 선형 회귀와 같은 ML 알고리즘에는 적용하지 않아야 합니다. 트리 계열의 ML 알고리즘은 이러한 특성을 반영하지 않으므로 레이블 인코딩도 별문제가 없습니다.


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

원–핫 인코딩(One-Hot Encoding)레이블 인코딩의 문제점을 해결하기 위한 인코딩 방식 으로, 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시하는 방식 입니다.

image


즉, 행 형태로 돼 있는 피처의 고유 값을 열 형태로 차원을 변환한 뒤, 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시 하며, 이러한 특성으로 원-핫(여러 개의 속성 중 단 한 개의 속성만 1로표시) 인코딩으로 명명하게 됐습니다.

원-핫 인코딩은 사이킷런에서 OneHotEncoder 클래스 로 변환이 가능합니다. 약간 주의할 점으로, 입력값으로 2차원 데이터가 필요하다는 것 과, OneHotEncoder 를 이용해 변환한 값이 희소 행렬(Sparse Matrix) 형태이므로 이를 다시 toarray() 메서드를 이용해 밀집행렬(Dense Matrix)로 변환해야 한다는 것 입니다.

OneHotEncoder 를 이용해 앞의 데이터를 원-핫인코딩으로 변환해 보겠습니다.

from sklearn.preprocessing import OneHotEncoder
import numpy as np

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

# 2차원 ndarray로 변환합니다. 
items = np.array(items).reshape(-1, 1)

# 원-핫 인코딩을 적용합니다. 
oh_encoder = OneHotEncoder()
oh_encoder.fit(items)
oh_labels = oh_encoder.transform(items)

# OneHotEncoder로 변환한 결과는 희소행렬이므로 toarray()를 이용해 밀집 행렬로 변환. 
print('인코딩 클래스:', oh_encoder.categories_)
print('OneHotEncoder로 변환한 값: 희소 행렬(Sparse Matrix)')
print(oh_labels)
print('원-핫 인코딩 데이터 : 밀집행렬(Dense Matrix) 변환')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)
[output]
인코딩 클래스: [array(['TV', '냉장고', '믹서', '선풍기', '전자레인지', '컴퓨터'], dtype='<U5')]
OneHotEncoder로 변환한 값: 희소 행렬(Sparse Matrix)
  (0, 0)	1.0
  (1, 1)	1.0
  (2, 4)	1.0
  (3, 5)	1.0
  (4, 3)	1.0
  (5, 3)	1.0
  (6, 2)	1.0
  (7, 2)	1.0
원-핫 인코딩 데이터 : 밀집행렬(Dense Matrix) 변환
[[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)


위 예제 코드의 변환 절차는 다음 그림과 같이 정리할 수 있습니다.

image


판다스 에는 문자열 카테고리 값을 숫자 형으로 변환할 필요 없이 바로 변환할 수 있는, 원-핫 인코딩을 더 쉽게 지원하는 API가 있습니다. get_dummies() 를 이용하면 됩니다.

import pandas as pd

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

image


get_dummies() 를 이용하면 숫자형 값으로 변환 없이도 바로 변환이 가능함을 알 수 있습니다.


피처 스케일링과 정규화

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


표준화데이터의 피처 각각이 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환하는것을 의미 합니다. 표준화를 통해 변환될 피처 $x$ 의 새로운 $i$ 번째 데이터를 ${x_i}_new$ 라고 한다면 이 값은 원래 값에서 피처 $x$ 의 평균을 뺀 값을 피처 $x$ 의 표준편차로 나눈 값으로 계산할 수 있습니다.

[{x_i}_new=\frac{x_i-mean(x)}{stdev(x)}]


정규화서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념 입니다. 즉, 개별 데이터의 크기를 모두 똑같은 단위로 변경하는 것 입니다.

새로운 데이터 ${x_i}_new$ 는 원래 값에서 피처 $x$ 의 최솟값을 뺀 값을 피처 $x$ 의 최댓값과 최솟값의 차이로 나눈 값으로 변환할 수 있습니다.

[{x_i}_new=\frac{x_i-min(x)}{max(x)-min(x)}]


사이킷런의 Normalizer 모듈은 선형대수에서의 정규화 개념이 적용됐으며, 개별 벡터의 크기를 맞추기 위해 변환하는 것을 의미합니다. 즉, 개별 벡터를 모든 피처 벡터의 크기로 나눠 줍니다. 세 개의 피처 $x,\ y,\ z$ 가 있다고 하면 새로운 데이터 ${x_i}_new$ 는 원래 값에서 세 개의 피처의 $i$ 번째 피처 값에 해당하는 크기를 합한 값으로 나눠줍니다.

[{x_i}_new=\frac{x_i}{\sqrt{x_i^2+y_i^2+z_i^2}}]


StandardScaler

StandardScaler 는 앞에서 설명한 표준화를 쉽게 지원하기 위한 클래스 입니다. 즉, 개별 피처를 평균이 0이고, 분산이 1인 값으로 변환 해줍니다. 이렇게 가우시안 정규 분포를 가질 수 있도록 데이터를 변환하는 것은 몇몇 알고리즘(데이터가 가우시안 분포를 가지고 있다고 가정하고 구현)에서 매우 중요합니다.

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())
[output]
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()transform() 메서드에 변환 대상 피처 데이터 세트를 입력하고 호출하면 간단하게 변환됩니다.

from sklearn.preprocessing import StandardScaler

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

#transform( )시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 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())
[output]
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 값으로 변환합니다). 데이터의 분포가 가우시안 분포가 아닐 경우에 Min, Max Scale을 적용해 볼 수 있습니다. MinMaxScaler가 어떻게 동작하는지 확인해 보겠습니다.

from sklearn.preprocessing import MinMaxScaler

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

# transform()시 scale 변환된 데이터 셋이 numpy ndarry로 반환되어 이를 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())
[output]
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 사이의 값으로 변환되는 스케일링이 적용됐음을 알 수 있습니다.


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

StandardScalerMinMaxScaler 와 같은 Scaler 객체를 이용해 데이터의 스케일링 변환 시 일반적으로 fit() 은 데이터 변환을 위한 기준 정보 설정을 적용하며 transform() 은 설정된 정보를 이용해 데이터를 변환하며, fit_transform()fit()transform() 을 한 번에 적용하는 기능을 수행합니다.

그런데 학습 데이터 세트와 테스트 데이터 세트에 이 fit()transform() 을 적용할 때 주의가 필요한데, 학습 데이터로 fit() 이 적용된 스케일링 기준 정보를 그대로 테스트 데이터에 적용해야 하며, 그렇지 않고 테스트 데이터로 다시 새로운 스케일링 기준 정보를 만들게 되면 학습 데이터와 테스트 데이터의 스케일링 기준 정보가 서로 달라지기 때문에 올바른 예측 결과를 도출하지 못할 수 있습니다.

다음 코드를 통해서 테스트 데이터에 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)


학습 데이터인 train_array 부터 MinMaxScaler 를 이용해 변환하겠습니다.

# 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))
[output]
원본 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. ]


학습 데이터는 0부터 10까지 값을 가지는데, 이 데이터에 MinMaxScaler 객체의 fit() 을 적용하면 최솟값 0, 최댓값 10이 설정되며 1/10 Scale이 적용되며, transform() 을 호출하면 1/10 scale로 학습 데이터를 변환하게 되며 원본 데이터 1은 0.1로 2는 0.2, 그리고 5는 0.5, 10은 1로 변환됩니다.


이번에는 테스트 데이터 세트를 변환하는데, fit() 을 호출해 스케일링 기준 정보를 다시 적용한 뒤 transform() 을 수행한 결과를 확인해 보겠습니다.

# 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))
[output]
원본 test_array 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0.  0.2 0.4 0.6 0.8 1. ]


출력 결과를 확인하면 학습 데이터와 테스트 데이터의 스케일링이 맞지 않음을 알 수 있습니다. 테스트 데이터의 경우는 최솟값 0, 최댓값 5이므로 1/5로 스케일링됩니다. 따라서 원본값 1은 0.2로, 원본값 5는 1로 변환이 됩니다. 앞서 학습 데이터는 스케일링 변환으로 원본값 2가 0.2로 변환됐고, 원본값 10이 1로 변환됐습니다. 이렇게 되면 학습 데이터와 테스트 데이터의 서로 다른 원본값이 동일한 값으로 변환되는 결과를 초래 합니다.

머신러닝 모델은 학습 데이터를 기반으로 학습되기 때문에 반드시 테스트 데이터는 학습 데이터의 스케일링 기준에 따라 변환돼야 합니다. 따라서 테스트 데이터에 다시 fit() 을 적용해서는 안 되며 학습 데이터로 이미 fit() 이 적용된 Scaler 객체를 이용해 transform() 으로 변환해야 합니다.


다음 코드는 테스트 데이터에 fit() 을 호출하지 않고 학습 데이터로 fit() 을 수행한 MinMaxScaler 객체의 transform() 을 이용해 데이터를 변환합니다. 출력 결과를 확인해 보면 학습 데이터, 테스트 데이터 모두 동일하게 변환됐음을 확인할 수 있습니다.

scaler = MinMaxScaler()
scaler.fit(train_array)
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))

# test_array에 Scale 변환을 할 때는 반드시 fit()을 호출하지 않고 transform() 만으로 변환해야 함. 
test_scaled = scaler.transform(test_array)
print('\n원본 test_array 데이터:', np.round(test_array.reshape(-1), 2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1), 2))
[output]
원본 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 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0.  0.1 0.2 0.3 0.4 0.5]


fit_transform() 을 적용할 때도 마찬가지입니다. fit_transform()fit()transform() 을 순차적으로 수행하는 메서드로, 학습 데이터에서는 상관없지만 테스트 데이터에서는 절대 사용해서는 안 됩니다. 학습과 테스트 데이터 세트로 분리하기 전에 먼저 전체 데이터 세트에 스케일링을 적용한 뒤 학습과 테스트 데이터 세트로 분리하는 것이 더 바람직 합니다.

학습 데이터와 테스트 데이터의 fit(), transform(), fit_transform() 을 이용해 스케일링 변환 시 유의할 점을 요약하면 다음과 같습니다.

  1. 가능하다면 전체 데이터의 스케일링 변환을 적용한 뒤 학습과 테스트 데이터로 분리
  2. 1이 여의치 않다면 테스트 데이터 변환 시에는 fit() 이나 fit_transform() 을 적용하지 않고 학습 데이터로 이미 fit()Scaler 객체를 이용해 transform() 으로 변환

이 유의 사항은 차원 축소 변환이나 텍스트의 피처 벡터화 변환 작업 시에도 동일하게 적용됩니다.

Read more

Scikit-Learn Model Selection 모듈

|

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


학습/테스트 데이터 세트 분리 - 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))
[output]
예측 정확도: 1.0

예측 결과가 100% 인 이유는 이미 학습한 학습 데이터 세트를 기반으로 예측했기 때문입니다. 즉, 모의고사를 이미 한 번 보고 답을 알고 있는 상태에서 모의고사 문제와 똑같은 본고사 문제가 출제됐기 때문 입니다. 따라서 예측을 수행하는 데이터 세트는 학습을 수행한 학습용 데이터 세트가 아닌 전용의 테스트 데이터 세트여야 합니다.

사이킷런의 train_test_split() 를 통해 원본 데이터 세트에서 학습 및 테스트 데이터 세트를 쉽게 분리할 수 있습니다. sklearn.model_selection 모듈에서 train_test_split 을 로드합니다.

  • train_test_split()
    • X: 피처 데이터 세트
    • y: 레이블 데이터 세트
    • test_size: 전체 데이터에서 테스트 데이터 세트 크기를 얼마나 샘플링할 것인가를 결정.
    • train_size: 전체 데이터에서 학습용 데이터 세트 크기를 얼마나 샘플링할 것인가를 결정.
    • shuffle: 데이터를 분리하기 전에 데이터를 미리 섞을지를 결정.
    • random_state: 호출할 때마다 동일한 학습/테스트용 데이터 세트를 생성하기 위한 난수 값.
    • 반환값: 튜플 형태. 순차적으로 학습용 피처 데이터 세트, 테스트용 피처 데이터 세트, 학습용 레이블 데이터 세트, 테스트용 레이블 데이터 세트 가 반환.

붓꽃 데이터 세트를 train_test_split() 를 이용해 테스트 데이터 세트를 전체의 30%로, 학습 데이터 세트를 70%로 분리하겠습니다.

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

dt_clf = DecisionTreeClassifier()
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(
    iris_data.data,
    iris_data.target,
    test_size=0.3,
    random_state=121
)


학습 데이터를 기반으로 DecisionTreeClassifier 를 학습하고 해당 모델을 이용해 예측 정확도를 측정해보겠습니다.

dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
[output]
예측 정확도: 0.9556


붓꽃 데이터는 데이터 양이 크지 않으므로(30% 정도인 테스트 데이터는 45개) 이를 통해 알고리즘의 예측 성능을 판단하기에는 그리 적절하지 않습니다.

학습을 위한 데이터의 양을 일정 수준이상으로 보장하는 것도 중요하지만, 학습된 모델에 대해 다양한 데이터를 기반으로 예측 성능을 평가해보는 것도 매우 중요 합니다.


교차 검증

과적합(Overfitting)모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우에는 예측 성능이 과도하게 떨어지는 것 을 말합니다. 하지만 고정된 학습 데이터와 테스트 데이터로 평가를 하다 보면 테스트 데이터에만 최적의 성능을 발휘할 수 있도록 편향되게 모델을 유도하는 경향 이 생기게 됩니다.

결국 테스트 데이터에만 과적합 되는 학습 모델이 만들어져 다른 테스트용 데이터가 들어올 경우에는 성능이 저하 됩니다.


ML은 데이터에 기반하며, 데이터는 이상치, 분포도, 다양한 속성값, 피처 중요도 등 여러 가지 ML에 영향을 미치는 요소를 가지고 있습니다. 특정 ML 알고리즘에서 최적으로 동작할 수 있도록 데이터를 선별해 학습한다면 실제 데이터와는 많은 차이가 있을 것이고 결국 성능 저하로 이어질 것입니다.


이러한 문제점을 개선하기 위해 교차 검증을 수행합니다. 교차 검증데이터 편중을 막기 위해서 별도의 여러 세트로 구성된 학습 데이터 세트와 검증 데이터 세트에서 학습과 평가를 수행하는 것 입니다. 그리고 각 세트에서 수행한 평가 결과에 따라 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있습니다.

대부분의 ML 모델의 성능 평가는 교차 검증 기반으로 1차 평가를 한 뒤에 최종적으로 테스트 데이터 세트에 적용해 평가하는 프로세스입니다. 테스트 데이터 세트 외에 별도의 검증 데이터 세트를 둬서 최종 평가 이전에 학습된 모델을 다양하게 평가하는 데 사용합니다.

image


K 폴드 교차 검증

K 폴드 교차 검증 은 가장 보편적으로 사용되는 교차 검증 기법으로, K개의 데이터 폴드 세트를 만들어서(K등분) K번만큼 각 폴트 세트에 학습과 검증 평가를 반복적으로 수행하는 방법 입니다.

다음 그림은 5 폴드 교차 검증을 수행합니다(즉, K가 5). 5개의 폴드된 데이터 세트를 학습과 검증을 위한 데이터 세트로 변경하면서 5번 평가를 수행한 뒤, 이 5개의 평가를 평균한 결과를 가지고 예측 성능을 평가 합니다.

image


이렇게 학습 데이터 세트와 검증 데이터 세트를 점진적으로 변경하면서 마지막 5번째(K번째)까지 학습과 검증을 수행하는 것 이 바로 K 폴드 교차 검증 입니다. 5개(K개)의 예측 평가를 구했으면 이를 평균해서 K 폴드 평가 결과로 반영 하면 됩니다.

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


먼저 KFold 클래스를 이용해 5개의 폴드 세트로 분리하는 KFold 객체를 생성하고, 붓꽃 데이터 세트를 교차 검증하고 예측 정확도를 구하겠습니다.

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

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])
[output]
붓꽃 데이터 세트 크기: 150

생성된 KFold 객체의 split() 을 호출해 전체 붓꽃 데이터를 5개의 폴드 데이터 세트로 분리합니다. 전체 붓꽃 데이터는 모두 150개이므로, 학습용 데이터 세트는 4/5인 120개, 검증 테스트 데이터 세트는 1/5인 30개로 분할됩니다.

KFold 객체는 split() 을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환 합니다. 실제로 학습용/검증용 데이터 추출은 반환된 인덱스를 기반으로 개발 코드에서 직접 수행해야 합니다. 교차 검증 수행을 통해 학습과 검증을 반복해 예측 정확도를 측정해보겠습니다.

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]
    cv_accuracy.append(accuracy)
    print('#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'.format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}\n'.format(n_iter, test_index))
    
# 개별 iteration별 정확도를 합하여 평균 정확도 계산 
print('### 평균 검증 정확도:', np.mean(cv_accuracy)) 
[output]
#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


Stratified K 폴드

Stratified K 폴드불균형한(imbalanced) 분포도를 가진 레이블(결정 클래스) 데이터 집합을 위한 K 폴드 방식 입니다.

불균형한 분포도 를 가진 레이블 데이터 집합은 특정 레이블 값이 특이하게 많거나 매우 적어서 값의 분포가 한쪽으로 치우치는 것 을 말합니다.


가령 대출 사기 데이터를 예측한다고 가정해 보겠습니다. 이 데이터 세트(대출 사기: 1, 정상 대출: 0)는 1억 건이고, 대출 사기가 약 1000건이 있다고 한다면 전체의 0.0001%의 아주 작은 확률로 대출 사기 레이블이 존재합니다.

이렇게 작은 비율로 1 레이블 값이 있다면 K 폴드로 랜덤하게 학습 및 테스트 세트의 인덱스를 고르더라도 레이블 값인 0과 1의 비율을 제대로 반영하지 못하는 경우가 쉽게 발생 합니다.

대출 사기 레이블이 1인 레코드는 비록 건수는 작지만 알고리즘이 대출 사기를 예측하기 위한 중요한 피처 값을 가지고 있기 때문에 매우 중요한 데이터 세트입니다. 따라서 원본 데이터와 유사한 대출 사기 레이블 값의 분포를 학습/테스트 세트에도 유지하는 게 매우 중요합니다.


Stratified K 폴드원본 데이터의 레이블 분포를 먼저 고려한 뒤 이 분포와 동일하게 학습과 검증 데이터 세트를 분배 하여, K 폴드가 원본 데이터 집합의 레이블 분포를 학습 및 테스트 세트에 제대로 분배하지 못하는 경우의 문제를 해결합니다.

먼저 K 폴드의 문제점을 확인해 보고 이를 사이킷런의 StratifiedKFold 클래스 를 이용해 개선해 보겠습니다.

import pandas as pd

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


레이블 값은 0(Setosa), 1(Versicolor), 2(Virginica) 값 모두 50개로 동일합니다. 이슈가 발생하는 현상을 도출하기 위해 3개의 폴드 세트를 KFold로 생성하고, 각 교차 검증 시마다 생성되는 학습/검증 레이블 데이터 값의 분포도를 확인해 보겠습니다.

kfold = KFold(n_splits=3)
# kfold.split(X)는 폴드 세트를 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('\n## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n{0}'.format(label_train.value_counts()))
    print('검증 레이블 데이터 분포:\n{0}'.format(label_test.value_counts()))
[output]
## 교차 검증: 1
학습 레이블 데이터 분포:
1    50
2    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
0    50
Name: label, dtype: int64

## 교차 검증: 2
학습 레이블 데이터 분포:
0    50
2    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
1    50
Name: label, dtype: int64

## 교차 검증: 3
학습 레이블 데이터 분포:
0    50
1    50
Name: label, dtype: int64
검증 레이블 데이터 분포:
2    50
Name: label, dtype: int64


교차 검증 시마다 3개의 폴드 세트로 만들어지는 학습 레이블과 검증 레이블이 완전히 다른 값으로 추출되었습니다. 예를 들어 첫 번째 교차 검증에서는 학습 레이블의 1, 2 값이 각각 50개가 추출되었고, 검증 레이블의 0값이 50개 추출되었습니다. 학습 레이블은 1, 2밖에 없으므로 0의 경우는 전혀 학습하지 못합니다. 반대로 검증 레이블은 0밖에 없으므로 학습 모델은 절대 0을 예측하지 못합니다. 이런 유형으로 교차 검증 데이터 세트를 분할하면 검증 예측 정확도는 0이 될 수밖에 없습니다.

StratifiedKFold 는 이렇게 KFold로 분할된 레이블 데이터 세트가 전체 레이블 값의 분포도를 반영하지 못하는 문제를 해결해 줍니다. 이번에는 동일한 데이터 분할을 StratifiedKFold 로 수행하고 학습/검증 레이블 데이터의 분포도를 확인해 보겠습니다.StratifiedKFold는 레이블 데이터 분포도에 따라 학습/검증 데이터를 나누기 때문에 split() 메서드에 인자로 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요하

from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=3)
n_iter = 0

# StratifiedKFold는 레이블 데이터 분포도에 따라 학습/검증 데이터를 나누기 때문에,
# split() 메서드에 인자로 피처 데이터 세트뿐만 아니라 레이블 데이터 세트도 반드시 필요.
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('\n## 교차 검증: {0}'.format(n_iter))
    print('학습 레이블 데이터 분포:\n{0}'.format(label_train.value_counts()))
    print('검증 레이블 데이터 분포:\n{0}'.format(label_test.value_counts()))
## 교차 검증: 1
학습 레이블 데이터 분포:
2    34
0    33
1    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
0    17
1    17
2    16
Name: label, dtype: int64

## 교차 검증: 2
학습 레이블 데이터 분포:
1    34
0    33
2    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
0    17
2    17
1    16
Name: label, dtype: int64

## 교차 검증: 3
학습 레이블 데이터 분포:
0    34
1    33
2    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
1    17
2    17
0    16
Name: label, dtype: int64


출력 결과를 보면 학습 레이블과 검증 레이블 데이터 값의 분포도가 거의 동일하게 할당됐음을 알 수 있습니다. 이렇게 분할이 되어야 레이블 값 0, 1, 2 를 모두 학습할 수 있고, 이에 기반해 검증을 수행할 수 있습니다. StratifiedKFold를 이용해 붓꽃 데이터를 교차 검증해 보겠습니다.

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

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

dt_clf = DecisionTreeClassifier(random_state=156)
skfold = StratifiedKFold(n_splits=3)
n_iter=0
cv_accuracy=[]

# StratifiedKFold의 split() 호출시 반드시 레이블 데이터 셋도 추가 입력 필요  
for train_index, test_index in skfold.split(features, 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]
    cv_accuracy.append(accuracy)
    print('#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'.format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}\n'.format(n_iter, test_index))
    
# 교차 검증별 정확도 및 평균 정확도 계산 
print('### 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('### 평균 검증 정확도:', np.round(np.mean(cv_accuracy), 4))
[output]
#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.9667


Stratified K 폴드의 경우 원본 데이터의 레이블 분포도 특성을 반영한 학습 및 검증 데이터 세트를 만들 수 있으므로 왜곡된 레이블 데이터 세트에서는 반드시 Stratified K 폴드를 이용해 교차 검증해야 합니다.

일반적으로 분류(Classification) 에서의 교차 검증은 Stratified K 폴드로 분할돼야 합니다. 회귀(Regression) 에서는 Stratified K 폴드가 지원되지 않습니다. 이유는 간단합니다. 회귀의 결정값은 이산값 형태의 레이블이 아니라 연속된 숫자값이기 때문에 결정값별로 분포를 정하는 의미가 없기 때문 입니다.


cross_val_score() - 교차 검증을 보다 간편하게

사이킷런은 교차 검증을 좀 더 편리하게 수행할 수 있게 해주는 API를 제공합니다. 대표적으로 cross_val_score() 입니다. classifier 가 입력되면 Stratified K 폴드 방식으로 레이블값의 분포에 따라 학습/테스트 세트를 분할하며(회귀는 K 폴드 방식으로 분할), 수행 후 반환 값은 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환 합니다.

  • cross_val_score()
    • estimator: 알고리즘 클래스
    • X: 피처 데이터 세트
    • y: 레이블 데이터 세트
    • scoring: 예측 성능 평가 지표
    • cv: 교차 검증 폴드 수

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

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

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))
[output]
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9667

cross_val_score() 는 cv로 지정된 횟수만큼 scoring 파라미터로 지정된 평가 지표로 평가 결값을 배열로 반환 합니다. 해당 API는 내부에서 Estimator 를 학습(fit), 예측(predict), 평가(evaluation)시켜주므로 간단하게 교차검증을 수행할 수 있습니다.

또한 내부적으로 StratifiedKFold를 이용하기 때문에 StratifiedKFold의 수행 결과와 비교했을때 각 교차 검증별 정확도와 평균 검증 정확도가 모두 동일함을 알 수 있습니다.


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


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

하이퍼 파라미터머신러닝 알고리즘을 구성하는 주요 구성 요소이며, 이 값을 조정해 알고리즘의 예측성능을 개선할 수 있습니다. 사이킷런은 GridSearchCV API 를 이용해 하이퍼 파라미터를 순차적으로 입력하면서, 교차 검증을 기반으로 편리하게 최적의 하이퍼 파라미터의 최적 값을 찾게 해줍니다.

예를 들어 결정 트리 알고리즘의 여러 하이퍼 파라미터를 순차적으로 변경하면서 최고 성능을 가지는 파라미터 조합을 찾고자 한다면 다음과 같이 파라미터의 집합을 만들고 이를 순차적으로 적용하면서 최적화를 수행할 수 있습니다.

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


하이퍼 파라미터는 다음과 같이 순차적으로 적용되며, 총 6회에 걸쳐 파라미터를 순차적으로 바꿔 실행하면서 최적의 파라미터와 수행 결과를 도출할 수 있습니다.

순번 max_depth min_samples_split
1 1 2
2 1 3
3 2 2
4 2 3
5 3 2
6 3 3


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

위의 경우 순차적으로 6회에 걸쳐 하이퍼 파라미터를 변경하면서 교차 검증 데이터 세트에 수행 성능을 측정합니다. CV가 3회라면 개별 파라미터 조합마다 3개의 폴딩 세트를 3회에 걸쳐 학습/평가해 평균값으로 성능을 측정합니다. 6개의 파라미터 조합이라면 총 CV 3회 X 6개 파라미터 조합 = 18회의 학습/평가가 이뤄집니다.


GridSearchCV 는 사용자가 튜닝하고자 하는 여러 종류의 하이퍼 파라미터를 다양하게 테스트하면서 최적의 파라미터를 편리하게 찾게 해주지만 동시에 순차적으로 파라미터를 테스트하므로 수행시간이 상대적으로 오래 걸리는 것에 유념해야 합니다.

  • GridSearchCV()
    • estimator: classifier, regressor, pipeline이 사용될 수 있음.
    • param_grid: key+리스트 값을 가지는 딕셔너리를 입력. estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정.
    • scoring: 예측 성능을 측정할 평가 방법을 지정.
    • cv: 교차 검증을 위해 분할되는 학습/테스트 세트의 개수를 지정.
    • refit: True로 생성 시 가장 최적의 하이퍼 파라미터를 찾은 뒤, 입력된 estimator 객체를 해당 하이퍼파라미터로 재학습.


결정 트리 알고리즘의 여러 가지 최적화 파라미터를 순차적으로 적용해 붓꽃 데이터를 예측 분석을 위해 GridSearchCV를 이용하겠습니다. 테스트할 하이퍼 파라미터 세트는 딕셔너리 형태하이퍼 파라미터의 명칭은 문자열 Key 값 으로, 하이퍼 파라미터의 값은 리스트 로 설정합니다.

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.data,
    iris_data.target,
    test_size=0.2,
    random_state=121
)
dtree = DecisionTreeClassifier()

# parameter 들을 dictionary 형태로 설정
parameters = {
    'max_depth': [1, 2, 3],
    'min_samples_split': [2, 3]
}


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

cv_results_gridsearchcv 의 결과 세트로서 딕셔너리 형태로 key 값과 리스트 형태의 value 값을 가집니다. cv_results_ 를 DataFrame으로 변환하면 내용을 좀 더 쉽게 볼 수 있으며, 이 중 주요 칼럼만 발췌해서 어떻게 GridSearchCV 가 동작하는지 좀 더 자세히 알아보겠습니다.

import pandas as pd

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

# 붓꽃 Train 데이터로 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']]

image


위의 결과에서 총 6개의 결과를 볼 수 있으며, 이는 하이퍼 파라미터를 순차적으로 총 6번 변경하면서 학습 및 평가를 수행했음을 나타냅니다.

  • params: 수행할 때마다 적용된 개별 하이퍼 파라미터값
  • rank_test_socre: 하이퍼 파라미터별로 성능이 좋은 score 순위. 1이 가장 뛰어난 순위이며 이때의 파라미터가 최적의 하이퍼 파라미터.
  • mean_test_score: 개별 하이퍼 파라미터별로 CV의 폴딩 테스트 세트에 대해 총 수행한 평가 평균값.


GridSearchCV 객체의 fit() 을 수행하면 최고 성능을 나타낸 하이퍼 파라미터의 값과 그때의 평가 결과 값이 각각 best_params_, best_score_ 속성에 기록됩니다. 이 속성을 이용해 최적 하이퍼 파라미터의 값과 그때의 정확도를 알아보겠습니다.

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


max_depth 가 3, min_samples_split 가 2일 때 검증용 폴드 세트에서 평균 최고 정확도가 97.50%로 측정됐습니다. GridSearchCV 객체의 생성 파라미터로 refit=True 가 디폴트입니다. refit=True 이면 GridSearchCV 가 최적 성능을 나타내는 하이퍼 파라미터로 Estimator 를 학습해 best_estimator_ 로 저장합니다.

이미 학습된 best_estimator_ 를 이용해 앞에서 train_test_split() 으로 분리한 테스트 데이터 세트에 대해 예측하고 성능을 평가해 보겠습니다.

# 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)))
[output]
테스트 데이터 세트 정확도: 0.9667


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

Read more

Scikit-Learn 기반 프레임워크

|

Estimator 이해 및 fit(), predict() 메서드

사이킷런은 API 일관성과 개발 편의성을 제공하기 위한 노력이 엿보이는 패키지입니다. 다양한 알고리즘을 구현한 모든 사이킷런 클래스는 ML 모델 학습을 위해서 fit() 을, 학습된 모델의 예측을 위해 predict() 메서드를 이용하여 간단하게 학습과 예측 결과를 반환합니다.

사이킷런은 Estimator라고 하는 수십가지 머신러닝 알고리즘과 모델을 제공합니다. 데이터셋을 기반으로 일련의 모델 파라미터들을 추정하는 객체를 Estimator라고 합니다. 당연히 Estimator 클래스는 fit()predict() 를 내부에서 구현하고 있습니다.


image


사이킷런의 주요 모듈

다음은 사이킷런의 주요 모듈을 요약한 것입니다. 많은 모듈이 있으나 자주 쓰이는 핵심 모듈 위주로 정리한 것입니다.

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

일반적으로 머신러닝 모델을 구축하는 주요 프로세스피처의 가공, 변경, 추출을 수행하는 피처 처리(feature processing), ML 알고리즘 학습/예측 수행, 그리고 모델 평가의 단계를 반복적으로 수행하는 것 입니다. 사이킷런 패키지는 머신러닝 모델을 구축하는 주요 프로세스를 지원하기 위해 매우 편리하고 다양하며 유연한 모듈을 지원합니다. 이러한 편리성, 다양성, 유연성이 바로 많은 ML 개발자가 사이킷런 파이썬 기반의 ML 개발 프레임워크로 선택하게 된 이유일 것입니다.


내장된 예제 데이터 세트

사이킷런에는 예제로 활용할 수 있는 간단하면서도 좋은 데이터 세트가 내장돼 있습니다. 이 데이터는 datasets 모듈에 있는 여러 API를 호출하여 사용할 수 있습니다. 사이킷런에 내장 되어 있는 데이터 세트는 분류나 회귀를 연습하기 위한 예제용도의 데이터 세트 와 분류나 클러스터링을 위해 표본 데이터로 생성될 수 있는 데이터 세트 로 나뉘어집니다.


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

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_1fw_pairs(): 얼굴 이미지 자료
  • fetch_rcv1(): 로이터 뉴스 말뭉치
  • fetch_mldata(): ML 웹사이트에서 다운로드


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

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

표본 데이터 생성기는 이 밖에도 많으며, 위의 2개 정도로도 여러 가지 사례에 사용할 수 있습니다.


분류나 회귀를 위한 연습용 예제 데이터가 어떻게 구성돼 있는지 좀 더 살펴보겠습니다. 사이킷런 내장된 이 데이터 세트는 일반적으로 딕셔너리 형태로 돼 있습니다. 키는 보통 data, target, target_name, feature_names, DESCR 로 구성돼 있습니다. 개별 키가 가리키는 데이터 세트의 의미는 다음과 같습니다.

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

data, target 은 넘파이 배열(ndarray) 타입이며, target_names, feature_names 는 넘파이 배열 또는 파이썬 리스트(list) 타입니다. DESCR 은 스트링 타입입니다. 먼저 붓꽃 데이터 세트를 생성해 보겠습니다.

from sklearn.datasets import load_iris

iris_data = load_iris()
print(type(iris_data))
[output]
<class 'sklearn.utils._bunch.Bunch'>


load_iris() API의 반환 결과는 sklearn.utils.Bunch 클래스입니다. Bunch 클래스는 파이썬 딕셔너리 자료형과 유사합니다. 데이터 세트에 내장돼 있는 대부분의 데이터 세트는 이와 같이 딕셔너리 형태의 값을 반환합니다. 딕셔너리 형태이므로 load_iris() 데이터 세트의 key값 을 확인해 보겠습니다.

keys = iris_data.keys()
print('붓꽃 데이터 세트의 키들:', keys)
[output]
붓꽃 데이터 세트의 키들: dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename', 'data_module'])


다음 그림에서 load_iris() 가 반환하는 붓꽃 데이터 세트의 각 키가 의미하는 값을 표시했습니다.

image


load_iris() 가 반환하는 객체의 키들이 가리키는 값을 다음 예제 코드에 출력했습니다.

print('feature_names 의 type:', type(iris_data.feature_names))
print('feature_names 의 shape:', len(iris_data.feature_names))
print(iris_data.feature_names)

print('\ntarget_names 의 type:', type(iris_data.target_names))
print('feature_names 의 shape:', len(iris_data.target_names))
print(iris_data.target_names)

print('\ndata 의 type:', type(iris_data.data))
print('data 의 shape:', iris_data.data.shape)
print(iris_data['data'])

print('\ntarget 의 type:', type(iris_data.target))
print('target 의 shape:', iris_data.target.shape)
print(iris_data.target)
[output]
feature_names 의 type: <class 'list'>
feature_names 의 shape: 4
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

target_names 의 type: <class 'numpy.ndarray'>
feature_names 의 shape: 3
['setosa' 'versicolor' 'virginica']

data 의 type: <class 'numpy.ndarray'>
data 의 shape: (150, 4)
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 ...
 [6.2 3.4 5.4 2.3]
 [5.9 3.  5.1 1.8]]

target 의 type: <class 'numpy.ndarray'>
target 의 shape: (150,)
[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]

Read more

Scikit-Learn을 이용한 붓꽃 품종 예측

|

사이킷런을 통해 붓꽃 데이터 세트로 붓꽃의 품종을 분류(Classification) 하는 간단한 머신러닝 모델을 만들어 보겠습니다. 붓꽃 데이터 세트는 꽃잎의 길이너비, 꽃받침의 길이너비 피처(Feature)를 기반 으로 꽃의 품종을 예측 하기 위한 것입니다.

image


분류(Classification) 는 대표적인 지도학습(Supervised Learning) 방법의 하나로, 지도학습은 학습을 위한 다양한 피처와 분류 결정값인 레이블(Label) 데이터로 모델을 학습한 뒤, 별도의 테스트 데이터 세트에서 미지의 레이블을 예측합니다. 즉 지도학습은 명확한 정답이 주어진 데이터를 먼저 학습한 뒤 미지의 정답을 예측하는 방식 입니다.


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

from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split


load_iris() 함수를 이용해 붓꽃 데이터 세트를 로딩한 후, 피처들과 데이터 값이 어떻게 구성돼 있는지 확인하기 위해 DataFrame으로 변환하겠습니다.

import pandas as pd

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

iris_data = iris.data # 피처(feature)
iris_label = iris.target # 레이블(결정 값)
print('iris target값:', iris_label)
print('iris target명:', iris.target_names)

# 붓꽃 데이터 세트를 자세히 보기 위해 DataFrame으로 변환
iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names)
iris_df['label'] = iris.target
iris_df.head(3)
[output]
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]
iris target명: ['setosa' 'versicolor' 'virginica']

image


학습용 데이터와 테스트용 데이터를 분리하겠습니다. 학습용 데이터와 테스트용 데이터는 반드시 분리해야 합니다. 학습 데이터로 학습된 모델이 얼마나 뛰어난 성능을 가지는지 평가하려면 테스트 데이터 세트가 필요하기 때문 입니다. 이를 위해 사이킷런은 train_test_split() API를 제공합니다.

  • train_test_split()
    • iris_data : 피처(Feature) 데이터 세트
    • iris_label : 레이블(Label) 데이터 세트
    • test_size : 전체 데이터 세트 중 테스트 데이터 세트의 비율
    • random_state : 호출할 때마다 같은 학습/테스트용 데이터 세트를 생성하기 위한 난수 발생 값
X_train, X_test, y_train, y_test = train_test_split(
    iris_data,
    iris_label,
    test_size=0.2,
    random_state=11
)


train_test_split() 은 학습용 피처 데이터 세트를 X_train 으로, 테스트용 피처 데이터 세트를 X_test 로, 학습용 레이블 데이터 세트를 y_train 으로, 테스트용 레이블 데이터 세트를 y_test 로 반환합니다.

이제 데이터를 기반으로 머신러닝 분류 알고리즘의 하나인 의사 결정 트리를 이용해 학습과 예측을 수행해 보겠습니다. 먼저 사이킷런의 의사 결정 트리 클래스인 DecisionTreeClassifier 를 객체로 생성합니다. 생성된 DecisionTreeClassifier 객체의 fit() 메서드에 학습용 피처 데이터 속성과 결정값 데이터 세트를 입력해 호출하면 학습을 수행 합니다.

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

# 학습 수행
dt_clf.fit(X_train, y_train)


학습 데이터를 기반으로 학습이 완료됐습니다. 이렇게 학습된 DecisionTreeClassifier 객체를 이용해 예측을 수행 하겠습니다. 예측은 반드시 학습 데이터가 아닌 다른 데이터를 이용해야 하며, 일반적으로 테스트 데이터 세트를 이용합니다.

DecisionTreeClassifier 객체의 predict() 메서드에 테스트용 피처 데이터 세트를 입력해 호출하면 학습된 모델 기반에서 테스트 데이터 세트에 대한 예측값을 반환하게 됩니다.

# 학습이 완료된 DecisionTreeClassifier 객체에서 테스트 데이터 세트로 예측 수행. 
pred = dt_clf.predict(X_test)


예측 결과를 기반으로 의사 결정 트리 기반의 DecisionTreeClassifier예측 성능을 평가 하여, 예측한 붓꽃 품종과 실제 테스트 데이터 세트의 붓꽃 품종이 얼마나 일치하는지 확인해 보겠습니다. 사이킷런은 정확도 측정을 위해 accuracy_score() 함수를 제공하며, 첫 번째 파라미터로 실제 레이블 데이터 세트, 두 번째 파라미터로 예측 레이블 데이터 세트 를 입력하면 됩니다.

from sklearn.metrics import accuracy_score
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
[output]
예측 정확도: 0.9333


붓꽃 데이터 세트로 분류를 예측한 프로세스

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


image

Read more