이 장에서 배울 것
이번 장에서는 모델을 평가하는 방법을 배운다. 지난 장에서는 로지스틱 회귀 모델의 정확도를 계산했다. 하지만 정확도 하나만으로 모델이 좋은지 판단하면 위험하다.
이번 장의 목표는 세 가지다.
분류 문제에서 정확도, 정밀도, 재현율, F1 점수를 구분한다.
회귀 문제에서 MAE, MSE, RMSE, R²를 해석한다.
K-겹 교차검증으로 모델 성능을 더 안정적으로 추정한다.
모델을 만드는 것만큼 중요한 일은 모델을 제대로 평가하는 것이다. 평가 기준이 잘못되면 좋은 모델을 버리거나, 위험한 모델을 좋은 모델로 착각할 수 있다.

왜 이 주제가 중요한가
머신러닝 모델은 결국 의사결정에 쓰인다. 스팸 메일을 걸러낼지, 환자에게 추가 검사를 권할지, 집값을 얼마로 예측할지 결정한다.
이때 "정확도 95%"라는 말만 보고 안심하면 안 된다. 스팸 필터가 중요한 업무 메일을 스팸으로 보내면 문제가 크다. 질병 검사 모델이 실제 환자를 정상이라고 판단하면 더 심각하다. 두 모델 모두 정확도가 높아 보여도, 어떤 종류의 실수를 했는지에 따라 의미가 완전히 달라진다.
평가 지표는 모델의 성능을 숫자로 요약한다. 하지만 모든 문제에 맞는 만능 지표는 없다. 먼저 문제의 목적을 이해하고, 어떤 실수가 더 큰 비용을 만드는지 생각해야 한다.
핵심 개념 설명
분류 지표: 정확도만으로는 부족하다
정확도는 전체 예측 중 맞힌 비율이다.
정확도 = 맞힌 예측 수 / 전체 예측 수클래스 비율이 비슷하고, 틀렸을 때의 비용도 비슷하다면 정확도는 좋은 출발점이다. 예를 들어 고양이와 강아지 사진이 비슷한 비율로 있고, 둘을 잘 구분하는지가 목적이라면 정확도를 볼 수 있다.
하지만 클래스가 불균형하면 정확도는 쉽게 착시를 만든다. 사기 거래가 1%뿐인 데이터에서 모든 거래를 정상이라고 예측해도 정확도는 99%다. 하지만 사기 거래는 하나도 잡지 못한다.
이럴 때 필요한 지표가 정밀도, 재현율, F1 점수다.
정밀도는 모델이 양성이라고 예측한 것 중 실제 양성이 얼마나 되는지를 본다. 여기서 양성은 좋다는 뜻이 아니라, 우리가 관심 대상으로 정한 클래스라는 뜻이다. 스팸 탐지에서는 스팸, 질병 탐지에서는 질병이 있는 사례를 양성으로 둘 수 있다.
정밀도 = 참양성 / (참양성 + 거짓양성)스팸 필터를 생각해보자. 모델이 스팸이라고 판단한 메일 중 실제 스팸이 많아야 한다. 정밀도가 낮으면 정상 메일이 스팸함으로 많이 들어간다. 중요한 계약 메일이나 학교 공지가 사라질 수 있다. 따라서 스팸 필터에서는 정밀도가 중요하다.
재현율은 실제 양성 중 모델이 얼마나 찾아냈는지를 본다.
재현율 = 참양성 / (참양성 + 거짓음성)질병 검사를 생각해보자. 실제 환자 중 얼마나 많이 찾아냈는지가 중요하다. 재현율이 낮으면 아픈 사람을 정상이라고 판단한다. 질병을 놓치는 비용이 크다면 재현율을 특히 중요하게 봐야 한다.
F1 점수는 정밀도와 재현율을 함께 보는 지표다. 둘 중 하나만 높고 다른 하나가 낮으면 F1 점수도 높게 나오기 어렵다. 클래스 불균형이 있고, 정밀도와 재현율을 함께 고려해야 할 때 자주 사용한다.
혼동 행렬: 모델의 실수를 표로 보기
혼동 행렬은 분류 모델의 예측 결과를 네 칸으로 나눈 표다. 여기서 양성은 우리가 찾고 싶은 대상이라고 생각하면 된다. 스팸 탐지에서는 스팸, 질병 검사에서는 환자가 양성이다.
예측 양성 예측 음성
실제 양성 참양성(TP) 거짓음성(FN)
실제 음성 거짓양성(FP) 참음성(TN)참양성(TP): 실제 양성을 양성으로 맞혔다.
거짓양성(FP): 실제 음성을 양성으로 잘못 판단했다.
참음성(TN): 실제 음성을 음성으로 맞혔다.
거짓음성(FN): 실제 양성을 음성으로 잘못 판단했다.
스팸 필터에서 거짓양성은 정상 메일을 스팸으로 보내는 실수다. 질병 검사에서 거짓음성은 실제 환자를 정상으로 판단하는 실수다. 같은 "오답"이라도 실제 영향은 다르다.
그래서 평가할 때는 점수 하나만 보지 말고 혼동 행렬을 함께 봐야 한다. 혼동 행렬은 모델이 어떤 방향으로 실수하는지 보여준다.
회귀 지표: 숫자 예측은 오차의 크기를 본다
회귀 문제는 숫자를 예측한다. 예를 들어 집값, 매출, 온도, 수요량을 예측한다. 회귀 모델은 맞고 틀림으로만 평가하기 어렵다. 실제값과 예측값이 얼마나 떨어져 있는지가 중요하다.
MAE는 평균절대오차다. 예측값과 실제값의 차이를 절댓값으로 바꾼 뒤 평균낸다.
MAE = 오차 절댓값의 평균MAE는 해석이 쉽다. 집값 예측에서 MAE가 3000만 원이라면 평균적으로 약 3000만 원 정도 빗나간다는 뜻이다.
MSE는 평균제곱오차다. 오차를 제곱한 뒤 평균낸다. 큰 오차를 더 강하게 벌준다.
RMSE는 MSE에 제곱근을 씌운 값이다. MSE보다 실제 단위에 가깝게 해석할 수 있다. 집값을 원 단위로 예측했다면 RMSE도 원 단위로 볼 수 있다.
R²는 모델이 데이터의 변동을 얼마나 설명하는지 나타낸다. 보통 1에 가까울수록 좋고, 0은 단순히 평균값으로 예측하는 수준에 가깝다는 뜻이다. 매우 나쁜 모델에서는 R²가 0보다 작게 나올 수도 있다. 이 말은 평균값만 예측하는 단순한 방법보다도 못했다는 뜻에 가깝다.
단, R²가 높다고 항상 좋은 모델은 아니다. 데이터가 잘못 나뉘었거나, 새 데이터에서 성능이 나쁘면 의미가 없다. 회귀 지표를 볼 때는 값의 단위도 반드시 확인해야 한다. MAE 10이 작은지 큰지는 문제에 따라 다르다. 시험 점수에서 10점 오차는 클 수 있고, 주택 가격에서 10원 오차는 거의 의미가 없다.
쉬운 비유 또는 사례
모델 평가는 시험 점수표를 보는 일과 비슷하다. 총점만 보면 학생이 무엇을 잘하고 못하는지 알기 어렵다. 수학은 잘하지만 글쓰기를 어려워할 수도 있고, 쉬운 문제는 잘 풀지만 응용 문제에서 많이 틀릴 수도 있다.
정확도는 총점에 가깝다. 혼동 행렬은 어떤 유형의 문제를 틀렸는지 보여주는 오답 노트다. 정밀도와 재현율은 오답의 방향을 더 자세히 보는 기준이다.
교차검증은 시험을 한 번만 보고 실력을 판단하지 않는 방법과 비슷하다. 한 번의 시험은 운이 좋거나 나쁠 수 있다. 여러 번 다른 문제 묶음으로 평가하면 실력을 더 안정적으로 추정할 수 있다.
직접 해보는 실습
아래 실습은 scikit-learn에 포함된 데이터만 사용한다. 외부 파일이 없어도 실행할 수 있다.
1단계: 분류 지표와 혼동 행렬 확인하기
유방암 데이터셋을 사용한다. scikit-learn의 원래 라벨은 0=malignant, 1=benign이다. 즉 0은 악성, 1은 양성 종양을 뜻한다. 이 실습에서는 평가 지표를 더 직관적으로 해석하기 위해 "찾아야 할 대상"인 악성을 1로 다시 바꾼다.
주의할 점이 있다. 여기서 양성 클래스(positive class)라는 말은 지표 계산에서 기준이 되는 클래스를 뜻한다. 종양이 양성(benign)이라는 의학적 의미와 헷갈리면 안 된다. 이 실습에서는 악성을 positive class로 둔다.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
# 데이터 불러오기
X, y_original = load_breast_cancer(return_X_y=True)
# 원래 데이터는 0=악성(malignant), 1=양성 종양(benign)이다.
# 이 실습에서는 악성을 관심 클래스, 즉 positive class 1로 바꾼다.
y = (y_original == 0).astype(int)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
model = make_pipeline(
StandardScaler(),
LogisticRegression(max_iter=1000)
)
model.fit(X_train, y_train)
pred = model.predict(X_test)
print("정확도:", accuracy_score(y_test, pred))
print("정밀도:", precision_score(y_test, pred))
print("재현율:", recall_score(y_test, pred))
print("F1 점수:", f1_score(y_test, pred))
print("혼동 행렬:n", confusion_matrix(y_test, pred))confusion_matrix의 기본 출력은 다음 순서로 읽는다.
[[TN, FP],
[FN, TP]]출력된 행렬에서 거짓음성(FN)이 몇 개인지 확인해보자. 이 값은 실제 악성인데 모델이 악성이 아니라고 판단한 경우다. 의료 문제에서는 이런 실수가 특히 중요하다.
2단계: 회귀 지표 확인하기
이번에는 당뇨병 진행 정도를 예측하는 회귀 문제를 사용한다.
from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import numpy as np
X, y = load_diabetes(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
reg = LinearRegression()
reg.fit(X_train, y_train)
pred = reg.predict(X_test)
mae = mean_absolute_error(y_test, pred)
mse = mean_squared_error(y_test, pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, pred)
print("MAE:", mae)
print("MSE:", mse)
print("RMSE:", rmse)
print("R²:", r2)MAE와 RMSE는 실제값의 단위와 함께 생각해야 한다. RMSE가 MAE보다 훨씬 크다면 몇 개의 큰 오차가 전체 성능을 끌어내리고 있을 수 있다. R²는 1에 가까울수록 좋지만, 반드시 다른 오차 지표와 함께 보자.
3단계: K-겹 교차검증 사용하기
단일 train/test 분할은 데이터가 어떻게 나뉘었는지에 영향을 받는다. 우연히 쉬운 테스트 데이터가 뽑히면 성능이 높아 보이고, 어려운 데이터가 뽑히면 낮아 보일 수 있다.
K-겹 교차검증은 데이터를 K개 묶음으로 나눈 뒤, 매번 하나의 묶음을 검증용으로 사용하고 나머지를 훈련용으로 사용한다.
1회차: [검증] [훈련] [훈련] [훈련] [훈련]
2회차: [훈련] [검증] [훈련] [훈련] [훈련]
3회차: [훈련] [훈련] [검증] [훈련] [훈련]
4회차: [훈련] [훈련] [훈련] [검증] [훈련]
5회차: [훈련] [훈련] [훈련] [훈련] [검증]5-겹 교차검증이라면 모델을 5번 학습하고 5번 평가한다. 마지막에는 평균 점수와 점수의 변동성을 함께 본다.
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y_original = load_breast_cancer(return_X_y=True)
y = (y_original == 0).astype(int)
model = make_pipeline(
StandardScaler(),
LogisticRegression(max_iter=1000)
)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv, scoring="f1")
print("각 Fold 점수:", scores)
print("평균 F1:", scores.mean())
print("표준편차:", scores.std())분류 문제에서는 클래스 비율을 유지해서 나누는 StratifiedKFold를 자주 쓴다. 특히 관심 클래스가 적을 때 유용하다.
4단계: 여러 모델 비교하기
교차검증은 모델 비교에도 좋다. 평균 점수만 보지 말고 표준편차도 함께 확인하자. 표준편차가 크면 데이터가 어떻게 나뉘느냐에 따라 성능이 많이 흔들린다는 뜻이다.
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
models = {
"Logistic Regression": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
"Decision Tree": DecisionTreeClassifier(random_state=42),
"kNN": make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))
}
for name, model in models.items():
scores = cross_val_score(model, X, y, cv=cv, scoring="f1")
print(name)
print(" 평균 F1:", round(scores.mean(), 4))
print(" 표준편차:", round(scores.std(), 4))결과는 다음과 같은 표로 정리하면 좋다.
| 모델 | 평균 F1 | 표준편차 | 해석 |
|---|---|---|---|
| Logistic Regression | 실행 결과 입력 | 실행 결과 입력 | 기준 모델로 적합한지 확인 |
| Decision Tree | 실행 결과 입력 | 실행 결과 입력 | 성능 변동이 큰지 확인 |
| kNN | 실행 결과 입력 | 실행 결과 입력 | 스케일 조정 후 성능 확인 |
이 표에서 가장 높은 평균만 고르지 말자. 평균이 조금 높아도 표준편차가 크면 안정성이 낮을 수 있다. 반대로 평균이 약간 낮아도 해석이 쉽고 성능이 안정적이라면 실무에서는 더 좋은 선택일 수 있다.
흔한 실수와 오류 분석
1. 정확도만 보고 모델을 선택하기
정확도는 이해하기 쉽지만 모든 문제에 충분하지 않다. 클래스 불균형이 있거나, 거짓양성과 거짓음성의 비용이 다르면 정밀도와 재현율을 함께 봐야 한다.
2. 양성 클래스가 무엇인지 정하지 않기
정밀도와 재현율은 "양성"을 기준으로 계산된다. 스팸 탐지에서는 스팸이 양성일 수 있고, 질병 검사에서는 환자가 양성일 수 있다. 양성 클래스를 잘못 잡으면 해석이 뒤집힌다. 특히 이 장의 유방암 예제처럼 benign을 한국어로 "양성 종양"이라고도 부르는 경우에는, 평가 지표의 positive class와 용어가 헷갈리지 않도록 반드시 라벨 의미를 확인해야 한다.
3. 회귀 지표의 단위를 확인하지 않기
MAE, RMSE는 문제의 단위와 함께 해석해야 한다. 같은 MAE 5라도 시험 점수에서는 큰 오차일 수 있고, 기온 예측에서는 매우 큰 오차일 수 있다.
4. 교차검증 전에 전처리를 전체 데이터에 적용하기
스케일 조정이나 결측치 처리 같은 전처리를 전체 데이터에 먼저 적용한 뒤 교차검증하면 검증 데이터의 정보가 훈련 과정에 새어 들어갈 수 있다. 이를 데이터 누수라고 한다. 그래서 실습처럼 make_pipeline으로 전처리와 모델을 묶는 습관이 좋다.
5. 평균 점수만 보고 변동성을 무시하기
교차검증 결과는 평균과 표준편차를 함께 봐야 한다. 평균은 전반적인 성능이고, 표준편차는 성능의 흔들림이다. 안정적인 모델은 여러 분할에서 비슷한 성능을 낸다.
요약
분류 문제에서는 정확도만으로 모델을 판단하면 안 된다. 정밀도는 양성이라고 예측한 것의 신뢰도를 보고, 재현율은 실제 양성을 얼마나 놓치지 않았는지 본다. F1 점수는 정밀도와 재현율의 균형을 본다. 여기서 양성은 좋다는 뜻이 아니라, 분석자가 관심 대상으로 정한 기준 클래스라는 뜻이다.
혼동 행렬은 모델의 실수를 네 가지로 나눈다. 참양성, 거짓양성, 참음성, 거짓음성을 보면 모델이 어떤 종류의 실수를 하는지 알 수 있다.
회귀 문제에서는 MAE, MSE, RMSE, R²를 사용한다. MAE는 직관적이고, MSE와 RMSE는 큰 오차에 더 민감하다. R²는 모델이 데이터의 변동을 얼마나 설명하는지 보여 주지만, 다른 오차 지표와 함께 해석해야 한다.
교차검증은 한 번의 데이터 분할에 의존하지 않고 모델 성능을 여러 번 평가한다. 평균 성능과 변동성을 함께 보면 모델을 더 안정적으로 비교할 수 있다.
퀴즈
정확도가 높아도 좋은 모델이 아닐 수 있는 이유는 무엇인가?
스팸 필터에서 정밀도가 낮으면 어떤 문제가 생길 수 있는가?
질병 검사에서 재현율이 중요한 이유는 무엇인가?
혼동 행렬의 네 칸 TP, FP, TN, FN을 각각 설명해보자.
MAE와 RMSE는 어떻게 다르게 해석해야 하는가?
R²가 1에 가까우면 무엇을 의미하는가? R²가 0보다 작을 수도 있는 이유는 무엇인가?
K-겹 교차검증은 단일 train/test 분할보다 왜 안정적인가?
교차검증에서 평균 점수와 표준편차를 함께 봐야 하는 이유는 무엇인가?
체크리스트
정확도, 정밀도, 재현율, F1 점수의 차이를 설명할 수 있다.
스팸 필터와 질병 검사에서 중요한 지표가 달라지는 이유를 이해했다.
평가 지표에서 말하는 양성 클래스가 무엇인지 확인할 수 있다.
혼동 행렬에서 TP, FP, TN, FN을 찾을 수 있다.
회귀 문제에서 MAE, MSE, RMSE, R²를 구분할 수 있다.
cross_val_score로 교차검증 점수를 계산할 수 있다.교차검증 결과의 평균과 표준편차를 함께 해석할 수 있다.
전처리와 모델을
Pipeline으로 묶어 데이터 누수를 줄일 수 있다.
다음 장 연결
이번 장에서는 모델을 어떻게 평가할지 배웠다. 이제 우리는 단순히 "점수가 높다"가 아니라 "어떤 기준에서, 얼마나 안정적으로 좋은가"를 따질 수 있다.
다음 장에서는 트리와 앙상블 모델을 배운다. 트리는 조건을 따라 데이터를 나누며 예측하고, 앙상블은 여러 모델을 함께 사용해 성능을 높인다. 이번 장에서 배운 평가 지표와 교차검증은 다음 장에서 여러 모델을 비교하는 기준이 된다.
이 노트는 보도·비평·교육·연구 목적으로 작성되었습니다. 저작권 문의가 있으시면 에서 알려주세요.