로지스틱 회귀를 최대우도 추정으로 풀기: 기울기와 헤세 행렬을 유도하여 뉴턴법으로 구하기
요약
본 기사는 로지스틱 회귀 분석을 최대우도 추정(MLE) 관점에서 깊이 있게 다룹니다. 목적함수와 기울기 및 헤세 행렬을 직접 유도하고, 뉴턴법을 사용하여 라이브러리 결과와 비교하는 과정을 설명합니다. 이를 통해 MLE의 원리와 로지스틱 회귀 모델링의 수학적 배경 지식을 습득할 수 있습니다.
핵심 포인트
- 로지스틱 회귀는 최대우도 추정(MLE) 개념으로 목적함수를 최소화하여 구한다.
- 최대우도 추정을 이해하기 위해 목적함수, 기울기, 헤세 행렬 유도가 필요하다.
- 뉴턴법을 사용하여 로지스틱 회귀의 파라미터를 직접 계산하고 검증할 수 있다.
서론
『분석가를 위한 수리 최적화 입문』(이토 모토하루・하라다 코헤이・마츠오카 유키, 소시무, 2026년)의 제3장을 네 편으로 나누어 기사로 작성하고 있습니다. 이 기사는 세 번째 글로, 로지스틱 회귀를 다룹니다.
로지스틱 회귀는 '지연이 발생하는지, 발생하지 않는지'와 같은 2가지 선택적 결과를 확률로 예측하는 방법입니다. scikit-learn에서는 LogisticRegression().fit(x, y)으로 구할 수 있지만, 그 내부적으로는 '최대우도 추정(Maximum Likelihood Estimation)'이라는 개념으로 만든 목적함수를 반복하여 최소화하고 있습니다. 본 기사에서는 이 목적함수와 기울기(gradient)를 직접 유도하고 뉴턴법(Newton's method)으로 풀어 라이브러리가 제시하는 답과 비교해 봅니다.
이 글을 다 읽고 나면, 다음 질문에 답할 수 있기를 목표로 합니다.
- 최대우도 추정이란 무엇인가. 최소제곱법(Least Squares Method)과 어떻게 연결되는가?
- 로지스틱 회귀의 목적함수는 어떻게 만드는가. 왜 오차 제곱합을 사용하지 않는가?
- 기울기와 헤세 행렬은 어떻게 유도하는가. 왜 기울기를 0으로 놓아도 직접적으로는 풀 수 없는가?
- 데이터에 따라 최대우도 추정의 답이 존재하지 않는 이유는 무엇인가?
수식에는 고등학교 수준의 미분・로그와 대학교 1학년 수준의 편미분・행렬을 사용합니다. 새로운 도구가 나올 때마다 짧은 예시로 설명하겠습니다.
이번에 만들 것
웹 서버의 가상 모니터링 데이터(200분 분)를 사용하여, CPU 사용률을 기반으로 그 1분 동안 지연(응답이 1초를 초과하는 것)이 발생할 확률을 예측합니다.
| 열 | 의미 | 값 |
|---|---|---|
| CPU 사용률 | 해당 1분간의 평균 (%) | 10~95 |
| 지연 | 응답이 1초를 초과했는지 | 1 (지연 있음), 0 (없음) |
데이터는 CPU 사용률 70%일 때 지연 확률이 정확히 50%가 되도록 하는 '정답'으로부터 만듭니다. 구한 파라미터가 정답에 가까운지를 통해 최적화가 올바르게 되었는지 확인할 수 있습니다.
만들 파일은 다음 5가지입니다.
| 파일 | 역할 |
|---|---|
delay_data.py | 가상 모니터링 데이터 생성 |
logistic_model.py | 음의 로그 우도・기울기・헤세 행렬 및 뉴턴법 |
logistic.py | 로지스틱 회귀를 풀어 SciPy나 scikit-learn과 비교 |
loss_compare.py | 오차 제곱합과 음의 로그 우도를 비교 |
separable.py | 완전히 분리 가능한 데이터에서 무슨 일이 일어나는지 확인 |
전제 조건
| 항목 | 구동한 환경 |
|---|---|
| OS | Windows 11 |
| ... |
환경 준비
1. 최대우도 추정: 데이터가 가장 많이 나올 파라미터를 선택하기
1-1. 우도란
최대우도 추정은 '이 파라미터라면, 손에 쥔 데이터가 얼마나 많이 나올지 계산하여, 그 값이 최대가 되는 파라미터를 선택하는' 방법입니다. 이 '나올 가능성'을 우도(Likelihood)라고 부릅니다.
간단한 예로 생각해 봅시다. 1분마다, 확률
미분을 0으로 놓으면
로그를 취한 것은 다음 두 가지 성질이 있기 때문입니다.
- 곱셈이 덧셈이 된다: $\log(pq) = \log p + \log q$로. 데이터가 늘어나도 합은 미분하기 쉽다\n- 크기 관계가 변하지 않는다: $f(x)$는 증가함수이므로, $\log$를 최대화하는 지점과 $L$을 최대화하는 지점은 같다$\n
1-2. 우도는 확률이 아니다
데이터가 연속값인 경우, 우도는 확률이 아니라 확률 밀도(Probability Density) 값으로 계산합니다. 예를 들어 정규분포를 가정한다면, 각 데이터의 정규분포 밀도를 곱한 것이 우도가 됩니다.
확률 밀도는 '그 값 근처에서 얼마나 많이 나올지'를 나타내는 값이며, 확률 그 자체는 아닙니다. 연속값에서는 특정 1점짜리 값이 나올 확률은 0입니다. 또한, 밀도는 1을 초과할 수도 있습니다.
| 값 | 크기 |
|---|---|
| 표준편차 0.1의 정규분포에서 평균에서의 밀도 | 3.989 (1 초과) |
| 표준정규분포에서 -0.01부터 0.01 범위에 들어갈 확률 | 0.0080 (폭을 좁힐수록 0에 가까워짐) |
우도는 '데이터가 나올 가능성을 비교하기 위한 값'이며, 확률처럼 합이 1이 되는 것은 아니라는 점을 기억해 두세요.
1-3. 정규분포를 가정하면 최소제곱법이 된다
회귀 분석에서 예측값과 실제값의 차이(오차) $\hat{y} - y$에 파라미터(회귀 계수)가 들어간 것은 두 번째 항의 오차 제곱합입니다. 정규분포의 오차를 가정했을 때 최대우도 추정은, 오차 제곱합을 최소로 만드는 최소제곱법과 같은 답이 됩니다.
최소제곱법은 최대우도 추정의 특별한 경우라고 볼 수 있습니다. 그리고 결과가 이진(2択) 데이터인 경우에는 정규분포 대신 다른 분포를 가정하면 된다는 것이 로지스틱 회귀의 아이디어입니다.
2. 로지스틱 회귀의 목적 함수 만들기
2-1. 로지스틱 함수로 확률 표현하기
CPU 사용률
| 0.0067 | 0.5 | 0.9933 |
2-2. 한 개 데이터의 우도(Likelihood)
2-3. 음의 로그 우도를 최소화하기
데이터가 독립이라면, 전체 우도는 각 데이터의 우도의 곱입니다. 여기에 로그를 취하면 합이 되고, 게다가 마이너스를 붙이면 '최대화'가 '최소화'로 바뀝니다.
이를 음의 로그 우도(Negative Log-Likelihood)라고 부릅니다. 기계 학습에서는 교차 엔트로피(Cross-Entropy)라고도 불립니다. 로지스틱 회귀는 이
코드에서는 이 식을 계산하기 쉬운 형태로 변형하여 사용합니다.
np.logaddexp(0, z)
은
2-4. 왜 오차 제곱합을 사용하지 않는가
회귀 분석과 마찬가지로, 예측한 확률과 실제 값(0 또는 1)의 차이의 제곱합을 사용하면 되지 않을까 생각할 수 있습니다. 두 가지 평가 방법을 비교해 보겠습니다.
"""지연되지 않은 데이터(y=0)에, 지연의 확률 p와 예측했을 때의 '패널티'를 두 가지 평가 방법으로 비교"""
import numpy as np
from scipy.special import expit
...
uv run python loss_compare.py
예측한 확률 p | 제곱 오차 p^2 | 음의 로그 우도 -log(1-p)
0.5 | 0.2500 | 0.69
0.9 | 0.8100 | 2.30
...
앞쪽 표는 지연되지 않은 데이터(
자신만만한 이상치를 제곱 오차는 가볍게 패널티를 준다 : 확률 99.99%로 예측하여 벗어나도, 제곱 오차는 최대 1 미만(0.9998)입니다. 음의 로그 우도는 9.21이고, 자신 있게 벗어날수록 끝없이 커집니다 -
제곱 오차는 볼록 함수가 아니다 : 뒤쪽은 파라미터 $\theta$를 움직였을 때의 2계 미분입니다. 제곱 오차는 $a$에서 음수가 되어 볼록 함수가 아닙니다. 음의 로그 우도는 조사한 지점에서는 모두 양수였습니다(3-4에서, 항상 볼록함을 보여줍니다) $a = 3$
볼록 함수가 아니면, 기울기가 0인 점이 최소라는 보장이 없고, 국소 최적해에 머무를 위험이 있습니다. 음의 로그 우도를 사용하면 이러한 걱정이 없습니다.
3. 기울기(Gradient)와 헤세 행렬(Hessian Matrix) 유도하기
3-1. 로지스틱 함수의 미분
먼저, 로지스틱 함수의 미분을 구합니다.
이를
미분이 원래 함수로 표현되는 것이 로지스틱 함수의 편리한 성질입니다.
3-2. 기울기는 '예측한 확률 − 실제 값'의 합
1개 데이터의 음의 로그 우도
'예측한 확률 − 실제 값'에 $ heta$를 곱하여, 회귀 분석의 기울기(예측값 − 실제 값)와 같은 형태가 됩니다.
3-3. 기울기를 0으로 두어도 직접는 풀 수 없다
회귀 분석에서는, 기울기를 0으로 두면
3-4. 헤세 행렬로부터 볼록 함수임을 확인하기
기울기를 다시 편미분하면, 헤세 행렬을 얻습니다. 3-1의
| 조사하는 값 | 계산 | 부호 |
|---|---|---|
| 대각 성분의 합 | 양수 | |
| 행렬식 | 0 이상 |
행렬식은,
따라서, 로지스틱 회귀의 음의 로그 우도는 볼록 함수이며, 기울기가 0이 되는 점을 찾으면 그것이 최소점입니다.
4. Python으로 풀기
4-1. 데이터 만들기
"""웹 서버의 가상의 모니터링 데이터: CPU 사용률과, 지연(응답이 1초를 초과했는지)"""
import numpy as np
from scipy.special import expit # 로지스틱 함수 1 / (1 + e^(-z))
...
expit
은 SciPy에 준비된 로지스틱 함수입니다. 1 / (1 + np.exp(-z))
로 직접 쓰면, RuntimeWarning: overflow encountered in exp
라는 경고가 나옵니다. expit
은 같은 값이라도 경고 없이 0을 반환합니다.
rng.uniform(size=n) < p
는, 0부터 1의 균일 난수가
4-2. 목적 함수・기울기・헤세 행렬과 뉴턴법
로지스틱 회귀의 음의 로그 우도(Negative Log-Likelihood), 기울기(Gradient), 헤세 행렬(Hessian)과 뉴턴법
import numpy as np
from scipy.special import expit # 로지스틱 함수 1 / (1 + e^(-z))
...
| 함수 | 식 | 대응하는 절 |
|---|---|---|
neg_log_likelihood | 2-3 | |
gradient | ||
| 3-2 | ||
hessian | ||
| 3-4 | ||
newton | ||
| — |
뉴턴법은 목적 함수를 2차 함수(포물선)로 근사하고, 그 최소점의 방향으로 나아가는 방법입니다. 보폭은 '1차 근사로 예상한 감소 폭이 tol 이하가 되면 멈춥니다.
hessian
의 X * (h * (1 - h))[:, None]는
4-3. 풀어보고, 라이브러리와 비교하기
"""로지스틱 회귀: 음의 로그 우도를 최소화하는 a, b를 구하고, SciPy나 scikit-learn과 비교한다"""
import numpy as np
from scipy.optimize import approx_fprime, minimize
...
| 부분 | 의미 | |
|---|---|---|
`approx_fprime` | 수치 미분(조금 벗어난 지점과의 차이)으로 기울기를 근사한다. 직접 유도한 식과 비교하여, 식의 오류를 찾아낸다. |
`minimize(..., method="BFGS")` | SciPy의 준뉴턴법 (BFGS법). 헤세 행렬을 사용하지 않고, 기울기의 변화로부터 근사한다. |
`LogisticRegression(C=C)` | scikit-learn의 로지스틱 회귀. `C=np.inf`일 때 정규화 없음, 기본값인 `C=1.0`일 때는 정규화 있음.
uv run python logistic.py
지연 시간 있음: 73 / 200 건
기울기(식) : [2384.28600025 42.03795282]
기울기(수치 미분): [2384.31738972 42.03796355]
...
결과를 순서대로 살펴봅니다.
- **기울기의 식은 정확하다**: 직접 유도한 식과 수치 미분이 위에서 4자리 이상 일치했습니다. 수치 미분에는 벗어난 폭에 따른 오차가 있어, 완벽하게 일치하지는 않습니다. -
**뉴턴법은 7회 만에 도달했다**: , a = 0.1369입니다. SciPy의 BFGS법(15회)과 정규화가 없는 scikit-learn도 같은 값이 되었습니다b = -9.437 -
**정답과의 차이**: 정답은 , a = 0.15였습니다. 데이터가 200건밖에 없기 때문에, 이 정도 오차가 생깁니다. 확률이 50%가 되는 CPU 사용률은 68.9%로, 정답의 70%에 가까운 값입니다b = -10.5 -
**기본값인 `C=1.0`**:
에서는 조금 다르고, 정규화가 없는 b = -9.434와 일치하지 않습니다-9.437
scikit-learn의 `LogisticRegression`은 기본적으로 정규화(계수가 너무 커지지 않도록 목적 함수에 벌칙 항을 추가하는 것)가 들어 있습니다. 따라서, 기본값 그대로는 최대 우도 추정과는 조금 다른 문제를 풀고 있는 것입니다. 벌칙의 강도는 `C`로 결정되며, `C`를 작게 할수록 정규화가 강해져서, 최대 우도 추정의 답과의 차이가 커집니다.
## 5. 완전히 분리 가능한 데이터에서는 답이 존재하지 않는다
로지스틱 회귀에는, 데이터에 따라 최대 우도 추정의 답이 존재하지 않을 수 있다는 함정이 있습니다. CPU 사용률이 70%를 넘으면 반드시 지연하고, 그 이하일 때는 반드시 지연하지 않는다는 데이터로 재구성하여 시도해봅니다.
"""완전히 분리 가능한 데이터에서는 음의 로그 우도를 최소화하는 점이 존재하지 않는다"""
import numpy as np
from sklearn.linear_model import LogisticRegression
...
uv run python separable.py
직접 구현 tol=0.01: a = 22.59, b = -1583.1, 음의 로그 우도 = 5.8e-04, 경계 = 70.09% (18 회)
직접 구현 tol=1e-06: a = 47.52, b = -3330.8, 음의 로그 우도 = 7.2e-08, 경계 = 70.09% (27 회)
scikit-learn C=inf: a = 23.00, b = -1612.4, 경계 = 70.11%
멈추는 조건 `tol`을 엄격하게 하면,
원인은, 이 데이터에서는,
반면, 경계(boundary)의 CPU 사용률(`C`)
을 넣으면 계수가 커질수록 패널티가 증가하기 때문에 답이 하나로 결정됩니다.
## 막혔을 때 확인 항목
| 오류 또는 증상 | 원인 | 대처 |
|---|---|---|
| 직접 계산한 계수와 scikit-learn의 계수가 약간 다름 | `LogisticRegression`은 기본적으로 정규화(`C=1.0`)가 적용됨 | 4-3과 같이, `C=np.inf`를 지정할 것 |
`FutureWarning: 'penalty' was deprecated in version 1.8 ...` | scikit-learn 1.8 이후로, `penalty` 인수가 비권장(deprecated)이 되었음 |
`penalty=None`은 `C=np.inf`로 변경됨 |
| 계수가 극단적으로 크고, 도구마다 값이 다름 | 데이터가 완전히 분리되어 있어, 최대우도 추정의 답이 존재하지 않음 | 5장과 같이, 경계(boundary)를 이용할 것 |
`RuntimeWarning: overflow encountered in exp` | 로지스틱 함수를 `1 / (1 + np.exp(-z))`로 직접 작성함 | `scipy.special.expit`이나 `np.logaddexp`를 사용할 것 |
| Git Bash에서, 출력을 파이프나 리다이렉트에 통과시키면 한글이 깨짐 | 출력지가 콘솔(console)이 아닐 경우, Python이 Windows의 문자 코드(cp932)로 출력함 | `PYTHONUTF8=1 uv run python logistic.py`와 같이, UTF-8 모드로 실행할 것 |
## 요약
본 기사에서는 로지스틱 회귀의 목적 함수를 최대우도 추정(Maximum Likelihood Estimation)으로부터 만들고, 기울기(gradient)와 헤세 행렬(Hessian matrix)을 유도하여 뉴턴법(Newton's method)으로 풀었습니다.
- 최대우도는 데이터가 나올 확률(likelihood)이 최대가 되는 파라미터를 선택하는 방법이며, 오차에 정규분포를 가정하면 최소제곱법과 같은 답이 되었습니다.
- 2가지 선택의 데이터에서는, 1개의 우도를 $ ext{h}^y (1 - ext{h})^{1 - y}$로 작성하고 로그를 취해 마이너스를 붙인 음의 로그우도(Negative Log-Likelihood)를 최소화한 $ ext{h}^y (1 - ext{h})^{1 - y} - ext{오차의 제곱합}$은, 자신만만한 이상치(outlier)에 대해서는 가벼운 패널티만 부여하고 볼록 함수(convex function)가 되지 않았습니다.
- 기울기는
AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기