03. Loss와 Accuracy가 다르게 움직이는 이유
복습 음성 · 11분 33초
모델을 학습시키면 Loss와 Accuracy가 나란히 나온다. 정확도가 높아지면 손실은 내려갈 것 같지만, 두 값이 꼭 반대로 움직이지는 않는다. 각 숫자가 무엇을 세는지 알아야 학습 결과를 읽을 수 있다.
작은 확률 예제로 손실과 정확도를 계산하면 두 지표가 어떤 정보를 담는지 비교할 수 있다. 학습 곡선도 각 지표의 계산 방식과 조건을 기준으로 읽는다.
1. Loss와 Metric은 맡은 역할로 구분한다
Loss(손실)는 현재 예측에 얼마나 벌점을 줄지 계산한 값이다. Gradient descent(경사하강법)로 학습할 때는 이 값이 줄어드는 방향을 구해 Parameter(매개변수)를 바꾼다.
입력 → 예측 → 손실 계산 → 기울기 계산
→ 가중치와 편향 갱신 → 다시 예측
Metric(평가지표)은 결과가 목적에 얼마나 맞는지 판단하는 기준이다. Accuracy(정확도)는 분류에서 맞힌 개수를 전체 개수로 나눈 값이다. 100개 중 85개를 맞혔다면 85%다.
손실과 지표를 학습 데이터용, 테스트 데이터용으로 나누면 헷갈린다. 학습 데이터에서도 둘 다 계산하고 검증 데이터에서도 둘 다 계산한다. 차이는 그 숫자를 매개변수를 바꾸는 목표로 쓰는지, 결과를 평가하는 기준으로 쓰는지에 있다.
같은 계산이 두 역할을 맡기도 한다. 회귀의 MSE(평균제곱오차)를 줄이면서 학습하고, 평가할 때도 MSE를 보고할 수 있다. 회귀에서는 보통 정확도 대신 MSE, MAE, RMSE, R²처럼 수치 오차에 맞는 지표를 사용한다.
2. 교차 엔트로피는 정답에 준 확률을 본다
분류에서 자주 쓰는 Cross-entropy(교차 엔트로피)는 정답 클래스에 낮은 확률을 주면 큰 벌점을 준다. 여기서는 정답이 한 클래스인 일반적인 분류를 생각한다.
한 샘플의 정답 클래스 확률을 p_correct라고 하면 계산은 다음처럼 줄어든다. log는 자연로그다.
한 샘플의 손실 = -log(p_correct)
여러 샘플의 손실 = 샘플별 손실의 평균
정답이 1인 이진 분류에서 1일 확률을 0.9로 예측했다면 손실은 약 0.105다. 0.5면 약 0.693, 0.01이면 약 4.605가 된다. 정답에 거의 가능성이 없다고 판단했을수록 벌점이 커진다.
주의할 부분은 정답 클래스의 확률이다. 정답이 0인데 모델이 1일 확률을 0.9라고 했다면, 정답 클래스에 준 확률은 1 - 0.9 = 0.1이다. 0.9를 그대로 로그에 넣으면 뜻이 뒤집힌다. scikit-learn의 log_loss()는 정답과 클래스 확률을 받아 이 계산을 한다. log_loss 설명
확률 0에 직접 로그를 취하면 무한대가 나온다. 아래 예제는 모두 0과 1 사이의 값을 사용한다. 라이브러리의 손실 함수에는 이런 수치 문제를 다루는 처리가 있으므로, 실제 모델에서 손실 함수를 직접 만들 때도 입력 범위와 계산 방식을 확인해야 한다.
3. 정확도는 올랐는데 손실도 커지는 작은 예제
정답 네 개를 [0, 1, 0, 1]로 정하고 예측 확률 두 묶음을 만들었다. 실제 모델을 학습한 결과가 아니라, 두 지표의 차이를 계산해보려는 숫자다.
| 예측 묶음 | 정답 클래스에 준 확률 | 정확도 |
|---|---|---|
| A | 0.80, 0.45, 0.45, 0.45 | 25% |
| B | 0.60, 0.60, 0.60, 0.001 | 75% |
이진 분류에서 1일 확률이 0.5 이상이면 1로 판단한다. A는 첫 번째만 맞히고, B는 앞의 세 개를 맞힌다. 하지만 B의 마지막 예측은 실제 정답에 0.001의 확률만 줬다. 이 한 건의 큰 손실이 평균에 영향을 준다.
import numpy as np
from sklearn.metrics import accuracy_score, log_loss
# 계산을 확인하려고 만든 예측값이다. 모델을 학습한 결과는 아니다.
y = np.array([0, 1, 0, 1])
probabilities = {
"A": np.array([0.20, 0.45, 0.55, 0.45]),
"B": np.array([0.40, 0.60, 0.40, 0.001]),
}
for name, p1 in probabilities.items():
prediction = (p1 >= 0.5).astype(int)
p_correct = np.where(y == 1, p1, 1 - p1)
losses = -np.log(p_correct)
print(f"{name}: Accuracy={accuracy_score(y, prediction):.0%}, "
f"Log loss={log_loss(y, p1, labels=[0, 1]):.4f}")
print("정답 클래스 확률:", np.round(p_correct, 3).tolist())
print("샘플별 손실:", np.round(losses, 4).tolist())
print(f"직접 계산한 평균 손실: {losses.mean():.4f}")
실행 결과는 다음과 같다.
A: Accuracy=25%, Log loss=0.6547
정답 클래스 확률: [0.8, 0.45, 0.45, 0.45]
샘플별 손실: [0.2231, 0.7985, 0.7985, 0.7985]
직접 계산한 평균 손실: 0.6547
B: Accuracy=75%, Log loss=2.1101
정답 클래스 확률: [0.6, 0.6, 0.6, 0.001]
샘플별 손실: [0.5108, 0.5108, 0.5108, 6.9078]
직접 계산한 평균 손실: 2.1101
A의 평균 손실은 0.6547, B는 2.1101이다. 맞힌 개수는 B가 더 많지만, 마지막 샘플의 손실 6.9078이 평균을 크게 올렸다.
반대 경우도 생각할 수 있다. 정답 클래스 확률이 0.30에서 0.45로 높아졌다면 손실은 줄어든다. 이진 분류의 0.5 경계는 아직 넘지 못했으므로 해당 샘플은 계속 오답이다. 정확도가 그대로여도 예측 확률은 달라지고 있을 수 있다.
그래서 두 지표가 서로 다르게 움직이면 어느 쪽 계산이 틀렸다고 단정하기 전에, 샘플별 확률과 손실을 살펴보는 편이 좋겠다.
4. 정확도를 그대로 학습 목표로 쓰기 어려운 이유
정답이 1일 때 예측 확률이 0.6에서 0.9로 달라져도 정확도에서는 둘 다 정답 한 개다. 0.1에서 0.4로 바뀌어도 둘 다 오답 한 개다. Threshold(임계값)를 넘는 순간에만 정답 여부가 바뀐다.
경사하강법은 매개변수를 조금 바꿨을 때 손실이 어떻게 변하는지를 이용한다. 정확도처럼 넓은 구간에서 값이 같고 경계에서 갑자기 바뀌는 계산은 이런 수정 방향을 얻기 어렵다. 보통은 교차 엔트로피처럼 학습에 쓸 수 있는 손실을 두고, 관심 있는 지표를 함께 기록한다.
F1도 여러 샘플의 정답·오답 개수를 모아 계산하므로 그대로 미분해서 학습에 넣기는 어렵다. 이를 근사하는 별도 학습 방법도 있지만 이번에는 기본적인 경사하강법의 흐름까지만 기억해둔다.
지표를 손실로 썼다는 이유만으로 과적합이 생기는 것은 아니다. 손실이 어떤 수정 방향을 주는지와 새로운 데이터에서 성능이 유지되는지는 각각 살펴볼 문제다.
5. 확률로 계산하는 설명과 코드의 입력은 구분하기
앞에서는 이해를 위해 확률로 교차 엔트로피를 계산했다. 실제 딥러닝 코드에서는 함수마다 받는 값이 다르다.
Logit(로짓)은 확률로 바꾸기 전의 점수다. 클래스 점수가 [2.0, 0.5, 0.1]이면 가장 큰 값의 위치인 0번 클래스를 선택한다. Softmax(소프트맥스)는 점수들을 합이 1인 확률로 바꾸는 계산이다.
PyTorch의 CrossEntropyLoss는 원시 로짓을 받는다. 일반적인 정답 클래스 번호 방식에서는 입력이 (배치 크기, 클래스 수), 정답이 (배치 크기,)의 정수 인덱스다. 이 모드에서는 정답에 torch.long을 사용한다. 함수가 내부적으로 필요한 로그 확률 계산을 처리하므로 입력에 Softmax를 한 번 더 붙이지 않는다. 확률 분포 형태의 정답을 받는 다른 모드도 있으니, 사용 중인 입력 방식을 구분해야 한다. CrossEntropyLoss 입력 규칙
이번 NumPy·scikit-learn 예제는 확률을 이미 정해 놓고 지표를 계산한 것이다. PyTorch 학습 코드에 같은 배열을 그대로 옮겨 넣는 예제는 아니다.
6. 학습·검증·테스트에서 하는 일이 다르다
학습 기록을 읽기 전에 어느 데이터에서 계산한 값인지 먼저 본다.
- Train set(학습 세트): 가중치와 편향을 갱신하는 데 사용한다.
- Validation set(검증 세트): 모델, 학습률, 학습을 끝낼 시점, 임계값 등을 고르는 데 사용한다.
- Test set(테스트 세트): 선택을 끝낸 모델의 성능을 마지막에 확인한다.
매 Epoch(에포크)마다 테스트 결과를 보고 설정을 바꾸면 테스트도 선택에 쓰인 셈이다. 이름을 테스트라고 붙여두었더라도 독립적인 최종 평가 역할은 유지되지 않는다.
검증 성능 역시 여러 번 보면서 선택한 결과다. 최종 성능을 보고할 때는 어느 단계에서 무엇을 골랐는지 함께 적어야 한다. 같은 고객이나 같은 파일의 복사본이 서로 다른 세트에 섞여 있는지도 확인한다.
7. 학습 곡선은 네 값을 함께 본다
여기서 Learning curve(학습 곡선)는 에포크가 지날 때 손실과 지표가 어떻게 변하는지 그린 그래프다. 학습 샘플 수에 따른 성능 그래프를 같은 이름으로 부르기도 하므로 가로축을 먼저 확인한다.
train_loss val_loss
train_accuracy val_accuracy
손실과 정확도는 단위와 범위가 다르므로 별도 그래프로 놓으면 읽기 편하다. 방향뿐 아니라 기준 모델보다 나은지, 학습과 검증 사이의 간격이 얼마나 되는지, 언제부터 방향이 달라졌는지 본다.
| 관찰한 모습 | 먼저 살펴볼 것 |
|---|---|
| 학습·검증 손실이 함께 감소 | 두 세트에서 성능이 개선되는지, 기준 모델보다 나은지 |
| 둘 다 성능이 낮고 거의 변하지 않음 | 입력·정답 연결, 학습률, 학습 시간, 모델 표현력 |
| 학습 손실은 감소하고 검증 손실은 상승 | 과적합 가능성, 분할과 데이터 분포 차이 |
| 손실이 크게 진동하거나 갑자기 커짐 | 학습률, 배치 구성, 이상 입력, 수치 문제 |
| 정확도는 고정되고 손실만 감소 | 경계 주변 확률과 샘플별 변화 |
| 정확도와 손실이 함께 상승 | 매우 큰 손실을 내는 오답이 있는지 |
Underfitting(과소적합)은 필요한 패턴을 충분히 배우지 못한 상태를 말한다. 두 세트의 성능이 함께 낮으면 모델이 너무 단순한지, 충분히 학습했는지 살펴본다. 다만 입력과 정답의 연결 오류나 학습률 문제도 비슷한 모양을 만들 수 있다.
Overfitting(과적합)은 학습 자료에 맞추는 동안 새 자료의 성능이 나빠지는 상태다. 학습 손실이 계속 줄고 검증 손실이 오르기 시작하면 의심할 수 있다. 그 시점의 모델을 저장하고 데이터 분할, 정칙화, 모델 크기 등을 점검한다. 곡선은 다음에 무엇을 검사할지 정하는 단서다. 원인을 확인하려면 추가 점검이 필요하다.
8. 검증 성능이 더 좋은 경우와 50%의 의미
검증 정확도가 학습 정확도보다 높을 때도 있다. 학습 중에는 Data augmentation(데이터 증강)으로 입력이 더 어려워졌거나 Dropout(드롭아웃)이 켜져 있을 수 있다. 학습 중 집계한 값은 매개변수가 바뀌는 여러 시점의 평균이고, 검증은 에포크가 끝난 모델로 계산했을 수도 있다.
비교할 때는 같은 평가 모드와 같은 집계 기준으로 다시 계산해본다. 손실에 정칙화 벌점이 포함되는지, 샘플 가중치를 적용했는지, 배치별 평균을 어떻게 합쳤는지도 확인한다. 검증 자료가 더 쉽거나 데이터 누수가 있는 경우도 함께 살펴본다.
정확도가 50% 부근에서 흔들린다고 바로 Vanishing gradient(기울기 소실)라고 결론 내릴 수도 없다. 균형 잡힌 이진 분류에서 50%는 참고할 만한 우연 수준이지만, 클래스 비율이 90:10이면 전부 다수 클래스로 예측해도 90%가 된다.
기울기 소실을 의심한다면 레이어별 기울기 크기와 실제 가중치 갱신을 확인해야 한다. 정확도 숫자만으로는 입력 오류, 모델 표현력, 학습률 문제를 구분할 수 없다.
9. 이상할 때 확인할 순서
복습용으로 아래 순서를 적어뒀다.
- 데이터 확인: 입력과 정답이 같은 샘플끼리 대응하는지, 값의 범위와 클래스 비율이 맞는지 본다.
- 작은 샘플로 확인: 학습 가능한 작은 자료조차 맞추지 못한다면 모델·손실·최적화 연결을 살펴본다. 작은 자료에 맞췄다는 사실은 일반화 성능과 구분한다.
- Baseline(기준 모델) 비교: 다수 클래스만 고르는 예측이나 단순한 모델보다 나은지 확인한다.
- 오답 확인: 손실이 큰 샘플, 잘못 붙은 정답, 특정 클래스에 몰린 오류를 본다.
- 학습 설정 확인: 학습률, 배치 크기, 기울기 크기,
NaN·Inf, 학습/평가 모드를 확인한다. - 조건 하나씩 변경: 같은 분할에서 비교하고 바꾼 설정을 기록한다.
여러 설정을 한 번에 바꾸면 점수가 달라진 이유를 찾기 어렵다. 최종적으로 의미 있는 비교가 필요할 때는 다른 시드나 분할에서도 결과가 비슷한지 확인하되, 이번 숫자 예제에서는 모델 학습까지 진행하지 않았다.
10. 언제 학습을 멈추고 어떤 모델을 남길까
Early stopping(조기 종료)은 검증 지표가 더 좋아지지 않을 때 학습을 멈추는 방법이다. 한 번의 흔들림에 바로 반응하기보다 얼마 동안 기다릴지 정한다.
Keras에서는 monitor로 확인할 값을, mode로 작아지는 쪽과 커지는 쪽 중 좋은 방향을 정한다. patience는 개선 없이 기다릴 에포크 수이고 min_delta는 개선으로 볼 최소 변화량이다. restore_best_weights=True를 지정하면 선택 지표가 가장 좋았던 시점의 가중치를 복원한다. 기본값은 False이므로, 이 옵션을 쓰려면 직접 지정해야 한다. EarlyStopping 설정
val_loss를 기준으로 멈출지 val_accuracy를 기준으로 멈출지는 목적에 맞춰 정한다. 둘의 최적 시점이 다를 수도 있다. 조기 종료 기준과 나중에 비교할 지표를 미리 적어두면, 유리한 숫자만 골라 설명하는 일을 줄일 수 있다.
모델을 오래 학습했다고 마지막 가중치가 가장 좋은 것은 아니다. 검증 기준이 가장 좋았던 Checkpoint(중간 저장본)를 남기고, 선택이 끝나면 테스트에서 평가한다.
11. 높은 정확도 다음에 확인할 것
정상 메일 990개와 스팸 10개에서 전부 정상이라고 하면 정확도는 99%다. 하지만 스팸을 찾아낸 비율은 0%다. 어떤 오류가 중요한지에 따라 정확도와 함께 볼 지표가 달라진다.
스팸으로 분류한 것 중 실제 스팸이 얼마나 되는지는 Precision(정밀도), 실제 스팸 중 얼마나 찾아냈는지는 Recall(재현율)로 본다. 확률값 자체를 사용하는 문제라면 예측한 확률과 실제 발생 비율이 맞는지도 따로 확인한다.
손실과 정확도는 서로 다른 정보를 담는다. 두 값의 방향이 다를 때는 정답 확률, 큰 손실을 내는 샘플, 계산에 사용한 데이터와 조건을 같이 보려고 한다.
AIFFEL 수업과 개인 복습 노트를 바탕으로 썼다. 위 음성은 복습용 TTS(음성 합성)이며 본문의 작은 예제와 실행 결과는 따로 작성했다. 음성 속 강의 순서와 블로그 회차는 다를 수 있다.
예제 실행 환경: Python 3.12.9, NumPy 2.5.2, scikit-learn 1.9.0. 2026년 9월 28일에 지표 계산 예제를 실행했다. 딥러닝 모델 학습과 조기 종료 실행은 이번 확인 범위에 포함하지 않았다.