회귀 분석을 '기울기가 0이 되는 지점'으로 해석하기: 단일 및 다중 회귀를 Python으로 확인
요약
본 기사는 회귀 분석을 '오차의 제곱합 최소화'라는 수리 최적화 문제로 접근합니다. 단일 및 다중 회귀를 직접 수식으로 풀고, 라이브러리 결과와 비교하는 과정을 설명하며, 미분과 행렬 개념을 활용하여 통계적 모델링 원리를 깊이 있게 이해하도록 돕습니다.
핵심 포인트
- 회귀 분석은 오차의 제곱합 최소화라는 최적화 문제이다.
- 최소값을 찾기 위해 '미분이 0'인 지점을 찾는 것이 핵심 단서가 된다.
- 다변수 함수에서는 편미분과 기울기(gradient) 개념을 사용한다.
서론
『분석가를 위한 수리 최적화 입문』(이토 모토하루・하라다 코헤이・마츠오카 유키, 소시무, 2026년)의 제3장은 '목적 함수가 미분 가능한 최적화'를 다루는 장입니다. 내용이 많아 4편으로 나누어 기사를 작성합니다. 이 글은 그 첫 번째 편으로, 회귀 분석을 수리 최적화 문제로 다시 풀어봅니다.
회귀 분석은 scikit-learn에서 LinearRegression().fit(x, y) 한 줄로 끝납니다. 하지만 그 안에서는 '오차의 제곱합을 최소화한다'는 최적화 문제가 해결되고 있습니다. 본 기사에서는 이 최적화 문제를 직접 수식으로 세우고, 직접 풀어보고, 라이브러리의 결과와 일치하는지 확인해 봅니다.
이 글을 읽은 후에는 다음 질문에 답할 수 있기를 목표로 합니다.
- 최소값을 찾을 때, 왜 '기울기가 0이 되는 지점'이 단서가 되는가. 그것만으로는 부족한 경우는 언제인가?
- 단일 회귀의 기울기와 절편을 구하는 공식은 어떤 식 변형에서 나오는가?
- 다중 회귀에서 자주 보는 (정규 방정식)은 무엇을 나타내는가? $X^ op X heta = X^ op y$ - 비슷한 정보의 열을 두 개 넣으면, 왜 답이 하나로 결정되지 않는가?
수식에는 고등학교 미분과 대학 1학년 정도의 편미분・행렬을 사용합니다. 새로운 도구가 나올 때마다 짧은 예시로 설명하겠습니다.
이번에 만들 것
웹 서버의 가상의 모니터링 데이터를 만들어, 평균 응답 시간을 예측하는 회귀식을 구합니다. 데이터는 1분 간격 기록으로 총 40분 분량입니다.
| 열 | 의미 | 범위 |
|---|---|---|
| 요청 수 | 1분당 온 요청의 수 (건/분) | 약 100~1,000 |
| ... | ||
| 데이터는 다음 '정답' 공식에 변동성(표준편차 10밀리초)을 더해 만들어집니다. 정답이 알고 있으므로, 최적화로 구한 값이 올바른지 확인할 수 있습니다. |
또한, 혼잡할수록 이미지 같은 큰 응답도 늘어난다는 관계도 넣어둡니다 (크기 $allingdotseq 30 + 0.1 imes$ 요청 수). 이 관계가 3-3에서 단일 회귀의 결과를 읽을 때 효과를 발휘합니다.
만들 파일은 다음 4개입니다.
| 파일 | 역할 |
|---|---|
server_data.py | 가상의 모니터링 데이터 생성 |
simple.py | 단일 회귀를 기울기가 0이 되는 연립방정식으로 풀기 |
multiple.py | 다중 회귀를 정규 방정식으로 풀기 |
collinear.py | 같은 정보의 열을 두 개 넣었을 때 무슨 일이 일어나는지 확인하기 |
이번에는 다음은 다루지 않습니다.
- 반복 계산으로 조금씩 답에 가까워지는 알고리즘(최단 경사법이나 뉴턴법 등). 회귀 분석은 기울기가 0이 되는 지점을 연립방정식으로 직접 풀 수 있기 때문입니다.
- 최대우도 추정의 생각. 회귀 분석을 확률의 언어로 재검토하는 방법이지만, 본 기사에서는 오차의 제곱합을 최소화하는 관점에 한정합니다.
전제 조건
| 항목 | 구동한 환경 |
|---|---|
| OS | Windows 11 |
| ... |
환경 준비
1. 최적해의 단서는 '기울기가 0'
1-1. 1변수: 기울기가 0인 지점을 찾기
먼저, 변수가 하나인 함수로 생각합니다.
미분하면
최소점에서 미분이 0이 되는 이유는 다음과 같습니다. 어떤 점
다만, 임계점(停留点)이 최소점일 필요는 없습니다.
| 함수 | 임계점 | 그 점은 |
|---|---|---|
| 최소 | ||
| ... | ||
| 즉, '미분이 0'은 최소값의 필요조건이지 충분조건이 아닙니다. 최소점은 반드시 임계점이지만, 임계점이 반드시 최소점인 것은 아닙니다. |
1-2. 다변수: 편미분과 기울기
변수가 2개 이상일 때는 편미분을 사용합니다. 편미분은 다른 변수를 상수로 간주하고 하나의 변수만으로 미분하는 것입니다. 예를 들어 다음 함수를 생각해 봅시다.
편미분을 나열한 벡터를 기울기(gradient)라고 부르고,
기울기는 그 점에서
1-3. 볼록 함수라면 '기울기가 0'으로 충분
기울기가 0이 되는 점이 최소점이라고 단정할 수 있는 대표적인 경우가, 목적 함수가 볼록(convex) 함수일 때입니다. 볼록 함수는 그래프가 그릇처럼 아래로 부푼 함수입니다. 볼록 함수에서는 임계점・국소 최적해・대역 최적해가 일치합니다.
2변수 함수의 볼록 여부는 2차 편미분 값을 나열한 행렬(헤세 행렬)로 판별할 수 있습니다. 2행 2열의 경우, 다음 두 가지가 모두 0 이상이면 볼록입니다 (어떤 점에서든 성립해야 합니다).
| 확인할 값 | 구하는 법 |
|---|---|
| 대각 성분의 합 | 좌상단 + 우하단 |
| 행렬식 | 좌상단 $ imes$ 우하단 $-$ 우상단 $ imes$ 좌하단 |
이 두 값은 헤세 행렬(Hessian matrix)의 두 고유값(eigenvalue)의 합과 곱에 해당합니다. 합과 곱이 둘 다 0 이상이라면, 고유값도 둘 다 0 이상입니다.
다만, 볼록 함수(convex function)라는 것은 '기울기가 0이면 최소'라고 말할 수 있는 충분조건일 뿐, 필요조건은 아닙니다.
| 함수 | 볼록성 | 임계점 | 최솟값 |
|---|---|---|---|
| 볼록 | 없음 | 없음 (0에 접근하지만 도달하지 않음) | |
| 볼록이 아님 |
2. 단회귀를 최적화 문제로 작성하기
2-1. 결정할 값은 기울기 a와 절편 b
회귀 분석은 예측하고 싶은 값을 [수식]으로 나타냅니다.
여기서 용어에 주의가 필요합니다. 통계학에서는
| 기호 | 통계적 명칭 | 최적화에서의 역할 |
|---|---|---|
| $a$ | 회귀 계수, 절편 | 결정 변수 (결정할 값) |
| $x_j$ | 설명 변수 | 상수 (데이터) |
| $y_j$ | 목적 변수 | 상수 (데이터) |
2-2. 목적 함수는 오차의 제곱합
제곱하는 이유는 다음 두 가지입니다.
- 플러스와 마이너스 오차가 상쇄되지 않음: 단순히 더하면, +10과 −10의 오차가 합계가 0이 되어버립니다.
- 미분 가능함: 절댓값($|y_j - (ax_j + b)|$)도 상쇄는 막을 수 있지만, 절댓값 그래프는 0점에서 꺾여 있어 그 지점에서는 미분이 불가능합니다. 제곱($rac{1}{2}(y_j - (ax_j + b))^2$)은 부드러워서 어디서든 미분할 수 있습니다.|y_j - (ax_j + b)|^ ext{2}
앞의 $rac{1}{2} imes ext{sum}$는
2-3. 편미분을 하여 연립방정식으로 만들기
이때 사용하는 것이 합성함수의 미분(연쇄 법칙, Chain Rule)입니다. '바깥쪽 미분 $ imes$ 안쪽 미분'으로 계산합니다. 예를 들어
이를 모든 데이터로 더하고, 앞의 $rac{1}{2} imes ext{sum}$는
오른쪽 식에서는,
이 두 개를 0으로 놓으면,
이 두 개의 식은 행렬을 사용하면 하나로 정리할 수 있습니다. 2행 2열의 행렬과 2개의 숫자를 나열한 벡터의 곱셈은 다음과 같이 계산합니다.
이 규칙을 적용하면, 연립방정식은 다음과 같이 작성됩니다.
왼쪽 행렬을 np.linalg.solve(A, c)
로 그대로 풀 수 있습니다.
2-4. 손으로 풀면 익숙한 공식이 됩니다
이 연립방정식을 손으로 풀면, 통계 교과서에 실린 공식이 나옵니다. 이하,
두 번째 식을
이는 회귀 직선이 평균의 지점이라는 의미입니다.
이를 첫 번째 식에 대입하고,
괄호 안은 평균으로부터의 편차 제곱합으로 다시 쓸 수 있습니다.
우변도 마찬가지로,
분자를
2-5. 이 임계점이 최소인 이유
기울기가 0이 되는 점을 찾았더라도, 1-1에서 본 것처럼 그것이 최소라고는 할 수 없습니다. 그래서,
2-3의 편미분을 다시 한번
이를 나열한 헤세 행렬은, 2-3의 연립방정식의 행렬
| 확인하는 값 | 계산 | 부호 |
|---|---|---|
| 대각 성분의 합 | 양수 | |
| 행렬식 | 0 이상 ( |
행렬식 변형에는, 2-4의
둘 다 0 이상이므로, 기울기가 0이 되는 점은 오차 제곱합이 최소가 되는 점이라고 말할 수 있습니다.
3. Python으로 단회귀를 풀기
3-1. 데이터 만들기
먼저, 가상의 모니터링 데이터를 만드는 파일입니다.
"""Web 서버의 가상 모니터링 데이터를 생성합니다 (난수 시드를 고정)"""
import numpy as np
# 정답 파라미터. 최적화로 이 값에 가까운 답이 나오는지 확인
...
| 행 | 의미 |
|---|---|
TRUE_A1 , TRUE_A2 , TRUE_B | |
| 정답 파라미터. 구한 값과 비교하는 데 사용 | |
default_rng(seed) | 난수 생성기. 시드를 고정했으므로, 몇 번 실행해도 같은 데이터가 나옴 |
size_kb = 30 + 0.1 * requests + ... | 혼잡한 시간대일수록 크기도 커진다는 관계를 넣음 |
3-2. 연립방정식을 풀기
2-3에서 만든 연립방정식을 풀어, 공식이나 scikit-learn의 답과 비교합니다.
"""단회귀: 기울기가 0이 되는 연립방정식을 풀어, 기울기 a와 절편 b를 구합니다"""
from pathlib import Path
import matplotlib
...
| 행 | 의미 |
|---|---|
A = np.array([[...], [...]]) | 2~3의 연립방정식 좌측 행렬 |
np.linalg.solve(A, c) | 연립방정식 |
a_formula ,b_formula | |
| --- | --- |
| 2-4에서 도출한 공식 | grad_a ,grad_b |
| --- | --- |
| 구한 값 | np.linalg.eigvalsh(A) |
matplotlib.use("Agg") | 화면을 띄우지 않고, 그림을 파일로 저장하기 위한 설정 |
실행합니다.
uv run python simple.py
연립방정식 : a = 0.0857, b = 67.50
공식 : a = 0.0857, b = 67.50
scikit-learn: a = 0.0857, b = 67.50
...
세 가지 방법으로, 같은 폴더에 저장된 simple.png가 산점도와 회귀 직선입니다.

3-3. 기울기가 정답인 0.05에서 벗어난 이유
정답의 기울기는 0.05인데, 구한 기울기는 0.0857이었습니다. 계산상의 오류는 아닙니다. 단일 회귀(simple regression)에서는 요청 수 외에도 크기(size)의 영향도 기울기에 섞이기 때문입니다.
data를 만들 때, 크기 $allingdotseq$ 30 + 0.1 $ imes$ 요청 수 라는 관계를 넣었습니다. 요청 수가 1 증가하면, 크기는 평균 0.1KB 늘어나고, 그만큼 응답 시간도 $0.3 imes 0.1 = 0.03$ 밀리초(ms) 늘어납니다. 단일 회귀는 크기를 알지 못하기 때문에, 이 0.03까지 요청 수의 효과로 계산합니다. 따라서 $0.05 + 0.03 = 0.08$이므로, $0.0857$은 거의 예상대로 벗어난 값입니다.
이 섞임(confounding)을 제거하려면, 크기 역시 설명 변수로 사용하는 다중 회귀(multiple regression)가 필요합니다.
4. 다중 회귀로 확장하기
4-1. 행렬로 정리해서 쓰기
설명에 사용하는 값이 2개가 되면, 예측식은
이대로 편미분해도 풀 수 있지만, 변수가 늘어날 때마다 식이 길어집니다. 그래서 행렬을 사용해 정리합니다. 핵심은 **모두 1인 열(column)을 더하여 절편(intercept)**을 만드는 것입니다.
오차의 제곱합(Sum of Squared Errors, SSE)은 다음과 같이 쓸 수 있습니다.
4-2. 기울기를 계산하면 정규방정식(Normal Equation)이 된다
2-3과 같은 연쇄 법칙(chain rule)으로, 안쪽 미분은
여기서, 안쪽 미분이 왜
예를 들어,
이는,
마찬가지로,
즉, 편미분한 식은 최종적으로 다음과 같습니다.
이것을 0으로 두고 이항하면, 다중 회귀의 정규방정식이 됩니다.
설명에 사용하는 값이 1개일 때(
순서만 바뀌었을 뿐, 2-3의 단일 회귀 행렬
볼록 함수인지도 확인합니다. 헤세 행렬은
어떤 방향이라는 뜻이며, 이는 고유값이 모두 0 이상인 것과 같습니다. 따라서 다중 회귀의 목적 함수 역시 볼록 함수(convex function)이고, 정규방정식의 해가 오차의 제곱합의 최솟값을 제공합니다.
4-3. Python으로 다중 회귀 풀기
"""다중 회귀: 정규방정식 $X^T X heta = X^T y$를 풀어, 절편과 두 개의 기울기를 구함"""
import numpy as np
from sklearn.linear_model import LinearRegression
...
| 행 | 의미 |
|---|---|
np.column_stack([np.ones(n), ...]) | 1의 열, 요청 수, 크기를 가로로 나열하여 |
np.linalg.solve(X.T @ X, X.T @ y) | 정규방정식 (Normal Equation) @은 행렬 곱셈, .T는 전치 |
np.linalg.lstsq(X, y, rcond=None) | 오차의 제곱합 (Sum of Squared Errors) |
X.T @ (X @ theta - y) | 4-2에서 도출한 기울기 (gradient) |
uv run python multiple.py
정답 : b = 60.00, a1 = 0.0500, a2 = 0.3000
정규방정식 : b = 57.98, a1 = 0.0507, a2 = 0.3176
lstsq : b = 57.98, a1 = 0.0507, a2 = 0.3176
...
요청 수의 계수는 단일 회귀의 $0.0857$에서 $0.0507$로 변하여 정답인 $0.05$에 가까워졌습니다. 크기의 영향을 별도의 계수
정답과 완전히 일치하지 않는 것은 데이터에 분산(variability)을 추가했기 때문입니다. 40개의 데이터로는 이 정도의 정확도로 추정할 수 있다는 의미입니다.
4-4. 같은 정보의 열을 2개 넣으면, 답이 하나로 결정되지 않는다
다중 회귀에서 실제로 발생하기 쉬운 함정 중 하나를 시도해 보겠습니다. 크기를 'KB'와 '바이트' 두 열에 모두 넣어보겠습니다. 바이트 열은 KB 열을 1024배 한 것일 뿐이므로, 새로운 정보는 아무것도 없습니다.
"""같은 정보를 담은 열(KB와 바이트)을 모두 넣으면, 기울기가 0이 되는 지점이 하나로 결정되지 않는다"""
import numpy as np
from sklearn.linear_model import LinearRegression
...
uv run python collinear.py
np.linalg.solve: LinAlgError: Singular matrix
lstsq : KB의 계수 = 3.029e-07, 바이트의 계수 = 3.102e-04
scikit-learn: KB의 계수 = 3.029e-07, 바이트의 계수 = 3.102e-04
...
np.linalg.solve
은 LinAlgError: Singular matrix
(행렬이 특이함) 오류로 멈췄습니다. 특이한 행렬이란 역행렬을 갖지 않는 행렬을 말합니다.
이는 기울기가 0이 되는 지점이 무수히 많기 때문에 발생합니다. 크기의 효과 0.3176을 KB 열과 바이트 열에 어떻게 분배하든 예측값은 완전히 같아지기 때문입니다. 예를 들어, 'KB의 계수를 0.3176, 바이트의 계수를 0'으로 하거나, 'KB의 계수를 0, 바이트의 계수를 0.3176÷1024'로 해도 오차 제곱합은 변하지 않습니다. 연립방정식의 답이 하나로 결정되지 않기 때문에, solve
은 풀 수 없습니다.
반면, lstsq
와 scikit-learn은 오류를 내지 않고 답을 하나 반환했습니다. np.linalg.lstsq
는 최소가 되는 답이 여러 개일 때는 계수 벡터의 길이가 가장 짧은 것을 반환한다고 문서에 쓰여 있습니다. scikit-learn의 LinearRegression
도 내부적으로 SciPy의 lstsq
를 사용하고 있습니다. 반환된 두 개의 계수를 KB로 환산하여 더하면 0.3176이 되며, 효과를 두 열에 나눈 답이라는 것을 알 수 있습니다.
함정에 빠졌을 때 확인 항목
| 오류나 증상 | 원인 | 대처 |
|---|---|---|
| Git Bash에서 출력을 파이프나 리다이렉트로 전달하면 한글이 깨짐 | 출력 목적지가 콘솔이 아니면, Python이 Windows의 문자 코드(cp932)로 출력함 | PYTHONUTF8=1 uv run python simple.py처럼 UTF-8 모드로 실행하기 |
np.linalg.LinAlgError: Singular matrix | 같은 정보의 열(단위가 다른 열 등)이 포함되어 답이 하나로 결정되지 않음 | 4-4와 같이, 열을 검토하기 |
ModuleNotFoundError: No module named 'sklearn.metrics._pairwise_distances_reduction._middle_term_computer' | 폴더 경로가 너무 깊어서 scikit-learn 파일의 경로가 Windows 상한(260자)을 초과함 | `C:\work |
| egression-demo`와 같이 짧은 경로로 프로젝트를 다시 만들기 |
세 번째는 필자가 깊은 폴더에서 시도했을 때 실제로 발생했던 오류입니다. 파일 자체는 존재하지만, 경로가 너무 길어서 읽어올 수 없었습니다. Windows에는 긴 경로를 활성화하는 설정(LongPathsEnabled
)도 있지만, 앱 측의 대응도 필요합니다(Microsoft 문서). 짧은 경로로 다시 만드는 것이 확실합니다.
요약
이 글에서는 회귀 분석을 '오차 제곱합을 최소화하는 최적화 문제'로서 직접 공식을 세워 풀었습니다.
- 최소점에서는 기울기가 0이 된다(필요조건). 목적함수가 볼록하면, 기울기가 0인 점이 최소점이 된다
- 단회귀의 목적함수를 편미분하여 0으로 놓고, 2변수 연립방정식을 만들었습니다. 손으로 풀면 '기울기 = 공분산 ÷ 분산' 공식이 되었습니다.
- 헤세 행렬이 연립방정식의 행렬과 같고, 행렬식이 0 이상이라는 것에서 볼록 함수임을 확인했습니다.
- 다중 회귀는, 1열을 더한 행렬
를 사용하여 정규 방정식 $X^ op X heta = X^ op y$로 정리했습니다. 단회귀의 기울기 0.0857이 정답인 0.05에서 벗어난 이유(크기의 영향 혼재)를 다중 회귀로 제거했습니다. - 같은 정보의 열을 두 개 넣으면, 기울기가 0인 점이 무수히 많아져서,
solve
오류, lstsq
은 최소 길이의 해를 반환했습니다.
라이브러리의 한 줄 뒤에는 '기울기를 0으로 두고 연립방정식을 푸는' 최적화가 있었습니다. 회귀 분석에서 기울기가 0이 되는 점을 직접 구할 수 있었던 것은, 목적 함수가 제곱합(sum of squares)이고 편미분이 1차식이 되었기 때문입니다.
참고 자료
논의

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn ML의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기