일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Babel
- CentOS
- 기초 수학
- deep learning
- docker
- nodejs
- React
- Redis
- python
- 블레이드 템플릿
- Machine Learning
- SQL
- NCP
- laravel
- fastapi
- nginx
- For
- webpack
- Go
- php
- rabbitmq
- javascript
- linux
- phpredis
- mariadb
- Node
- AWS
- Backbone.js
- Switch
- Redux
- Today
- Total
개발일기
기초 수학 - 편미분과 선형 회귀 모델 응용 본문
선형 회귀 모델에 적용
편미분을 활용하면 다변수 함수의 편미분 값 계산이 가능하다는 것을 알 수 있다. 이를 선형 회귀 모델에 적용시켜 경사도 벡터를 구한 후, 이를 활용하여 비용 함수를 최소화시킬 수 있다. 비용 함수를 최소화시키기 위해 사용하는 최적화 알고리즘으로 경사 하강법을 주로 사용하며 이를 통해 해당 모델에 주어진 m과 b 파라미터의 적합한 값을 구해낼 수 있다.
비용 함수(Cost Function)
먼저 $ y = mx + b $ 라는 함수에서 $ x $ 를 입력 변수, $ y $ 를 출력 변수라면 $ x $ 값의 변화에 따라 $ y $ 값이 종속되어 결정된다. 이에 $ x $를 독립 변수, $ y $ 를 종속 변수라 칭한다. 독립 변수 $ x $ 가 1일 때, 종속 변수 $ y $ 가 5라면 이를 y와 근접하게 예상한 $ \hat{y} $ 예상값과 $ y $ 의 차이를 오차라고 칭하는데 이 예측값과 실제값의 차이를 측정하는 함수를 비용 함수라 칭한다. 비용 함수에는 평균 제곱 오차(Mean Squarred Error), 평균 절대 오차(Mean Absolute Error) 등이 있다.
경사도 벡터(Gradient Vector)
각 파라미터의 편미분 값을 벡터로 나열한 것을 의미한다. 나열된 편미분 값은 비용 함수에 따라 계산된다. 선형 회귀 모델은 주로 평균 제곱 오차로 편미분 값을 계산한다. 수학 수식으로는 $ \nabla $ 를 앞에 붙여 표시한다. 예를 들어 z의 경사도 벡터는 $ \nabla z $ 로 표시한다.
선형 회귀 모델의 가장 기본적인 형태인 $ y = mx + b $ 함수를 사용한다. 이 예제는 $ y $ 가 $ x $ 에 비례하여 변하는 모델이며 수학적으로 계수 m은 기울기, b는 절편이라 칭한다. 다중 선형 회귀 모델과 같은 여러 독립 변수를 포함하는 모델도 존재하지만 간단하게 확인하기 위해 $ y = mx + b $ 와 같은 단일 선형 회귀를 사용한다.
$ y = mx + b $ 의 경사도 벡터는 파라미터인 m과 b의 편미분 값을 벡터에 넣어 표현한다. $ \left( \frac{\partial y}{\partial m}, \frac{\partial y}{\partial b} \right) $ 형태로 나타나며 경사도 벡터는 $ \nabla y $ 로 표현한다. 즉 이 함수의 경사도 벡터는 $ \nabla y = \left( \frac{\partial y}{\partial m}, \frac{\partial y}{\partial b} \right) $ 로 표현한다.
import torch
xs = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 입력값
ys = torch.tensor([0.1, 0.18, 0.24, 0.32, 0.43, 0.55, 0.6, 0.78, 0.9, 1.0 ]) # 출력값
i = 9 # 임시로 하나의 입력값 지정
x = xs[i]
y = ys[i]
m = torch.tensor([0.9]).requires_grad_() # 임의의 초기값 지정
b = torch.tensor([0.1]).requires_grad_() # 임의의 초기값 지정
y_hat = m * x + b # y 추정치
c = (y_hat - y)**2 # 실제 y값과 추정치를 비교하여 오차를 계산 (제곱 오차)
print('y_hat: ', y_hat) # 추정치
print('Cost: ', c) # 오차
c.backward() # 역전파
print('m grad: ', m.grad) # m의 경사
print('b grad: ', b.grad) # b의 경사
"""
y_hat: tensor([9.1000], grad_fn=<AddBackward0>)
Cost: tensor([65.6100], grad_fn=<PowBackward0>)
m grad: tensor([162.])
b grad: tensor([16.2000])
"""
이 모델은 게임 플레이 하는 시간에 따른 게임 진척도를 나타낸다. 10시간을 플레이하면 게임을 클리어할 수 있다고 가정된 임의의 모델이다. x는 게임 플레이 시간이며 y는 게임 진척도이다. xs에 게임 플레이 시간을 나타내는 입력 변수를 나열했고 ys는 게임 진척도를 나타내는 출력 변수를 나열했다. xs의 마지막 입력 변수만 특별히 지정하여 오차를 구하고자 하기에 i에 9를 대입하였고 m과 b는 임의의 초기 값으로 지정하여 오차를 구할 수 있게 지정한다.
c는 비용 함수의 값을 의미한다. 비용 함수 중 하나인 평균 제곱 오차를 변형해서 하나의 값에 대한 오차를 구하고 있다. 현재 하나의 값만 추출하여 확인하기에 제곱 오차로 볼 수 있으며 결과 값을 c에 대입한다. c에 역전파를 호출하여 경사도를 계산할 수 있도록 한 후, 각 파라미터 m과 b의 경사도를 구할 수 있다.
$ \hat{y} $ 는 $ \hat{y} = mx + b $ 이며 x에 대한 y값을 예측한다. 실제 $ y $ 값과 예측값을 비교한 오차 c를 구할 수 있으며 $ C = (\hat{y} - y)^2 $ 로 표현한다. 오차를 제곱한 C를 통해 경사도 벡터를 구할 수 있으며 이전에 배운 연쇄 법칙을 바탕으로 계산이 진행된다.
- 연쇄 법칙을 적용하여 $ \hat{y} - y $ 가 $ u $ 로 변하게 된다. $ C = u^2 $ 로 표현할 수 있다.
- C에 대한 $ u $ 의 편미분을 하면 $ \frac{\partial C}{\partial u} = 2u $ 가 된다.
- u = $ \hat{y} - y $ 이며 $ \hat{y} = mx + b $ 이기 때문에 u = $ mx + b - y $ 가 된다.
- $ u $ 에 대한 $ m $ 편미분을 하면 $ \frac{\partial u}{\partial x} = x $ 가 된다.
- $ u $ 에 대한 $ b $ 편미분을 하면 $ \frac{\partial u}{\partial b} = 1 $ 이 된다.
- 이를 종합하면 C에 대한 $ m $ 편미분은 $ \frac{\partial C}{\partial m} = \frac{\partial C}{\partial u}\cdot \frac{\partial u}{\partial m} $ 이 된다.
- 계산하면 $ (2u)x $ 가 되며, 여기서 $ u $ 는 $ u = \hat{y} - y $ 이기 때문에 $ \frac{\partial C}{\partial m} = 2x(\hat{y} - y) $ 로 표현한다.
- C에 대한 $ b $ 편미분은 $ \frac{\partial C}{\partial b} = \frac{\partial C}{\partial u}\cdot \frac{\partial u}{\partial b} $ 가 된다.
- 계산하면 $ (2u)\cdot 1 $ 이 되며 $ \frac{\partial C}{\partial b} = 2(\hat{y} - y) $ 가 된다.
이를 종합하면 C의 경사도 벡터는 $ \nabla C = \left[ \frac{\partial C}{\partial m}, \frac{\partial C}{\partial b} \right] $ 로 표현한다.
제곱 오차를 통해 각 파라미터의 경사도를 구했으니 해당 경사도를 자동미분의 grad값과 비교했을 때 일치하는지 비교하여 식의 성립 여부를 확인할 수 있다.
# Result
print('the partial derivative of C with respect to m - (m grad)', 2 * x * (y_hat - y).item())
print('the partial derivative of C with respect to b - (b grad)', 2 * (y_hat - y).item())
# the partial derivative of C with respect to m - (m grad) tensor(162.)
# the partial derivative of C with respect to b - (b grad) 16.200000762939453
item(): 텐서 값만 반환한다.
식의 계산 결과를 출력하는 코드를 보면 위에서 본 m의 경사와 b의 경사와 값이 일치하는 것을 확인할 수 있다.
위의 구성을 응용하여 하나의 $ x $, $ y $ 값이 아닌 전체 $ x$ , $ y $ 값을 사용하면 평균 제곱 오차(MSE)를 구할 수 있다.
x = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 입력값
y = torch.tensor([0.1, 0.18, 0.24, 0.32, 0.43, 0.55, 0.6, 0.78, 0.9, 1.0 ]) # 출력값
m = torch.tensor([0.9]).requires_grad_() # 임의의 초기값 지정
b = torch.tensor([0.1]).requires_grad_() # 임의의 초기값 지정
def regression(m, x, b):
return m * x + b
y_hat = regression(m, xs, b) # x개수에 따른 y추정치 목록
C = 1/y_hat.shape[0] * torch.sum((y_hat - y)**2) # MSE - 평균 오차 제곱
C.backward()
print('MSE: ', C.item())
print('m gard: ', m.grad.item())
print('b grad: ', b.grad.item())
"""
MSE: 25.868818283081055
m gard: 63.10999298095703
b grad: 9.079999923706055
"""
수학 수식으로 표현하면 $ \text{C} = \frac{1}{n} \sum_{i=1}^{n}(\hat{y} - y)^2 $ 와 같고, 이를 통해 평균 제곱 오차와 m, b 의 경사를 알 수 있다. 현재 이 함수는 비용 함수를 통해 계산한 오차가 25이며 꽤나 큰 오차를 가지고 있다. 이 오차를 최소화하여 추정치와 실제값이 유사하게 만드는 머신 러닝을 통해 올바른 파라미터를 구할 수 있다.
경사 하강법(Gradient Descent)
비용 함수를 통해 발생한 오차를 줄여나가며 매개 변수를 변경해 나간다. 위에 보이는 m, b 매개 변수의 경사를 0에 가까워지도록 하는 최적화 알고리즘을 의미한다. 만약 매개 변수의 경사가 음수라면 0에 가까워지도록 오른쪽으로 이동시켜 경사값을 증가시킨다. 반대로 경사가 양수라면 0에 가까워지도록 왼쪽으로 이동시켜 경사값을 감소시킨다. 이러한 과정을 통해 파라미터들은 올바른 값으로 조정하여 데이터를 올바르게 예측할 수 있는 모델을 만들 수 있다. 경사 하강법에는 SGD, Adagrad, Adam 등이 있으며 이와같은 선형 회귀 모델에서는 주로 확률적 경사 하강법(SGD)을 사용한다.
확률적 경사 하강법(SGD) 활용
pytorch의 SGD를 통해 모델을 학습시켜 m, b 파라미터를 조정하여 올바른 값을 찾아낼 수 있다.
import torch
import torch.nn as nn
import torch.optim as optim
# 신경망 모델의 기본 클래스
class LinearRegressionModel(nn.Module):
def __init__(self): # 생성자
super(LinearRegressionModel, self).__init__() # nn.Module 초기화 및 호출
self.m = nn.Parameter(torch.tensor([0.5])) # m 파라미터
self.b = nn.Parameter(torch.tensor([0.5])) # b 파라미터
def forward(self, x): # 필수 구현 메서드
return self.m * x + self.b # mx + b
x = torch.tensor([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) # 입력값
y = torch.tensor([0.1, 0.18, 0.24, 0.32, 0.43, 0.55, 0.6, 0.78, 0.9, 1.0 ]) # 출력값
model = LinearRegressionModel() # 클래스 생성
print('Parameter Lists')
for name, param in model.named_parameters():
print(name, ': ', param.item()) # parameter name, value
epochs = 1000 # 학습 횟수
C = nn.MSELoss() # 비용 함수(평균 제곱 오차 사용)
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum = 0.9) # 경사하강법 SGD 사용, 파라미터, 학습률(lr)과 모멘텀(momentum) 지정
print('\\n')
for epoch in range(epochs): # 모델 학습 - range로 epochs만큼 반복할 수 있게 설정
outputs = model(x) # 입력 x에 따른 출력값을 예측
loss = C(outputs, y) # 예측값과 실제 출력값의 손실을 계산(여기서는 MSE 사용)
optimizer.zero_grad() # 변화도 누적을 초기화
loss.backward() # 역전파를 통해 경사를 계산. 연쇄 법칙을 바탕으로 진행됨
optimizer.step() # 옵티마이저가 파라미터를 업데이트함. 학습률과 loss에 비례하여 값이 업데이트됨
if(epoch % 100) == 0: # 100번째마다 현재 진행도 및 손실값 출력
print(f'Epoch [{epoch+1}/{epochs}], Loss: {loss.item():.5f}')
data_m = 0
data_b = 0
for name, param in model.named_parameters():
if name == 'm':
data_m = param.item() # m 파라미터
elif name == 'b':
data_b = param.item() # b 파라미터
print('\\n')
print('m: ', data_m)
print('b: ', data_b)
print('y_hat: ', data_m * x + data_b) # y 예측값
print('y: ', y) # y값
"""
Parameter Lists
m : 0.5
b : 0.5
Epoch [1/1000], Loss: 8.81682
Epoch [101/1000], Loss: 0.00141
Epoch [201/1000], Loss: 0.00119
Epoch [301/1000], Loss: 0.00119
Epoch [401/1000], Loss: 0.00119
Epoch [501/1000], Loss: 0.00119
Epoch [601/1000], Loss: 0.00119
Epoch [701/1000], Loss: 0.00119
Epoch [801/1000], Loss: 0.00119
Epoch [901/1000], Loss: 0.00119
m: 0.10181818157434464
b: -0.04999999701976776
y_hat: tensor([0.0518, 0.1536, 0.2555, 0.3573, 0.4591, 0.5609, 0.6627, 0.7645, 0.8664,
0.9682])
y: tensor([0.1000, 0.1800, 0.2400, 0.3200, 0.4300, 0.5500, 0.6000, 0.7800, 0.9000,
1.0000])
"""
- nn.Module: 신경망 모델의 기본 클래스로 선형 회귀 등 다른 모델에서도 사용할 수 있는 기본 클래스다. init 생성자에 파라미터를 정의하며 forward라는 메서드에 순전파(forward pass) 연산을 정의한다.
- epochs: 학습 횟수를 지정한다.
- nn.MSELoss: 손실 함수를 지정한다. 비용 함수와 비슷한 개념인데 비용 함수는 오차에 대한 전체 평균 또는 합계를 나타낼 때 사용하는 명칭이며 손실 함수는 개별 데이터의 예측 값과 실제 값의 차이를 비교할 때 사용하는 명칭이다.
- optim: 사용할 최적화 알고리즘을 지정한다.
- SGD(parameters, lr, momentum): Stochastic Gradient Descent의 줄임말로 선형 회귀 모델에서 자주 사용되는 최적화 알고리즘이다. 확률적 경사 하강법이라 칭한다.
- parameters: 최적화 알고리즘에 사용될 파라미터들을 나열한다. 여기선 m과 b가 속한다.
- lr(Learning Rate - 학습률): 각 반복마다 모델 파라미터를 업데이트할 비율을 결정하는 변수다. 매 반복마다 비용 함수의 경사와 학습률에 비례하여 모델 파라미터가 변화한다.
- 높은 학습률: 학습률을 높게 설정하면 모델 파라미터를 크게 업데이트하므로 학습 속도가 빠른 편이다. 하지만 너무 크면 수렴하지 못한채 발산하게 된다. 발산이란 올바른 모델 파라미터를 찾지 못하여 최적점을 찾지 못한 상태로 이전 단계보다 더 오차가 큰 상태로 변하는 것을 의미한다.
- 낮은 학습률: 상대적으로 안정적인 학습이 보장되지만 학습 속도가 느린 편이다.
- Momentum: 이전 학습에서 조정된 방향으로 경사가 움직일 수 있게 도와준다. 경사 하강법의 성능을 향상시키는 역할을 한다.
- outputs: forward 메서드를 호출하여 x에 따른 예측값을 계산한다.
- loss: 예측값과 실제 값 사이의 손실을 계산한다.
- optimizer.zero_grad(): 변화도를 0으로 변경한다. 주로 backward()를 사용하기 전에 사용한다. pytorch는 매 학습마다 경사값을 backward에 계속 더해주는 특성을 가지고 있다. zero_grad()로 초기화를 하지 않으면 의도치 않은 상태로 학습이 진행될 수 있다.
- loss.backward(): 역전파로 경사도를 계산한다.
- optimizer.step(): 최적화 알고리즘을 통해 도출된 경사도를 사용하여 파라미터를 업데이트한다. 이를 호출하면 한 번의 학습 단계가 완료되었다고 할 수 있다.
- 순전파를 통해 모델에 입력 데이터를 전달하여 예측값을 계산한다.
- 손실 함수를 통해 예측값과 실제값의 손실 차이를 계산한다.
- 손실 함수에 대한 경사도를 역전파로 계산한다.
- 계산된 결과를 바탕으로 파라미터를 업데이트한다.
- 반복 횟수인 epochs만큼 위의 4개 단계를 반복한다.
위의 단계를 반복하면서 모델은 올바른 파라미터를 가지도록 학습해 나간다. 학습으로 도출된 m, b 파라미터를 실제 y값과 비교하여 학습이 잘 되었는지 확인할 수 있다. 표본 데이터인 $ x $와 $ y $의 수가 적기 때문에 어느정도의 차이는 발생하지만 꽤나 실제와 근접한 값이 예측되는 것을 확인할 수 있다.
참고 사이트:
https://github.com/jonkrohn/ML-foundations/blob/master/notebooks/gradient-descent-from-scratch.ipynb
https://pytorch.org/docs/stable/generated/torch.optim.SGD.html
https://colab.research.google.com/drive/1700LpGC13Mn2kNS_jgYpzzb3V84x6ekL?usp=sharing
https://www.buymeacoffee.com/flashback_music
'AI > 기초 수학' 카테고리의 다른 글
기초 수학 - 혼동 행렬(The Confusion Matrix) (0) | 2024.07.15 |
---|---|
기초 수학 - 적분(Integration)의 개념(부정 적분, 정적분) (0) | 2024.07.14 |
기초 수학 - 편미분의 연쇄 법칙(Chain Rule In Partial Derivative) (0) | 2024.07.03 |
기초 수학 - 편미분(Partial Derivative) (0) | 2024.06.29 |
기초 수학 - Pytorch, Tensorflow를 활용한 자동 미분 (0) | 2024.06.24 |