선형 회귀
기본적인 선형 회귀의 식은 다음과 같다.
\[\hat{y} = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ...+ \theta_n x_n\]- $\hat{y}$ 는 예측값이다.
- n은 특성의 수이다.
- $x_i$ 는 i번째 특성값이다.
- $\theta_j$ 는 j번째 모델 파라미터이다.
벡터 형태로 간단하게 쓸 수도 있다.
\[\hat{y} = h_{\theta}(x) = \mathbf{\theta}^T \mathbf{x}\]최소화할 비용함수 식은 다음과 같다.
\[MSE(\mathbf{X}, h_{\theta}) = \frac{1}{m} \displaystyle \sum_{i=1}^{m}{(\mathbf{\theta}^T \mathbf{x}^{(i)} - y^{(i)})^2}\]앞선 chapter들에서는 사이킷런의 LinearRegression()
을 사용해서 선형회귀를 수행했다. 하지만 사이킷런의 LinearRegression()
이 기반을 두고 있는 scipy.linalg.lstsq()
함수를 직접 호출할 수도 있다.
theta_best_svd, residuals, rank, s = np.linalg.lstsq(X_b, y, rcond=1e-6)
theta_best_svd # theta 값 출력
scipy.linalg.lstsq()
함수는 유사역행렬을 이용해서 계산을 수행한다. 특잇값 분해(Singular Value Decomposition)를 이용해서 계산되는 유사역행렬을 곧이어 살펴볼 정규방정식보다 더 효율적이고 극단적인 경우도 처리할 수 있다.
정규방정식
비용함수를 최소화하는 $\theta$ 를 찾기 위한 해석적인 방법으로 정규방정식이 있다. 식은 다음과 같다.
\[\mathbf{\hat{\theta} = (X^T X)^{-1} X^T y}\]코드 구현은 다음과 같이 한다.
from sklearn.preprocessing import add_dummy_feature
theta_best = np.linalg.inv(X_b.T @ X_b) @ X_b.T @ y
정규방정식의 계산 복잡도는 $O(n^{2.4})$ 에서 $O(n^{3})$ 사이이다. 선형 회귀의 계산 복잡도는 약 $O(n^{2})$ 이다.
경사 하강법 기본
반복해서 많이 살펴본 내용으로, 링크를 통해 대체한다.
새롭게 알고 넘어갈 내용만 살펴보자.
- 선형 회귀를 위한 MSE 비용함수는 볼록 함수(convex function)이므로, 지역 최솟값이 없고 하나의 전역 최솟값만 있다.
- 특성의 스케일이 다를 경우 학습이 느려질 수 있다.
- 모델 파라미터가 많아질수록 공간의 차원이 커지고 검색이 어려워진다.
배치 경사 하강법
매 경사 하강법 스텝에서 전체 데이터를 모두 활용해 계산하는 것을 배치 경사 하강법(Batch Gradient Descent)이라고 한다. 물론 아래와 같은 gradient vector를 이용해서 한 번에 계산할 수도 있지만, 매우 큰 training set에서는 아주 느리다.
\[\nabla{MSE(\theta)} = \begin{pmatrix} \frac{\partial}{\partial \theta_0}\nabla{MSE(\theta)} \\ \frac{\partial}{\partial \theta_1}\nabla{MSE(\theta)} \\ \vdots \\ \frac{\partial}{\partial \theta_n}\nabla{MSE(\theta)} \end{pmatrix} = \frac{2}{m} \mathbf{X^T(X \theta - y)}\]코딩으로 구현한 과정은 다음과 같다.
eta = 0.1 # learning rate
n_epochs = 1000
m = len(X_b) # 샘플의 개수
np.random.seed(42)
theta = np.random.randn(2, 1)
for epoch in range(n_epochs):
gradients = 2 / m * X_b.T @ (X_b @ theta - y)
theta = theta - eta * gradients
확률적 경사 하강법
확률적 경사 하강법(Stochastic Gradient Descent, SGD)은 한 번에 랜덤으로 뽑은 하나의 샘플에 대해서만 경사를 계산하는 방식이다. 배치 경사 하강법보다는 당연히 불안정하고 전역 최적에 수렴할 가능성도 떨어지지만, 확실히 빠르다. 전역 최적이 수렴하지 못하는 문제는 매 반복에서 학습률을 조정하는 학습 스케줄을 이용해서 해결할 수 있다.
n_epochs = 50
t0, t1 = 5, 50 # 학습 스케줄 파라미터
def learning_schedule(t):
return t0 / (t + t1)
np.random.seed(42)
theta = np.random.randn(2, 1)
for epoch in range(n_epochs):
for iteration in range(m):
random_index = np.random.randint(m) # 샘플 하나를 랜덤하게 뽑기 위한 index 선정
xi = X_b[random_index:random_index + 1]
yi = y[random_index:random_index + 1]
gradients = 2 * xi.T @ (xi @ theta - yi) # 하나의 샘플이므로 m으로 나눌 필요 없다.
eta = learning_schedule(epoch * m + iteration) # 학습 스케줄링
theta = theta - eta * gradients
사이킷런을 사용하면 다음과 같다.
from sklearn.linear_model import SGDRegressor
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-5, penalty=None, eta0=0.1, n_iter_no_change=100, random_state=42)
sgd_reg.fit(X, y.ravel())
여기서 tol
은 손실에 대한 한계점으로, n_iter_no_change
동안 이 값보다 손실이 작아질 때까지 실행된다. 바꿔 말해, n_iter_no_change
동안 손실이 tol
아래라면 실행이 끝난다. eta0
은 학습률이다.
미니배치 경사 하강법
확률적 경사 하강법에서 샘플을 한 개만 사용하는 것이 께름칙했다면 미니배치 경사 하강법(Mini-batch Gradient Descent)을 사용하면 된다. 미니배치라 불리는 임의의 작은 샘플 세트에 대해서 gradient를 계산하고 파라미터를 업데이트 한다. 당연히 SGD보다 더 많은 샘플로 실시하기 때문에 좀 더 안정적이다.
n_epochs = 1000
t0, t1 = 5, 50 # 학습 스케줄 파라미터
def learning_schedule(t):
return t0 / (t + t1)
np.random.seed(42)
theta = np.random.randn(2, 1)
for epoch in range(n_epochs):
for iteration in range(m):
random_index = np.random.randint(m)
xi = X_b[random_index:random_index + 10] # 배치 사이즈를 10으로 정함.
yi = y[random_index:random_index + 10]
gradients = 2 / m * xi.T @ (xi @ theta - yi)
eta = learning_schedule(epoch * m + iteration)
theta = theta - eta * gradients
다항 회귀
데이터가 단순한 직선이 아니라 복잡한 형태라도 선형 모델을 사용해서 해결할 수 있다. 가장 간단한 것은 각 특성의 거듭제곱을 새로운 특성으로 추가하고, 이렇게 확장된 특성을 포함하는 데이터셋으로 선형 모델을 훈련하는 것이다. 이를 다항 회귀(Polynomial Regression)라고 한다.
새로운 특성으로 추가할 때는 사이킷런의 PolynomialFeatures
를 이용한다. 이 떄 degree
인자를 설정해야 하는데, degree=d
는 특성이 n개인 배열을 특성이 $\frac{(n + d)!}{d!n!}$ 개인 배열로 변환한다.
from sklearn.preprocessing import PolynomialFeatures
poly_features = PolynomialFeatures(degree=2, include_bias=False)
X_poly = poly_features.fit_transform(X)
별도의 출처 표시가 있는 이미지를 제외한 모든 이미지는 강의자료에서 발췌하였음을 밝힙니다.
댓글남기기