선형 회귀

반디·2023년 1월 21일
0

ML/DL

목록 보기
1/5

이하 내용은 '핸즈온 머신러닝'의 Cahp 4. 선형회귀 파트를 바탕으로 정리한 내용입니다.

선형 모델은 입력 특성의 가중치 합과 편향(bias)을 더해 예측을 만드는 모델입니다.

y^=θ0+θ1++θnxn\hat{y} = \theta_0 + \theta_1 + \cdots + \theta_n x_n
  • y^\hat{y}: 예측값, nn: 특성 수, xix_i: i번째 특성값, θj\theta_j: j번째 모델 파라미터

즉, 차수가 1차인 식으로 예측값을 표현하는 것입니다.
위 식을 벡터 형태로 간단하게 쓰면 다음과 같습니다.

y^=hθ(x)=θx\hat{y} = h_\theta({\bf x}) = \theta \cdot {\bf x}
  • θ=(θ0,,θn)\theta = (\theta_0, \ldots, \theta_n), x=(x0,x1,,xn){\bf x} = (x_0, x_1, \ldots, x_n) with x0=1x_0 = 1

위 표현에서 hθ(x)h_\theta({\bf x})를 가설함수라고 부릅니다.

비용 함수

회귀 모델에 가장 흔히 사용되는 측정 지표는 평균 제곱근 오차(RMSE) 이므로, 이를 최소화하는 θ\theta를 찾도록 훈련해야 합니다.
그러나 평균 제곱 오차(MSE)를 최소화하는 것은 RMSE와 같은 문제이고 계산이 더 간단하므로 MSE를 최소하는 θ\theta를 찾도록 합니다.
즉, 비용 함수를 다음과 같이 설정합니다.

MSE(X,hθ)=1mΣi=1m(θTx(i)y(i))2MSE(X, h_\theta) = \frac{1}{m} \Sigma_{i=1}^m (\theta^Tx^{(i)} - y^{(i)})^2

Note.

비용 함수를 최소화하는 방법으로 정규방정식과 경사하강법에 대해 알아보겠습니다.

1. 정규방정식 (normal equation)

정규방정식에 대해서는 이전 포스팅에서 간략하게 살펴보았습니다.

우리가 풀어야하는 방정식은 Xθ=yX{\bf \theta} = {\bf y}입니다. 위 방정식의 최소제곱해는 다음 정규방정식의 해와 같습니다:

XTXθ=XTy.X^TX{\bf \theta} = X^T{\bf y}.

행렬 XX의 column들이 linearly independent하다면, 위 정규방정식의 해 θ^\hat\theta를 바로 구할 수 있습니다.

θ^=(XTX)1XTy\hat\theta = (X^TX)^{-1}X^T{\bf y}
  • y=(y(1),,y(n)){\bf y} = (y^{(1)}, \ldots, y^{(n)}): 타깃 벡터

즉, 위 θ^\hat\theta가 바로 비용 함수를 최소화하는 θ\theta값입니다.

코드를 통해 위 공식을 테스트해보겠습니다.

X = 2 * np.random.rand(100, 1)
y = 4 + 3 * X + np.random.randn(100, 1)

임의로 선형 데이터셋을 정의합니다. y=4+3x1y = 4+3x_1+가우시안잡음 형태입니다.

X_b = np.c_[np.ones((100, 1)), X]  # bias와 잘 곱해지도록 모든 샘플에 x0 = 1을 추가합니다.  
theta_best = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y)

가우시안잡음 때문에 오차가 나기는 하지만, 결과값이 실제 값인 (4, 3)과 유사한값임을 확인할 수 있습니다.

사이킷런을 이용해서도 선형회귀를 수행할 수 있습니다.

from sklearn.linear_model import LinearRegression

lin_reg = LinearRegression()
lin_reg.fit(X, y)
lin_reg.intercept_, lin_reg.coef_ #intercept: 편향, coef_: 가중치 

LinearRegression 클래스는 scipy.linalg.lstsq() 함수를 기반으로 하는데요, scipy.linalg.lstsq() 함수는 θ=X+y\theta = X^+{\bf y}를 계산합니다.
X+X^+XX의 유사역행렬(무어-펜로즈 역행렬)로 np.linalg.pinv() 함수를 이용해 직접 구할 수 있습니다.

유사역행렬 자체는 SVD 기법을 이용해 계산할 수 있습니다.
SVD에 의해 XX 를 세 행렬의 곱 X=UΣVTX= U \Sigma V^T으로 표현할 때, 유사역행렬 X+X^+X+=VΣ+UTX^+ = V \Sigma^+ U^T로 계산됩니다. 여기서 Σ+\Sigma^+Σ\Sigma에서 특정 값보다 작은 원소들을 모두 0으로 바꾸고, 0이 아닌 숫자들은 역수로 치환한 후, 행렬을 전치(transpose)하여 만든 행렬입니다.

XX의 column들이 linearly independent하지 않아서 XTXX^TX의 역행렬이 존재하지 않거나 m<nm<n이라면, 정규방정식을 이용할 수 없는데요, 이 경우에도 유사역행렬은 항상 구할 수 있습니다!

  • 계산복잡도
    • 정규방정식: O(n2.4)O(n3)O(n^{2.4}) \sim O(n^3); (n+1)×(n+1)(n+1) \times (n+1) 행렬의 역행렬을 구하는 계산복잡도
    • 사이킷런 LinearRegression(): O(n2)O(n^2)
    • 두 방법 모두, 훈련 세트의 샘플 수에 대해서는 선형적으로 증가 O(m)O(m)

특성과 훈련 샘플이 너무 많아 메모리에 담을 수 없는 경우에는 어떻게 해야할까요?

2. 경사 하강법

경사 하강법은 여러 종류의 문제에서 최적의 해법을 찾을 수 있는 일반적인 최적화 알고리즘입니다. 기본 아이디어는 비용함수를 최소화하기 위해서 반복하여 파라미터를 조정해가는 것입니다.
즉, 파라미터 벡터 θ\theta에 대해, 비용 함수의 현재 그레디언트를 계산하고, 그레디언트가 감소하는 방향으로 진행합니다.

https://www.javatpoint.com/gradient-descent-in-machine-learning

경사하강법에서 step의 크기는 학습률(learning rate) 하이퍼파라미터로 결정됩니다.
학습률이 너무 작으면 수렴하기까지 오래걸리고, 학습률이 너무 크면 최솟값 포인트를 건너뛰게되어 수렴값을 찾지못할 가능성이 있습니다.

또한, 무작위 초기화 때문에 global minimum(전역 최솟값)이 아닌 local minimum(지역 최솟값)에 수렴할 수 있습니다. 혹은 global minium에 도달하기 전에 일찍 멈춰버릴 수 있습니다.

https://winder.ai/402-optimisation-and-gradient-descent/

다행히 선형 회귀를 위한 MSE 비용함수는 볼록함수(convex function)이므로 이와 같은 문제가 발생하지 않습니다. 다시 말해서, 1. 하나의 global minimum만 존재하고 2. 연속함수이며 3. 기울기가 갑자기 변하지 않는다는 것입니다. 따라서, 학습률이 너무 높지 않고 충분한 시간이 주어지면, 경사 하강법을 통해 global minimum에 가까운 해를 찾을 수 있습니다.

이 때, 특성들의 scale이 다르면 수렴하는 데 시간이 오래 걸리므로, 경사하강법을 사용할 때는 특성들의 스케일을 맞춰주어야 합니다! (ex. StandardScaler)

1. 배치 경사 하강법

경사 하강법을 구현하려면 각 모델 파라미터 θj\theta_j에 대해 비용 함수의 그레디언트를 계산해야 합니다. 이 그레디언트를 편도함수(partial derivative)라고 합니다.

θjMSE(θ)=2mΣi=1m(θTx(i)y(i))xj(i)\frac{\partial}{\partial \theta_j}MSE(\theta) = \frac{2}{m}\Sigma_{i=1}^m (\theta^Tx^{(i)}-y^{(i)})x_j^{(i)}

그레디언트 벡터를 통해 한번에 계산할 수 있습니다.

ΔθMSE(θ)=(θ0MSE(θ)θnMSE(θ))=2mXT(Xθy)\Delta_\theta MSE(\theta) = \begin{pmatrix} \frac{\partial}{\partial \theta_0}MSE(\theta)\\ \vdots \\ \frac{\partial}{\partial \theta_n}MSE(\theta) \end{pmatrix} = \frac{2}{m}X^T(X\theta - y)

매 경사 하강법 스텝에서 전체 훈련세트에 대해서 계산을 수행하므로 매우 큰 훈련 세트에서는 아주 느립니다. 그러나 특성 수에 민감하지 않으므로, 특성의 수가 아주 많은 경우에는 정규방정식이나 SVD 분해보다 경사 하강법이 빠릅니다.

convex 함수는 아래로 볼록한 함수이므로, 위로 향하는 그레디언트 벡터가 구해지면 반대방향인 아래로 가야합니다. 따라서 θΔθMSE(θ)\theta - \Delta_\theta MSE(\theta)를 수행하는데요, 이 때, 내려가는 스텝의 크기를 학습률 η\eta로 조정해줍니다.
결과적으로 경사 하강법의 스텝은 θηΔθMSE(θ)\theta - \eta\Delta_\theta MSE(\theta)가 됩니다.

이를 코드로도 쉽게 구현해볼 수 있습니다.

eta = 0.1  # 학습률
n_iterations = 1000
m = 100

theta = np.random.randn(2,1)  # 랜덤 초기화

for iteration in range(n_iterations):
    gradients = 2/m * X_b.T.dot(X_b.dot(theta) - y)
    theta = theta - eta * gradients

적절한 학습률이나 반복 횟수는 어떻게 지정할까요?
학습률을 찾는 데는 그리드 서치를 사용할 수 있습니다. 반복 횟수는 일단 아주 크게 지정하고 그레디언트 벡터가 특정 값 (허용오차) 보다 작아지면 알고리즘을 중지하는 방식으로 설정할 수 있습니다.

2. 확률적 경사 하강법 (Stochastic Gradient Descent; SGD)

앞서 살펴본 배치 경사 하강법은 매 스텝에서 전체 훈련 세트를 사용하므로, 훈련 세트가 크면 매우 느려지게 됩니다. 따라서 이를 개선하기 위해 확률적 경사 하강법이 제안되었습니다.

확률적 경사 하강법은 매 스텝에서 한 개의 샘플을 무작위로 선택하고 그 하나의 샘플에 대한 그레디언트를 계산합니다. 한 개의 샘플만 메모리에 있으면 되므로 훨씬 빠릅니다.

반면, 무작위성이 있으므로 배치 경사 하강법보다 훨씬 불안정합니다. 부드럽게 감소하지 않고 위아래로 요동치며 평균적으로 감소합니다. 또한, local minimum에서 탈출시켜 global minimum을 찾을 가능성이 높지만, global minimum에 도달하는 것을 보장하지는 않습니다.

이를 어떻게 개선할 수 있을까요?

학습률을 점진적으로 감소시킵니다. 시작할 때는 학습률을 크게 하여 local minimum에 빠지지 않게 하고, 점차 줄여 global minimum에 도달할 수 있도록 유도합니다. 매 반복에서 학습률을 결정하는 함수를 학습 스케줄이라고 합니다.

코드로 살펴보겠습니다.

n_epochs = 50 #전체 훈련 세트에 대해서 50번 반복
t0, t1 = 5, 50  # 학습 스케줄 하이퍼파라미터

def learning_schedule(t):
    return t0 / (t + t1) #분모가 분자보다 항상 큼, t값도 점차 커지므로 점점 감소하는 함수임 

theta = np.random.randn(2,1)  # 랜덤 초기화

for epoch in range(n_epochs):
    for i in range(m):  
        random_index = np.random.randint(m) 
        xi = X_b[random_index:random_index+1] #랜덤으로 샘플 고름 
        yi = y[random_index:random_index+1]
        gradients = 2 * xi.T.dot(xi.dot(theta) - yi) #그레디언트 계산 
        eta = learning_schedule(epoch * m + i) #학습률 업데이트 
        theta = theta - eta * gradients #다음 스텝 계산 
        theta_path_sgd.append(theta)       

*epoch: 모든 학습 데이터를 검색한 이동 횟수

Note.
확률적 경사 하강법을 이용할 때, 훈련 샘플이 IID(independent and identically distributed)를 만족해야 평균적으로 파라미터가 전역 최저점을 향해 진행한다고 보장할 수 있습니다. 훈련하는 동안 샘플을 섞어서 간단히 위 조건을 만족하도록할 수 있습니다.
*IID: 각 샘플이 선택될 확률이 동일하고 각 샘플이 선택되는 사건은 독립적이어야 함

scikit-learn을 이용하면 SGD 방식으로 선형회귀를 구현할 수 있습니다.

from sklearn.linear_model import SGDRegressor

#tol: 허용오차를 0.001로 설정 
sgd_reg = SGDRegressor(max_iter=1000, tol=1e-3, penalty=None, eta0=0.1, random_state=42)
sgd_reg.fit(X, y.ravel())

3. 미니배치 경사 하강법

각 스텝에서 미니배치라 부르는 임의의 작은 샘플 세트에 대해 그레디언트를 계산합니다. 행렬 연산에 최적화된 하드웨어 (GPU 등)를 이용하면 SGD 보다 성능을 향상 시킬 수 있습니다.
미니배치를 어느 정도 크게 하면, 파라미터 공간에서 SGD 보다 덜 불규칙하게 움직입니다. (그러나, local minimum에서 빠져나오기는 더 어려울 수 있습니다.)

현재까지 알고리즘을 선형 회귀를 이용해 비교하면 다음과 같습니다. (m: 훈련 샘플 수, n: 특성 수)
(핸즈온 머신러닝 표 4-1)

알고리즘m이 클 때외부 메모리 학습 지원n이 클 때하이퍼 파라미터 수스케일 조정 필요사이킷런
정규방정식빠름No느림0NoN/A
SVD빠름No느림0NoLinearRegression
배치 경사 하강법느림No빠름2 (학습률, 반복횟수)YesSGDRegressor
확률적 경사 하강법빠름Yes빠름\ge 2YesSGDRegressor
미니배치 경사 하강법빠름Yes빠름\ge 2YesSGDRegressor

참고문헌

profile
꾸준히!

0개의 댓글