ML/분류(Classification)

사용자 행동 인식(Human Activity Recognition) 데이터 세트를 활용한 결정트리 실습

야뤼송 2024. 5. 22. 08:30
반응형
UCI 머신러닝 리포지토리에서 제공하는 사용자 행동 인식 데이터 셋을 이용하여 결정트리를 실습해 보자. 

 

 

1. 사용자 행동 인식 데이터의 데이터 확인 및 정확도 측정

 

사용자 행동 인식 데이터 세트는 30명의 스마트 폰 센서를 장착한 뒤, 사람의 동작과 관련된 여러가지 피처를 수집한 데이터이고 이 데이터를 기반으로 사용자의 행동이 어떤 동작인지를 예측해야 한다.

 

데이터는 아래 경로에서 다운로드 가능하다

https://archive.ics.uci.edu/dataset/240/human+activity+recognition+using+smartphones

 

먼저 feature.txt 파일을 DataFrame으로 로드한다. feature 데이터는 561개의 row와 2개의 column으로 구성되어있다.

 

import pandas as pd

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

feature_name_df

▶ Out

 

 

데이터의 특징을 조금 더 살펴보기 위해 샘플로 10개만 추출해보자.

사람의 동작과 관련하여 평균, 표준편차가 X, Y, Z축의 피처값들로 구성되어있는 것을 유추해볼 수 있다.

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

▶ Out

전체 피처명에서 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']

 

 

피처명이 중복이 존재할 수 있으므로 중복된 피처명이 있는지를 확인해보면  42개가 존재하고 있다. 

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

▶ Out


중복된 피처들은 기존 피처명에 _숫자를 부여하여 새로운 데이터 프레임을 가질 수 있도록 해줘야한다.

def get_new_feature_name_df(old_feature_name):
    feature_dup_df = pd.DataFrame(data=old_feature_name.groupby('column_name').cumcount(),
                                  columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name.reset_index(), feature_dup_df, how='outer')
    print(new_feature_name_df)
    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

 

해당 메소드가 정상 동작하는지는 확인해보자. 중복된 피처명에 _1, _2, _3이 추가로 부여되서 생성되는 것을 확인할 수 있다.

df = pd.DataFrame([['a'], ['a'], ['a'], ['b'], ['b'], ['a']], columns=['column_name'])
display(df)
df.groupby('column_name').cumcount()
print('--------------------------------')
df_new = get_new_feature_name_df(df)
display(df_new)

▶ Out

 

 

이제 중복된 피처명을 수정하여 신규 피처명의 DataFrame을 생성하는 함수를 적용하여 학습 피처 데이터셋(X_train), 테스트 데이터셋(X_test), 학습 레이블 데이터셋(y_train), 테스트 레이블 데이터셋(y_test)를 가져오는 함수를 생성한다.

import pandas as pd

def get_human_dataset( ):
    
    # 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
    feature_name_df = pd.read_csv('./human_activity/features.txt',sep='\s+',
                        header=None,names=['column_index','column_name'])
    
    # 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 DataFrame생성. 
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    
    # DataFrame에 피처명을 컬럼으로 부여하기 위해 리스트 객체로 다시 변환
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    
    # 학습 피처 데이터 셋과 테스트 피처 데이터을 DataFrame으로 로딩. 컬럼명은 feature_name 적용
    X_train = pd.read_csv('./human_activity/train/X_train.txt',sep='\s+', names=feature_name )
    X_test = pd.read_csv('./human_activity/test/X_test.txt',sep='\s+', names=feature_name)
    
    # 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
    y_train = pd.read_csv('./human_activity/train/y_train.txt',sep='\s+',header=None,names=['action'])
    y_test = pd.read_csv('./human_activity/test/y_test.txt',sep='\s+',header=None,names=['action'])
    
    # 로드된 학습/테스트용 DataFrame을 모두 반환 
    return X_train, X_test, y_train, y_test


X_train, X_test, y_train, y_test = get_human_dataset()

 

 

학습 피처 데이터셋의 정보를 확인하여 데이터 전처리 작업이 필요한지 확인해보자. dtypes이 float이므로 별도로 인코딩 작업은 필요 없다.

print('## 학습 피처 데이터셋 info()')
print(X_train.info())

▶ Out

## 학습 피처 데이터셋 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

 

 

이제 위에서 생성한 학습 데이터로 결정트리를 이용하여 예측 정확도를  구해보자.

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

# 예제 반복 시 마다 동일한 예측 결과 도출을 위해 random_state 설정
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('결정 트리 예측 정확도: {0:.4f}'.format(accuracy))

▶ Out

결정 트리 예측 정확도: 0.8548

 

 

DecisionTreeClassifier의 하이퍼 파라미터 추출하여 어떤 값을 사용하였는지 확인해보자

# DecisionTreeClassifier의 하이퍼 파라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())

▶ Out

결정 트리 예측 정확도: 0.8548
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': 156, 'splitter': 'best'}

 

 

2. 하이퍼파라미터를 조정한 예측성능 확인


이제 성능에 영향을 주는 하이퍼 파라미터를 조정하여 정확도의 변화를 알아보자.

min_samples_split은 16으로 고정하였고 max_depth는 6~24까지 7개의 리스트로 나누어서 다시 정확도를 측정해보자.

 

max_depth가 8일 때, 그리고 min_samples_split이 16일 때 가장 높은 평균 정확도를 가지는 것을 확인할 수 있다.

from sklearn.model_selection import GridSearchCV

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

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_)

▶ Out

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

 

 

max_depth 값의 변화에 따라 정확도 수치가 어떻게 달라지는지 확인해보면 8일 때 가장 높은 값을 가지는 것을 확인할 수 있다.

# GridSearchCV객체의 cv_results_ 속성을 DataFrame으로 생성. 
cv_results_df = pd.DataFrame(grid_cv.cv_results_)

# max_depth 파라미터 값과 그때의 테스트(Evaluation)셋, 학습 데이터 셋의 정확도 수치 추출
cv_results_df[['param_max_depth', 'mean_test_score']]

 

▶ Out

 

 

이번에는 min_samples_split 값에 변화를 주고 어떤 값일 때 정확도가 높은지 확인해보자. 16,18,24 3개의 값이 주어졌을 때  16이 가장 높은 값을 가지는 것을 확인할 수 있다.

params = {
    'max_depth' : [ 8 , 12, 16 ,20], 
    'min_samples_split' : [16, 18, 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_)

▶ Out

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

 

 

3. 최고 정확도 측정

 

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))

▶ Out

결정 트리 예측 정확도:0.8565

 

 

추가로 각 피처들의 중요도를 feature_importances_속성을 이용하여 확인할 수 있다.

ftr_importances_values = best_df_clf.feature_importances_
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
ftr_importances.sort_values(ascending=False)

▶ Out

tGravityAcc-min()-X                0.253354
fBodyAccJerk-bandsEnergy()-1,16    0.212584
angle(Y,gravityMean)               0.139001
fBodyAccMag-energy()               0.115478
tGravityAcc-arCoeff()-Z,2          0.101885
                                     ...   
tBodyGyroJerk-correlation()-Y,Z    0.000000
tBodyGyroJerk-correlation()-X,Z    0.000000
tBodyGyroJerk-correlation()-X,Y    0.000000
tBodyGyroJerk-arCoeff()-Z,3        0.000000
fBodyAcc-sma()                     0.000000
Length: 561, dtype: float64

 

 

위의 결과를  막대그래프를 이용하면 한눈에 쉽게 볼 수 있다.

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()

▶ Out

반응형