본문 바로가기
TYLER SONGBlog
← Blog index
LLM Research6 min read

ML 기초: 지도학습에서 loss와 metric은 왜 다른 걸 써야 하나

오늘은 머신러닝의 가장 기본 뼈대인 지도학습(supervised learning)을 배운다. feature와 label이 뭔지, 회귀와 분류가 어떻게 다른지, 그리고 학습 중에 최적화하는 loss와 사람이 성능을 판단하는 metric이 왜 같은 개념이 아닌지를 sklearn 코드와 NumPy 직접 구현으로 비교하며 익힌다.

By 송민성

오늘의 목표

머신러닝 문제를 볼 때 "입력이 뭐고 출력이 뭐고, 뭘 줄이려고 학습하고, 뭘로 잘했다 못했다를 판단하는지"를 스스로 말할 수 있게 되는 것이 오늘 목표다. 이 뼈대는 이후 신경망, LLM 학습 어디를 봐도 똑같이 반복된다.

개념과 직관

지도학습(supervised learning)은 "입력과 정답 쌍을 잔뜩 보여주면서, 새로운 입력에 대해 정답을 맞히는 규칙을 찾는 것"이다. 학생에게 문제와 답이 적힌 문제집을 주고 패턴을 익히게 하는 것과 비슷하다.

  • feature(특징): 입력으로 쓰는 숫자들. 예를 들어 집값을 예측한다면 "평수, 방 개수, 지어진 연도" 같은 것들이 feature다. 보통 하나의 데이터는 여러 개의 feature를 가진 벡터(vector, 숫자를 순서대로 나열한 것)로 표현된다.
  • label(정답, 타깃): 우리가 맞히고 싶은 값. 집값 예측이면 "실제 집값"이 label이다.
  • 회귀(regression): label이 연속적인 숫자일 때. 예: 집값, 온도, 시험 점수.
  • 분류(classification): label이 몇 개의 범주(category) 중 하나일 때. 예: 스팸/스팸아님, 고양이/개.

그림으로 그리면 이렇다.

text
feature(입력 벡터) --[모델]--> 예측값
                                  |
                          label(정답)과 비교
                                  |
                                loss 계산

loss(손실)는 "모델이 지금 얼마나 틀렸는지"를 숫자 하나로 나타낸 것이고, 학습 중에 이 숫자를 줄이는 방향으로 파라미터를 계속 조정한다. 회귀에서 자주 쓰는 loss는 평균제곱오차(MSE, mean squared error)다. 텍스트 수식으로 쓰면:

text
MSE = (1/N) * sum( (예측값 - 실제값)^2 )

분류에서 자주 쓰는 loss는 교차 엔트로피(cross entropy, 확률 분포 간 차이를 재는 방법)인데, 오늘은 sklearn이 내부적으로 처리해주므로 개념만 알아둔다.

metric(평가지표)은 loss와 다르다. loss는 "학습 중에 미분해서 파라미터를 업데이트하기 위한 숫자"이고, metric은 "사람이 결과를 보고 이해하기 위한 숫자"다. 예를 들어 분류에서는 정확도(accuracy, 전체 중 맞춘 비율)를 metric으로 쓰지만, accuracy는 미분이 안 되는(계단처럼 뚝뚝 끊기는) 값이라 학습용 loss로 쓰기 어렵다. 그래서 학습은 cross entropy로 하고, 평가는 accuracy로 보는 식으로 역할이 나뉜다.

코드로 직접 해보기

1단계: 데이터 없이 원리부터 — NumPy로 선형회귀를 직접 만들어본다.

python
import numpy as np # feature 하나짜리 가짜 데이터: x는 공부 시간, y는 시험 점수 np.random.seed(0) x = np.random.rand(100) * 10 # 0~10 사이 값 100개 y = 3 * x + 5 + np.random.randn(100) # 실제 규칙: y = 3x + 5 (+ 노이즈) # 파라미터 초기화 (기울기 w, 절편 b) w, b = 0.0, 0.0 lr = 0.01 # 학습률(learning rate): 한 번에 얼마나 이동할지 for epoch in range(200): y_pred = w * x + b error = y_pred - y loss = np.mean(error ** 2) # MSE # 경사(gradient) 직접 계산: loss를 w, b로 미분한 값 grad_w = np.mean(2 * error * x) grad_b = np.mean(2 * error) w -= lr * grad_w b -= lr * grad_b print(f"학습된 w={w:.2f}, b={b:.2f}, 최종 loss={loss:.4f}")

이걸 돌리면 w가 3에, b가 5에 가까워지는 걸 볼 수 있다. 이게 "학습"이라는 단어의 실체다: loss를 줄이는 방향으로 파라미터를 반복해서 조금씩 움직이는 것.

2단계: 같은 걸 sklearn으로.

python
from sklearn.linear_model import LinearRegression X = x.reshape(-1, 1) # sklearn은 항상 2차원 입력(샘플 수, feature 수)을 요구한다 model = LinearRegression() model.fit(X, y) # 학습 print("계수(w):", model.coef_) print("절편(b):", model.intercept_) print("R^2 score:", model.score(X, y)) # 회귀용 metric

NumPy로 직접 구한 w, b와 sklearn 결과가 비슷하게 나오는지 비교해본다. 완전히 같지는 않을 수 있는데, sklearn은 경사하강이 아니라 다른 방식(정규방정식 등)으로 정확한 해를 구하기 때문이다.

3단계: 분류 — LogisticRegression.

python
from sklearn.linear_model import LogisticRegression from sklearn.datasets import make_classification from sklearn.metrics import accuracy_score X_cls, y_cls = make_classification(n_samples=200, n_features=4, random_state=0) clf = LogisticRegression() clf.fit(X_cls, y_cls) y_pred = clf.predict(X_cls) print("accuracy (metric):", accuracy_score(y_cls, y_pred)) print("score() 결과:", clf.score(X_cls, y_cls)) # 내부적으로 accuracy와 같다

여기서 fit은 loss를 줄이며 학습, predict는 학습된 모델로 예측, score는 metric으로 평가하는 것임을 각각 확인한다.

오늘의 실험

질문: feature를 표준화(standardization, 평균 0 표준편차 1로 맞추는 것)하면 학습이 더 빨리 수렴하는가?

방법:

  1. 위 NumPy 선형회귀 코드에서 x 값을 0~10이 아니라 0~1000처럼 스케일을 크게 바꿔본다.
  2. 같은 학습률(lr=0.01)로 200 epoch 돌렸을 때 loss가 줄어드는지, 아니면 발산(값이 점점 커지다 nan이 되는 것)하는지 관찰한다.
  3. from sklearn.preprocessing import StandardScaler로 x를 표준화한 뒤 같은 학습률로 다시 돌려서 loss 곡선을 비교한다.
  4. epoch마다 loss를 리스트에 저장해서 표준화 전/후를 나란히 출력하거나 그래프로 그려본다.

기록할 것: "표준화 전 최종 loss / 표준화 후 최종 loss", "몇 epoch만에 loss가 어느 수준 아래로 내려갔는지". feature의 스케일이 크면 경사(gradient)도 커져서 학습률을 그대로 쓰면 발산하기 쉽다는 걸 몸으로 확인하는 게 목표다.

왜 이걸 배우나 (LLM / Post-training 연결)

지금 만든 "feature → 모델 → loss로 학습 → metric으로 평가"라는 뼈대는 LLM 학습에서도 똑같이 반복된다. 사전학습(pretraining)에서는 다음 토큰 예측 loss를 줄이지만, 실제로 모델이 쓸만한지는 별도의 평가 metric(정확도, 사람 선호도 등)으로 판단한다. Post-training(SFT, RLHF 등)에서도 "학습 중 최적화하는 목적함수"와 "최종적으로 사람이 보는 성능 지표"가 다르다는 감각이 그대로 이어지므로, 오늘 이 구분을 명확히 해두는 게 중요하다.

초보자가 막히는 지점

  • X.reshape(-1, 1)을 빼먹고 1차원 배열을 그대로 넣으면 sklearn이 에러를 낸다. sklearn은 항상 "(샘플 수, feature 수)" 형태의 2차원 입력을 기대한다.
  • loss와 metric을 같은 것으로 착각해서 "loss가 낮으니 accuracy도 당연히 높겠지"라고 넘겨짚는 경우가 많다. 실제로는 loss가 줄어도 metric이 기대만큼 안 오르는 경우가 자주 생기니 항상 둘 다 확인하는 습관을 들인다.
  • 학습률(lr)을 너무 크게 주면 loss가 줄어들지 않고 오히려 커지거나 nan이 뜬다. 이럴 땐 lr을 10배 줄여서 다시 시도한다.
  • model.score()가 회귀에서는 R^2, 분류에서는 accuracy를 반환하는 등 모델 종류에 따라 다른 값을 준다는 걸 모르고 숫자만 보고 헷갈리는 경우가 있다. 항상 어떤 metric인지 문서나 출력 이름으로 확인한다.

알고리즘 (병행 1시간)

오늘은 LeetCode Easy 1문제 + Medium 1문제(정렬 관련)를 푼다.

  • 문제를 풀기 전에 먼저 입력/출력 예시로 손으로 한 번 시뮬레이션해본다.
  • 풀이 후 기록할 것:

- 접근 방법: 어떤 자료구조·알고리즘을 썼는지 (예: 정렬 후 투 포인터, 해시맵 등) - 시간복잡도: 정렬을 썼다면 보통 O(n log n)이 기준선이 된다는 점을 의식하며 계산 - 틀렸다면 왜 틀렸는지: 엣지 케이스(빈 입력, 중복값, 음수 등)를 놓쳤는지, 아니면 로직 자체가 잘못됐는지 구분해서 한 줄로 남긴다.

이 기록을 누적하면 나중에 비슷한 유형이 나왔을 때 "아, 그때 이 실수 했었지"를 빠르게 떠올릴 수 있다.