[4장-1] 분류(classfication)와 결정트리(decision tree)
1. 분류란?
- 학습 데이터로 주어진 데이터의 피처와 레이블값을 머신러닝 알고리즘으로 학습해 모델을 생성하고, 생성된 모델에 새로운 데이터 값이 주어졌을 때 미지의 레이블 값을 예측하는 것
2. 결정트리와 앙상블
1) 결정 트리
- 매우 쉽고 유연하고 적용될 수 있는 알고리즘, 데이터의 스케일링이나 정규화 등의 사전 가공의 영향이 매우 적음
- 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내는 트리 기반의 분류규칙을 만든다
- 장점: 쉽다, 직관적이다, 사전 가공 영향도가 크지 않다.
- 단점: 과적합으로 알고리즘 성능이 떨어진다. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝이 필요하다
2) 앙상블
- 매우 많은 여러개의 약한 학습기를 결합해 확률적 보완과 오류가 발생한 부분에 대한 가중치를 계속 업데이트하면서 예측 성능을 향상시키는데 결정트리가 좋은 약한 학습기가 됨
3. Graphviz 설치
(1) 실행 파일 설치 /
Download
Graph Visualization Software
graphviz.org
(2) 설치 진행
(3) 내PC → 속성 → 네트워크 및 인터넷 → 고급 네트워크 설정 → 시스템 속성 → 환경변수 → PATH 클릭
→ Graphviz 클릭
(4) anaconda prompt → 관리자권한으로 실행 → pip install graphviz 클릭
4. 결정 트리 모델의 시각화(Decision Tree Visualization)
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')
# DecisionTree Classifier 생성
dt_clf = DecisionTreeClassifier(random_state=156)
# 붓꽃 데이터를 로딩하고, 학습과 테스트 데이터 셋으로 분리
iris_data = 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=11)
# DecisionTreeClassifer 학습.
dt_clf.fit(X_train , y_train)
DecisionTreeClassifier(random_state=156)
from sklearn.tree import export_graphviz
# export_graphviz()의 호출 결과로 out_file로 지정된 tree.dot 파일을 생성함.
export_graphviz(dt_clf, out_file="tree.dot", class_names=iris_data.target_names , \
feature_names = iris_data.feature_names, impurity=True, filled=True)
import graphviz
# 위에서 생성된 tree.dot 파일을 Graphviz 읽어서 Jupyter Notebook상에서 시각화
with open("tree.dot") as f:
dot_graph = f.read()
graphviz.Source(dot_graph)
5. 결정트리 실습
1) 데이터 다운로드
UCI Machine Learning Repository: Human Activity Recognition Using Smartphones Data Set
UCI Machine Learning Repository: Human Activity Recognition Using Smartphones Data Set
Human Activity Recognition Using Smartphones Data Set Download: Data Folder, Data Set Description Abstract: Human Activity Recognition database built from the recordings of 30 subjects performing activities of daily living (ADL) while carrying a waist-moun
archive.ics.uci.edu
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
# features.txt 파일에는 피처 이름 index와 피처명이 공백으로 분리되어 있음. 이를 DataFrame으로 로드.
feature_name_df = pd.read_csv('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/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])
전체 피처명에서 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']
# 중복피처명 확인
feature_dup_df = feature_name_df.groupby('column_name').count()
feature_dup_df
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)
- 42개의 중복 피처명 확인
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
df = pd.DataFrame([['a'], ['a'], ['a'], ['b'], ['b'], ['a']], columns=['A'])
display(df)
df.groupby('A').cumcount()
import pandas as pd
def get_human_dataset( ):
# 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
feature_name_df = pd.read_csv('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/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('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/train/X_train.txt',sep='\s+', names=feature_name )
X_test = pd.read_csv('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/test/X_test.txt',sep='\s+', names=feature_name)
# 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
y_train = pd.read_csv('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/train/y_train.txt',sep='\s+',header=None,names=['action'])
y_test = pd.read_csv('C:/Users/eznay/UCI HAR Dataset (1)/UCI HAR Dataset/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()
# X_train.info() : 간략하게 확인
print('## 학습 피처 데이터셋 info()')
print(X_train.info())
- 학습데이터 세트: 7352개
- 레코드로 561개 피처, 피처 전부 float형 → 인코딩 수행 안해도 됨 확인
print(y_train['action'].value_counts())
# 사이킷런 Decision Tree Classifier를 이용해 동작 예측 분류 수행
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))
# DecisionTreeClassifier의 하이퍼 파라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())
결정 트리 예측 정확도: 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'}
- 85.48% 정확도 확인
# max_depth 값을 변화시키면서 예측 성능 확인
- min_samples_split는 16으로 고정, max_depth를 6,8,10 처럼 늘리면서 예측 성능
- 교차검증: 5개 세트
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_)
- max_depth 8일때 5개의 폴드 세트의 최고 평균 정확도 85.49% 도출
# 5개의 CV 세트에서 max_depth 값에 따라 예측 성능이 어떻게 변했는지 확인
- GridSearchCV 객체의 cv_results 속성은 CV세트에 하이퍼 파라미터를 순차적으로 입력했을 때의 성능 수치 가지고 있음
# 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']]
- mean_test_score: 5개 CV 세트에서 검증용 데이터 세트의 정확도 평균 수치
- mean_test_score가 8일때 가장 높은 정확도 가짐
# min_samples_split은 16으로 고정하고 max_depth의 변화에 따른 값을 측정
max_depths = [ 6, 8 ,10, 12, 16 ,20, 24]
# max_depth 값을 변화 시키면서 그때마다 학습과 테스트 셋에서의 예측 성능 측정
for depth in max_depths:
dt_clf = DecisionTreeClassifier(max_depth=depth, min_samples_split=16, 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} 정확도: {1:.4f}'.format(depth , accuracy))
max_depth = 6 정확도: 0.8551
max_depth = 8 정확도: 0.8717
max_depth = 10 정확도: 0.8599
max_depth = 12 정확도: 0.8571
- max_depth가 8일때 가장 높은 정확도 보임
- 8 이후로 정확도 떨어지는것을 보며 깊이가 깊어질수록 데이터세트의 정확도 감소 볼 수 있음
# max_depth와 min_samples_split을 같이 변경하면서 정확도 성능 튜닝
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.8549
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}
- max_depth가 8, min_samples_split이 16일때 가장 최고의 정확도 나타남
# 별도 분리된 테스트 데이터 세트에 해당 파라미터 적용
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.8717
# 결정 트리에서 각 피처의 중요도를 feature_importances 속성을 이용해 확인
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()