# True positive란?
정답이라고 예측했고 정답이였다.
positive라고 예측했고 진짜 positive 였다
# False Negative란?
정답이 아니라고 예측했고 아니였다.
negative라고 예측했으나 positive였다.
# ROC 곡선이 가운데 직선에 가까울수록 성능이 떨어지며, 멀수록 성능이 좋다고 여겨진다
O
# 정밀도와 재현율은 상호 보완적인 평가지표로 한쪽을 강제로 높이면 다른쪽도 올라간다
x
# from sklearn.metrics import
# 로 혼동행렬, 정확도, 정밀도, 재현율을 불러오세요
confusion_matrix, accuracy_score, precision_score, recall_score
# 불균형한 데이터란 어느 한쪽이 적은 데이터를 말한다
O
# Recall 과 Precision은 ()관계이다
반비례
trade off
# 보기에서 틀린것을 고르세요
# 1. Positive 예측값이 많아지면 상대적으로 재현율 값이 높아진다
# 2. 임계값이 낮아질수록 Positive로 예측할 확률이 높아진다
# 3. "암 환자중에 검사로 얼마나 암 환자를 잘 골라냈는가?"는 재현율과 관련이 없다
# 4. TP/(TP+FN)은 재현율의 공식이다
# 5. ROC 곡선은 X축으로 특이성을, Y축으로는 민감도를 설정한다
3
# 보기에서 틀린것을 고르세요
# 1. 정밀도와 재현율은 상호 보완적인 평가지표이다
# 2. 정밀도를 올리면 재현율이 떨어지는 현상을 트레이드 오프라 한다
# 3. "정상인 중 얼마나 많은 사람을 암환자로 오진했는가?" 는 특이도와 관련이 있다.
# 4. 임계값을 낮출수록 True 값이 적어진다
# 5. ROC 곡선 면적에 기반한 AUC 값은 1에 가까울수록 좋은 수리라 여겨진다
4
# 특이성(specificity) : TN / (FP + TN)
# 보기에서 틀린것을 고르세요
# 1. 이진 분류 모델에서는 예측확률이 클 레이블 값으로 예측한다
# 2. 재현율이 중요한 경우에는 양성지표를 Negative로 잘못 판단시 큰 영향을 가지는 경우이다
# 3. F1 스코어는 정밀도와 재현율을 결합한 지표이다
# 4. F1 스코어는 정밀도와 재현율이 큰 차이를 가질수록 높은 값을 가지낟
# 5. FPR을 0으로 만드려면 임계값을 1로 지정한다.
# 보기에서 틀린것을 고르세요
# 1. 정밀도가 중요한 경우는 음성데이터를 양성으로 잘못 판단시 큰 영향이 발생하는 경우이다
# 2. TP/(TP+TP)는 정밀도의 공식이다
# 3. True Positive Rate는 재현율이다
# 4. 전체 환자를 Positive라 예측하고 실제 양성인 환자가 n명이라면 재현율은 0이된다.
# 5. 전체 환자중 1명만 Positive라 예측하고 나머지를 전부 Negative라 예측하면 정밀도는 1이 된다.
4
# 정밀도 재현율(민감도) 특이성에 대한 수식을 쓰세요
정밀도 = TP / (TP + FP)
재현율 = TP / (TP + FN)
특이성 = TN / (FP + TN)
# 정밀도나 재현율이 극단적인 차이를 가질 경우 좋은 평가 지표가 될 수 없기 때문에
# 둘을 결합한 지표는 무엇인가
F1 score
(위키북스의 '파이썬 머신러닝 완벅 가이드' 개정 2판으로 공부하고 있습니다.)
먼저 이전에 사용한 get_clf_eval()함수에 AOC AUC 값을 측정하는 로직을 추가하면서, 함수의 인자를 늘려준다.
# 평가 지표 함수
# 오차행렬, 정확도, 정밀도, 재현율을 한꺼번에 계산한다
from sklearn.metrics import accuracy_score, precision_score , recall_score , confusion_matrix
def get_clf_eval(y_test, pred = None, pred_proba = None) :
confusion = confusion_matrix(y_test, pred) # 오차행렬
accuracy = accuracy_score(y_test, pred) # 정확도
precision = precision_score(y_test, pred) # 정밀도
recall = recall_score(y_test, pred) # 재현율
f1 = f1_score(y_test, pred) # F1 스코어
# ROC-AUC
roc_auc = roc_auc_score(y_test, pred_proba)
print('오차행렬')
print(confusion)
# ROC-AUC 추가
print(f'정확도:{accuracy:.4f}, 정밀도:{precision:.4f}, 재현율:{recall:.4f}, f1_score:{f1:.4f}, AUC:{roc_auc:.4f}')
사용할 라이브러리들 로드
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, precision_score, recall_score, roc_auc_score
from sklearn.metrics import f1_score, confusion_matrix, precision_recall_curve, roc_curve
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
데이터를 읽어와서 0/1 값과 대략적인 데이터 모양을 확인한다.
diabetes_data = pd.read_csv('pima_diabetes.csv')
print(diabetes_data['Outcome'].value_counts())
diabetes_data[:3]

Pregnancies : 임신횟수
Glucose : 포도당 부하 검사 수치
BloodPressure : 혈압
SkinThickness : 팔 삼두근 뒤쪽의 피하지방 측정값(mm)
Insulin : 혈청 인슐린(mu U/ml)
BMI : 체질량지수(체중(kg)/키(m))^2)
DiabetesPedigreeFunction : 당뇨 내력 가중치 값
Age : 나이
Outcome : 클래스 결정 값(0 or 1)
0 500
1 268
Negative 값(0)이 500개, Positive 값(1)이 268개로 0의 값이 상대적으로 많다. 음... 이정도 불균형은 괜찮은 걸까?
diabetes_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 Pregnancies 768 non-null int64
1 Glucose 768 non-null int64
2 BloodPressure 768 non-null int64
3 SkinThickness 768 non-null int64
4 Insulin 768 non-null int64
5 BMI 768 non-null float64
6 DiabetesPedigreeFunction 768 non-null float64
7 Age 768 non-null int64
8 Outcome 768 non-null int64
dtypes: float64(2), int64(7)
memory usage: 54.1 KB
# feature data set : X
# label data set : Y
# 가장 마지막은 Outcome 칼럼으로 레이블 값이기 때문에 X, Y에 적절히 담기
X = diabetes_data.iloc[:, :-1]
y = diabetes_data.iloc[: ,-1]
# train-test set 나누기
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state=156)
# 확인
X_train
print(len(X_train), len(X_test))
614 154
# 로지스틱 회귀로 학습/예측/평가 를 수행하겠다
lr_clf = LogisticRegression(solver = 'liblinear')
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
# output이 두개가 나오는데 뒤( =1값 =positive값)만 취하겠다.
pred_proba = lr_clf.predict_proba(X_test)[:,1]
get_clf_eval(y_test, pred, pred_proba)
오차행렬
[[87 10]
[26 31]]
정확도:0.7662, 정밀도:0.7561, 재현율:0.5439, f1_score:0.6327, AUC:0.8343
이때 책에서는 lr_clf = LogisticRegression(solver = 'linear')라고 되어있는데 (solver = 'liblinear') 의 모양으로 사용해주어야 제대로 나온다. (작은 데이터에 적합한 알고리즘 설정값이라 하며, L1 L2 제약조건을 모두 지원한다고)
diabetes_data.describe() 데이터 값을 보면 min() 값이 0으로 되어 있는 feature가 생각보다 많다. 예를들어 포도당 수치인 Glucose 칼럼에도 0값이 들어있는데 이는 말이 되지를 않기 때문에 이 값들을 모두 평균으로 대체하기로 한다.
# 0값 검사해보기
zero_features = ['Glucose', 'BloodPressure', 'SkinThickness', 'Insulin', 'BMI']
# 전체 데이터 수
total_count = diabetes_data['Glucose'].count()
# feature별로 반복하면서 데이터 값이 0인 건수를 추출하고 퍼센트로 확인해본다.
for feature in zero_features :
zero_cnt = diabetes_data[diabetes_data[feature] == 0][feature].count()
print(f'{feature}\n0 건수는 {zero_cnt:.4f}, 퍼센트는 {100*zero_cnt/total_count:.4f}')
print()
Glucose
0 건수는 5.0000, 퍼센트는 0.6510
BloodPressure
0 건수는 35.0000, 퍼센트는 4.5573
SkinThickness
0 건수는 227.0000, 퍼센트는 29.5573
Insulin
0 건수는 374.0000, 퍼센트는 48.6979
BMI
0 건수는 11.0000, 퍼센트는 1.4323
# 0을 각 칼럼값의 평균으로 대체
zero_mean = diabetes_data[zero_features].mean()
diabetes_data[zero_features] = diabetes_data[zero_features].replace(0, zero_mean)
# 확인
diabetes_data[zero_features]

X = diabetes_data.iloc[: ,:8]
y = diabetes_data.iloc[: ,8]
# 표준화
# 일괄적으로 스케일링 적용
scaler = StandardScaler()
X = scaler.fit_transform(X)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state=156)
# 로지스틱 회귀로 학습/예측/평가 를 수행
lr_clf = LogisticRegression(solver = 'liblinear')
lr_clf.fit(X_train, y_train)
pred = lr_clf.predict(X_test)
# output이 두개가 나오는데 뒤( =1값 =positive값)만 취하겠다.
pred_proba = lr_clf.predict_proba(X_test)[:,1]
get_clf_eval(y_test, pred, pred_proba)
오차행렬
[[83 14]
[25 32]]
정확도:0.7468, 정밀도:0.6957, 재현율:0.5614, f1_score:0.6214, AUC:0.8318
음... 이전
정확도:0.7662, 정밀도:0.7561, 재현율:0.5439, f1_score:0.6327, AUC:0.8343
평균값으로 대체한 후
정확도:0.7468, 정밀도:0.6957, 재현율:0.5614, f1_score:0.6214, AUC:0.8318
로 결과가 나왔다. 이외에도 전체적으로 책과는 다르게 수치가 떨어진 모습을 확인할 수 있었다. 그렇다면 앞에서 임계값을 낮춘상태에서 예측을 하면 결과가 어떻게 나올까. 사이킷런의 predict() 메서드는 임계값을 마음대로 변환할 수 없으므로 별도의 로직으로 이를 구연해야 한다. 앞에서 살펴본 Binarizer 클래스를 이용해 predict_proba()로 추출한 예측 결과 확률 값을 변환하여 변경된 임계값에 따른 예측 클래스 값을 구해보자(결과가 궁금하다)
from sklearn.preprocessing import Binarizer
# 임계값을 0.48로 설정
binarizer = Binarizer(threshold = 0.48)
pred_proba = lr_clf.predict_proba(X_test)[:,1]
pred_th_048 = binarizer.fit_transform(pred_proba.reshape(-1, 1))
get_clf_eval(y_test, pred_th_048, pred_proba)
오차행렬
[[83 14]
[25 32]]
정확도:0.7468, 정밀도:0.6957, 재현율:0.5614, f1_score:0.6214, AUC:0.8318
결과적으로
그냥 했을 때
정확도:0.7662, 정밀도:0.7561, 재현율:0.5439, f1_score:0.6327, AUC:0.8343
평균값으로 대체한 후
정확도:0.7468, 정밀도:0.6957, 재현율:0.5614, f1_score:0.6214, AUC:0.8318
임계값 변화를 주었을때
정확도:0.7468, 정밀도:0.6957, 재현율:0.5614, f1_score:0.6214, AUC:0.8318
음............
ML 알고리즘 중 직관적으로 이해하기 쉬운 알고리즘이다. 분류와 회귀가 모두 가능하며(범주형 연속형 수치를 모두 예측할 수 있다는 말) 범주예측, 즉 분류과정은 학습을 통해 데이터에 있는 규칙을 자동으로 찾아내 트리 기반의 분류 규칙을 만드는 것이며, 규칙을 가장 쉽게 표현하는 방법은 if/else를 기반으로 나타내는 것이다.

규칙 노드(Decision Node)는 규칙 조건이 되고 리프 노드(Leaf Node)는 결정된 클래스 값이며, 새로운 규칙 조건마다 서브 트리(Sub Tree) 가 생성된다. Data Set에 피처가 있고 이러한 피처가 결합해 규칙 조건을 만들 때마다 새로운 규칙 노드가 생성된다.
하지만 많은 규칙이 있다는 것은 곧 분류를 결정하는 방식이 복잡해진다는 얘기이고 이는 곧 과적합으로 이어질 가능성이 높아진다.
→ 트리의 깊이(Depth)가 깊어질 수록 예측 성능이 저하될 가능성이 있다는 말이다.

결정트리를 분할 할때는 최대한 균일하게 데이터 셋을 구성할 수 있도록 분할하는 노력이 필요함. 위 그림에서 균일한 데이터의 순서를 매기자면 정보의 균일도가 높은 C>B>A 순이 될것이다.
데이터 C에서는 하나의 공을 뽑았을 때 별다른 예측 정보가 없어도 검은색 공이라고 쉽게 예측해 볼수 있으나
데이터 A에서는 데이터를 판단하기 위해 더 많은 정보가 필요하다
이렇게 정보의 균일도는 예측에 영향을 끼치게 되는데, 결정 노드는 정보의 균일도가 높은 데이터 셋을 먼저 선택할 수 있도록 규칙 조건을 만든다. 이러한 '정보의 균일도'를 측정하는 대표적인 방법으로는 정보 이득 과 지니계수가 있다.
결정트리는 정보의 균일도라는 룰을 기반으로 하기 때문에 알고리즘이 쉽고 직관적이며, 정보의 균일도만 신경쓰면 되기 때문에 (스케일링과 같은) 데이터 전처리 과정에 얽매일 필요가 앖다. 하지만 과적합으로 인해 정확도가 떨어질 확률이 높아 유의 해야한다.
| 결정 트리의 장점 | 결정 트리의 단점 |
|---|---|
| 쉽다. 직관적이다. 피처의 스케일링이나 정규화 등의 사전 가공 영향도가 크지 않다. |
과적합으로 알고리즘 성능이 떨어진다. 이를 극복하기 위해 트리의 크기를 사전에 제한하는 튜닝이 필요 |
위와 같이 순도 100%가 될 때까지 분할하며 찾을 수 도 있지만, 이렇게 Train Data 기반 모델의 정확도를 높이기 위해 계속하여 조건을 추가하다보면 결국 트리 깊이가 커지고(깊어지고), 결과적으로는 복잡한 학습 모델에 이르게 된다. 이는 실제 상황(Test Data Set)에 유연하게 대처할 수 없어서 오히려 예측 성능이 떨어지는 결과를 초래할 수 있다.
→ 즉, 한없이 훈련시키지 말고 트리의 크기를 사전에 제한하는 것이 오히려 성능 튜닝에 더 도움을 주는 것이다.
정보 이득(Information Gain)1 - 엔트로피 지수지니 계수엔트로피란 확률변수의 불확실성을 수치로 나타낸 것으로 엔트로피가 높을수록 불확실성이 높다고 볼 수 있다. 아래와 같은 식으로 정의된다.
(Pk = A 영역에 속하는 레코드 가운데에 k 범주에 속하는 레코드 비율)

동전을 던졌을 때 앞/뒷면이 나올 확률이 동일하게 1/2이라면

으로 계산해볼 수 있다. 앞면이 나올 확률이 0.25라면 뒷면이 나올 확률이 0.75일거고 이때 엔트로피는

위 아래 같은 크기의 영역으로 분할한 후 엔트로피를 구하면 이전보다 0.2만큼 감소한 것을 확인할 수 있는데, 이는 불확실성의 감소가 데이터의 순도를 증가시키고 이는 정보를 획득하는데 유의미한 결과를 가져온 것이라 볼 수 있다.

(따라서 모델은 분할하는 것이 분할하기 전보다 더 좋다는 판단을 내리고 데이터를 두 개의 부분집합으로 분할하게 된다.)
→ 한 줄 정리
엔트로피 지수 : 주어진 데이터 집합의 혼합(혼잡)도를 의미하며 서로 다른 값들이 많이 섞여있을 수록 값이 커지고, 같은 값이 섞여있으면 값이 낮아진다
지니 계수 : 불평등 지수로 값이 0일때 가장 평등하고 1에 가까워질수록 불평등해진다.
모든 데이터가 같은 값을 가지면 엔트로피 지수는 0이 되며, 머신러닝에서는 각 영역의 지니계수가 낮은 속성을 기준으로 분할 한다.
분류를 위한 DecisionTreeClassifer 와 회귀를 위한 DecisionTreeRegressor 모두 파라미터는 동일하다.
| 파라미터 명 | 설명 |
|---|---|
| min_samples_split | ✔ 노드를 분할하기 위한 최소한의 샘플 데이터 수로 과적합을 제어하는 데 사용됨 ✔ 디폴트는 2이고 작게 설정할 수록 분할되는 노드가 많아져서 과적합 가능성 증가 |
| min_samples_leaf | ✔ 분할이 될 경우 왼쪽과 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터 수 ✔ 큰 값으로 설정될수록, 분할될 경우가 왼쪽과 오른쪽의 브랜치 노드에서 가져야 할 최소한의 샘플 데이터 수 조건을 만족시키기 어려우므로 노드 분할을 상대적으로 덜 수행함 ✔ min_samples_split와 유사하게 과적합 제어용도이다. 그러나 비대칭적 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있으므로 이 경우는 작게 설정 필요 |
| max_features | ✔ 최적의 분할을 위해 고려할 최대 피처 갯수. 디폴트는 None으로 데이터 세트의 모든 피처를 사용해 분할 수행 ✔ int 형으로 지정하면 대상 피처의 개수, float형으로 지정하면 전체 피처 중 대상 피처의 퍼센트 ✔ sqrt 는 전체 피처 중 sqrt(전체 피처 갯수) 만큼 선정 ✔ auto 로 지정하면 sqrt와 동일 ✔ 'log'는 전체 피처 중 log2(전체 피처 개수) 선정 ✔ None은 전체 피처 선정 |
| max_depth | ✔ 트리의 최대 깊이를 규정 ✔ 디폴트는 None. 완벽하게 클래스 결정 값이 될 때까지 깊이를 계속 키우며 분할하거나 노드가 가지는 데이터 개수가 min_samples_splt보다 작아질 때까지 계속 깊이를 증가시킨다 ✔ 깊이가 깊어지면 min_samples_splt설정대로 최대 분할하여 과적합 할 수 있으므로 적절한 값으로 제어 필요 |
| max_leaf_nodes | 말단 노드(Leaf)의 최대 개수 |
실습 데이터는 UCI 머신러닝 리포지토리(Machine Learning Repository)에서 제공하는 '사용자 행동 인식(Human Activity Recognition)' 데이터 셋으로 한다. 해당 데이터는 30명에게 스마트 폰 센서를 작착한 뒤 사람의 동작과 관련한 여러가지 feature를 수집한 데이터이다.
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
# features.txt 파일에는 피처 이름 index와 피처명이 공백으로 분리되어 있음. 이를 DataFrame으로 로드.
feature_name_df = pd.read_csv('./UCI HAR Dataset/UCI HAR Dataset/features.txt',sep='\s+',
header=None,names=['column_index','column_name'])
feature_name_df[:3]

feature_name_df.info()
>
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 561 entries, 0 to 560
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 column_index 561 non-null int64
1 column_name 561 non-null object
dtypes: int64(1), object(1)
memory usage: 8.9+ KB
features.txt에는 561개의 index와 name이 저장되어 있음을 확인할 수 있다. 하지만 여기에는 중복된 피처명이 존재하기 때문에 이를 확인하고 변경해주는 작업이 필요함을 느낄 수 있다.
# 중복확인
# column_name으로 그룹지어서 수를 센 후 column_index로 정렬시킨다
temp = feature_name_df.groupby("column_name").count().sort_values(by="column_index", ascending=False)
# 만약 컬럼 인덱스가 1 이상이라면 그 값을 반환해준다
feature_dup = temp[temp["column_index"] > 1]
print("중복된 피처 수:", feature_dup.count()[0])
feature_dup.head()

# 중복된 피처명 정리
def get_new_feature_name_df(old_feature_name_df):
# column_name으로 그룹지어서 cumcount()로 피처별 중복 존재시 숫자를 부여
# reset_index()로 column_index를 생성한다
feature_dup = pd.DataFrame(old_feature_name_df.groupby("column_name").cumcount()).reset_index()
# features.txt의 column_index는 1부터 시작이기 때문에
feature_dup.columns = ["column_index", "dup_cnt"]
feature_dup["column_index"] = feature_dup["column_index"] + 1
# column_index를 기준으로 머지 후 중복컬럼명 변경
new_feature_name_df = pd.merge(old_feature_name_df, feature_dup, how='outer')
# 만약 x라는 컬럼명이 있다면 x_1, x_2와 같이 변경되도록 한다
new_feature_name_df['column_name'] = new_feature_name_df.apply(\
lambda x: x.column_name + "_" + str(x.dup_cnt)
if x.dup_cnt > 0
else x.column_name, axis=1)
return new_feature_name_df
데이터 불러오는 함수 작성
import pandas as pd
def get_human_dataset( ):
# 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
feature_name_df = pd.read_csv('./UCI HAR Dataset/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('./UCI HAR Dataset/UCI HAR Dataset/train/X_train.txt',sep='\s+', names=feature_name )
X_test = pd.read_csv('./UCI HAR Dataset/UCI HAR Dataset/test/X_test.txt',sep='\s+', names=feature_name)
# 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
y_train = pd.read_csv('./UCI HAR Dataset/UCI HAR Dataset/train/y_train.txt',sep='\s+',header=None,names=['action'])
y_test = pd.read_csv('./UCI HAR Dataset/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()
그래프 그리기
import seaborn as sns
plt.figure(figsize=(10,5))
frequency = y_train['action'].value_counts()
label = []
for key, value in frequency.to_dict().items():
label.append(f"{key}: {value}")
plt.pie(frequency,
startangle = 180,
counterclock = False,
explode = [0.03] * len(label),
autopct = '%1.1f%%',
labels = label,
colors = sns.color_palette('pastel', len(label)),
wedgeprops = dict(width=0.7)
)
plt.axis('equal')
plt.show()

feature 레이블 값 확인. 1, 2, 3, 4, 5, 6 총 6개의 값을 가졌고, 특정한 값으로 쏠리기보다는 비교적 고르게 분포되어있음을 확인할 수 있었다.
하이퍼 파라미터 별 디폴트 성능 평가. 사이킷 런의 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))
결정 트리 예측 정확도: 0.8548 로 약 58.48% 의 정확도가 나왔다.
# DecisionTreeClassifier의 하이퍼 파라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())
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'}
다음은 GridSearchCV 를 사용하여 성능 평가를 했는데. n_jobs에 따른 차이가 궁금했다. (n_jobs = 디폴트 값은 1이며, 수를 늘릴수록 그만큼 CPU 코어를 사용하여 멀티 프로세스를 진행시킨다. 따라서 속도가 증가한다.. 고 되어있었기에 %%time를 사용하여 체크해보기로)
%%time
from sklearn.model_selection import GridSearchCV
params = {
'max_depth' : [6, 8, 10, 12, 16, 20, 24],
'min_samples_split' : [16]
}
# n_jobs = -1 = 있는 cpu 모두 쓰겠다.
grid_cv = GridSearchCV(dt_clf, param_grid = params, scoring='accuracy', cv = 5, verbose = 3, n_jobs = -1)
grid_cv.fit(X_train, y_train)
print(f'GridSearchCV 최고 평균 정확도 수치 : {grid_cv.best_score_:.4f}')
Fitting 5 folds for each of 7 candidates, totalling 35 fits
GridSearchCV 최고 평균 정확도 수치 : 0.8549
CPU times: total: 3.16 s
Wall time: 33.7 s
time이 [3.16 s , 33.7s]로 잡혔다. 다른건 동일한 상황에서 n_jobs만 변경해보자
%%time
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 = 3, n_jobs = 1)
grid_cv.fit(X_train, y_train)
print(f'GridSearchCV 최고 평균 정확도 수치 : {grid_cv.best_score_:.4f}')

어.... 굉장히 뭐가 많이 출력되었다. 마지막에 있는 시간만 확인해보면 [1min 43s, 1min 44s]로 확연히 차이가 나는 걸 확인할 수 있었다!
GridSearchCV 최고 평균 정확도 수치 : 0.8549 의 결과값을 통해 약 85.55이 정확도를 보였음을 알 수 있었다.
print("GridSearchCV 최적 하이퍼 파라미터:", grid_cv.best_params_)
>
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}
표로 비교, 확인해볼 수도 있었다.
cv_results_df = pd.DataFrame(grid_cv.cv_results_)
cv_results_df[["param_max_depth", "mean_test_score"]]
