Python/Sklearn

[Python]Human Activity Recognition Using by "Decision Tree"

씩2 2022. 3. 17. 15:21

이번 포스팅에서는 스마트폰으로 사람의 움직임을 측정해 담은 데이터셋을 의사결정나무를 통해서 분류해보도록 하겠습니다.

먼저 필요한 모듈들을 임포트 해주도록 하겠습니다.

 

from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import train_test_split
from sklearn.tree import export_graphviz
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
import seaborn as sns
import pandas as pd
import matplotlib as plt
import os

DecisonTreeClassifer와 Regression으로 나뉘는데 이번 포스팅에서는 분류를 해야하기때문에 Classifier을 임포트 해주면 됩니다.

graphviz는 시각화 툴입니다. 맥에서는 사용하기위해서 설치가 필요하기에 이후에 따로 포스팅 하도록 하겠습니다.

 

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

# 피처명 index를 제거하고, 피처명만 리스트 객체로 생성한 뒤 샘플로 10개만 추출
feature_name = feature_name_df.iloc[:, 1].values.tolist()
print('전체 피처명에서 10개만 추출:', feature_name[:10])

이 코드는 분류하는데 크게 영향은 없는 코드입니다.

하지만 나중에 분류를 하고 어떠한 성분이 분류에 크게 영향을 미치는지 확인하기 위해서는 필요한 코드입니다.

피쳐들이 중복된 네임이 있기때문에 사용자 정의 함수를 만들어 중복 변수를 _1,_2등으로 바꾸는 정의 함수를 만들겠습니다.

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('column_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')
    new_feature_name_df['column_name'] = new_feature_name_df[['column_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)
    return new_feature_name_df

pd.options.display.max_rows = 999
new_feature_name_df = get_new_feature_name_df(feature_name_df)
new_feature_name_df[new_feature_name_df['dup_cnt'] > 0]


def get_human_dataset():
    feature_name_df = pd.read_csv('./activity/features.txt', sep='\s+',
                                  header=None, names=['column_index', 'column_name'])
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    X_train = pd.read_csv('./activity/train/X_train.txt', sep='\s+', names=feature_name)
    X_test = pd.read_csv('./activity/test/X_test.txt', sep='\s+', names=feature_name)
    y_train = pd.read_csv('./activity/train/y_train.txt', sep='\s+', header=None, names=['action'])
    y_test = pd.read_csv('./activity/test/y_test.txt', sep='\s+', header=None, names=['action'])
    return X_train, X_test, y_train, y_test

1번째 사용자 정의 함수는 각 피쳐네임에 중복된 것을 바꿔주는 함수입니다.

2번재 사용자 정의함수는 트레이닝데이터와, 테스트 데이터를 로드해주는 사용자 정의 함수 입니다.

 

이제 데이터를 모두 로드 했으니 확인을 해보도록 하겠습니다.

print(X_train.info())
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7352 entries, 0 to 7351
Columns: 561 entries, tBodyAcc-mean()-X to angle(Z,gravityMean)
dtypes: float64(561)
memory usage: 31.5 MB
None

데이터 타입은 모두 float형으로 이루어진 데이터 프레임임을 알 수 있습니다.

dt_clf = DecisionTreeClassifier(random_state=1)
dt_clf.fit(X_train , y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred)
print('결정 트리 예측 정확도: {0:.4f}'.format(accuracy))
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())

의사결정나무를 생성하고 위처럼 정확도를 확인해 보도록 하겠습니다.

 

결정 트리 예측 정확도: 0.8643
DecisionTreeClassifier 기본 하이퍼 파라미터:
 {'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'random_state': 1, 'splitter': 'best'}

예측 정확도는 86.43% 가 나왔습니다. 기본적인 파라미터의 성능으로 나온 결과치이기때문에 최적의 파라미터를 찾아야합니다.

 

 

우선 깊이가 몇 일때 최적의 파라미터인지 확인을 해보도록 하겠습니다.

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('GridSearchCV 최고 평균 정확도 수치:{0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)

 

params라는 변수에 깊이를 6,8,10,12,16,20,24 등으로 설정을 해준 다음 GridSearchCV를 이용하여 최적의 깊이를 찾아보도록 하겠습니다. 위코드는 실행시 시간이 다소 걸릴 수있습니다.

Fitting 5 folds for each of 7 candidates, totalling 35 fits
GridSearchCV 최고 평균 정확도 수치:0.8526
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8}

최대 깊이가 8일때 가장 높은 정확도를 가지는 것으로 확인이 됩니다.

 

이번에는 깊이와, 최소샘플분할수를 지정하여 다시한번 최적의 하이퍼파라미터를 조정해보도록 하겠습니다.

params = {
    'max_depth' : [ 8 , 12, 16 ,20],
    'min_samples_split' : [16,24],
}

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

의사결정나무의 경우는 깊이가 깊어질수록 과적합의 위험도 더욱 올라가기에 적절한 파라미터 조정이 필요하다고 생각이 됩니다.

Fitting 5 folds for each of 8 candidates, totalling 40 fits
GridSearchCV 최고 평균 정확도 수치: 0.8458
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}

검정후 결과를 보니 깊이는 8, 최소 샘플 분할갯수는 16개일 때 최고의 정확도를 보여준다고 합니다.

조정된 파라미터를 가지고 다시 예측을 해보도록하겠습니다.

 

GridSearchCV에서는 최적의 파라미터가 best_estimator_에 저장이 되기때문에 따로 생성하지않고 예측을 진행하도록 하겠습니다.

best_df_clf = grid_cv.best_estimator_

pred1 = best_df_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred1)
print('결정 트리 예측 정확도:{0:.4f}'.format(accuracy))

 

결과값은 다음과 같습니다.

결정 트리 예측 정확도:0.8724

예측도가 87.24%로 하이퍼파라미터를 지정하지않고 의사결정나무를 생성했을 때보다 높은 정확도를 보여줌을 확인 할 수있습니다.

이것으로 사이킷런을 이용한 의사결정나무에대한 포스팅을 마치겠습니다.