2-5. 타이타닉 컴피티션 실습
In [1]:
# 라이브러리 불러오기
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

import warnings
warnings.filterwarnings('ignore')
In [2]:
# 훈련 데이터셋 확인
train = pd.read_csv('Titanic/input/train.csv')
train.head(3)
Out[2]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
In [3]:
print('\n ### 훈련 데이터셋 정보 ### \n')
print(train.info())
 ### 훈련 데이터셋 정보 ### 

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
PassengerId    891 non-null int64
Survived       891 non-null int64
Pclass         891 non-null int64
Name           891 non-null object
Sex            891 non-null object
Age            714 non-null float64
SibSp          891 non-null int64
Parch          891 non-null int64
Ticket         891 non-null object
Fare           891 non-null float64
Cabin          204 non-null object
Embarked       889 non-null object
dtypes: float64(2), int64(5), object(5)
memory usage: 83.6+ KB
None

Age, Cabin, Embarked 컬럼에서 Null 값이 있는데 머신러닝 모델에는 Null값을 허용하지 않기 때문에 이를 어떻게 처리할지 결정해야함

In [4]:
### Age는 평균값으로 대체
train['Age'].fillna(train['Age'].mean(), inplace = True)

### 나머지는 'N'으로 대체
train['Cabin'].fillna('N', inplace=True)
train['Embarked'].fillna('N', inplace=True)
print('데이터 Null 값 개수: ', train.isnull().sum().sum())
데이터 Null 값 개수:  0

Sex, Cabin, Embarked --> 세 가지 문자열 Feature에 대해 값과 데이터 수 살펴보기

In [5]:
print('Sex 값 분포 :\n', train['Sex'].value_counts())
print('\nCabin 값 분포: \n', train['Cabin'].value_counts())
print('\nEmbarked 값 분포: \n', train['Embarked'].value_counts())
Sex 값 분포 :
 male      577
female    314
Name: Sex, dtype: int64

Cabin 값 분포: 
 N                  687
G6                   4
C23 C25 C27          4
B96 B98              4
C22 C26              3
F2                   3
E101                 3
D                    3
F33                  3
B49                  2
D26                  2
B57 B59 B63 B66      2
E44                  2
B58 B60              2
D33                  2
F G73                2
E8                   2
C93                  2
D35                  2
B18                  2
B5                   2
E25                  2
D17                  2
B20                  2
C65                  2
E24                  2
D36                  2
C124                 2
C92                  2
B35                  2
                  ... 
E68                  1
B42                  1
A5                   1
D19                  1
E17                  1
B19                  1
B101                 1
E77                  1
B102                 1
D47                  1
C85                  1
E49                  1
C106                 1
C30                  1
C103                 1
C110                 1
E63                  1
A26                  1
C45                  1
A10                  1
B38                  1
C148                 1
C86                  1
B41                  1
E36                  1
C47                  1
F38                  1
C70                  1
A7                   1
C46                  1
Name: Cabin, Length: 148, dtype: int64

Embarked 값 분포: 
 S    644
C    168
Q     77
N      2
Name: Embarked, dtype: int64

Sex와 Embarked 값은 크게 문제가 없으나, Cabin의 경우 N이 687건으로 가장 많음

Cabin은 첫 번째 글자가 좌석의 등급 등과 연관이 있을 수도 있으므로 첫 글자만 추출

In [6]:
train['Cabin'] = train['Cabin'].str[:1]
print(train['Cabin'].head(3))
0    N
1    C
2    N
Name: Cabin, dtype: object

이번에는 성별에 따른 생존자 수를 비교

In [7]:
train.groupby(['Sex','Survived'])['Survived'].count().to_frame()
Out[7]:
Survived
Sex Survived
female 0 81
1 233
male 0 468
1 109

Survived는 예측의 대상이 되는 타겟 컬럼으로 0이면 사망, 1이면 생존

여자는 81/233 = 74.2% 가량 생존했고, 남자는 109/577 = 18.8% 생존

In [8]:
sns.barplot(x='Sex', y='Survived', data=train)
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1c4f8358>

부자와 가난한 사람의 생존률 차이는 어떤지 살펴보기 --> Pclass+Sex

In [9]:
sns.barplot(x='Pclass', y='Survived', hue='Sex', data=train)
Out[9]:
<matplotlib.axes._subplots.AxesSubplot at 0x1a1c9774a8>

여성의 경우 1, 2등급 모두 생존률이 높고 3등급으로 가면 생존률이 많이 떨어짐 남성의 경우도 등급이 좋을 수록 생존률이 높으나, 남성 1등급의 생존률이 여성 3등급의 생존률보다 낮음

이번에는 Age에 따른 생존 확률

In [10]:
# Age 값에 따라 연령층을 분류하는 함수 정의. 이후 apply lambda에 이용

def get_category(age):
    cat = ''
    if age <= -1 : cat = 'Unknown'
    elif age <= 5 : cat = 'Baby'
    elif age <= 12 : cat = 'Child'
    elif age <= 18 : cat = 'Teenager'
    elif age <= 25 : cat = 'Student'
    elif age <= 35 : cat = 'Young Adult'
    elif age <= 60: cat = 'Adult'
    else : cat = 'Elderly'
    
    return cat

# barplot 크게 설정
plt.figure(figsize=(12, 6))

# X축의 값을 순차적으로 표시하기 위한 설정
group_names = ['Unknown', 'Baby', 'Child', 'Teenager', 'Student', 'Young Adult', 'Adult', 'Elderly']

# lambda 식에 위에서 생성한 get_category() 함수를 반환 값으로 지정
# get_category(x)는 입력값으로 Age 컬럼 값을 받아서 해당되는 cat 반환
train['Age_cat'] = train['Age'].apply(lambda x: get_category(x))
sns.barplot(x='Age_cat', y='Survived', hue='Sex', data=train, order=group_names)
train.drop('Age_cat', axis=1, inplace=True)

여자 Baby의 경우에는 비교적 생존률이 높고, Child의 경우 생존률이 낮음, Elderly의 경우에는 생존률이 매우 높음

Sex, Age, Pclass 등이 생존 여부에 중요한 Feature인 것으로 보임

이제 남아있는 문자열 카테고리 Feature를 숫자형으로 변환 --> 사이킷런의 LabelEncoder() 이용

  • LabelEncoder 객체는 카테고리 값의 유형수에 따라 0 ~ (유형수-1) 까지 숫자 값으로 변환
In [11]:
from sklearn.preprocessing import LabelEncoder

def encode_features(dataDF) :
    features = ['Cabin', 'Sex', 'Embarked']
    for feature in features:
        le = LabelEncoder()
        le = le.fit(dataDF[feature])
        dataDF[feature] = le.transform(dataDF[feature])
        
    return dataDF

train = encode_features(train)
train.head()
Out[11]:
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris 1 22.0 1 0 A/5 21171 7.2500 7 3
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... 0 38.0 1 0 PC 17599 71.2833 2 0
2 3 1 3 Heikkinen, Miss. Laina 0 26.0 0 0 STON/O2. 3101282 7.9250 7 3
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) 0 35.0 1 0 113803 53.1000 2 3
4 5 0 3 Allen, Mr. William Henry 1 35.0 0 0 373450 8.0500 7 3

지금까지 Feature를 가공한 내역을 정리하여 함수로 다시 정리

In [12]:
# 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.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

다시 원본 데이터를 호출해서 transform_features 함수로 가공해보기

In [13]:
# 원본 데이터 재로딩
titanic_df = pd.read_csv('Titanic/input/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)

내려받은 학습 세트를 기반으로 하여 train_test_split() 을 이용하여 별도의 테스트 데이터 세트를 추출

In [14]:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X_titanic_df, y_titanic_df, test_size = 0.2, random_state = 11)

ML 알고리즘인 DecisionTree, RandomForest, Logistic Regression을 이용하여 생존자 예측

  • train_test_split() 으로 분리한 학습 데이터와 테스트 데이터를 기반으로 머신러닝 모델을 학습(fit)하고, 예측(predict)
  • 성능평가는 정확도로 평가: accuracy_score() 를 사용
In [15]:
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

# DecisionTree, RandomForest, LogisticRegression을 위한 사이킷런 Classifier 클래스 생성
dt_clf = DecisionTreeClassifier(random_state=11)
rf_clf = RandomForestClassifier(random_state=11)
lr_clf = LogisticRegression()

# DecisionTreeClassifier 학습/예측/평가
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)
print('DecisionTreeClassifier 정확도 : {0:.4f}'.format(accuracy_score(y_test, dt_pred)))
      
# RandomForestClassifier 학습/예측/평가
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)
print('RandomForestClassifier 정확도 : {0:.4f}'.format(accuracy_score(y_test,rf_pred)))
      
# LogisticRegression 학습/예측/평가
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)
print('LogisticRegression 정확도 : {0:.4f}'.format(accuracy_score(y_test, lr_pred)))
DecisionTreeClassifier 정확도 : 0.7877
RandomForestClassifier 정확도 : 0.8324
LogisticRegression 정확도 : 0.8659

3개의 알고리즘 중 Logistioc Regression이 높은 정확도를 나타내고 있음

하지만 아직 최적화 작업을 수행하지 않았고, 데이터 양도 충분치 않기 때문에 어떤 알고리즘이 가장 좋은 성능인지 평가할 수는 없음

교차 검증을 위한 kFold 클래스를 사용

In [16]:
from sklearn.model_selection import KFold

def exec_kfold(clf, folds):
    # 폴드 세트가 5개인 kFold 객체 생성, 폴드 수 만큼 예측결과 저장을 위한 리스트 객체 생성
    kfold = KFold(n_splits=folds)
    scores = []
    
    # kFold 교차검증 수행
    for iter_count, (train_index, test_index) in enumerate(kfold.split(X_titanic_df)):
        # X_train_df 데이터에서 교차검증별로 학습과 검증 데이터를 가리키는 인덱스 생성
        X_train, X_test = X_titanic_df.values[train_index], X_titanic_df.values[test_index]
        y_train, y_test = y_titanic_df.values[train_index], y_titanic_df.values[test_index]
        # Classifier 학습, 예측, 정확도 계산
        clf.fit(X_train, y_train)
        predictions = clf.predict(X_test)
        accuracy = accuracy_score(y_test, predictions)
        scores.append(accuracy)
        print("교차검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
        # 5개 fold에서 평균 정확도 계산
    mean_score = np.mean(scores)
    print("평균 정확도 : {0:.4f}".format(mean_score))
    
# exec_kfold 호출
exec_kfold(dt_clf, 5)
교차검증 0 정확도: 0.7542
교차검증 1 정확도: 0.7809
교차검증 2 정확도: 0.7865
교차검증 3 정확도: 0.7697
교차검증 4 정확도: 0.8202
평균 정확도 : 0.7823

교차 검증을 위한 cross_val_score() 사용

In [17]:
from sklearn.model_selection import cross_val_score

scores = cross_val_score(dt_clf, X_titanic_df, y_titanic_df, cv=5)
for iter_count, accuracy in enumerate(scores):
    print("교차검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))
    
print("평균정확도: {0: .4f}".format(np.mean(scores)))
교차검증 0 정확도: 0.7430
교차검증 1 정확도: 0.7765
교차검증 2 정확도: 0.7809
교차검증 3 정확도: 0.7753
교차검증 4 정확도: 0.8418
평균정확도:  0.7835

kFold와 cross_val_score의 점수가 다른 것은, cross_val_score 는 StratifiedKFold를 이용해 세트를 분할하기 때문

GridSearchCV를 이용하여 DecisionTreeClassifier 최적 하이퍼 파라미터를 찾고, 예측 성능을 측정

  • CV = 5로 지정하고, max_depth, min_samples_split, min_samples_leaf를 변경하면서 성능을 측정
  • 최적 하이퍼 파라미터와 그 때의 예측을 출력하고, 최적 하이퍼파라미터로 학습된 Estimator를 이용해 위의 train_test_split()으로 분리된 테스트 데이터 세트에 예측을 수행하여 정확도 측정
In [18]:
from sklearn.model_selection import GridSearchCV

parameters = {'max_depth': [2, 3, 5, 10],
                        'min_samples_split' : [2, 3, 5],
                         'min_samples_leaf' : [1, 5, 8]}

grid_dclf = GridSearchCV(dt_clf, param_grid=parameters, scoring='accuracy', cv=5)
grid_dclf.fit(X_train, y_train)

print("GridSearch 최적 하이퍼 파라미터: ", grid_dclf.best_params_)
print("GridSearch 최고 정확도:{:.4f} ".format(grid_dclf.best_score_))
best_dclf = grid_dclf.best_estimator_

# GridSearch의 최적 하이퍼파라미터로 학습된 Estimator로 예측 및 평가 수행
predictions = best_dclf.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
print('테스트 세트에서의 DecisionTreeClassifier 정확도: {0:.4f}'.format(accuracy))
GridSearch 최적 하이퍼 파라미터:  {'max_depth': 3, 'min_samples_leaf': 1, 'min_samples_split': 2}
GridSearch 최고 정확도:0.7992 
테스트 세트에서의 DecisionTreeClassifier 정확도: 0.8715
/anaconda3/lib/python3.7/site-packages/sklearn/model_selection/_search.py:841: DeprecationWarning: The default of the `iid` parameter will change from True to False in version 0.22 and will be removed in 0.24. This will change numeric results when test-set sizes are unequal.
  DeprecationWarning)

최적 하이퍼 파라미터인 max_depth = 3, min_samples_leaf = 1, min_samples_split = 2 로 DecisionClassifier를 학습시킨 뒤 모델 정확도가 약 87.15%로 향상

실제로는 하이퍼 파라미터 튜닝으로 스코어가 이렇게 증가하지는 않음

  1. 익명 2021.03.11 09:38

    비밀댓글입니다

2-4. 데이터 전처리
In [1]:
from IPython.display import Image

ML 알고리즘에서의 데이터 입력

  • 결측값(NaN, Null)을 허용하지 않음 --> Null 값은 고정된 다른 값으로 변환해야 함

  • 문자열 값을 입력값으로 허용하지 않음 --> 문자열 값을 인코딩해서 숫자형으로 변환해야 함

1. 데이터 인코딩

(1). 레이블 인코딩(Label Encoding)

 : 카테고리 피처를 코드형 숫자로 변환

  • LabelEncoder 클래스로 구현: 객체 생성 후 fit(), transform()
In [2]:
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 3 2 2]

TV는 0, 냉장고는 1, 믹서는 2, 선풍기는 3, 전자레인지는 4, 컴퓨터는 5로 변환

이처럼 데이터가 적을 경우에는 직관적으로 알 수 있지만, 많은 경우에는 어려움

classes_ : 변환된 인코딩 값에 대한 원본 값을 가지고 있음

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

inverse_transform() : 인코딩된 값을 다시 디코딩할 수 있음

In [4]:
print('디코딩 원본 값: ', encoder.inverse_transform([0, 1, 4, 5, 3, 3, 2, 2]))
디코딩 원본 값:  ['TV' '냉장고' '전자레인지' '컴퓨터' '선풍기' '선풍기' '믹서' '믹서']
  • 단점 : 일괄적인 숫자 값으로 변환이 되면서 몇몇 알고리즘에는 예측성능이 떨어지는 경우가 발생. (숫자 값의 크고 작음에 대한 특성이 반영되기 때문)

    --> 회귀 알고리즘에는 적용하면 안됨, 트리 계열의 알고리즘은 숫자의 이런 특성을 반영하지 않으므로 레이블 인코딩도 별 문제가 없음

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

  • 바로 위에서 이야기한 레이블 인코딩의 문제점을 해결하기 위한 방식으로 OneHotEncoder 클래스로 쉽게 변환
  • 피처 값의 유형에 따라 새로운 피처를 추가해 고유 값에 해당하는 칼럼에만 1을 표시하고 나머지에는 0으로 표시하는 방법
  • 주의점
    • OneHotEncoder 변환하기 전에 모든 문자열 값이 숫자형 값으로 변환이 되어 있어야 함
    • 입력값으로 2차원 데이터가 필요함
In [5]:
from sklearn.preprocessing import OneHotEncoder
import numpy as np

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

encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
labels = labels.reshape(-1,1)
In [6]:
# 원-핫 인코딩을 적용
oh_encoder = OneHotEncoder(categories='auto')
oh_encoder.fit(labels)
oh_labels = oh_encoder.transform(labels)
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('\n원-핫 인코딩 데이터 차원')
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)
In [7]:
labels
Out[7]:
array([[0],
       [1],
       [4],
       [5],
       [3],
       [3],
       [2],
       [2]])
In [8]:
oh_labels
Out[8]:
<8x6 sparse matrix of type '<class 'numpy.float64'>'
	with 8 stored elements in Compressed Sparse Row format>
  • get_dummies() : 판다스에서 이용할 수 있는 원핫 인코딩 API
    • 사이킷런의 OneHotEncoder와는 달리 문자열 카테고리 값을 숫자형으로 변환할 필요 없이 바로 변환됨
In [9]:
import pandas as pd

df = pd.DataFrame({'item' : ['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']})
df
Out[9]:
item
0 TV
1 냉장고
2 전자레인지
3 컴퓨터
4 선풍기
5 선풍기
6 믹서
7 믹서
In [10]:
pd.get_dummies(df)
Out[10]:
item_TV item_냉장고 item_믹서 item_선풍기 item_전자레인지 item_컴퓨터
0 1 0 0 0 0 0
1 0 1 0 0 0 0
2 0 0 0 0 1 0
3 0 0 0 0 0 1
4 0 0 0 1 0 0
5 0 0 0 1 0 0
6 0 0 1 0 0 0
7 0 0 1 0 0 0

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

  • 피처 스케일링(Feature Scaling) : 서로 다른 변수의 값 범위를 일정한 수준으로 맞추는 작업(표준화, 정규화 등)

  • 표준화(Standardization) : 데이터 피처의 각각이 평균이 0이고 분산이 1인 가우시안 정규분포를 가진 값으로 변환

In [11]:
Image('image/standardization.jpg', width = 300)
Out[11]:
  • 정규화(Normalization) : 서로 다른 피처의 크기를 통일하기 위해 크기를 변환해주는 개념 (즉, 개별 데이터 크기를 모두 똑같은 단위로 변경)
In [12]:
Image('image/min-max-normalisation.jpg', width = 300)
Out[12]:

(1) StandardScaler

    : 표준화를 쉽게 지원하기 위한 클래스로 개별 피처를 평균이 0, 분산이 1인 값으로 변환
  • RBF 커널을 이용하는 서포트 벡터 머신, 선형회귀, 로지스틱 회귀는 데이터가 가우시안 분포를 가지고 있다고 가정하고 구현했기 때문에, 사전에 표준화를 적용하는 것은 예측 향상에 중요한 요소
In [13]:
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)
iris_df.head()
Out[13]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 5.1 3.5 1.4 0.2
1 4.9 3.0 1.4 0.2
2 4.7 3.2 1.3 0.2
3 4.6 3.1 1.5 0.2
4 5.0 3.6 1.4 0.2
In [14]:
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
In [15]:
# StandardScaler를 이용하여 표준화

from sklearn.preprocessing import StandardScaler

# Scaler 객체 생성
scaler = StandardScaler()

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

# transform() 변환시 스케일 변환된 데이터세트가 Numpy ndarray로 반환되 이를 데이터 프레임으로 변환
iris_df_scaled = pd.DataFrame(data= iris_scaled, columns = iris.feature_names)
iris_df_scaled.head()
Out[15]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 -0.900681 1.019004 -1.340227 -1.315444
1 -1.143017 -0.131979 -1.340227 -1.315444
2 -1.385353 0.328414 -1.397064 -1.315444
3 -1.506521 0.098217 -1.283389 -1.315444
4 -1.021849 1.249201 -1.340227 -1.315444
In [16]:
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에 아주 가깝게 변환되었음

(2) MinMaxScaler

    : 데이터값을 0과 1사이의 범위 값으로 변환(음수값이 있으면 -1에서 1값으로 변환)

  • 데이터의 분포가 가우시안 분포가 아닐 경우에 적용해 볼 수 있음
In [17]:
from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler 객체 생성
scaler = MinMaxScaler()

# MinMaxScaler로 데이터 세트 변환. 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)
iris_df_scaled.head()
Out[17]:
sepal length (cm) sepal width (cm) petal length (cm) petal width (cm)
0 0.222222 0.625000 0.067797 0.041667
1 0.166667 0.416667 0.067797 0.041667
2 0.111111 0.500000 0.050847 0.041667
3 0.083333 0.458333 0.084746 0.041667
4 0.194444 0.666667 0.067797 0.041667
In [18]:
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
2-3. Model Selection 모듈 소개

model_selection :

학습 데이터와 테스트 데이터 세트를 분리하거나 교차 검증 분할 및 평가, 그리고 Estimator의 하이퍼파라미터 튜닝을 위한 다양한 함수와 클래스를 제공

1. 학습/테스트 데이터 세트 분리 : train_test_split()

  • 테스트 데이터 세트를 이용하지 않고 학습 데이터 세트로만 학습하고 예측할 때의 문제
In [1]:
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

위의 예측결과가 100% 정확한 이유는 이미 학습한 데이터 세트 기반으로 예측했기 때문

실제 예측에서는 학습하지 않은 데이터를 기반으로 예측을 해야함

때문에 학습데이터 자체를 학습데이터와 테스트 데이터로 분할하여 모델이 얼마나 잘 예측하는지 평가해봄

train_test_split() 의 파라미터

  • test_size : 전체 데이터 세트에서 테스트 데이터 세트 크기를 얼마로 샘플링할 것인지 결정, 기본값 : 0.25
  • train_size : 전체 데이터 세트에서 학습용 데이터 세트 크기를 얼마로 샘플링 할 것인지 결정, test_size를 주로 사용해서 잘 쓰는 파라미터는 아님
  • shuffle : 데이터를 분리하기 전에 데이터를 미리 섞을지 결정, 기본값은 True, 데이터를 분산시켜 보다 효율적인 학습/테스트 데이터 세트를 만드는데 사용
  • random_state : 난수 값을 지정하면 여러번 다시 수행해도 동일한 결과가 나오게 해줌

반환 값은 튜플 형태: (1) 학습용 데이터 피처 데이터 셋 (2) 테스트용 데이터 피처 데이터셋, (3) 학습용 데이터 레이블 데이터 셋, (4) 훈련용 데이터 레이블 데이터셋

In [7]:
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)
In [8]:
# 학습용 데이터를 기반으로 DecisionTreeClassifier를 학습하고 모델을 이용해 예측 정확도를 측정
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:.4f}'.format(accuracy_score(y_test, pred)))
예측 정확도: 0.9556

2. 교차 검증(Cross Validation)

위의 방법으로 고정된 학습용 데이터와 테스트 데이터를 나누어 모델을 평가할 때는 과적합(Overfitting)에 취약함

교차 검증 : 결국 모의고사를 많이 보는 것 --> 데이터 편중을 막기 위해 별도의 여러 세트로 구성된 학습 데이터 셋과 검증 데이터 셋에서 학습과 평가를 수행

이 결과를 바탕으로 하이퍼 파라미터 튜닝 등의 모델 최적화를 더욱 손쉽게 할 수 있음

(1) K-Fold 교차검증(K-Fold Cross Validation)

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

사이킷런에서는 KFoldStratifiedKFold를 제공함

In [16]:
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()

# 5개의 폴드세트를 분리하는 kFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터세트 크기: ', features.shape[0])
붓꽃 데이터세트 크기:  150

KFold(n_splits= 5)로 객체를 생성했기 때문에 150개의 데이터세트에서 120개의 학습용 데이터, 30개의 테스트 데이터 세트를 5번 만든다

  • KFold 객체는 split() 을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환하므로 실제 분할된 데이터 추출은 코드로 직접 수행해야함
In [21]:
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('#<