
분류 모델을 다루어 봤다면 metric으로 accuracy와 f1 score를 많이 사용하게 됩니다. 이 중에 F1 점수가 실제로 어떤 의미가 있고, 어떤 방식으로 계산하는지 확인해 보겠습니다. 추가로 Multi Class인 경우에 어떤 방식으로 계산이 가능한지도 설명해보겠습니다.
1. Accuracy 대신 F1 score 쓰는 이유
사실 분류 모델은 accuracy 기준으로 설명하게 되는 경우가 많습니다. 가장 직관적이기 때문이죠. 하지만 데이터에 있는 class가 불균형 할수록 accuracy 정확도는 의미가 적은 수치가 됩니다.
예를 들어, 100명중에 한명이 코로나에 걸렸다고 가정해 봅시다. 이때 단순하게 100명 전부 코로나에 걸리지 않았다고 예측하면 99/100으로 정확도 99%의 엄청난 모델이 되어 버립니다. 수치 상으로는 맞는 말이지만, 실제로는 99%에서 큰 의미를 찾기 힘듭니다. 이런 경우 모델의 성능을 더 정확하게 판단하기 위해 F1 score를 사용합니다.
2. Binary F1 score 계산하는 방법

위의 예시처럼 코로나 확진 / 미확진 두 가지의 분류로 이루어진 경우를 예시로 들어보겠습니다.
Positive Negative, True False
일단 True Positive (TP), True Negative(TN), False Positive(FP), False Negative(FN) 에 대한 개념이 필요합니다. True나 positive 둘다 비슷한 느낌으로 다가오지만 의미가 다릅니다.
True False 는 옳게 예측 했는지 아닌지, Positive Negative는 Positive로 예측했는지 Negative 로 예측했는지 입니다. 예로 들어, 코로나 감염된 것으로 예측했다면 positive, 아니라고 예측했다면 Negative 입니다.
TP TN FP FN 에 감을 잡아 봅시다. 맞췄는지 틀렸는지, 어떻게 예측했는지를 기준으로 보시면 됩니다.
True Positive : 감염자를 감염 (P)이라고 옳게 (T) 예측한 경우
True Negative : 비감염자를 비감염 (N) 이라고 옳게 (T) 예측한 경우
False Positive : 비감염자를 감염 (P) 이라고 잘못(F) 예측한 경우
False Negative : 감염자을 비감염 (N) 이라고 잘못 (F) 예측한 경우
Precision (정밀도) 와 Recall (재현율)
위에서 알아본 분류로 정밀도와 재현율에 대해 알아보겠습니다.
정밀도는 모델이 Positive 라고 분류한 것들 중에, 실제로 Positive인 비율입니다. 얼마나 정밀하게 Positive 를 찾아냈는지 에 대한 수치입니다. 설명대로 식을 쓰면 “ TP / (TP + FP) “ 입니다.
재현율은 실제 Positive 인것들 중에 Positive라고 잘 에측한 비율입니다. 얼마나 Positive 를 잘 재현했는지 에 대한 수치입니다. 식으로 쓰면 “TP / (TP + FN)” 가 됩니다. 위의 예시를 다시 보면 TP + FN은 실제로 감염자들의 수 임을 확인할 수 있습니다.
F1 Score 의 의미
F1 = 2 * precision * recall / (precision + recall)
F1 score는 정밀도와 재현율의 조화평균입니다. 두가지 지표 모두를 반영하는 수치를 만들기 위해 사용합니다. 조화 평균을 사용한 이유는 둘 중에 낮은 쪽에 더 큰 영향을 받도록 하여 균형있는 모델을 찾기 위함입니다. 따라서 F1 수치가 높다면 전반적인 성능 자체가 높다고 할 수 있습니다.
Multi class F1 Score
지금까지의 예시는 모두 0, 1 을 분류하는 binary classification 이었습니다. 멀티 class 의 경우는 어떻게 계산할 수 있을까요? 다중 클래스의 경우는 각 클래스 별로 F1 스코어를 구한 뒤, 그 값을 합쳐서 계산합니다.
예를 들어 보겠습니다.
y_true = ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'd', 'd']
y_pred = ['a', 'c', 'd', 'b', 'b', 'b', 'a', 'c', 'd', 'c', 'd']
위와 같은 결과에서, 4x4 행렬로 다시 정리해보면
a b c d
a 1 0 1 1
b 0 3 0 0
c 1 0 1 1
d 0 0 1 1
column은 예측, row는 실제값 입니다.
다중 클래스에서는 각 클래스 별로 확인을 해야 합니다.
클래스 a 입장에서 보면, TP = 1 FP = 1 FN = 2 TN = 5 가 됩니다.
따라서 Precision (정밀도)는 TP / (TP+FP) = 1 / 2 = 0.5
Recall (재현율)은 TP / (TP+FN) = 1 / 1+2 = 1/3 = 0.33
F1 score는 2 * ( 1/2 * 1/3 ) / (1/2 + 1/3) = 약 0.4 가 됩니다.
같은 방식으로 b, c, d 각 클래스 별로 진행해 줍니다.
모델의 전체 성능을 평가하기 위한 종합적인 F1 스코어를 구하는 방식은 3가지가 있습니다.
1. Macro F1 Score
이 방식은 단순하게 모든 클래스의 F1 점수의 평균입니다.
따라서 (0.4 + 1 + 0.33 + 0.4 ) / 4 = 약 0.533 이 됩니다.
2. Micro F1 Score
Micro F1 은 모든 클래스들의 TP, FP, FN을 사용하여 계산합니다.
전체 TP = 1 (a) + 3 (b) + 1 (c) + 1 (d) = 6
전체 FP = 1 + 0 + 2 + 2 = 5
전체 FN = 2 + 0 + 2 + 1 = 5
따라서 micro 수치들을 구해보면
micro precision = tp / (tp + fp) = 6 / (6 + 5) = 6/11
micro recall = tp / (tp + fn) = 6 / (6 + 5) = 6/11
micro f1 = 2 * (6/11 * 6/11) / (6/11 + 6/11) = 0.545
3. Weighted F1 Score
가중치를 적용한 F1 점수는 각 클래스 별 실제 표본 수로 가중치를 부여하여 계산합니다.
클래스 a, b, c, d의 표본 수는 각각 [ 3, 3, 3, 2] 개 입니다.
따라서 (3 * 0.4 + 3 * 1 + 3 * 0.33 + 0.4 * 2) / 11 = 0.5445 가 됩니다.
코드 예시
사실 파이썬 라이브러리들로 계산하면 쉽습니다. 의미만 기억하고 계산은 코딩으로...
y_true = ['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'c', 'd', 'd']
y_pred = ['a', 'c', 'd', 'b', 'b', 'b', 'a', 'c', 'd', 'c', 'd']
labels = ['a', 'b', 'c', 'd']
conf_matrix = confusion_matrix(y_true, y_pred, labels=labels)
conf_matrix_df = pd.DataFrame(conf_matrix, index=labels, columns=labels)
print("Confusion Matrix:")
print(conf_matrix_df)
print()
TP = np.diag(conf_matrix)
FP = np.sum(conf_matrix, axis=0) - TP
FN = np.sum(conf_matrix, axis=1) - TP
TN = conf_matrix.sum() - (FP + FN + TP)
print("\nClass-wise TP, FP, FN, TN:")
for i, label in enumerate(labels):
print(f"Class {label} - TP: {TP[i]}, FP: {FP[i]}, FN: {FN[i]}, TN: {TN[i]}")
print()
# F1 Score 계산
f1_scores = f1_score(y_true, y_pred, labels=labels, average=None)
# 각 클래스의 F1 점수 출력
for label, score in zip(labels, f1_scores):
print(f'F1 Score for class {label}: {score:.2f}')
# Macro, Micro, Weighted F1 Score 계산
macro_f1 = f1_score(y_true, y_pred, labels=labels, average='macro')
micro_f1 = f1_score(y_true, y_pred, labels=labels, average='micro')
weighted_f1 = f1_score(y_true, y_pred, labels=labels, average='weighted')
# 결과 출력
print(f'Macro F1 Score: {macro_f1:.3f}')
print(f'Micro F1 Score: {micro_f1:.3f}')
print(f'Weighted F1 Score: {weighted_f1:.3f}')