본문 바로가기
AI 공부/머신러닝

(머신러닝) 성능측정

by AI Sonny 2022. 9. 6.
728x90

모델 성능 평가지표(Metric)

  • 실제값과 모델에 의해 예측된 값을 비교하여 모델의 성능을 측정하는 것
  • 모델 평가 목적: Over FItting을 방지하고 최적의 모델을 찾기 위해서

 


회귀 문제 성능측정

 

사이킷런에서 제공해주는 데이터셋

  • 당뇨병 진행도를 예측하는 데이터셋

 

데이터 가져오기

 

from sklearn.datasets import load_diabetes
diabetes = load_diabetes()
diabetes.data, diabetes.target

 

학습셋과 검증셋 분리

 

SEED = 42
from sklearn.model_selection import train_test_split

x_train, x_valid, y_train, y_valid = train_test_split(data,target,random_state = SEED)
x_train.shape, x_valid.shape, y_train.shape, y_valid.shape

=> ((331, 10), (111, 10), (331,), (111,))

 

Linear Regression 모델을 이용한 학습 및 예측

 

from sklearn.linear_model import LinearRegression

model = LinearRegression()
model.fit(x_train,y_train)
pred = model.predict(x_valid)
pred

=>
array([137.94979889, 182.53621462, 129.85554049, 292.55738727,
       124.86559124,  89.73987275, 255.96040544, 177.64691719,
        87.88678516, 107.93733249,  93.26418526, 171.11859131,
        56.06450108, 207.22092723,  99.79206335, 131.71477524,
       215.86564416, 252.18825848, 201.56370519, 220.38853067,
       204.35710556,  88.90708813,  68.53068596, 190.02202826,
       153.9129781 , 164.02499711, 192.83524491, 187.16510251,
        46.60505083, 109.31176844, 175.05178809,  87.8091125 ,
       130.37787111, 186.56591449, 172.474085  , 188.61455112,
       124.0734688 , 119.16492145, 147.74614571,  59.27160039,
        70.90301607, 109.30498566, 170.97369589, 156.56765896,
       168.68372176,  61.54562801,  71.19297293, 111.94403965,
        54.31821851, 165.81456881, 152.78238871,  63.80826526,
       110.07732165, 108.6406539 , 179.16116053, 156.79589523,
        93.28778814, 212.92031906, 119.28369688,  70.70909587,
       186.68094494, 205.65924203, 140.02745143, 106.54570273,
       131.56044364, 204.17886366, 173.28771583, 168.65292359,
       123.58530603, 145.48887871, 182.55324099, 200.92532802,
       234.1265505 , 148.65425454,  82.2675761 , 162.10939001,
       191.415392  , 208.42448442, 159.81021311, 207.0829118 ,
       108.51857719, 141.86705133,  51.8871943 ,  52.84276976,
       114.22398715,  77.83689884,  82.21006125,  56.81868815,
       167.18534084, 188.0216126 , 153.36919669, 242.28745175,
       108.00705808,  63.19263597,  55.15197266, 197.58205898,
       248.94621618, 184.27491828, 104.75983131,  63.4507554 ,
       196.41823897, 110.04940238, 296.72023354,  98.13506422,
       151.08851299, 105.09389123, 131.61291086, 128.2943136 ,
       165.9988908 , 185.58555347, 111.88333319])

 


회귀 평가 지표 

 

회귀 평가 지표는 이해하고, 각 특징을 파악하려 잘 쓰기만 하면 된다.

 

MSE(Mean Squared Error)

  • 실제값과 예측값의 차이를 제곱한 뒤 평균화
  • 이상치에 민감
  • 직관적이지 못하다.
  • 평가지표로 사용하는 것보다 주로 손실함수로 사용된다.
from sklearn.metrics import mean_squared_error # 예측값과 정답값의 차이
mse = mean_squared_error(y_valid,pred)
mse

=> 2848.2953079329445

 

RMSE(Root Mean Squared Error)

  • MSE에 루트
  • 이상치에 민감
  • 평가지표로 많이 사용
import numpy as np # 예측값과 정답값의 차이
np.sqrt(mse)

=> 53.36942296795932

 

MAE(Mean Absolute Error)

  • 실제값과 예측값의 차이를 절대값으로 변환해서 평균화
from sklearn.metrics import mean_absolute_error # 예측값과 정답값의 차이
mae = mean_absolute_error(y_valid,pred)
mae

=> 41.548363283252066

 

MAPE(Mean Absolute Percentage Error)

  • 실제값에 대한 절대오차 비율의 평균을 퍼센트로 표현
  • y가 0에 가까워질수록 분모가 작아지므로 수치가 커진다.
  • y가 0이면 계산이 안된다. (0을 제외하고 계산)
def mape(true,pred):
    return np.mean(np.abs((true - pred) / true))
mape(y_valid,pred)

=> 0.3731095161631557

 

SMAPE(Symmetric Mean Absolute Percentage Error)

  • 기존의 MAPE의 단점을 보완
  • MAPE와 다른점은 각 실제값과 예측값을 절대값으로 변경 후 합으로 나눈다.
  • MAPE와 다르게 실제값의 0이 존재해도 계산이 가능하다.
  • 예측값이 실제값보다 작을 때, 분모가 작아지기 때문에 오차가 커지게 된다. 즉, 과소추정에 대한 패널티를 줄 수 있다.
def smape(true,pred):
    error = np.abs(true-pred) / (np.abs(true) + np.abs(pred))
    return np.mean(error)
smape(y_valid,pred)

=> 0.15271360402048711

 

평가지표는 대부분 1에 가까울수록 좋다.

 


분류문제 성능 측정

 

사이킷런에서 제공해주는 데이터셋

  • 0~9 손글씨 이미지 받아오기
from sklearn.datasets import load_digits
digits = load_digits()

 

8 X 8 크기의 이미지가 Flatten 되어있다.

 

digits.target[:4]

=> array([0, 1, 2, 3])

 

슬라이싱을 이용하여 값 확인

 

import matplotlib.pyplot as plt
fig,ax = plt.subplots(2,2)
ax[0,0].imshow(digits.data[0].reshape(8,8),cmap = "gray")
ax[0,1].imshow(digits.data[1].reshape(8,8),cmap = "gray")
ax[1,0].imshow(digits.data[2].reshape(8,8),cmap = "gray")
ax[1,1].imshow(digits.data[3].reshape(8,8),cmap = "gray")
plt.show()

 

위 코드의 결과

 

  • 3을 맞추는 문제로 재정의
data = digits.data
target = (digits.target == 3).astype(int)
target.mean() # 0과 1의 수에 대한 평균

=> 0.1018363939899833

 


혼동행렬(Confusion Matrix)

  • 이진분류에서 성능지표로 활용되는 행렬
  • 이진 분류에서 예측 오류가 어느정도 되는지와 어떠한 유형의 예측 오류가 발생하는지를 나타내는 지표
  • Actual: 실제값, predicted: 모델의 예측값
  • Precision (정밀도)
    • 양성(1)으로 예측한 값들 중에 맞춘 비율
  • Recall (재현율)
    • TPR
    • 실제 양성값들 중에 맞춘 비율
  • TP가 중요하다. 1을 중점으로 판별
  • FPR = FP/TN+FP (1-Specificity)
    • 실제 음성값들 중에 못 맞춘 비율

 

TN,FP,FN,TP 안에 해당되는 개수가 들어간다. T=맞췄다. N=0으로 예측, FP는 반대이다.

 


임계값(thresholds)

  • 모델을 분류해서 확률(0~1) 또는 음수에서 양수사이에 실수를 예측값으로 출력
  • 사이킷런에서는 predict_proba, decision_function 메소드를 제공
  • predict_proba : 0.5 이상이면 1로 예측 (경우에 따라 수치를 변경하면서 적용)
  • decision_function: 0 이상이면 1로 예측

 

Accuracy 의 한계

  • 오류 중에서 FN 오류를 줄이는 것이 더 중요한 경우 (암인데 정상이라 한 경우)
  • 오류 중에 FP 오류를 줄이는 것이 더 중요한 경우 (좋은 날씨(1), 나쁜날씨(0) 좋은 날씨를 맞춰 체육대회 날 맞추기)
  • 정확도는 위에 두가지 오류에 정도의 차이를 구분할 수 없기 때문에 적절한 성능지표가 될 수 없다.
  • 특별한 경우가 아니면 잘 사용하지 않는다. (0과 1의 비율이 50% 이면 사용)
  • 음성(0)이 양성(1)보다 훨씬 많은 경우 음성(0)으로만 예측해도 높은 정확도를 보이기 때문에 적절한 성능지표가 될 수 없다.

 

  • Dummy
from prompt_toolkit.layout import dummy
from sklearn.dummy import DummyClassifier

dummy = DummyClassifier(strategy='most_frequent')
dummy.fit(x_train,y_train)
pred_dummy = dummy.predict(x_valid)

 

  • Decision Tree
from sklearn.tree import DecisionTreeClassifier

tree = DecisionTreeClassifier(max_depth=3,random_state=SEED)
tree.fit(x_train,y_train)
pred_tree = tree.predict(x_valid)

 

  • 정확도 평가
from sklearn.metrics import accuracy_score
score = accuracy_score(y_valid,pred_dummy)
print(f"dummy: {score}") # 모델을 적용한 것이 아닌데도 정확도가 높다.
score = accuracy_score(y_valid, pred_tree)
print(f"tree: {score}")

=> dummy: 0.8977777777777778
tree: 0.9688888888888889

 

수치가 이상하여 평가지표로 활용되지 않는다.

 


Precision vs Recall

  • FP를 줄이는 것이 목표일 때 Precision 사용
  • FN을 줄이는 것이 목표일 때 Recall 사용
from sklearn.metrics import precision_score, recall_score
precision_score(y_valid,pred_dummy) # tp가 0이라 0
recall_score(y_valid,pred_dummy)

=> 0.0
precision_score(y_valid,pred_tree), recall_score(y_valid,pred_tree)

=> (0.8636363636363636, 0.8260869565217391)

 

Precision-Recall Trade-Off

  • 확률을 낮출수록 정밀도가 떨어지게 되고, 재현율이 올라간다.
  • 절충관계라 단독으로 사용하지 않는다.
threshold = 0.9 # 0.9와 0.1의 차이가 크게 난다.
pred = np.where(pred_proba > threshold, 1, 0)

precision_score(y_valid,pred), recall_score(y_valid,pred)

=> (0.9024390243902439, 0.8043478260869565) ==> 0.9
=> (0.6268656716417911, 0.9130434782608695) ==> 0.1

 


F1-score

  • Precision 과 Recall의 조화평균
  • 정밀도와 재현율이 어느 한쪽으로 치우치지 않았을 때 높은 점수가 나온다.
  • Precision과 Recall은 Trade-off 관계이기 때문에, 이 둘의 조화 평균값인 F1-Score를 많이 사용한다.
    F1=2PrecisionRecallPrecision+Recall

 

  • precision = 0.1, recall = 0.9이라 가정해보자
(0.1+0.9) / 2 # 산술평균

=> 0.5

2 * 0.1 * 0.9 / (0.1+0.9) # 조화평균 (한쪽에 치우쳐 있어 점수가 낮게 나옴)

=> 0.18000000000000002

 

한쪽으로 치우쳐 값이 낮게 나오는 것을 알 수 있다.

 

  • recall, precision, F1-score 한번에 보기
from sklearn.metrics import classification_report # 다중분류에서 사용한다! (이진분류는 굳이?)
print("##dummy##")
print(classification_report(y_valid,pred_dummy))
print("###tree###")
print(classification_report(y_valid,pred_tree))

=>
##dummy##
              precision    recall  f1-score   support

           0       0.90      1.00      0.95       404
           1       0.00      0.00      0.00        46

    accuracy                           0.90       450
   macro avg       0.45      0.50      0.47       450
weighted avg       0.81      0.90      0.85       450

###tree###
              precision    recall  f1-score   support

           0       0.98      0.99      0.98       404
           1       0.86      0.83      0.84        46

    accuracy                           0.97       450
   macro avg       0.92      0.91      0.91       450
weighted avg       0.97      0.97      0.97       450

 

다중 분류에서 사용된다.

 


ROC curve(Receiver Operating Characteristic curve)

  • FPR를 X축으로, TPR을 Y축으로 해서 둘간의 관계를 표현한 그래프
    • FPR = FP / (FP+TN)
    • TPR = TP / (FN+TP)
  • FPR이 천천히 진행하면서 TPR이 빠르게 진행해야 범위가 넓고 좋다.

  • ROC Curve
pred_dummy = dummy.predict_proba(x_valid)[:,1] # 1에 대한 확률만 가져오겠다.
pred_tree = tree.predict_proba(x_valid)[:,1]

from sklearn.metrics import RocCurveDisplay
fig,ax = plt.subplots()
RocCurveDisplay.from_predictions(y_valid,pred_dummy, ax = ax) # (정답값, 모델에 대한 확률값,ax) dummy = 0.5
RocCurveDisplay.from_predictions(y_valid,pred_tree, ax = ax)
plt.show()

위 그림과 같이 FPR이 천천히 진행하면서 TPR이 빠르게 진행하면 범위가 넓어진다.

 

범위가 넓을수록 성능은 좋다고 볼 수 있다.

 

AUROC(ROC AUC)

  • ROC Curve의 밑부분 면적
  • 넓을수록 모형 성능이 좋다.
  • 확률로 대처하기 때문에 인계값의 어떻게 선택되었는지 무관하게 모델의 예측 품질을 측정할 수 있다.
  • Poor model (0.5 ~ 0.7) 형편없는 모델
  • Fair model(0.7 ~ 0.8) 괜찮은 모델
  • Good model (0.8 ~ 0.9) 좋은 모델
  • Excellent model (0.9 ~ 1.0) 현실에서 나오기 힘든 엄청난 모델

 

Multi classification 에서의 F1-score

  • micro (전체 클래스에 대하여 TP,FP,FN를 구한 뒤 F1을 계산)
  • macro (각 클래스에 대하여 F1-score를 구한 뒤 산술 평균)
  • weigthed (각 클래스에 대하여 F1-score를 구한 뒤 각 클래스가 차지하는 비율에 따라 가중 평균)
digits = load_digits()

data = digits.data
target = digits.target

x_train,x_valid,y_train,y_valid = train_test_split(data,target,random_state = SEED)
x_train.shape, x_valid.shape, y_train.shape ,y_valid.shape

=> ((1347, 64), (450, 64), (1347,), (450,))

 

print(classification_report(y_valid,pred))

=>
              precision    recall  f1-score   support

           0       1.00      0.91      0.95        43
           1       0.32      0.32      0.32        37
           2       0.56      0.66      0.60        38
           3       0.87      0.85      0.86        46
           4       0.80      0.78      0.79        55
           5       0.73      0.19      0.30        59
           6       0.95      0.89      0.92        45
           7       0.90      0.68      0.78        41
           8       0.37      0.61      0.46        38
           9       0.52      0.85      0.65        48

    accuracy                           0.67       450
   macro avg       0.70      0.67      0.66       450
weighted avg       0.71      0.67      0.66       450

 

0은 잘 맞췄고, 5는 잘 못 맞췄다. (recall = 실제값 중 맞춘 비율, f1 치우침을 반영한 비율)
 
f1_score(y_valid,pred,average="micro")

=> 0.6688888888888889

-------------------------------------------------------

f1_score(y_valid,pred,average="macro") 

=> 0.6620047429878985

-------------------------------------------------------

f1_score(y_valid,pred,average="weighted") # 잘 안쓴다.

=> 0.6616398619763888

 

못 맞추는 클래스에서 대해서 패널티를 주고 싶다면 macro 사용 아니면 micro 사용

 


소프트맥스 함수(Softmax Function)

  • 각 클래스에 대한 확률을 출력
  • 입력받은 값들을 0 ~ 1 사이의 값들로 모두 정규화하며 출력값들의 합은 항상 1이 되는 특성을 가진 함수
tree.predict_proba(x_valid).sum(axis = 1) # 확률을 다 더하니 1이 나온다.

=>
array([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.,
       1., 1., 1., 1., 1., 1., 1., 1.])

 

 

음의 로그함수

  • x에 1이 들어가면 0이고, x가 0에 가까울수록 기하급수적으로 늘어난다.

 


Multi classification 에서의 logloss (다중분류에서 자주 사용)

  • 모델이 예측한 확률값을 반영해서 평가한다.
  • 0에 가까울수록 좋은 모델
  • 정답에 해당하는 확률값들을 음의 로그함수에 넣어서 나온 값들을 평균내서 평가
  • 낮은 확률로 정답을 예측한 클래스에 패널티를 주기 위해 사용

 

0에 가까울수록 수치가 커지기 때문에 패널티를 주기 쉽다.

 

from sklearn.metrics import log_loss
pred = tree.predict_proba(x_valid)
pred

=>
array([[0.        , 0.00819672, 0.        , ..., 0.        , 0.05737705,
        0.        ],
       [0.        , 0.13461538, 0.        , ..., 0.01923077, 0.03205128,
        0.42307692],
       [0.        , 0.04724409, 0.05511811, ..., 0.00787402, 0.02362205,
        0.03937008],
       ...,
       [0.        , 0.        , 0.73239437, ..., 0.13380282, 0.04225352,
        0.01408451],
       [0.        , 0.00819672, 0.        , ..., 0.        , 0.05737705,
        0.        ],
       [0.        , 0.        , 0.        , ..., 0.        , 0.        ,
        0.        ]])

 

log_loss(y_valid,pred) 

=> 2.1078887104736785

 

 y_valid 자동으로 원핫인코딩된다.

 

 


복습하다가 셔플을 왜하는지에 궁금했다.

 

데이터를 셔플하는 이유는 수집단계에서 어떠한 규칙에 의해 정렬될 가능성이 크기 때문에

 

데이터의 분포가 편향적이게 되고 학습에 방해를 준다는 것을 알게 되었다.

 

 

728x90

댓글