머신러닝 모델에 따른 평가지표 이해 및 숙달하기
실제값 - 예측값 = 0
이 되면 오차가 없는 것으로, 모델 성능은 당연하게도 오차가 적을수록 좋다.분류
와 회귀
로 나뉘어지며,
- 분류 :
*F1 - score
,*ROC-AUC
,정확도
,정밀도
,재현율
등
- 다중분류 평가지표
- 이중분류 평가지표
- 회귀 :
*R-squared
,*RMSE
,MSE
,MAE
등
- 회귀 평가지표
- 머신러닝 모델은 Train 데이터로 학습 후 Test 데이터로 결과를 확인하는데,
Train 데이터로 학습 후 Test 데이터를 입력해 예측한 결과를 바로 제출하게 되면
해당 모델의 학습이 제대로 되었는지, 더 최적화할수는 없는지 등을 확인할 수 없게 된다.- 따라서, Train 데이터를 학습용과 검증용으로 분리하여 모델의 성능을 확인하고,
최적화한 후에, Test 데이터를 입력한 결과를 제출하는 것이 좋다.
검증용 데이터를 입력한 결과가 최적화가 되어있는지,
모델의 성능이 좋은지 확인하기 위해
평가지표
를 확인해야 한다!
# 머신러닝 예시 코드 import pandas as pd from sklearn.ensemble import RandomForestClassifier # train train = pd.DataFrame({ 'f1': [2, 3, 5, 7, 11, 13, 17, 19, 23, 29], 'f2': [30, 28, 26, 24, 22, 20, 18, 16, 14, 12], 'target': ['A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'A', 'B'] }) # test test = pd.DataFrame({ 'f1': [7, 9, 15], 'f2': [23, 18, 26] }) # target 데이터 분리 target = train.pop('target') # 머신러닝 학습 및 예측 clf = RandomForestClassifier(random_state=0) clf.fit(train, target) pred = clf.predict(test) print(pred)
# 머신러닝 예시 코드(predict) import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import accuracy_score train = pd.DataFrame({ 'f1': [2, 3, 5, 7, 11, 13, 17, 19, 23, 29], 'f2': [30, 28, 26, 24, 22, 20, 18, 16, 14, 12], 'target': ['A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'A', 'B'] }) # 검증 데이터 분리 target = train.pop('target') X_train, X_val, y_train, y_val = train_test_split(train, target, test_size=0.3, random_state=0) # 머신러닝 학습 및 예측 clf = RandomForestClassifier(random_state=0) clf.fit(X_train, y_train) y_pred = clf.predict(X_val) print("val:", y_pred) # 예측값 print("정확도:", accuracy_score(y_val, y_pred)) # 정확도 평가 # test test = pd.DataFrame({ 'f1': [7, 9, 15], 'f2': [23, 18, 26] }) pred = clf.predict(test) print("test:", pred)
# 이진분류 데이터 # 크게 0과 1로 분류되는 실제, 예측값 / 'A', 'B' 등 문자열로 분류되는 실제, 예측값이 있다. import pandas as pd y_true = pd.DataFrame([0, 1, 1, 0, 0, 1, 1, 1, 1, 0]) #실제값 y_pred = pd.DataFrame([0, 0, 1, 1, 0, 0, 0, 1, 1, 0]) #예측값 y_true_str = pd.DataFrame(['B', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'A', 'B']) #실제값 y_pred_str = pd.DataFrame(['B', 'B', 'A', 'A', 'B', 'B', 'B', 'A', 'A', 'B']) #예측값
ROC 분석? (Receiver Operating Characteristic Curve)
민감도와 1 - 특이도 로 그려지는 곡선.
오차행렬표에 대한 이해
오차 행렬은 분류의 예측 범주와 실제 데이터의 분류 범주를 교차 표 형태로 정리한 행렬
민감도와 특이도
ROC Curve에 활용되는 값은
민감도(Sensitivity)
와특이도(Specificity)
!
민감도(SE) = TP / TP + FN
: 실제 양성 (Actual Positive) 대 맞춘 양성 (True Positive) 의 비율
- 양성으로 예측해서 맞췄거나 음성으로 예측했는데 틀린 경우를 합친 것이 실제 양성에 해당
(위 표를 참고!)
음성으로 예측했는데 틀린 경우 (FN)의 비중이 낮을수록 검사의 정확성이 증가하기 때문에
민감도는 클수록 좋다!
특이도(SP) = TN / TN + FP
: 실제 음성 (Actual Negative) 대 맞춘 음성 (True Negative) 의 비율
- 음성으로 예측해서 맞췄거나 양성으로 예측했는데 틀린 경우를 합친 것이 실제 음성에 해당
(위 표를 참고!)
특이도도 민감도와 마찬가지로 클수록 좋다.
실제 양성인 값을 제대로 판단하는 비율이 크더라도 음성인 값을 제대로 걸러내지 못한다면
검사 전체의 정확성이 떨어질 수 있기 때문이다.
특이도의 값이 클수록 실제 음성인 값을 잘 필터링한다는 것이기에 (FP)의 비중이 낮을수록 좋다.
ROC CURVE
X축은 (1 - 특이도), Y축은 민감도가 되는 곡선 그래프
- ROC는 민감도(SE)와 특이도(SP)의 관계를 그래프로 나타낸 것인데,
실제로는 민감도(SE)와 FPR(=1-SP)을 그래프로 나타낸다.
그래야 우리에게 익숙하고 읽기 쉬운 우상향 그래프로 표현되기 때문
AUC
(Area Under the Curve)
AUC는 ROC curve의 곡선 아래 영역을 나타내며 1에 가까울수록 성능이 우수하다고 판단한다.
0.5인 경우 최악의 성능. (모델이 분류 능력이 없다고 보면 된다.)
만약, AUC 값이 0.5 미만이라면 Labeling 또는 Algorithm 이 잘못되었을 가능성이 크다.
AUC 면적에 대한 평가기준 (2005년)
# 머신러닝 예시 코드(predict_proba) import pandas as pd from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score df = pd.DataFrame({ 'f1': [2, 3, 5, 7, 11, 13, 17, 19, 23, 29], 'f2': [30, 28, 26, 24, 22, 20, 18, 16, 14, 12], 'target': ['A', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'A', 'B'] }) target = df.pop('target') X_train, X_val, y_train, y_val = train_test_split(df, target, test_size=0.5, random_state=0) clf = RandomForestClassifier(random_state=42) clf.fit(X_train, y_train) y_pred = clf.predict_proba(X_val) print(y_pred) # 예측값 roc_auc_score(y_val, y_pred[:,1]) # 정확도 평가
# ROC-AUC *** from sklearn.metrics import roc_auc_score import numpy as np # 실제값 (0: 음성, 1: 양성) y_true = pd.DataFrame([0, 1, 0, 1, 1, 0, 0, 0, 1, 1]) # 예측값 중 양성(1) 확률 y_pred_proba = np.array([ [0.8, 0.2], [0.1, 0.9], [0.77, 0.23], [0.6, 0.4], [0.2, 0.8], [0.4, 0.6], [0.6, 0.4], [0.8, 0.2], [0.3, 0.7], [0.4, 0.6] ]) roc_auc = roc_auc_score(y_true, y_pred_proba[:,1]) print("ROC-AUC:", roc_auc)
y_true_str = pd.DataFrame(['B', 'A', 'A', 'B', 'B', 'A', 'A', 'A', 'A', 'B']) #실제값 y_pred_proba_str = np.array([ [0.8, 0.2], [0.1, 0.9], [0.77, 0.23], [0.6, 0.4], [0.2, 0.8], [0.4, 0.6], [0.6, 0.4], [0.8, 0.2], [0.3, 0.7], [0.4, 0.6] ]) # 예측값 roc_auc = roc_auc_score(y_true, y_pred_proba_str[:,1]) print("ROC-AUC:", roc_auc)
정밀도와 재현율을 결합해 만든 지표,
정밀도와 재현율이 어느 한 쪽으로 치우치치 않는 수치를 나타낼 때 F1 스코어는 높은 값을 가진다.
F1 Score = 2 * Precision(정밀도) * Recall(재현율) / (Precision + Recall)
- F1 스코어는 0 ~ 1 사이의 값을 가지며, 1에 가까울수록 성능이 좋다는 것을 의미한다.
# F1 스코어(F1 Score) *** from sklearn.metrics import f1_score f1 = f1_score(y_true, y_pred) print("F1 스코어:", f1)
# 문자열 분류 데이터는 정답이 되는 값(양성값)을 설정해줘야 한다. f1 = f1_score(y_true_str, y_pred_str, pos_label='A') # positive label : 양성값 설정 print("F1 스코어:", f1)
전체 데이터 중 정확하게 예측한 데이터의 수,
정확도는 불균형한 데이터의 경우에는 적합하지 않은 평가지표이다.
정확도 = TP + TN / TP + TN + FP + FN
# 정확도(Accuracy) from sklearn.metrics import accuracy_score accuracy = accuracy_score(y_true, y_pred) print("정확도:", accuracy)
accuracy = accuracy_score(y_true_str, y_pred_str) print("정확도:", accuracy)
# 다중분류 데이터 y_true = pd.DataFrame([2, 2, 3, 3, 2, 1, 3, 3, 2, 1]) # 실제값 y_pred = pd.DataFrame([2, 2, 1, 3, 2, 1, 1, 2, 2, 1]) # 예측값 y_true_str = pd.DataFrame(['B', 'B', 'C', 'C', 'B', 'A', 'C', 'C', 'B', 'A']) # 실제값 y_pred_str = pd.DataFrame(['B', 'B', 'A', 'C', 'B', 'A', 'A', 'B', 'B', 'A']) # 예측값
다중분류에서는 average =
인자에 신경을 써야 한다.
micro
평균은 모든 클래스의 거짓 양성(FP), 거짓 음성(FN), 진짜 양성(TP)의 총 수를 헤아린 다음,
정밀도, 재현율, f1-점수를 이 수치로 계산한다.
macro
평균은 클래스별 f1-점수에 가중치를 주지 않는다.
클래스 크기에 상관없이 모든 클래스를 같은 비중으로 다룬다.
weighted
평균은 클래스별 샘플 수로 가중치를 두어 f1-점수의 평균을 계산한다.
각 샘플을 똑같이 간주한다며 micro
, 각 클래스를 동일한 비중으로 고려한다면 macro
평균이 좋다.
# F1 스코어(F1 Score) *** from sklearn.metrics import f1_score f1 = f1_score(y_true, y_pred, average='macro') # average= micro, macro, weighted print("F1 스코어:", f1) f1 = f1_score(y_true_str, y_pred_str, average='macro') print("F1 스코어:", f1)
accuracy_score
를 임포트하여 (실제값, 예측값) 을 배치한다.
# 다중분류 데이터(확률값) y_true = pd.DataFrame([0, 1, 2, 0, 1]) # 실제값 y_pred_proba = pd.DataFrame([[0.2, 0.5, 0.3], [0.7, 0.2, 0.1], [0.4, 0.3, 0.3], [0.4, 0.1, 0.5], [0.1, 0.8, 0.1]], columns=[0, 1, 2]) # 예측값(각 클래스 확률) y_pred_proba
# 원 핫 인코딩 pd.get_dummies(y_true[0])
ovo
와 ovr
중 인자 선택을 해줘야 한다.
ovo
: one-vs-one (일대일) 을 의미하며 가능한 모든 클래스 쌍 조합의 평균 acu 를 계산,
average = 'macro' 로 설정했을 때 클래스 불균형에 민감하지 않다.
ovr
: one-vs-rest (일대나머지) 를 의미하며 나머지에 대한 각 클래서 auc 를 계산,
클래스 불균형이 'rest'에 영향을 미치기 때문에 average = 'macro'로 설정해도 클래스 불균형에 민감하다.
from sklearn.metrics import roc_auc_score # 수치형일 때는 자동으로 원핫인코딩에서 제외함. 컬럼 지정 필요 y_true_onehot = pd.get_dummies(y_true[0]) # 인코딩된 순서와 확률 컬럼 순서가 같은지 확인 print("y_true의 원-핫 인코딩된 컬럼 순서:", y_true_onehot.columns) print("y_pred_proba의 컬럼 순서:", y_pred_proba.columns) # 'ovo' 방식 roc_score_ovo = roc_auc_score(y_true_onehot, y_pred_proba, multi_class='ovo') print("ROC AUC (OvO):", roc_score_ovo) # 'ovr' 방식 roc_score_ovr = roc_auc_score(y_true_onehot, y_pred_proba, multi_class='ovr') print("ROC AUC (OvR):", roc_score_ovr)
# 다중분류 데이터(확률값) y_true_str = pd.DataFrame(['A', 'B', 'C', 'A', 'B']) # 실제값 y_pred_proba = pd.DataFrame([[0.2, 0.5, 0.3], [0.7, 0.2, 0.1], [0.4, 0.3, 0.3], [0.4, 0.1, 0.5], [0.1, 0.8, 0.1]], columns=['A', 'B', 'C']) # 예측값(각 클래스 확률) from sklearn.metrics import roc_auc_score # 원핫인코딩 y_true_onehot = pd.get_dummies(y_true_str) # 인코딩된 순서와 확률 컬럼 순서가 같은지 확인 print("y_true의 원-핫 인코딩된 컬럼 순서:", y_true_onehot.columns) print("y_pred_proba의 컬럼 순서:", y_pred_proba.columns) # 'ovo' 방식 roc_score_ovo = roc_auc_score(y_true_onehot, y_pred_proba, multi_class='ovo') print("ROC AUC (OvO):", roc_score_ovo) # 'ovr' 방식 roc_score_ovr = roc_auc_score(y_true_onehot, y_pred_proba, multi_class='ovr') print("ROC AUC (OvR):", roc_score_ovr)
model.classes_
from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split # 예제 데이터 생성 X, y = make_classification(n_samples=1000, n_features=20, n_informative=15, n_redundant=5, n_classes=3, random_state=0) X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) # RandomForestClassifier 모델 생성 및 훈련 rf = RandomForestClassifier(random_state=0) rf.fit(X_train, y_train) # 검증 데이터에 대한 클래스 확률 예측 pred_proba = rf.predict_proba(X_val) pred_proba # 클래스 확률 출력 print(pred_proba[:3]) # 훈련 데이터에 있는 클래스 레이블의 순서 출력 print("클래스 레이블의 순서:", rf.classes_)
# 각 클래스의 확률을 데이터프레임의 컬럼으로 만들기 import pandas as pd pred_proba = rf.predict_proba(X_val) result_df = pd.DataFrame() result_df['A'] = pred_proba[:, 0] result_df['B'] = pred_proba[:, 1] result_df['C'] = pred_proba[:, 2] result_df.head(3)
# 회귀 데이터 import pandas as pd y_true = pd.DataFrame([0, 2, 5, 2, 4, 4, 7, 10]) # 실제값 y_pred = pd.DataFrame([1.14, 2.53, 4.87, 3.08, 4.21, 5.53, 7.51, 10.32]) # 예측값
평균 절대 오차
실제 값과 예측 값의 차이를 절대값으로 변환하여 평균한 값.
즉, 모든 오차의 절대값을 취한 평균이 된다.
MAE는 △X
(△X (error) = xi-x / xi - Prediction value , x ; real value)
의 절대값을 취하여
에러의 크기가 그대로 반영이되는 지표이다.
그렇기 때문에 예측 결과물의 에러가 10이 나온것이 5로 나온것 보다 2배가 나쁜 도메인에서 쓰기 적합하다.
이상치(abnormal value)가 많을때 사용하면 용이하다.
에러의 크기가 그대로 반영되기때문에 수치가 낮을수록 정확성이 높다는것으로 생각하면 된다.
(실제값과 예측값의 에러가 낮다 -> 모델이 근사적으로 일치하는 모델이 된다)
코드
from sklearn.metrics import mean_absolute_error
mean_absolute_error(y_test,y_pred)
+)
# MAE(Mean Absolute Error) from sklearn.metrics import mean_absolute_error mae = mean_absolute_error(y_true, y_pred) print("MAE:", mae)
실제 값과 예측 값의 차이를 제곱하여 평균한 값.
MSE는 △X
(△X (error) = xi-x / xi - Prediction value , x ; real value)
의 넓이의 합의 평균
특이값이 존재하면 수치가 많이 늘어난다 (제곱하기 때문에)
통계적 추정의 정확성에 대한 질적인 척도로 수치가 작을수록 정확성이 높은것으로 판단한다.
코드
from sklearn.metrics import mean_squared_error
mean_squared_error(y_test, y_pred)
+)
# MSE(Mean Squared Error) from sklearn.metrics import mean_squared_error mse = mean_squared_error(y_true, y_pred) print("MSE:", mse)
MSE 값은 Error의 제곱의 평균으로 실제 Error보다 큰 Error들의 평균을 가지는 특성이 있다.
그렇기 때문에 MSE값에 √ root를 씌워 RMSE 값을 사용하게 된다. (정밀도의 개념)
MAE와 MSE의 장점을 모두 가지고 있다.
Error에 제곱하여 루트N으로 나누게 되어 가중치는 높아진다. 다시말해 큰 Error값에 대해 패널티를 크게준다.
즉 MSE보다 RMSE가 더 RUBUST하다고 말할수 있다.
Error 손실이 기하급수적으로 올라가는 상황에서 자주 사용하게 된다.
(머신러닝에서 specific 한 value에 영향을 덜 받기 위해 자주 사용.)
RMSE값이 적을 수록 더 좋은 성능이라고 평가할 수 있다.
코드
import numpy as np
from sklearn.metrics import mean_squared_error
MSE = mean_squared_error(y_test, y_pred)
np.sqrt(MSE)
# sklearn 은 mse만 제공하기 때문에 rmse는 직접 만들어 써야한다.
# RMSE(Root Mean Squared Error) *** from sklearn.metrics import mean_squared_error mse = mean_squared_error(y_true, y_pred) rmse = mse ** 0.5 print("RMSE:", rmse)
R2 (R-squared) - 분산기반 예측 성능 평가
RMSE는 에러의 크기에 대한 정보는 주지만, 실제 데이터 값의 크기의 정보는 주지 않는다.
즉 RMSE의 값이 같더라도 , 데이터 값의 크기가 서로 다르면 성능은 다르게 평가되어야 하는데
이 때 사용하는것이 R2 평가 모델이다.
에러의 크기(주로 MSE)를 데이터의 크기(주로 데이터의 분산데이터)로 선택하여 나누어준다.
R2의 성능은 1에 가까울 수록 좋은 성능을 나타낸다.
코드
from sklearn.metrics import r2_score
r2 = r2_score(y, lr.predict(x_2))
+)
# 결정 계수(R-squared) *** from sklearn.metrics import r2_score r2 = r2_score(y_true, y_pred) print("결정 계수:", r2)