본문 바로가기

Lecture ML

머신러닝 강좌 #13] 결정 트리 실습 - 사용자 행동 인식 [Human Learning Repository]

반응형

결정 트리를 이용해 UCI Machine Learning Repository에서 제공하는 Human Activity Reconition데이터 세트에 대한 예측 분류를 수행해 보겠습니다.

 

import pandas as pd
import matplotlib.pyplot as plt

feature_name_df = pd.read_csv('./data/UCI_Har/features.txt', sep='\s+', header=None, names=['column_index', 'coulmn_name'])

feature_name = feature_name_df.iloc[:,1].values.tolist()
print(feature_name_df.info())
# print(feature_name_df.head())
# print(feature_name_df.iloc[:,1])
# print(feature_name_df.iloc[:,1].values)
# print(feature_name_df.iloc[:,1].values.tolist())
print('전체 피처명에서 10개만 추출', feature_name[:10])

#전체 피처명에서 10개만 추출 ['tBodyAcc-mean()-X', 'tBodyAcc-mean()-Y', 'tBodyAcc-mean()-Z', 'tBodyAcc-std()-X', 'tBodyAcc-std()-Y', 'tBodyAcc-std()-Z', 'tBodyAcc-mad()-X', 'tBodyAcc-mad()-Y', 'tBodyAcc-mad()-Z', 'tBodyAcc-max()-X']

 

결괏값을 보면 중복된 피처명을 가지고 있습니다. 이 중복된 피처명들을 이용해 데이터 파일을 데이터 세트 DataFrame에 로드하면 오류가 발생합니다. 따라서 중복된 피처명에 대해서는 원본 피처명에 _1 또는 _2를 추가로 부여해 변경한 뒤에 이를 이용해서 데이터를 DataFrame에 로드하겠습니다. 

 

feature_dup_df = feature_name_df.groupby('coulmn_name').count()
print(feature_dup_df[feature_dup_df['column_index'] > 1].count())
print(feature_dup_df[feature_dup_df['column_index'] > 1].head())

# column_index    42
# dtype: int64
#                               column_index
# coulmn_name                               
# fBodyAcc-bandsEnergy()-1,16              3
# fBodyAcc-bandsEnergy()-1,24              3
# fBodyAcc-bandsEnergy()-1,8               3
# fBodyAcc-bandsEnergy()-17,24             3
# fBodyAcc-bandsEnergy()-17,32             3

 

총 42개의 피처명이 중복돼 있습니다. 이 중복된 피처명에 대해서는 원본 피처명에 _1 또는 _2를 추가호 부여해 새로운 피처명을 가지는 DataFrame을 반환하는 함수인 get_new_feature_name_df()를 생성하겠습니다.

 

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('coulmn_name').cumcount(), columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
    # print(new_feature_name_df.info())
    
    new_feature_name_df['column_name'] = new_feature_name_df[['coulmn_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1]) if x[1] > 0 else x[0], axis=1)
    # new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    # print(new_feature_name_df.info())
    return new_feature_name_df

 

!groupby.cumcount() 이해하기

df = pd.DataFrame([['a'], ['a'], ['a'], ['b'], ['b'], ['a'], ['c']], columns=['A'])
print(df)
print(df.groupby('A').cumcount())
print(df.groupby('A').count())

결과

#print(df.groupby('A').cumcount())
   A
0  a
1  a
2  a
3  b
4  b
5  a
6  c

#print(df.groupby('A').count())
0    0
1    1
2    2
3    0
4    1
5    3
6    0

 

다음으로는 train디렉토리에 있는 학습용 피처 데이터 세트와 레이블 데이터 세트, test디렉터리에 있는 테스트용 피처 데이터 파일과 레이블 데이터 파일을 각각 학습/테스트용 DataFrame에 로드하겠습니다.

import pandas as pd
def get_human_dataset():

    #각 데이터 파일은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
    feature_name_df = pd.read_csv('./data/UCI_Har/features.txt', sep='\s+', header=None, names=['column_index', 'coulmn_name'])
    new_feature_name_df = get_new_feature_name_df(feature_name_df)

    #DataFrame에 피처명을 칼럼으로 부여하기 이해 리스트 객체로 다시 변환
    feature_name = new_feature_name_df.iloc[:,1].values.tolist()
    # print(new_feature_name_df.iloc[:,1])
    # print(new_feature_name_df.iloc[:,1].values)
    # print(new_feature_name_df.iloc[:,1].values.tolist())

    X_train = pd.read_csv('./data/UCI_Har/train/X_train.txt', sep='\s+', names=feature_name)
    X_test = pd.read_csv('./data/UCI_Har/test/X_test.txt', sep='\s+', names=feature_name)

    y_train = pd.read_csv('./data/UCI_Har/train/y_train.txt', sep='\s+', header=None, names=['action'])
    y_test = pd.read_csv('./data/UCI_Har/test/y_test.txt', sep='\s+', header=None, names=['action'])

    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()
print(X_train.info())

info()로 데이터를 확인하면 전부 float형의 숫자 형이므로 별도의 카테고리 인코딩은 수행할 필요가 없습니다. action의 레이블 값의 분포를 확인하기 위해 간략하게 value_counts()를 확인해보겠습니다.

 

print(y_train['action'].value_counts())

#Label값 확인해보기
6    1407
5    1374
4    1286
1    1226
2    1073
3     986

 

이제부터 DecisionTreeClassifier를 이용해 동작 예측 분류를 수행해 보겠습니다. 먼저 DEcisionTreeClassfier의 하이퍼 파라미터는 모두 디폴트 값으로 설정해 수행하고 이때의 하이퍼 파라미터 값을 모두 추출해 보고  GridSearchCV를 이용해 사이킷런 결정 트리의 깊이를 조절할 수 있는 하이퍼 파라미터인 max_depth값을 변화시키면서 예측 성능을 확인해 보겠습니다.

 

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

dt_clf = DecisionTreeClassifier(random_state=156)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print('Accuracy {0:.4f}'.format(accuracy))
print('Show all hyper parameter : ', dt_clf.get_params())

from sklearn.model_selection import GridSearchCV
params = {'max_depth' : [6,8,10,12,16,20,24]}

grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1)
grid_cv.fit(X_train, y_train)
print('Best Score: ', grid_cv.best_score_)
print('Best Hyper Parameter: ', grid_cv.best_params_)

#GridSearchCV 객체의 cv_results_ 속성을 DataFrame으로 생성
cv_results_df = pd.DataFrame(grid_cv.cv_results_)
print(cv_results_df.info())
print(cv_results_df[['param_max_depth', 'mean_test_score']])

dt_clf = DecisionTreeClassifier(random_state=156, max_depth=16)
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred)
print('Accuracy {0:.4f}'.format(accuracy))
print('Show all hyper parameter : ', dt_clf.get_params())

 

GridSearch객체의 cv_results_속성은 CV세트에서 max_depth값에 따라 어떻게 성능이 변했는지 알 수 있게 합니다. max_depth에 따른 평가 데이터 세트의 평균 정확도 수치를 mean_test_score값으로 추출이 가능합니다. 

 

  param_max_depth  mean_test_score
0               6         0.850791
1               8         0.851069
2              10         0.851209
3              12         0.844135
4              16         0.851344
5              20         0.850800
6              24         0.849440

값을 보면 10일때 정점을 찍고, 이를 넘어가면서 정확도가 계속 떨어집니다. 결정 트리는 더 완벽한 규칙을 학습 데이터 세트에 적용하기 위해 노드를 지속적으로 분할하면서 깊이가 깊어지고 더욱더 복잡한 모델이 됩니다. 깊어진 트리는 학습 데이터 세트에는 올바른 예측 결과를 가져올지 모르지만, 검증 데이터 세트에서는 오히려 과적합으로 인한 성능 저하를 유발하게 됩니다. 

 

별도의 테스트 데이터 세트에서 순차적으로 max_depth를 확인을 해보면 아래와 같습니다. max_depth가 8을 정점으로 하락함을 알 수 있습니다.

max_depth = [6,8,10,12,16,20,24]
for depth in max_depth:
    dt_clf = DecisionTreeClassifier(max_depth=depth, random_state=156)
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)
    accuracy = accuracy_score(y_test, pred)
    print('max_depth = {0}, accuracy = {1:.4f}'.format(depth, accuracy))
Best Score:  0.8513444970102249
Best Hyper Parameter:  {'max_depth': 16}
max_depth = 6, accuracy = 0.8558
max_depth = 8, accuracy = 0.8707
max_depth = 10, accuracy = 0.8673
max_depth = 12, accuracy = 0.8646
max_depth = 16, accuracy = 0.8575
max_depth = 20, accuracy = 0.8548
max_depth = 24, accuracy = 0.8548

 

결정트리는 깊이가 깊어질수록 과적합의 영향력이 커지므로 하이퍼 파라미터를 이용해 깊이를 제어할 수 있어야 합니다. 복잡한 모델보다도 트리 깊이를 낮춘 단순한 모델이 더욱 효과적인 결과를 가져올 수 있습니다.  이후 GridSearchCV객체인 grid_cv의 속성인 best_estimator_는 최적 하이퍼 파라미터인 max_deph 8, min_samples_split 16으로 학습이 완료된 Estimator객체입니다. 이를 이용해 테스트 데이터 세트에 예측을 수행하겠습니다.

grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1)
grid_cv.fit(X_train, y_train)
print('GridSearchCV 최고 평균 정확도 수치 ', grid_cv.best_score_)
print('GridSearchCV 최적 하이퍼 파라미터 ', grid_cv.best_params_)

best_df_clf = grid_cv.best_estimator_
pred1 = best_df_clf.predict(X_test)
accuracy = accuracy_score(y_test, pred1)
print('Accuracy :', accuracy)

 

결과는 아래와 같습니다.

GridSearchCV 최고 평균 정확도 수치  0.8548794147162603
GridSearchCV 최적 하이퍼 파라미터  {'max_depth': 8, 'min_samples_split': 16}
Accuracy : 0.8717339667458432

 

마지막으로 결정 트링서 각 피처의 중요도를 feature_importances_ 속성을 이용해 알아보겠습니다. 중요도가 높은 순으로 Top 20 피처를 막대그래프로 표현하겠습니다.

import seaborn as sns

ftr_importances_values = best_df_clf.feature_importances_

#Top 중요도로 정렬을 쉽게 하고, 시본(seaborn)의 막대그래프로 쉽게 표현하기 위해 Series 변환
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)

#중요도값 순으로 Series를 정렬
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]
plt.figure(figsize=(8,6))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20, y=ftr_top20.index)
plt.show()

 

반응형