서울대학교 딥러닝 기초 강의를 듣고 정리한 내용이다.
여러 gradient descent 방법에 대해 알아보자.
먼저 gradient descent 방법이란 무엇일까? 위키피디아에 의하면 아래와 같다.
In mathematics, gradient descent (also often called steepest descent) is a first-order iterative optimization algorithm for finding a local minimum of a differentiable function.
1) 미분 가능한 함수에서 local minimum을 찾기 위해 2) 1차 미분 값을 사용하는 최적화 방법이다.
딥러닝에서 미분 가능한 함수는 cost function이고 이는 파라미터의 함수이다. 결국, cost function에서 minimum을 찾고 그때의 파라미터 값을 최적해로 보는 것이다.
논의를 진전시키기 위해 cost function을 $h(\boldsymbol{\theta})$이라고 하자. 여기서 $\boldsymbol{\theta} = (\theta_1, \cdots, \theta_p)$로, $p$차원 벡터이다. 다양한 cost function의 종류가 있겠지만 여기서는 아래와 같은 MSE cost function을 생각해보자.
\[ J(\boldsymbol{\theta}) = \dfrac{1}{2} \sum^m_{i=1} (h_{\boldsymbol{\theta}} (x^{(i)}) - y^{(i)})^2 \]
또한 논의를 편하게 진행하기 위해 linear regression 문제를 생각해본다. 즉, 모델에 대한 함수 $h$를 아래와 같이 가정한다.
\[ h_{\boldsymbol{\theta}}(x) = \sum^p_{j=1} \theta_j x_j \]
이때 $j$번째 파라미터에 대한 gradient descent update는 아래와 같이 정의된다.
\[ \theta_j := \theta_j - \alpha \dfrac{\partial}{\partial \theta_j}J(\boldsymbol{\theta}) \]
convex cost function을 가정해볼 때, 1차 미분값과 반대 방향으로 움직여야 optimal point에 다다를 수 있다. 위 그림에서 $\theta_j$에서 미분 값이 양수인데, $\theta_j$의 왼쪽으로 가야 $\theta^*$에 도착할 수 있다. 따라서 gradient descent update 정의에서 앞에 -가 붙는 것이다. 또한 얼마만큼 움직일 것인지를 결정하는 것이 $\alpha$이다. 크다면, 한 스탭에서 크게 움직인다.
식을 더 전개해보면,
잘 보면 모든 데이터 $m$에 대해 update을 진행하는 것을 알 수 있다. 이러한 update 방식을 batch gradient descent라고 한다.
\[ \theta_j := \theta_j - \alpha \sum^m_{i=1} (h_{\boldsymbol{\theta}} (x^{(i)}) - y^{(i)}) x^{(i)}_j \]
batch gradient descent은 모든 데이터를 사용하기 때문에 수렴이 안정적이지만, 수렴하지 않는다 (?) 무슨 얘기냐면, 오래 걸린다는 뜻이다. 이에 대한 대안으로 나온게 stochastic gradient descent이다. SGD는 한번의 업데이트를 할 때 모든 데이터를 사용하는게 아니라 하나의 데이터를 사용한다.
\[ \theta_j := \theta_j - \alpha (h_{\boldsymbol{\theta}} (x^{(i)}) - y^{(i)}) x^{(i)}_j \]
SGD는 빠르지만, 수렴이 안정적이지 못하다. 마지막으로 SGD와 BGD의 중간 성격으로 나온 것이 Mini-Batch gradient descent이다. 이 알고리즘은 하나의 스텝을 업데이트할 때 데이터의 일부분을 샘플링하여 gradient descent을 진행한다.
\[ \theta_j := \theta_j - \alpha \sum^k_{i=1} (h_{\boldsymbol{\theta}} (x^{(i)}) - y^{(i)}) x^{(i)}_j \]
pytorch implementation
이 글의 코드를 보며 정리했다.
1차원 입력값으로 총 100개의 데이터가 있고, x와 y의 관계는 대략 2차 함수라고 가정해보자.
x = torch.unsqueeze(torch.linspace(-1, 1, 100), dim=1)
y = x.pow(2) + 0.2*torch.rand(x.size())
이제 x, y와의 함수를 approximation하는 딥러닝 neural network을 batch gradient descent, stochastic gradient descent, mini batch gradient descent으로 학습해본다.
우선 1차원 입력값을 받고 10차원 hidden layer와 relu activation function을 통과하여 최종 결과값을 내는 network을 생각해보자.
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.predict = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.predict(x)
return x
criterion = torch.nn.MSELoss()
batch gradient descent 방법은 한번 파라미터를 업데이트할 때 전체 데이터를 사용하는 방법이다. 아래 코드를 잘 보면, 모델의 입력값으로 들어가는 곳에 $x$가 들어가고, 이로부터 100개의 전체 데이터를 사용함을 알 수 있다.
net_gradient_descent = Net(n_feature=1, n_hidden=10, n_output=1)
optimizer_gradient_descent = torch.optim.SGD(net_gradient_descent.parameters(), lr=0.2)
loss_list = []
for t in range(1000):
prediction = net_gradient_descent(x)
loss = criterion(prediction, y)
loss_list.append(loss)
optimizer_gradient_descent.zero_grad()
loss.backward()
optimizer_gradient_descent.step()
if t % 100 == 0:
print('epoch: {}\tLoss = {:.3f} \n'.format(t, loss), end="")
결과 값을 그려보면 아래와 같다. loss가 매우 smooth하게 변함을 알 수 있다. 전체 데이터를 사용했기 때문이다. 그러나, 실제 문제 상황에서는 모델이 훨씬 복잡하고 데이터도 많기 때문에 이렇게 smooth하게 loss가 줄어들지 않는다.
다음으로 stochastic gradient descent 방법을 사용해보자.
sgd는 파라미터를 한번 업데이트할 때 하나의 데이터를 사용한다고 했다. 아래 코드를 보면, 파라미터를 한번 업데이트할 때, 입력값으로 주어지는 데이터가 1개이다. 모든 데이터에 대해서 이렇게 for loop을 돌고 바깥 for loop에서 epoch을 1000으로 설정한다.
net_stoch_gradient_descent = Net(n_feature=1, n_hidden=10, n_output=1)
optimizer_stoch_gradient_descent = torch.optim.SGD(net_stoch_gradient_descent.parameters(), lr=0.2)
loss_list = []
for t in range(1000):
for xi, yi in zip(x,y):
predi = net_stoch_gradient_descent(xi)
loss = criterion(predi, yi)
loss_list.append(loss)
optimizer_stoch_gradient_descent.zero_grad()
loss.backward()
optimizer_stoch_gradient_descent.step()
if t % 100 == 0:
print('epoch: {}\tLoss = {:.3f} \n'.format(t, loss), end="")
batch gradient descent에 비해 매우 noisy하게 loss 수렴함을 알 수 있다. 파라미터를 한번 업데이트할 때 하나의 데이터만 사용하기 때문이다.
bgd와 sgd의 중간점으로 나온 것이 mini batch gradient descent이다. 하나의 batch에 들어갈 데이터 개수(16)을 정하고 전체 데이터 수에서 batch에 들어갈 데이터 개수를 나눔으로써 batch의 수를 구한다. batch별로 16개의 데이터가 있으면, 16개의 데이터를 사용하여 gradient descent을 진행한다.
net_batch_gradient_descent = Net(n_feature=1, n_hidden=10, n_output=1)
optimizer_batch_gradient_descent = torch.optim.SGD(net_batch_gradient_descent.parameters(), lr=0.2)
loss_list = []
batch_size = 16
n_batches = int(len(x) / batch_size)
print(n_batches)
for epoch in range(len(x)):
for batch in range(n_batches):
batch_X, batch_y = x[batch*batch_size:(batch+1)*batch_size,], y[batch*batch_size:(batch+1)*batch_size,]
prediction = net_batch_gradient_descent(batch_X)
loss = criterion(prediction, batch_y)
loss_list.append(loss)
optimizer_batch_gradient_descent.zero_grad()
loss.backward()
optimizer_batch_gradient_descent.step()
if epoch % 10 == 0:
print('epoch: {}\tLoss = {:.3f} \n'.format(epoch, loss), end="")
loss가 bgd에 비해서는 noisy하지만 sgd에 비해서는 훨씬 덜 noisy하다.
실제 딥러닝 문제를 해결할 때는 거의 대부분 mini batch gradient descent을 사용한다고 한다. 아마도 이유는.. 1) 전체 데이터를 사용하지 않음으로써 시간을 단축 2) 수렴 속도는 bgd, sgd의 중간 이기 때문일 것으로 생각된다.
'ML&DL > Basics' 카테고리의 다른 글
[DL / Paper review] Auto-Encoding Variational Bayes (2) | 2023.05.06 |
---|---|
[DL] Why multiple layer perceptron? (0) | 2023.04.25 |
[DL] Backpropagation (0) | 2023.03.20 |
[ML / DL] Stochastic Gradient Descent 식 유도하기 (0) | 2023.03.19 |
[ML / DL] Cost function과 Maximum likelihood estimation과의 관계 (0) | 2023.03.19 |
댓글