개념
모델(The Model)
선형 회귀는 입력(input) x와 출력(output) y 사이에 선형 관계(linear relationship)가 있다고 가정합니다.
y = wx + b
w(weight/slope, 가중치/기울기): x가 1 증가할 때 y가 얼마나 변하는지를 나타냅니다.
b(bias/intercept, 편향/절편): x = 0일 때 y의 값입니다.
입력이 여러 개, 즉 특성(feature)이 여러 개이면 다음처럼 확장됩니다.
y = w1*x1 + w2*x2 + ... + wn*xn + b
벡터 형태(vector form)로는 y = w^T * x + b입니다.
목표는 모든 학습 예제(training example)에 대해 예측값(predicted y)이 실제값(actual y)에 최대한 가깝도록 w와 b를 찾는 것입니다.
비용 함수(Cost Function): 평균제곱오차(Mean Squared Error)
"최대한 가깝다"를 어떻게 측정할까요? 예측이 얼마나 틀렸는지를 하나의 숫자로 나타내야 합니다. 가장 흔한 선택은 평균제곱오차(Mean Squared Error; MSE)입니다.
MSE = (1/n) * sum((y_predicted - y_actual)^2)
왜 제곱(square)을 사용할까요? 두 가지 이유가 있습니다. 첫째, 큰 오차(large error)에 작은 오차(small error)보다 훨씬 큰 벌점(penalty)을 부여합니다. 오차 10은 오차 1보다 10배가 아니라 100배 더 나쁩니다. 둘째, 제곱 함수는 매끄럽고(smooth) 모든 곳에서 미분 가능(everywhere differentiable)하므로 최적화(optimization)가 직관적입니다.
비용 함수는 표면(surface)을 만듭니다. 단일 가중치 w와 편향 b에 대한 MSE 표면은 그릇(bowl), 즉 볼록 포물면(convex paraboloid)처럼 생겼습니다. 그릇의 바닥이 MSE가 최소화(minimize)되는 지점입니다. 학습(training)은 그 바닥을 찾는 일입니다.
경사하강법(Gradient Descent)
경사하강법은 내리막 방향(downhill)으로 한 걸음씩 내딛는 일을 반복해 그릇의 바닥을 찾습니다.
flowchart TD
A[Initialize w and b randomly] --> B[Compute predictions: y_hat = wx + b]
B --> C[Compute cost: MSE]
C --> D[Compute gradients: dMSE/dw, dMSE/db]
D --> E[Update parameters]
E --> F{Cost low enough?}
F -->|No| B
F -->|Yes| G[Done: optimal w and b found]
기울기(gradient)는 두 가지를 알려줍니다. 각 매개변수를 어느 방향으로 움직일지, 얼마나 움직일지입니다.
y_hat = wx + b인 MSE의 기울기는 다음과 같습니다.
dMSE/dw = (2/n) * sum((y_hat - y) * x)
dMSE/db = (2/n) * sum(y_hat - y)
갱신 규칙(update rule)은 다음과 같습니다.
w = w - learning_rate * dMSE/dw
b = b - learning_rate * dMSE/db
학습률(learning rate)은 보폭(step size)을 조절합니다. 너무 크면 최소점(minimum)을 지나쳐(overshoot) 발산(diverge)합니다. 너무 작으면 학습이 지나치게 오래 걸립니다. 보통 시작 값으로는 0.01, 0.001, 0.0001을 사용합니다.
선형 회귀에는 반복(iteration) 없이 최적 가중치(optimal weight)를 바로 주는 공식이 있습니다.
w = (X^T * X)^(-1) * X^T * y
행렬(matrix)을 역행렬로 만들어(invert) 한 단계에 w를 구합니다. 작은 데이터셋(small dataset)에서는 완벽하게 동작합니다. 하지만 수백만 행(million row)이나 수천 개 특성(thousand feature)처럼 큰 데이터셋에서는, 행렬 역연산(matrix inversion)이 특성 수에 대해 O(n^3)이기 때문에 경사하강법을 더 선호합니다.
다중 선형 회귀(Multiple Linear Regression)
특성이 여러 개이면 모델은 다음과 같습니다.
y = w1*x1 + w2*x2 + ... + wn*xn + b
모든 것이 동일합니다. MSE가 비용 함수이고, 경사하강법이 모든 가중치를 동시에 갱신합니다. 차이는 직선이 아니라 초평면(hyperplane)을 적합(fit)한다는 점뿐입니다.
여기서는 특성 스케일링(feature scaling)이 중요합니다. 어떤 특성은 01 범위이고 다른 특성은 01,000,000 범위이면 비용 표면(cost surface)이 길쭉해져 경사하강법이 힘들어집니다. 학습 전에 특성을 표준화(standardize)합니다. 평균(mean)을 빼고 표준편차(standard deviation)로 나눕니다.
다항 회귀(Polynomial Regression)
관계가 선형이 아니라면 어떻게 할까요? 다항 특성(polynomial feature)을 만들어 두면 여전히 선형 회귀를 사용할 수 있습니다.
y = w1*x + w2*x^2 + w3*x^3 + b
이것은 가중치 (w1, w2, w3)에 대해서는 여전히 선형이므로 "linear" regression이라 부릅니다. 단지 x의 비선형 특성(nonlinear feature)을 사용하는 것뿐입니다.
차수가 높은(higher-degree) 다항식은 더 복잡한 곡선(curve)을 적합할 수 있지만 과적합(overfitting) 위험이 있습니다. 차수가 10인 다항식은 10개 점으로 이뤄진 데이터셋의 모든 점을 지나갈 수 있지만, 새로운 데이터에서는 형편없는 예측을 내놓을 수 있습니다.
결정 계수(R-Squared Score)
MSE는 얼마나 틀렸는지 알려주지만 y의 스케일(scale)에 따라 값이 달라집니다. 결정 계수(R-squared, R^2)는 스케일에 영향을 받지 않는(scale-independent) 지표입니다.
R^2 = 1 - (sum of squared residuals) / (sum of squared deviations from mean)
= 1 - SS_res / SS_tot
R^2 = 1.0: 완벽한 예측(perfect prediction)입니다.
R^2 = 0.0: 매번 평균(mean)을 예측하는 것보다 나을 게 없습니다.
R^2 < 0.0: 평균을 예측하는 것보다도 나쁩니다.
정규화 미리보기(Regularization Preview): 릿지 회귀(Ridge Regression)
특성이 많으면 모델이 큰 가중치를 부여하며 과적합할 수 있습니다. 릿지 회귀(L2 정규화(L2 Regularization))는 벌점을 더합니다.
Cost = MSE + lambda * sum(w_i^2)
벌점 항(penalty term)은 큰 가중치를 억제합니다. 초매개변수(hyperparameter) lambda가 균형(tradeoff)을 조절합니다. lambda가 클수록 가중치는 작아지고 정규화는 강해집니다. 이후 lesson에서 더 깊게 다룹니다. 지금은 이런 기법이 있다는 점과 그것이 왜 도움이 되는지를 알면 됩니다.
만들어 보기
Step 1: 샘플 데이터 생성
import random
import math
random.seed(42)
TRUE_W = 3.0
TRUE_B = 7.0
N_SAMPLES = 100
X = [random.uniform(0, 10) for _ in range(N_SAMPLES)]
y = [TRUE_W * x + TRUE_B + random.gauss(0, 2.0) for x in X]
print(f"{N_SAMPLES}개 샘플을 생성했습니다")
print(f"실제 관계식: y = {TRUE_W}x + {TRUE_B} (+ 잡음)")
print(f"처음 5개 점: {[(round(X[i], 2), round(y[i], 2)) for i in range(5)]}")
Step 2: 경사하강법으로 선형 회귀를 처음부터(from scratch) 구현
class LinearRegression:
def __init__(self, learning_rate=0.01):
self.w = 0.0
self.b = 0.0
self.lr = learning_rate
self.cost_history = []
def predict(self, X):
return [self.w * x + self.b for x in X]
def compute_cost(self, X, y):
predictions = self.predict(X)
n = len(y)
cost = sum((pred - actual) ** 2 for pred, actual in zip(predictions, y)) / n
return cost
def compute_gradients(self, X, y):
predictions = self.predict(X)
n = len(y)
dw = (2 / n) * sum((pred - actual) * x for pred, actual, x in zip(predictions, y, X))
db = (2 / n) * sum(pred - actual for pred, actual in zip(predictions, y))
return dw, db
def fit(self, X, y, epochs=1000, print_every=200):
for epoch in range(epochs):
dw, db = self.compute_gradients(X, y)
self.w -= self.lr * dw
self.b -= self.lr * db
cost = self.compute_cost(X, y)
self.cost_history.append(cost)
if epoch % print_every == 0:
print(f" Epoch {epoch:4d} | Cost: {cost:.4f} | w: {self.w:.4f} | b: {self.b:.4f}")
return self
def r_squared(self, X, y):
predictions = self.predict(X)
y_mean = sum(y) / len(y)
ss_res = sum((actual - pred) ** 2 for actual, pred in zip(y, predictions))
ss_tot = sum((actual - y_mean) ** 2 for actual in y)
return 1 - (ss_res / ss_tot)
print("=== 선형 회귀 학습(Gradient Descent) ===")
model = LinearRegression(learning_rate=0.005)
model.fit(X, y, epochs=1000, print_every=200)
print(f"\n학습된 식: y = {model.w:.4f}x + {model.b:.4f}")
print(f"실제 식: y = {TRUE_W}x + {TRUE_B}")
print(f"R-squared: {model.r_squared(X, y):.4f}")
Step 3: 정규방정식(Normal Equation, 닫힌형 해)
class LinearRegressionNormal:
def __init__(self):
self.w = 0.0
self.b = 0.0
def fit(self, X, y):
n = len(X)
x_mean = sum(X) / n
y_mean = sum(y) / n
numerator = sum((X[i] - x_mean) * (y[i] - y_mean) for i in range(n))
denominator = sum((X[i] - x_mean) ** 2 for i in range(n))
self.w = numerator / denominator
self.b = y_mean - self.w * x_mean
return self
def predict(self, X):
return [self.w * x + self.b for x in X]
def r_squared(self, X, y):
predictions = self.predict(X)
y_mean = sum(y) / len(y)
ss_res = sum((actual - pred) ** 2 for actual, pred in zip(y, predictions))
ss_tot = sum((actual - y_mean) ** 2 for actual in y)
return 1 - (ss_res / ss_tot)
print("\n=== 정규방정식(Normal Equation, Closed-Form) ===")
model_normal = LinearRegressionNormal()
model_normal.fit(X, y)
print(f"학습된 식: y = {model_normal.w:.4f}x + {model_normal.b:.4f}")
print(f"R-squared: {model_normal.r_squared(X, y):.4f}")
단일 특성(single feature)에서는 공분산(covariance)과 분산(variance)으로 닫힌형 해를 구할 수 있습니다. 다중 특성에서는 행렬 형태(matrix form)의 정규방정식을 사용합니다.
Step 4: 다중 선형 회귀(Multiple Linear Regression)
특성이 여러 개이면 모든 가중치를 동시에 갱신합니다.
class MultipleLinearRegression:
def __init__(self, n_features, learning_rate=0.01):
self.weights = [0.0] * n_features
self.bias = 0.0
self.lr = learning_rate
self.cost_history = []
def predict_single(self, x):
return sum(w * xi for w, xi in zip(self.weights, x)) + self.bias
def predict(self, X):
return [self.predict_single(x) for x in X]
def compute_cost(self, X, y):
predictions = self.predict(X)
n = len(y)
return sum((pred - actual) ** 2 for pred, actual in zip(predictions, y)) / n
def fit(self, X, y, epochs=1000, print_every=200):
n = len(y)
n_features = len(X[0])
for epoch in range(epochs):
predictions = self.predict(X)
errors = [pred - actual for pred, actual in zip(predictions, y)]
for j in range(n_features):
grad = (2 / n) * sum(errors[i] * X[i][j] for i in range(n))
self.weights[j] -= self.lr * grad
grad_b = (2 / n) * sum(errors)
self.bias -= self.lr * grad_b
cost = self.compute_cost(X, y)
self.cost_history.append(cost)
if epoch % print_every == 0:
print(f" Epoch {epoch:4d} | Cost: {cost:.4f}")
return self
def r_squared(self, X, y):
predictions = self.predict(X)
y_mean = sum(y) / len(y)
ss_res = sum((actual - pred) ** 2 for actual, pred in zip(y, predictions))
ss_tot = sum((actual - y_mean) ** 2 for actual in y)
return 1 - (ss_res / ss_tot)
random.seed(42)
N = 100
X_multi = []
y_multi = []
for _ in range(N):
size = random.uniform(500, 3000)
bedrooms = random.randint(1, 5)
age = random.uniform(0, 50)
price = 50 * size + 10000 * bedrooms - 1000 * age + 50000 + random.gauss(0, 20000)
X_multi.append([size, bedrooms, age])
y_multi.append(price)
def standardize(X):
n_features = len(X[0])
means = [sum(X[i][j] for i in range(len(X))) / len(X) for j in range(n_features)]
stds = []
for j in range(n_features):
variance = sum((X[i][j] - means[j]) ** 2 for i in range(len(X))) / len(X)
stds.append(variance ** 0.5)
X_scaled = []
for i in range(len(X)):
row = [(X[i][j] - means[j]) / stds[j] if stds[j] > 0 else 0 for j in range(n_features)]
X_scaled.append(row)
return X_scaled, means, stds
y_mean_val = sum(y_multi) / len(y_multi)
y_std_val = (sum((yi - y_mean_val) ** 2 for yi in y_multi) / len(y_multi)) ** 0.5
y_scaled = [(yi - y_mean_val) / y_std_val for yi in y_multi]
X_scaled, x_means, x_stds = standardize(X_multi)
print("\n=== 다중 선형 회귀(Multiple Linear Regression, 특성 3개) ===")
print("특성: 집 크기(house size), 침실 수(bedrooms), 연식(age)")
multi_model = MultipleLinearRegression(n_features=3, learning_rate=0.01)
multi_model.fit(X_scaled, y_scaled, epochs=1000, print_every=200)
print(f"\n가중치(표준화 후): {[round(w, 4) for w in multi_model.weights]}")
print(f"편향(표준화 후): {multi_model.bias:.4f}")
print(f"R-squared: {multi_model.r_squared(X_scaled, y_scaled):.4f}")
집의 크기(house size), 침실 수(bedroom count), 연식(age)으로 가격을 예측하는 합성 데이터셋(synthetic dataset)을 만들고, 특성을 표준화한 뒤 학습합니다.
Step 5: 다항 회귀(Polynomial Regression)
class PolynomialRegression:
def __init__(self, degree, learning_rate=0.01):
self.degree = degree
self.weights = [0.0] * degree
self.bias = 0.0
self.lr = learning_rate
def make_features(self, X):
return [[x ** (d + 1) for d in range(self.degree)] for x in X]
def predict(self, X):
features = self.make_features(X)
return [sum(w * f for w, f in zip(self.weights, row)) + self.bias for row in features]
def fit(self, X, y, epochs=1000, print_every=200):
features = self.make_features(X)
n = len(y)
for epoch in range(epochs):
predictions = [sum(w * f for w, f in zip(self.weights, row)) + self.bias for row in features]
errors = [pred - actual for pred, actual in zip(predictions, y)]
for j in range(self.degree):
grad = (2 / n) * sum(errors[i] * features[i][j] for i in range(n))
self.weights[j] -= self.lr * grad
grad_b = (2 / n) * sum(errors)
self.bias -= self.lr * grad_b
if epoch % print_every == 0:
cost = sum(e ** 2 for e in errors) / n
print(f" Epoch {epoch:4d} | Cost: {cost:.6f}")
return self
def r_squared(self, X, y):
predictions = self.predict(X)
y_mean = sum(y) / len(y)
ss_res = sum((actual - pred) ** 2 for actual, pred in zip(y, predictions))
ss_tot = sum((actual - y_mean) ** 2 for actual in y)
return 1 - (ss_res / ss_tot)
random.seed(42)
X_poly = [x / 10.0 for x in range(0, 50)]
y_poly = [0.5 * x ** 2 - 2 * x + 3 + random.gauss(0, 1.0) for x in X_poly]
x_max = max(abs(x) for x in X_poly)
X_poly_norm = [x / x_max for x in X_poly]
y_poly_mean = sum(y_poly) / len(y_poly)
y_poly_std = (sum((yi - y_poly_mean) ** 2 for yi in y_poly) / len(y_poly)) ** 0.5
y_poly_norm = [(yi - y_poly_mean) / y_poly_std for yi in y_poly]
print("\n=== 다항 회귀(Polynomial Regression, 차수 2 대 차수 5) ===")
print("실제 관계식: y = 0.5x^2 - 2x + 3")
print("\n차수 2:")
poly2 = PolynomialRegression(degree=2, learning_rate=0.1)
poly2.fit(X_poly_norm, y_poly_norm, epochs=2000, print_every=500)
print(f" R-squared: {poly2.r_squared(X_poly_norm, y_poly_norm):.4f}")
print("\n차수 5:")
poly5 = PolynomialRegression(degree=5, learning_rate=0.1)
poly5.fit(X_poly_norm, y_poly_norm, epochs=2000, print_every=500)
print(f" R-squared: {poly5.r_squared(X_poly_norm, y_poly_norm):.4f}")
print("\n차수 2는 실제 곡선에 잘 맞습니다. 차수 5는 학습 데이터에 조금 더 잘 맞지만")
print("새로운 데이터에서는 과적합 위험이 있습니다.")
실제 관계(true relationship)가 이차식(quadratic)이라면 차수 2의 다항식이 잘 맞습니다. 차수 5는 학습 데이터(training data)를 조금 더 잘 적합할 수 있지만 새로운 데이터에서는 과적합 위험이 있습니다.
Step 6: 릿지 회귀(Ridge Regression, L2 정규화)
class RidgeRegression:
def __init__(self, n_features, learning_rate=0.01, alpha=1.0):
self.weights = [0.0] * n_features
self.bias = 0.0
self.lr = learning_rate
self.alpha = alpha
def predict_single(self, x):
return sum(w * xi for w, xi in zip(self.weights, x)) + self.bias
def predict(self, X):
return [self.predict_single(x) for x in X]
def fit(self, X, y, epochs=1000, print_every=200):
n = len(y)
n_features = len(X[0])
for epoch in range(epochs):
predictions = self.predict(X)
errors = [pred - actual for pred, actual in zip(predictions, y)]
mse = sum(e ** 2 for e in errors) / n
reg_term = self.alpha * sum(w ** 2 for w in self.weights)
cost = mse + reg_term
for j in range(n_features):
grad = (2 / n) * sum(errors[i] * X[i][j] for i in range(n))
grad += 2 * self.alpha * self.weights[j]
self.weights[j] -= self.lr * grad
grad_b = (2 / n) * sum(errors)
self.bias -= self.lr * grad_b
if epoch % print_every == 0:
print(f" Epoch {epoch:4d} | Cost: {cost:.4f} | L2 penalty: {reg_term:.4f}")
return self
print("\n=== 릿지 회귀(Ridge Regression, L2 Regularization) ===")
print("다중 회귀와 같은 데이터를 사용하고, alpha=0.1을 적용합니다")
ridge = RidgeRegression(n_features=3, learning_rate=0.01, alpha=0.1)
ridge.fit(X_scaled, y_scaled, epochs=1000, print_every=200)
print(f"\n릿지 가중치: {[round(w, 4) for w in ridge.weights]}")
print(f"일반 가중치: {[round(w, 4) for w in multi_model.weights]}")
print("릿지 가중치는 L2 벌점 때문에 더 작아집니다(0 쪽으로 축소됩니다).")
릿지(Ridge)는 MSE에 alpha * sum(w^2) 벌점을 더합니다. 가중치를 0 쪽으로 축소(shrink)해 과적합을 줄입니다.
사용하기
실제 운영 환경에서는 보통 scikit-learn을 사용해 같은 작업을 수행합니다.
from sklearn.linear_model import LinearRegression as SklearnLR
from sklearn.linear_model import Ridge
from sklearn.preprocessing import PolynomialFeatures, StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
np.random.seed(42)
X_sk = np.random.uniform(0, 10, (100, 1))
y_sk = 3.0 * X_sk.squeeze() + 7.0 + np.random.normal(0, 2.0, 100)
X_train, X_test, y_train, y_test = train_test_split(X_sk, y_sk, test_size=0.2, random_state=42)
lr = SklearnLR()
lr.fit(X_train, y_train)
y_pred = lr.predict(X_test)
print("=== Scikit-learn 선형 회귀 ===")
print(f"계수(w): {lr.coef_[0]:.4f}")
print(f"절편(b): {lr.intercept_:.4f}")
print(f"R-squared (test): {r2_score(y_test, y_pred):.4f}")
print(f"MSE (test): {mean_squared_error(y_test, y_pred):.4f}")
poly = PolynomialFeatures(degree=2, include_bias=False)
X_poly_sk = poly.fit_transform(X_train)
X_poly_test = poly.transform(X_test)
lr_poly = SklearnLR()
lr_poly.fit(X_poly_sk, y_train)
print(f"\n차수 2 다항 회귀 R-squared: {r2_score(y_test, lr_poly.predict(X_poly_test)):.4f}")
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
ridge = Ridge(alpha=1.0)
ridge.fit(X_train_scaled, y_train)
print(f"릿지 회귀 R-squared: {r2_score(y_test, ridge.predict(X_test_scaled)):.4f}")
print(f"릿지 회귀 계수: {ridge.coef_[0]:.4f}")
처음부터 만든 구현과 scikit-learn은 같은 결과를 냅니다. 차이는 scikit-learn이 경계 사례(edge case), 수치적 안정성(numerical stability), 성능 최적화(performance optimization)를 함께 처리해 준다는 점입니다. 실제 운영에서는 라이브러리를 사용합니다. 처음부터 만든 버전은 내부에서 어떤 일이 일어나는지 이해하기 위해 활용합니다.
산출물 만들기
이 lesson의 최종 산출물은 outputs/skill-regression.md입니다. 문제 특성에 따라 일반 선형 회귀(ordinary linear regression), 다항 회귀(polynomial regression), 릿지(Ridge), 라쏘(Lasso), 엘라스틱 넷(Elastic Net) 중 어떤 접근을 선택할지 판단하는 참고 자료(reference)로 사용합니다.
연습문제
- 배치 경사하강법(batch gradient descent), 확률적 경사하강법(stochastic gradient descent; SGD), 미니배치 경사하강법(mini-batch gradient descent)을 구현합니다. 같은 데이터셋에서 수렴 속도(convergence speed)를 비교합니다. 어느 방식이 가장 빠르게 수렴(converge)하나요? 어느 방식의 비용 곡선(cost curve)이 가장 매끄러운가요?
- 삼차 함수(
y = ax^3 + bx^2 + cx + d + noise)에서 데이터를 생성합니다. 차수 1, 3, 10인 다항식을 적합하고 학습 R^2와 시험(test) R^2를 비교합니다. 어느 차수에서 과적합이 명확히 드러나나요?
- 라쏘 회귀(Lasso regression)를 구현합니다. L1 정규화는
penalty = alpha * sum(|w_i|)로 정의됩니다. 다중 특성 주택 데이터(multi-feature housing data)에서 학습하고, 릿지(Ridge)와 비교해 어떤 가중치가 0이 되는지 확인합니다. 왜 L1은 희소 해(sparse solution)를 만들고 L2는 그렇지 않은가요?
핵심 용어
| 용어 | 흔한 설명 | 실제 의미 |
|---|
| 선형 회귀(Linear Regression) | "데이터에 직선을 긋는다" | wx+b와 실제 y 사이의 제곱 차이 합(squared difference sum)을 최소화하는 가중치 w와 편향 b를 찾는 방법입니다. |
| 비용 함수(Cost Function) | "모델이 얼마나 나쁜가" | 모델 매개변수를 예측 오차(prediction error)를 나타내는 하나의 숫자로 사상(mapping)하는 함수입니다. 최적화가 이 값을 최소화합니다. |
| 평균제곱오차(Mean Squared Error) | "제곱 오차의 평균" | (1/n) * sum((predicted - actual)^2)입니다. 큰 오차에 더 큰 가중치로 벌점을 부여합니다. |
| 경사하강법(Gradient Descent) | "내리막으로 걷기" | 편미분(partial derivative)을 사용해 비용 함수를 줄이는 방향으로 매개변수를 반복적으로 조정하는 기법입니다. |
| 학습률(Learning Rate) | "보폭" | 경사하강법 한 단계마다 매개변수가 얼마나 바뀌는지를 조절하는 스칼라(scalar)입니다. |
| 정규방정식(Normal Equation) | "직접 풀기" | 반복 없이 최적 가중치를 주는 닫힌형 해 w = (X^T X)^-1 X^T y입니다. |
| 결정 계수(R-Squared) | "적합이 얼마나 좋은가" | 모델이 y의 분산 중 얼마나 설명하는지 나타냅니다. 음의 무한대(negative infinity)부터 1.0까지 가능합니다. |
| 특성 스케일링(Feature Scaling) | "특성을 비교 가능하게 만들기" | 특성을 비슷한 범위(range)로 변환해 경사하강법이 더 빨리 수렴하도록 만듭니다. |
| 정규화(Regularization) | "복잡도에 벌점 주기" | 비용 함수에 가중치를 축소하는 항을 더해 과적합을 막습니다. |
| 릿지 회귀(Ridge Regression) | "L2 정규화" | MSE에 lambda * sum(w_i^2) 벌점을 더한 선형 회귀입니다. |
| 다항 회귀(Polynomial Regression) | "선형 수식으로 곡선 적합하기" | 다항 특성(x, x^2, x^3, ...) 위에서 선형 회귀를 수행합니다. 가중치에 대해서는 여전히 선형입니다. |
| 과적합(Overfitting) | "학습 데이터를 외움" | 학습 데이터의 잡음(noise)까지 적합해 새로운 데이터에서 실패하는 모델을 사용하는 현상입니다. |
더 읽을거리