삶의 공유
회귀 분석으로 주택 가격 예측하기 — 단순 회귀부터 RANSAC까지 한 번에 이해하기 본문

0. 들어가며
이 글에서는 **“연속적인 타깃 값을 예측하는 회귀 분석”**을
머신러닝교과서의 예제를 기반으로 정리해 봅니다.
- 이론: 단순 선형 회귀, 다중 선형 회귀
- 실습: 주택 데이터셋으로
- EDA(탐색적 데이터 분석)
- 경사 하강법으로 직접 회귀 모델 만들기
- 사이킷런 LinearRegression
- 이상치에 강한 RANSACRegressor
각 코드가 왜 그런 식으로 작성되었는지,
수식·개념·코드의 연결을 중심으로 설명합니다.
1. 회귀 분석 이론 정리
1-1. 단순 선형 회귀란?
하나의 특성(설명 변수)과 연속적인 타깃(응답 변수) 사이의 직선 관계를 모델링하는 것.
예를 들어,
- xx: 방 개수(RM)
- yy: 주택 가격(MEDV)
의 관계를 다음과 같은 직선으로 근사합니다.

- w1: 기울기 (slope)
- w0: 절편 (intercept)
- y^: 모델이 예측한 값
잔차(오차, residual)란?
각 데이터 포인트에 대해:

- 실제 값 와
- 예측 값 y^ 사이의 수직 거리입니다.
이 잔차들이 전체적으로 작아지도록 직선의 기울기와 절편을 찾는 것이 최소제곱법입니다.
그래프 상에서:
- x축: 방 개수
- y축: 주택 가격
- 가운데 직선: 회귀 직선
- 각 점에서 직선까지의 수직선이 “잔차”
1-2. 다중 선형 회귀란?
여러 개의 설명 변수와 연속적인 타깃 변수 사이의 선형 관계를 모델링.
예를 들어,
- : 방 개수(RM)
- : 공해 농도(NOX)
- : 주택 가격(MEDV)
라면,

이 됩니다.
기하학적으로 보면:
- 단순 회귀(1개 특성): 2차원 평면에서 직선
- 다중 회귀(2개 특성): 3차원 공간에서 평면
- 더 많은 특성: 고차원 공간에서 초평면(hyperplane)
즉, “회귀선”이 고차원으로 확장된 것이 초평면이라고 이해하면 됩니다.
2. 주택 데이터셋으로 EDA(탐색적 데이터 분석)
모델을 만들기 전에 먼저 데이터를 눈으로 확인하는 것이 중요합니다.
여기서는 LSTAT, INDUS, NOX, RM, MEDV 5개의 변수로 예를 듭니다.
import matplotlib.pyplot as plt
from mlxtend.plotting import scatterplotmatrix
cols = ['LSTAT', 'INDUS', 'NOX', 'RM', 'MEDV']
scatterplotmatrix(df[cols].values, figsize=(10, 8),
names=cols, alpha=0.5)
plt.tight_layout()
plt.show()

2-1. 산점도 행렬(scatter plot matrix)
각 변수 쌍에 대해 산점도를 그려서
- 선형 관계가 있는지,
- 변수 간 경향성이 있는지,
- 눈으로 확인할 수 있습니다.
예를 들어,
- RM vs MEDV: 방 개수가 많을수록 가격이 대체로 올라가는지
- LSTAT vs MEDV: 저소득층 비율이 높을수록 가격이 내려가는지 등
2-2. 상관 관계 행렬 & 히트맵
정량적으로 변수 간 선형 관계를 확인하기 위해 상관 관계 행렬을 사용합니다.
from mlxtend.plotting import heatmap
import numpy as np
cm = np.corrcoef(df[cols].values.T)
hm = heatmap(cm, row_names=cols, column_names=cols)
plt.show()
- np.corrcoef(df[cols].values.T)
- 각 변수의 피어슨 상관계수(Pearson correlation coefficient) 를 계산
- heatmap(...)
- 상관계수를 색으로 표현

피어슨 상관계수 수식
두 변수 x,yx, y의 피어슨 상관계수 는:

- 가 1에 가까울수록: 강한 양의 선형 관계
- -1에 가까울수록: 강한 음의 선형 관계
- 0에 가까울수록: 선형 관계가 약함
※ 특성이 표준화되어 있으면 상관 관계 행렬과 공분산 행렬이 동일합니다.
3. 최소제곱 선형 회귀 & 경사 하강법
3-1. 최소제곱법이란?
선형 회귀에서 목표는
“모든 데이터 포인트에 대해 잔차(예측오차)의 제곱합이 가장 작아지도록
기울기와 절편을 찾는 것”
입니다.
비용 함수(손실 함수)를 다음과 같이 둘 수 있습니다.

여기서

입니다.
3-2. 경사 하강법(Gradient Descent)이란? ✅ (설명 추가)
경사 하강법은 말 그대로 “기울기(경사)를 따라 내려가는 방법” 입니다.
- 처음에는 임의의 가중치 에서 시작
- 비용 함수 의 기울기(gradient) 를 계산
- 기울기가 내려가는 방향으로 조금씩 이동:
w←w−η⋅∇wJ(w)
- : 학습률(learning rate) — 한 번에 얼마나 크게 움직일지
- 이 과정을 반복하면서 비용이 줄어들고, 최소값 근처로 수렴하게 됩니다.
4. 직접 구현해보는 선형 회귀: LinearRegressionGD
class LinearRegressionGD(object):
def __init__(self, eta=0.001, n_iter=20):
self.eta = eta # 학습률
self.n_iter = n_iter # 반복(Epoch) 횟수
def fit(self, X, y):
self.w_ = np.zeros(1 + X.shape[1]) # 가중치 초기화 (절편 포함)
self.cost_ = [] # 매 Epoch의 비용(J)을 저장
for i in range(self.n_iter):
output = self.net_input(X) # 선형 결합값 계산
errors = (y - output) # 오차(실제 - 예측)
# 가중치 업데이트 (경사 하강법)
self.w_[1:] += self.eta * X.T.dot(errors) # 특성 가중치
self.w_[0] += self.eta * errors.sum() # 절편 업데이트
# 비용 함수 (SSE / 2)
cost = (errors**2).sum() / 2.0
self.cost_.append(cost)
return self
def net_input(self, X):
# ※ 원문 코드에 self.W_ 오타가 있을 수 있습니다. self.w_가 맞습니다.
return np.dot(X, self.w_[1:]) + self.w_[0]
def predict(self, X):
return self.net_input(X)
동작 방식 자세히 설명
- __init__
- eta: 경사 하강법의 한 스텝 크기
- n_iter: 전체 데이터를 몇 번 반복해서 학습할지(epoch 수)
- fit(X, y)
- self.w_ = np.zeros(1 + X.shape[1])
- 특성 개수 + 1 (절편) 만큼 0으로 초기화
- 반복문 for i in range(self.n_iter):
- 각 epoch마다 전체 데이터 한 번씩 사용
- output = self.net_input(X)
- 선형 회귀식 y^=Xw+b\hat{y} = Xw + b 계산
- errors = (y - output)
- 오차 벡터: 실제 값 - 예측 값
- self.w_[1:] += self.eta * X.T.dot(errors)
- 경사 하강법 업데이트 식에서
∂J∂wj=−∑ixij(yi−y^i)\frac{\partial J}{\partial w_j} = - \sum_i x_{ij} (y_i - \hat{y}_i)
를 이용한 형태입니다.
- 경사 하강법 업데이트 식에서
- self.w_[0] += self.eta * errors.sum()
- 절편 항에 대한 gradient는 단순히 오차의 합에 비례
- cost = (errors**2).sum() / 2.0
- SSE(오차 제곱합)를 2로 나눈 값 = 비용 함수
- self.cost_.append(cost)
- 나중에 수렴 여부 확인을 위해 epoch별 비용 기록
- self.w_ = np.zeros(1 + X.shape[1])
- net_input(X)
- 입력 XX에 대한 선형 결합
- y^=Xw+b\hat{y} = Xw + b
- predict(X)
- 회귀 모델의 예측 함수
- 단순히 net_input을 호출 (활성화 함수가 선형이기 때문)
5. 주택 데이터셋에 적용: RM → MEDV 예측
5-1. 특성/타깃 선택 + 표준화
X = df[['RM']].values
y = df['MEDV'].values
from sklearn.preprocessing import StandardScaler
sc_x = StandardScaler()
sc_y = StandardScaler()
X_std = sc_x.fit_transform(X)
y_std = sc_y.fit_transform(y[:, np.newaxis]).flatten()
- X = df[['RM']].values
- 방 개수 하나만 특성으로 사용 (단순 회귀)
- y = df['MEDV'].values
- 주택 가격을 타깃으로 사용
- StandardScaler
- 평균 0, 표준편차 1이 되도록 변환
- y[:, np.newaxis]
- 1차원 벡터 y를 (n,1) 형태로 바꿔서 스케일러에 맞춤
- .flatten()
- 다시 1차원으로 돌림
왜 표준화를 하나요?
- 경사 하강법은 특성의 스케일에 민감합니다.
- 특성 값이 너무 크면 gradient가 커져 발산하거나, 너무 작으면 수렴이 느립니다.
- 표준화를 통해 안정적인 학습이 가능합니다.
5-2. 경사 하강법 회귀 모델 학습
lr = LinearRegressionGD()
lr.fit(X_std, y_std)
- 앞에서 정의한 LinearRegressionGD를 사용
- 표준화된 X, y를 넣고 fit
- lr.w_에 학습된 기울기/절편(표준화 공간 기준) 저장
- lr.cost_에 epoch별 비용 함수 값 저장
5-3. 학습이 잘 되었는지 확인: 비용 함수 그래프
plt.plot(range(1, lr.n_iter+1), lr.cost_)
plt.ylabel('SSE')
plt.xlabel('Epoch')
plt.show()
- x축: Epoch(반복 횟수)
- y축: SSE(오차 제곱합)
- Epoch이 증가할수록 SSE가 감소하는지 확인
→ 단조 감소 + 어느 시점에서 평평해지면 잘 수렴했다고 볼 수 있음

6. 사이킷런 LinearRegression으로 더 간단하게
from sklearn.linear_model import LinearRegression
slr = LinearRegression()
slr.fit(X, y)
print('기울기: %.3f' % slr.coef_[0])
print('절편: %.3f' % slr.intercept_)
기울기: 9.102
절편: -34.671
동작 설명
- LinearRegression()
- 기본적으로 최소제곱 해(closed-form solution) 를 사용
- 내부적으로 경사 하강법이 아닌 w=(XTX)−1XTy 형태의 해를 이용해 파라미터를 구함 (정규 방정식)
- slr.fit(X, y)
- 표준화하지 않은 원본 X, y를 바로 사용해도 잘 동작
- slr.coef_
- 기울기(들)
- slr.intercept_
- 절편
경사 하강법 대비 장점:
- 반복과정 없이 바로 해를 구함
- 스케일에 덜 민감
단, 특성 수가 매우 크면(수십만 이상) 역행렬 계산 비용이 부담될 수 있음.
7. RANSAC으로 이상치에 강한 회귀 모델 만들기
선형 회귀는 이상치(outlier) 에 매우 민감합니다.
몇 개의 극단적인 점 때문에 기울기가 크게 왜곡될 수 있습니다.
그래서 정상적인 점(inlier)만 골라 모델을 학습하는 방법인
RANSAC(Random Sample Consensus)를 사용합니다.
7-1. RANSAC 알고리즘 개념 정리
- 랜덤하게 일부 샘플을 뽑아 임시 모델 학습
- 이 모델로 전체 데이터 예측
- 오차(잔차)가 허용 임계값 이내인 포인트 → 정상치(inlier) 로 간주
- 정상치만 모아서 다시 모델 재학습
- 위 과정을 여러 번 반복하고
- 가장 많은 inlier를 가진 모델
- 혹은 성능이 특정 기준 이상인 모델을 최종 선택
7-2. RANSACRegressor 코드 및 파라미터 설명 ✅
from sklearn.linear_model import RANSACRegressor, LinearRegression
ransac = RANSACRegressor(
base_estimator=LinearRegression(), # (최근 버전에서는 estimator 인자)
max_trials=100,
min_samples=50,
loss='absolute_error',
residual_threshold=5.0,
random_state=0
)
ransac.fit(X, y)
주요 파라미터 설명
- base_estimator=LinearRegression()
- Inlier에 대해 사용될 기본 회귀 모델
- 여기서는 일반 선형 회귀를 사용
- max_trials=100
- RANSAC 반복 횟수의 최대 값
- 즉, “랜덤 샘플링 + 모델 학습”을 최대 몇 번 시도할 것인지
- min_samples=50
- 매 반복마다 뽑아서 임시 모델을 학습할 최소 샘플 수
- 너무 작으면 모델이 불안정해지고, 너무 크면 계산량↑
- loss='absolute_error'
- 오차를 측정할 때 사용할 손실 함수
- absolute_error → |y - y_hat|
- 절대값 기준으로 잔차를 평가 → 이상치에 덜 민감
- residual_threshold=5.0
- “이 점은 정상치다” 라고 볼 수 있는 오차의 최대 허용 범위
- 예: 오차가 5 이하인 점만 inlier로 취급
- 문제에 따라 적절한 값을 사람이 정해야 하는 것이 단점
- random_state=0
- 랜덤 샘플링을 재현 가능하게 하기 위한 시드값
7-3. RANSAC 결과: 정상치 vs 이상치 시각화
inlier_mask = ransac.inlier_mask_
outlier_mask = np.logical_not(inlier_mask)
line_X = np.arange(3, 10, 1)
line_y_ransac = ransac.predict(line_X[:, np.newaxis])
plt.scatter(X[inlier_mask], y[inlier_mask],
c='steelblue', edgecolors='white',
marker='o', label='Inliers')
plt.scatter(X[outlier_mask], y[outlier_mask],
c='limegreen', edgecolors='white',
marker='s', label='Outliers')
plt.plot(line_X, line_y_ransac,
color='black', lw=2)
plt.xlabel('Average number of rooms [RM]')
plt.ylabel('Price in $1000s [MEDV]')
plt.legend(loc='upper left')
plt.show()

- ransac.inlier_mask_
- 각 샘플이 inlier인지(True/False) 표시하는 boolean 배열
- outlier_mask = np.logical_not(inlier_mask)
- inlier가 아닌 점 → outlier로 표시
- plt.scatter(...)
- 파란 동그라미: 정상치
- 초록 네모: 이상치
- line_y_ransac = ransac.predict(...)
- 학습된 RANSAC 회귀 직선을 그리기 위한 예측 값
7-4. 기울기 & 절편 비교
print('기울기: %.3f' % ransac.estimator_.coef_[0])
print('절편: %.3f' % ransac.estimator_.intercept_)
기울기: 10.735
절편: -44.089
- ransac.estimator_
- 최종적으로 선택된 inlier들로 학습한 LinearRegression 모델
- coef_[0], intercept_
- 이 모델의 기울기와 절편
보통:
- RANSAC 없이 전체 데이터로 학습한 직선과
- RANSAC으로 inlier만 사용한 직선은 서로 다른 결과가 나옵니다.
장점: 이상치의 영향을 줄여 좀 더 안정적인 회귀선을 얻을 수 있음
단점: 항상 일반화 성능이 좋아진다고 장담할 수는 없음
→ 검증 데이터에서 성능을 반드시 확인해야 함
🔍 전체 핵심 요약
- 단순 선형 회귀: 하나의 특성과 타깃 사이를 직선으로 근사.
- 기울기, 절편, 잔차 개념 이해가 중요.
- 다중 선형 회귀: 여러 특성을 사용하는 고차원 초평면.
- EDA:
- 산점도 행렬: 변수 간 관계를 직관적으로 확인
- 상관 관계 행렬 + 히트맵: 선형 관계를 수치로 정량화
- 최소제곱 & 경사 하강법:
- 잔차 제곱합을 최소화하는 방향으로 가중치를 업데이트
- LinearRegressionGD 로 직접 구현하며 원리 이해
- 사이킷런 LinearRegression:
- 반복 최적화 없이 닫힌 해(정규 방정식)로 가중치 계산
- RANSAC 회귀:
- 이상치에 강한 회귀 모델
- inlier만을 이용해 회귀선 학습 → 이상치 영향 감소
📝 참고 및 출처
본 포스팅은 Sebastian Raschka, Vahid Mirjalili 저서
『Python Machine Learning (머신러닝 교과서)』의 예제와 개념을
학습·실습 목적으로 재구성한 글입니다.
원문 내용 및 코드는 해당 저서의 저작권에 귀속되며,
본 글은 교육적 이해를 돕기 위한 요약·해설 형태로 작성되었습니다.
'Data Scientist > ML' 카테고리의 다른 글
| 정답 없는 데이터에서 보물을 찾아라! 머신러닝 군집 분석 (Cluster Analysis) 완벽 가이드 🗺️ (0) | 2025.12.09 |
|---|---|
| 선형 회귀의 한계를 넘어서: 다항 회귀와 랜덤 포레스트로 비선형 데이터 정복하기 (0) | 2025.12.02 |
| [실전프로젝트] IMDb 영화 리뷰 감성 분석(2편) - 로지스틱 회귀 최적화 & 대용량 온라인 학습 (0) | 2025.10.27 |
| [실전프로젝트] IMDb 영화 리뷰 감성 분석(1편) - 텍스트 전처리와 BoW 모델 이해 (0) | 2025.10.20 |
| [ML] Ensemble(앙상블) 기본 다지기 (1) | 2024.02.13 |