컴퓨터공학 300 주제 시리즈의 265번째 글이다. 전체 지도는 여기.

한 줄 요약

로지스틱 회귀는 선형 결합 z = wᵀx 를 시그모이드에 통과시켜 0~1 사이의 확률로 바꾸고, 로그 손실(교차 엔트로피)을 최소화해 학습하는 분류 모델이다. 이름에 “회귀”가 붙었지만 분류기다.

왜 필요한가

분류 문제에 선형 회귀를 그대로 쓰면 곤란하다. 예측값이 -0.3 이나 1.7 처럼 확률로 읽을 수 없는 값이 나오고, 결정 경계에서 먼 점 몇 개가 직선을 끌고 가 경계가 엉뚱하게 움직인다.

로지스틱 회귀는 이 문제를 풀면서도 선형 모델의 장점(빠름, 해석 가능)을 지킨다. 신용 평가, 클릭 예측, 의료 위험도처럼 “확률”이 필요한 곳에서 지금도 기준 모델이다. 그리고 신경망 분류기의 마지막 층(시그모이드·소프트맥스 + 교차 엔트로피)이 바로 로지스틱 회귀다. 여기서 이해한 것이 딥러닝 분류 전체로 이어진다.

핵심 개념

시그모이드

σ(z) = 1 / (1 + e^(−z))

z:    −∞ ... −2    0    2  ... +∞
σ(z):  0  ... 0.12 0.5 0.88 ...  1
어떤 실수든 (0, 1) 로 눌러 담는다. 모델은 P(y=1 x) = σ(wᵀx) 다.

오즈와 로짓

σ 의 역함수를 풀면

log( p / (1 − p) ) = wᵀx

p/(1−p) 는 오즈, 그 로그를 로짓이라 한다. 즉 로지스틱 회귀는 로그 오즈를 선형으로 모델링한다. 해석도 여기서 나온다. 가중치 wj 가 0.7 이면, 특징 xj 가 1 늘 때 오즈가 e^0.7 ≈ 2 배가 된다.

결정 경계

p ≥ 0.5 이면 1 로 분류한다고 하면, σ(z) = 0.5 인 z = 0 이 경계다. 즉 wᵀx = 0 인 초평면. 그래서 로지스틱 회귀는 선형 분류기다. 원 모양 경계가 필요하면 x², x·y 같은 특징을 직접 만들어 넣어야 한다.

손실 — 로그 손실

정답 y ∈ {0, 1}, 예측 확률 p 에 대해

L = −[ y·log p + (1 − y)·log(1 − p) ]

정답이 1 인데 p = 0.01 이라고 자신 있게 틀리면 −log 0.01 ≈ 4.6 으로 크게 벌받는다. MSE 대신 이 손실을 쓰는 이유는 두 가지다. 확률 모델의 최대우도 추정과 정확히 같고, 시그모이드와 결합했을 때 손실이 w 에 대해 볼록해서 지역 최솟값이 없다.

기울기는 놀랄 만큼 깔끔하다.

∂L/∂w = (p − y) · x

선형 회귀의 (ŷ − y)·x 와 같은 모양이다. “예측과 정답의 차이 × 입력”이라는 이 형태는 신경망의 출력층에서도 그대로 나온다.

다중 클래스 — 소프트맥스

클래스가 K 개면 클래스마다 점수 zk 를 만들고

P(y = k | x) = exp(zk) / Σj exp(zj)

로 정규화한다. 이것이 소프트맥스 회귀(다항 로지스틱 회귀)다.

정규화와 분리 가능한 데이터

데이터가 완벽하게 선형 분리되면 로그 손실은 w 를 무한히 키울수록 계속 줄어든다. 가중치가 발산한다. 그래서 실무 구현은 기본으로 L2 정규화를 건다. scikit-learn 의 LogisticRegression 도 기본 벌점이 L2 이고, 강도는 C(정규화 세기의 역수)로 조절한다.

직접 해 보기

공부 시간으로 합격 여부를 예측하는 1차원 예제다. 넘파이로 경사 하강법을 직접 돌린다.

import numpy as np
rng = np.random.default_rng(1)
# 공부 시간(x) -> 합격(1)/불합격(0)
x = np.r_[rng.normal(2, 1, 30), rng.normal(5, 1, 30)]
y = np.r_[np.zeros(30), np.ones(30)]
X = np.column_stack([np.ones_like(x), x])
sig = lambda z: 1 / (1 + np.exp(-z))
w = np.zeros(2)
for step in range(3000):
    p = sig(X @ w)
    w -= 0.1 * X.T @ (p - y) / len(y)          # 로그 손실의 기울기
p = sig(X @ w)
eps = 1e-12
loss = -np.mean(y*np.log(p+eps) + (1-y)*np.log(1-p+eps))
acc = np.mean((p >= 0.5) == y)
print("가중치 [b, w]:", w.round(3), " 결정 경계 x =", round(-w[0]/w[1], 3))
print(f"로그 손실={loss:.3f}  정확도={acc:.3f}")
for h in (2, 3.5, 5):
    print(f"x={h}: P(합격)={sig(w[0]+w[1]*h):.3f}")

실행 결과:

가중치 [b, w]: [-8.939  2.715]  결정 경계 x = 3.293
로그 손실=0.078  정확도=0.983
x=2: P(합격)=0.029
x=3.5: P(합격)=0.637
x=5: P(합격)=0.990

결정 경계는 공부 시간 약 3.3 시간이다. 두 무리의 평균(2, 5) 사이다. 중요한 것은 출력이 확률이라는 점이다. 3.5 시간은 경계를 겨우 넘어 63.7% 로, 5 시간은 99% 로 판단한다. 같은 “합격” 예측이라도 확신의 정도가 다르고, 이 숫자로 임계값을 업무에 맞게 조정할 수 있다.

코드의 갱신식 X.T @ (p - y) 가 위에서 본 기울기 (p − y)·x 그대로다.

현업에서는

  • 확률이 곧 의사결정 입력이다. 대출 심사는 “부도 확률 3%” 를 받아 금리를 정한다. 이때는 확률이 실제 빈도와 맞는지(보정, calibration)가 정확도만큼 중요하다.
  • 임계값은 0.5 가 아니어도 된다. 암 검진처럼 놓치는 비용이 크면 임계값을 낮춘다. 다음 글들의 평가 지표와 연결된다.
  • 해석이 필요한 규제 영역에서 여전히 선호된다. 계수를 오즈비로 설명할 수 있기 때문이다.
  • 피처 엔지니어링이 성능을 좌우한다. 선형 경계밖에 못 그리므로, 비선형 관계는 구간화나 상호작용 특징으로 넣어 줘야 한다.

확인 문제

  1. 로지스틱 회귀의 결정 경계(임계값 0.5)는 어떤 모양인가?
  2. 가중치 w = 0.7 의 의미를 오즈로 설명하라.
  3. 분류에 MSE 대신 로그 손실을 쓰는 이유 두 가지는?
  4. 학습 데이터가 완벽하게 선형 분리될 때 정규화 없이 학습하면 어떻게 되는가?
  5. 예제에서 x = 3.5 의 예측은 1 인데, 이것을 “확실히 합격” 이라고 보고하면 왜 문제인가?

풀이

  1. wᵀx = 0 인 초평면(1차원에서는 점, 2차원에서는 직선).
  2. 해당 특징이 1 증가하면 오즈가 e^0.7 ≈ 2.01 배가 된다.
  3. 최대우도 추정과 같아 확률적 해석이 명확하고, 시그모이드와 결합해 볼록 함수가 되어 최적화가 안정적이다.
  4. 손실을 계속 줄이려고 가중치 크기가 무한히 커진다. 확률이 0 과 1 로 극단화되고 수치적으로 불안정해진다.
  5. 확률이 63.7% 에 불과하다. 경계 근처라 틀릴 가능성이 상당하므로 확률을 함께 보고해야 한다.

더 읽을거리 (References)