파이썬 머신러닝 완벽 가이드 (권철민 저)을 요약정리했습니다.
결정트리
결정 트리는 ML 알고리즘 중 직관적으로 이해하기 쉬운 알고리즘이다. 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 트리 기반의 분류 규칙을 만드는 것이다. 일반적으로 규칙을 가장 쉽게 표현하는 방법은 if/else 기반으로 나타내는 것이다.
데이터의 어떤 기준을 바탕으로 규칙을 만들어야 가장 효율적인 분류가 될 것인가가 알고리즘 성능을 크게 좌우 한다.
1. 결정트리의 구조
다음 그림은 결정 트리의 구조를 간략하게 나타낸 것이다.
규칙 노드는 규칙 조건이고,리프 노드는 결정된 클래스 값이다. 그리고 새로운 규칙 조건마다 서브 트리(Sub Tree)가 생성된다. 데이터 셋에 피처가 있고 이러한 피처가 결합해 규칙 조건을 만들 때 마다 규칙 노드가 만들어 진다.
하지만 많은 규칙이 있다는 것은 복잡해진다는 얘기이고,이는 곧 과적합으로 이어지기 쉽다.
즉, 트리의 깊이가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성이 높다
[결정 트리 사진]
가능한 적은 결정노드로 높은 예측 정확도를 가지려면 데이터를 분류할 때 최대한 많은 데이터 셋이 해당 분류에 속할 수 있도록(정보 균일도가 높게) 결정 노드의 규칙을 결정 해야 한다.
정보 균일도를 측정하는 대표적인 방법은 엔트로피를 이용한 정보 이득(Information Gain)지수와 지니 계수가 있다.
- 정보 이득 : 서로 다른 값이 섞여 있으면 엔트로피가 높고, 같은 값이 섞여 있으면 엔트로피가 낮은 것을 이용해 1에서 엔트로피 지수를 뺀 값으로 정한다. 즉 정보이득 지수 = 1 - 엔트로피 지수 이다. 결정 트리는 정보 이득이 높은 속성을 기준으로 분할 한다.
- 지니 계수 : 데이터가 다양한 값을 가질수록 평등하며 특정 값으로 쏠릴 경우엔느 불평등 한 값으로 다형성이 낮을수록 균일도가 높다는 의미로서, 1로 갈수록 균일도가 높으므로 지니계수가 높은 속성을 기준으로 분할한다.
사이킷런의 DecisionTreeClassifier는 기본으로 지니 계수를 이용해 데이터 셋을 분할한다.
[사이킷런-결정트리 사진]
2. 결정 트리 모델의 특징
결정 트리의 가장 큰 장점은 정보의 '균일도'라는 룰을 기반으로 하고 있어서 알고리즘이 쉽고 직관적이라는 점이다. 정보의 균일도만 신경 쓰면 되기 때문에 특별한 경우를 제외하고는 각 피처의 스케일링과 정규화 같은 전처리 작업이 필요 없다. 반면에 결정 트리 모델의 가장 큰 단점은 과적합으로 정확도가 떨어진다는 것이다. 피처 정보의 균일도에 따른 룰 규칙으로 서브 트리를 계속 만들다 보면 피처가 많고 균일도가 다양하게 존재할 수록 트리의 깊이가 깊어지고 복잡해질 수 밖에 없다.
결정 트리의 장점 | 결정 트리의 단점 |
---|---|
쉽다. 직관적이다. | 과적합으로 알고리즘 성능이 떨어진다. |
피처의 스케일리이나 정규화 등의 사전 가공 영향도가 크지 않음 | 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝 필요. |
3.결정 트리 파라미터
사이킷런은 결정트리 알고리즘을 위한 DecisionTreeClassifier(분류)/DecisionTreeRegressor(회귀) 클래스를 제공한다.
사이킷런의 결정 트리 구현은 CART(Classification And Regression Trees) 알고리즘을 기반으로 한다. 주요 파라미터는 다음과 같다.
파라미터명 | 설명 |
---|---|
min_samples_split | 노드를 분할하기 위한 최소한의 샘플 데이터 수 |
min_samples_leaf | 말단 노드(leaf)가 되귀 위한 최소한의 샘플 데이터 수 |
max_feautres | 최적의 분할을 위해 고려할 최대 피처 개수 |
max_depth | 트리의 최대 깊이를 규정 |
max_leaf_nodes | 말단 노드의 최대 개수 |
4. 결정 트리(Decision TREE) 과적합(Overfitting)
과적합 문제를 시각화로 알아보자.
make_classification() : 분류를 위한 테스트용 데이터 생성 함수 를 이용해서 2개의 피처가 3가지 유형의 클래스 값을 가지는 데이터 셋을 만들고 이를 그래프 형태로 시각화 해보자.
from sklearn.datasets import make_classification
import matplotlib.pyplot as plt
%matplotlib inline
plt.title("3 Class values with 2 Features Sample data creation")
# 2차원 시각화를 위해서 feature는 2개, 결정값 클래스는 3가지 유형의 classification 샘플 데이터 생성.
X_features, y_labels = make_classification(n_features=2, n_redundant=0, n_informative=2,
n_classes=3, n_clusters_per_class=1,random_state=0)
# plot 형태로 2개의 feature로 2차원 좌표 시각화, 각 클래스값은 다른 색깔로 표시됨.
plt.scatter(X_features[:, 0], X_features[:, 1], marker='o', c=y_labels, s=25, cmap='rainbow', edgecolor='k')
<matplotlib.collections.PathCollection at 0x256517442c8>
각 피처가 X,Y 축으로 나열된 2차원 그래프이며, 3개의 클래스 값 구분은 색깔로 돼있다.
먼저 결정트리 생성에 별다른 제약이 없도록 하이퍼 파라미터가 디폴트인 Classifier를 학습하고 결정 기준 경계를 시각화해 보자.
import numpy as np
# Classifier의 Decision Boundary를 시각화 하는 함수
def visualize_boundary(model, X, y):
fig,ax = plt.subplots()
# 학습 데이타 scatter plot으로 나타내기
ax.scatter(X[:, 0], X[:, 1], c=y, s=25, cmap='rainbow', edgecolor='k',
clim=(y.min(), y.max()), zorder=3)
ax.axis('tight')
ax.axis('off')
xlim_start , xlim_end = ax.get_xlim()
ylim_start , ylim_end = ax.get_ylim()
# 호출 파라미터로 들어온 training 데이타로 model 학습 .
model.fit(X, y)
# meshgrid 형태인 모든 좌표값으로 예측 수행.
xx, yy = np.meshgrid(np.linspace(xlim_start,xlim_end, num=200),np.linspace(ylim_start,ylim_end, num=200))
Z = model.predict(np.c_[xx.ravel(), yy.ravel()]).reshape(xx.shape)
# contourf() 를 이용하여 class boundary 를 visualization 수행.
n_classes = len(np.unique(y))
contours = ax.contourf(xx, yy, Z, alpha=0.3,
levels=np.arange(n_classes + 1) - 0.5,
cmap='rainbow', clim=(y.min(), y.max()),
zorder=1)
from sklearn.tree import DecisionTreeClassifier
# 특정한 트리 생성 제약없는 결정 트리의 Decsion Boundary 시각화.
dt_clf = DecisionTreeClassifier().fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)
일부 이상치 데이터 까지 분류하기 위해 분할이 자주 일어나서 결정 기준 경계가 매우 많아졌다. 결정 트리의 기본 하이퍼 파라미터 설정은 리프 노드 안에 데이터가 모두 균일하거나 하나만 존재해야 하는 엄격한 분할 기준으로 인해 결정 기존 경계가 많아지고 복잡해 졌다. 이렇게 복잡한 학습 모델은 학습 데이터 셋의 특성과 약간만 다른 형태의 데이터 셋을 예측하면 예측 정확도가 떨어지게 된다 .
이번에는 min_smaples_leaf = 6을 설정해서 결정 기준 경계를 살펴보자
# min_samples_leaf=6 으로 트리 생성 조건을 제약한 Decision Boundary 시각화
dt_clf = DecisionTreeClassifier( min_samples_leaf=6).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)
이상치에 크게 반응하지 않으면서 좀 더 일반화된 분류 규칙에 따라 분류됐음을 알 수 있다.
다양한 테스트 데이터 셋을 기반으로 한 결정 트리 모델의 예측 성능은 첫 번째 모델보다는 트리 생성 조건을 제약한 모델이 더 뛰어날 가능성이 높다.
결정 트리 실습 - 사용자 행동 인식 데이터 셋
UCI에서 제공하는 사용자 행동 인식 데이터셋으로 예측 분류를 수행해 보자.
해당 데이터는 30명에게 스마트폰 센서를 장착한 뒤 사람의 동작과 관련된 여러 가지 피처를 수집한 데이터다.
피처는 모두 561개가 있으며, 공백으로 분리돼 있다.
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
# features.txt 파일에는 피처 이름 index와 피처명이 공백으로 분리되어 있음. 이를 DataFrame으로 로드.
feature_name_df = pd.read_csv('C:/Users/ariz/Documents/human_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])
전체 피처명에서 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']
피처명을 보면 인체의 움직임과 관련된 속성의 평균/표준편차가 X, Y, Z축 값으로 돼 있음을 유추할 수 있다.
원본 데이터에 중복된 Feature 명으로 인하여 Pandas에서 Duplicate name 에러가 발생하므로 중복 Feature에 대해 전처리를 해줘야 한다.
중복된 피처명을 확인
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()
column_index 42
dtype: int64
column_index | |
---|---|
column_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 |
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
테스트용 피처 데이터 와 레이블 데이터 파일을 각각 학습/테스트 용 DataFrame에 로드.
import pandas as pd
def get_human_dataset( ):
# 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
feature_name_df = pd.read_csv('C:/Users/ariz/Documents/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('C:/Users/ariz/Documents/human_activity/train/X_train.txt',sep='\s+', names=feature_name )
X_test = pd.read_csv('C:/Users/ariz/Documents/human_activity/test/X_test.txt',sep='\s+', names=feature_name)
# 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
y_train = pd.read_csv('C:/Users/ariz/Documents/human_activity/train/y_train.txt',sep='\s+',header=None,names=['action'])
y_test = pd.read_csv('C:/Users/ariz/Documents/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()
print('## 학습 피처 데이터셋 info()')
print(X_train.info())
## 학습 피처 데이터셋 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
학습 데이터 셋은 7352개의 레코드로 561개의 피처를 가지고 있다.
print(y_train['action'].value_counts())
6 1407
5 1374
4 1286
1 1226
2 1073
3 986
Name: action, dtype: int64
레이블 값은 1, 2, 3, 4, 5, 6의 6개 값이고 분포도는 특정 값으로 왜곡돼지 않고 비교적 고르게 분포 되어 있다.
사이킷런의 DecisionTreeClassifier를 이용해 동작 예측 분류를 수행해 보자.
먼저 DecisionTreeClassifier의 하이퍼 파라미터는 모두 디폴트 값으로 설정해 수행하고, 이때의 하이퍼 파라미터 값을 모두 추출해 보자.
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_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'presort': 'deprecated', 'random_state': 156, 'splitter': 'best'}
약 85.48%의 정확도를 나타내고 있다.
이번에는 결정 트리의 깊이를 튜닝해보자.
GridSearchCV를 이용해 결정 트리의 깊이를 조절할 수 있는 파라미터인 max_depth 값을 변화 시키면서 예측 성능을 확이해 보자.
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('GridSearchCV 최고 평균 정확도 수치:{0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)
Fitting 5 folds for each of 7 candidates, totalling 35 fits
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 35 out of 35 | elapsed: 1.6min finished
GridSearchCV 최고 평균 정확도 수치:0.8513
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 16}
max_depth : 8 일때 5-fold 셋의 최고 평균 정확도 결과가 약 85.26%로 나왔다.
5개의 CV 셋에서 max_depth 값에 따라 어떻게 예측 성능이 변했는디 GridSearchCV 객체의 cv_results 속성을 통해 살펴보자.
# 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']]
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 |
결정 트리는 더 완벽한 규칙을 학습 데이터 셋에 적용하기위해 노드를 지속적으로 분할하면서 깊이가 깊어지고 더욱 복잡한 모델이 된다. 깊어진 트리는 학습 데이터 셋에는 올바른 예측 결과를 가져올지 모르지만, 검즘 데이터 셋에서는 오히려 과적합으로 인한 성능 저하를 유발하게 된다.
이번에는 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
[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done 40 out of 40 | elapsed: 2.0min finished
GridSearchCV 최고 평균 정확도 수치: 0.8549
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}
max_depth가 8, min_samples_split가 16 일 때 가장 최고의 정확도로 약 85.5% 를 나타낸다.
앞 에서 GridSearchCV 객체인 grid_cv의 속성인 best_estimator_는 최적 하이퍼 파라미터인 max_depth 8, min_samples_split 16 으로 학습이 완료된 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.8717
max_depth 8 , min_samples_split 16 일 때 테스트 데이터 셋의 예측 정확도는 약 87.17% 이다. 마지막으로 결정 트리에서 각 피처의 중요도를 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=(10,8))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20 , y = ftr_top20.index)
plt.show()
막대 그래프상에서 확인해 보면 이중 가장 높은 중요도를 가진 Top 5의 피처들이 매우 중요하게 규칙생성에 영향을 미치고 있는 것을 알 수 있다.
'머신러닝' 카테고리의 다른 글
LightGBM (0) | 2020.08.21 |
---|---|
앙상블 학습 개요 (0) | 2020.08.20 |
머신러닝 성능 평가 (0) | 2020.08.20 |
numpy, pandas 기초 (0) | 2020.08.20 |
타이타닉 생존자 예측 -사이킷런 (0) | 2020.08.19 |