
데이터에 가장 잘 맞는 직선을 찾는 선형 회귀
- 1머신러닝은 데이터에서 규칙을 찾는다, 지도·비지도·강화학습
- 2문제 정의부터 배포까지, 머신러닝 프로젝트의 전 과정
- 3데이터에 가장 잘 맞는 직선을 찾는 선형 회귀읽는 중
- 4모델이 얼마나 틀렸는지 재는 비용 함수
- 5비용 함수의 바닥까지 걸어 내려가는 경사하강법
집값을 예측해야 한다고 해보자. 수백 건의 거래 기록이 있고 각 집의 면적과 거래가가 담겨 있다. 정답이 붙은 데이터로 모델을 학습시켜 새로운 입력에 대한 연속적인 출력값을 맞히는 문제, 곧 회귀(Regression)다.
이 문제에 대한 가장 단순한 답이 선형 회귀(Linear Regression) 다. 단순하다고 얕볼 게 아니다. 가설 함수, 파라미터 학습, 오차 측정, 최적화라는 ML의 핵심 개념이 전부 이 안에 들어 있고, 복잡한 신경망도 결국 이 뼈대 위에서 돌아간다.
직선 하나로 예측한다는 것
선형 회귀의 아이디어는 데이터를 가장 잘 관통하는 직선을 하나 긋는 것이다. 면적 와 집값 의 관계를 이렇게 쓴다.
는 기울기(weight)로 가 1 늘 때 가 얼마나 변하는지를 나타내고, 는 절편(bias)으로 가 0일 때의 값이다. ML에서는 이 식을 가설 함수(Hypothesis Function) 라고 부른다. “데이터를 이 형태의 식으로 설명할 수 있다”는 가설이라는 뜻이다.
그렇다면 학습(Learning) 은 와 를 데이터에 맞게 조정하는 과정이다. 아무 직선이나 그어놓고 데이터를 보며 점점 더 잘 맞는 직선으로 고쳐 나간다. 모델이 “학습한다”는 말의 실제 내용이 이것이다.
scikit-learn으로는 두 줄이면 끝난다.
import numpy as np
from sklearn.linear_model import LinearRegression
area = np.array([60, 75, 85, 95, 110, 120, 140, 155]).reshape(-1, 1) # m²
price = np.array([2.1, 2.8, 3.2, 3.6, 4.1, 4.5, 5.2, 5.8]) # 억
model = LinearRegression().fit(area, price)
print(f"{model.coef_[0]:.5f}, {model.intercept_:.5f}") # 0.03804, -0.08176기울기가 0.038이므로 면적이 1m² 늘 때마다 집값이 약 380만 원 오른다고 모델이 읽어낸 것이다. model.predict([[100]])이 하는 일도 를 계산하는 것뿐이다.
무엇이 좋은 직선인가
“가장 잘 맞는” 직선이라는 말에는 아직 기준이 없다. 기준은 잔차(Residual) 다. 잔차는 실제값에서 예측값을 뺀 값이고, 데이터 포인트마다 하나씩 생긴다.
잔차가 크다는 건 그 지점을 잘 못 맞추고 있다는 뜻이다. 그러면 잔차를 전부 더한 값을 줄이면 될까? 그렇지 않다. 직선 위에 있는 점의 잔차는 양수(과소 예측), 아래에 있는 점의 잔차는 음수(과대 예측)라서 서로 상쇄된다. 모든 점을 크게 빗나간 직선도 합이 0이 나올 수 있다.
그래서 잔차를 제곱해서 더한 잔차 제곱합(RSS) 을 최소화 대상으로 삼는다. 제곱하면 부호가 사라지고, 크게 빗나간 점일수록 더 무거운 벌점을 받는다. 이 값을 데이터 개수로 나눈 것이 회귀의 표준 비용 함수인 평균 제곱 오차(MSE)다.
잔차 제곱합을 최소로 만드는 해
잔차 제곱합을 최소화하는 와 는 반복 없이 한 번에 계산할 수 있다. 잔차 제곱합을 파라미터로 미분해 0으로 놓고 푼 결과가 정규 방정식(Normal Equation) 이다.
는 특성끼리의 관계를 담고 는 특성과 정답 사이의 관계를 담는다. 역행렬이 둘을 이어 붙여 최적의 계수를 만든다. numpy로 직접 짜보면 sklearn이 돌려준 것과 같은 숫자가 나온다.
X = np.hstack([np.ones((len(area), 1)), area]) # 절편 항을 위한 1 열을 앞에 붙인다
w = np.linalg.inv(X.T @ X) @ X.T @ price
print(np.round(w, 5)) # [-0.08176 0.03804]sklearn이 내놓는 것도 같은 해다. 다만 역행렬을 직접 구하지는 않고 특이값 분해 기반의 최소제곱 풀이(scipy.linalg.lstsq)를 쓴다. 가 거의 특이한 경우에도 버티기 위해서다.
특성이 많아지면 정규 방정식은 쓰기 어렵다
역행렬 계산량이 특성 수 에 대해 대략 이다. 특성이 수만 개면 단 한 번의 계산조차 감당하기 어렵고, 가 역행렬을 갖지 않는 경우도 생긴다. 이 지점부터는 기울기를 따라 조금씩 내려가는 반복 알고리즘이 사실상 유일한 선택지가 된다.
선형 회귀가 전제하는 것
선형 회귀는 데이터가 네 가지 조건을 만족할 때 제대로 동작한다. 이걸 모르고 쓰다가 결과를 잘못 읽는 경우가 많다.
| 가정 | 의미 | 위반 시 증상 |
|---|---|---|
| 선형성 | 와 사이에 직선 관계가 있다 | 잔차 플롯에 곡선 패턴 |
| 독립성 | 데이터 포인트끼리 서로 독립이다 | 시계열에서 자기상관 |
| 등분산성 | 잔차가 흩어진 정도가 전 구간에서 일정하다 | 잔차 플롯에 부채꼴 |
| 정규성 | 잔차가 정규 분포를 따른다 | Q-Q 플롯에서 이탈 |
이 중 선형성과 등분산성은 잔차 플롯(Residual Plot) 한 장으로 같이 본다. 가로축에 예측값, 세로축에 잔차를 찍은 산점도다. 잔차가 예측값 전 범위에서 0 주변에 고르게 흩어져 있으면 두 가정이 다 통과다. U자 패턴이 보이면 다항 회귀나 로그 변환을 검토하고, 부채꼴이면 가중 최소제곱이나 변환을 검토한다. 나머지 둘은 도구가 따로다. 정규성은 Q-Q 플롯으로 보고, 독립성은 데이터가 어떻게 수집됐는지를 봐야 알 수 있다.
실전에서 네 가정을 완벽히 만족하는 데이터는 거의 없다. 중요한 건 위반 여부를 확인하는 습관 쪽이다.
이상치 하나가 직선을 끌어당긴다
선형 회귀는 이상치에 유난히 약하다. 잔차를 제곱해서 최소화하기 때문에, 혼자 멀리 떨어진 점의 잔차가 제곱되면서 나머지를 전부 합친 것보다 커지고 직선이 그쪽으로 끌려간다.
앞의 8개 데이터에 면적 155m², 가격 9억짜리 거래 한 건을 더해보자. 기울기가 0.0380에서 0.0528로, 절편이 -0.082에서 -1.354로 움직인다. 점 하나가 직선 전체를 비틀어버린 것이다.
그래서 학습 전에 데이터를 그려보는 단계를 건너뛸 수 없다. 이상치가 측정 오류나 입력 실수라면 걷어내고, 실제로 존재하는 값이라면 큰 잔차에 덜 반응하는 비용 함수나 이상치에 강한 모델로 바꾼다.
마치며
선형 회귀가 하는 일은 결국 두 개의 숫자를 정하는 것이다. 그 둘을 정하는 기준이 잔차 제곱합이고, 기준을 최소로 만드는 값은 정규 방정식으로 한 번에 구해진다.
단순함이 곧 강점이다. 값 하나만 봐도 어떤 특성이 예측에 얼마나 기여하는지 바로 읽히고, 그래서 새 문제를 만나면 먼저 돌려보는 기준선(baseline) 자리를 지금도 지키고 있다. 약점도 같은 자리에서 나온다. 관계가 직선이 아니면 아무리 학습해도 맞지 않고, 이상치 하나에 직선이 통째로 끌려간다. 두 가지 모두 잔차 플롯 한 장이면 확인된다.