논문

하루에도 수만개의 글자를 읽고 있습니다. 하루에도 수백장의 종이를 들춰 읽습니다.
이것은 그 읽기에 대한 일기입니다.

Caffe Tutorial 5. Solver

Solver

Solver는 model의 최적화를 조율합니다. 이 과정에서 network의 loss를 향상시키기 위해서 전방향 추론(forward inference)와 파라미터의 업데이트를 위한 역방향(backward) 그래디언트 계산을 조정합니다. 학습(learning)을 좌우하는 것은 최적화와 파라미터 갱신을 생성하는 Solver와 loss와 그래디언트를 생성하는 Net 두가지입니다.

Caffe의 solver는 다음의 것들이 준비되어있습니다.

  • Stochastic Gradient Descent (type: "SGD")
  • AdaDelta (type: "AdaDelta")
  • Adaptive Gradient (type: "AdaGrad")
  • Adam (type: "Adam")
  • Nesterov's Accelerated Gradient (type: "Nesterov")
  • RMSprop (type: "RMSProp")

Solver는 다음의 역할들을 수행합니다.

  1. 최적화 기록을 위한 뼈대를 준비하고 학습을 위한 training network와 평가를 위한 test network를 생성합니다.
  2. 전방향 / 역방향 진행과 파라미터 갱신을 통하여 반복적으로 최적화 작업을 수행합니다.
  3. (주기적으로) test network를 평가합니다.
  4. 최적화 중에 model과 solver의 상태를 저장해둡니다.

이러한 작업들은 아래에 열거된 시점의 각 반복에서 수행됩니다.

  1. 출력과 loss를 계산하기 위한 network 전방향 루틴 호출
  2. 그래디언트 계산을 위한 network 역방향 루틴 호출
  3. 그래디언트를 solver method에 적용하여 파라미터를 업데이트 할 때
  4. 학습률(learning rate), 학습추세, 방법에 따라 solver의 상태를 갱신할 때

이러한 부분은 model의 초기화부터 학습이 완료될 때까지 모든 가중치를 취할때마다 반복됩니다.

Caffe의 model과 마찬가지로, solver또한 CPU / GPU 모드에서 실행됩니다.

Methods

Solver method는 loss의 최소화를 위한 일반적인 최적화 방법을 제시합니다. 데이터셋 $D$가 있다 가정할 때, 목적 함수의 최적화는 모든 $|D|$개 데이터의 평균 loss가 됩니다. 이는 다음과 같습니다.

$$ L(W) = \frac{1}{|D|} \sum_i^{|D|} f_W\left(X^{(i)}\right) + \lambda r(W) $$

여기서 $f_W\left(X^{(i)}\right)$는 데이터 $X^{(i)}$의 loss를 나타내고, $r(W)$는 가중치 $\lambda$와 곱해지는 정규화 항입니다. $|D|$는 매우 처질 수 있으므로, 실제로는 각 solver의 반복마다 목적함수의 stochastic approximation을 하기 위해 $N \lt\lt |D| $개의 미니배치를 뽑아서 사용합니다.

$$ L(W) \approx \frac{1}{N} \sum_i^N f_W\left(X^{(i)}\right) + \lambda r(W) $$

Model은 전방향 진행 시 $f_W$를 계산하고, 역방향 진행에서 그래디언트 $\nabla f_W$를 계산합니다.

파라미터 업데이트 $\nabla W$는 에러 그래디언트 $\nabla f_W$, 정규화 그래디언트 $\nabla r(W)$와 다른 기타 항들로부터 solver에 의해 생성됩니다.

SGD

Stochastic gradient descent(type: "SGD") 는 그래디언트의 반대 방향 $\nabla L(W)$과 이전에 가중치가 갱신량 $V_t$의 선형 조합으로 가중치 $W$를 업데이트합니다. 학습률 $\alpha$는 역 그래디언트에 주는 가중치가 됩니다. 모멘텀 $\mu$는 이전 업데이트의 가중치를 주는 역할을 합니다.

다시 말하자면, $t+1$번째 반복에서 이전에 가중치 갱신량 $V_t$와 현재 가중치 $W_t$가 주어졌을 경우, 갱신될 가중치 량 $V_{t+1}$과 갱신 이후 가중치 결과 $W_{t+1}$를 다음의 수식으로 계산합니다.

$$ V_{t+1} = \mu V_t - \alpha \nabla L(W_t)$$
$$ W_{t+1} = W_t + V_{t+1} $$

최상의 결과를 위해서는 학습의 "하이퍼파라미터(hyperparameters)" ($\alpha$, $\mu$)의 튜닝이 필요합니다. 어디서부터 시작해야할지 모르겠다면 아래의 "Rules of thumb"를 살펴보기 바랍니다. 더 많은 정보는 Leon Bottou의 Stochastic Gradient Descent Trick을 참조하면 됩니다.

Rules of thumb for setting the learning rate $\alpha$ and momentum $\mu$

SGD를 이용하여 딥러닝을 할 때 좋은 전략은 학습률 $\alpha$를 $0.01 = 10^{-2}$부근에서 초기화한 다음, 훈련시키는 동안 loss가 명백하네 plateau에 다다랐을 때 상수 부분 10을 감소시키는 작업을 몇번 수행하는 것입니다. 또한 일반적으로 모멘텀 $\mu = 0.9$ 혹은 비슷한 값으로 사용합니다. 모멘텀은 반가중치 갱신을 부드럽게 만들어주어 SGD를 이용한 딥러닝이 안정적이고 빠르게 동작하도록 만들어 줍니다.

이것은 Krizhevsky가 ILSVRC-2012 대회에서 CNN으로 우승한 유명한 전략입니다. Caffe는 이 전략을 SolverParameter에서 구현하기 쉽도록 ./examples/imagenet/alexnet_solver.prototxt에 구현해 두었습니다.

학습률 정책을 위와 같이 사용하려면 아래의 몇줄을 Solver prototxt에 아무데나 적어두면 됩니다.

위와 같은 세팅으로 모멘텀 $\mu=0.9$을 고정하여 사용해보겠습니다. $\alpha=0.01=10^{-2}$의 base_lr로부터 훈련을 시작하여 처음 100,000번 반복을 한 후에 학습률에 gamma($\gamma$)를 곱하여 $\alpha' = \alpha \gamma = (0.01) (0.1) = 0.001 = 10^{-3}$으로 다음 반복 100,000-200,000번째을 훈련할 것입니다. 다음 $\alpha'' = 10^{-4}$을 이용하여 200,000-300,000번에서 사용하고 마지막으로 $\alpha''' = 10^{-5}$을 이용하여 350,000번까지 훈련시킬 것입니다. 이는 max_iter: 350000로 설정하였기 때문입니다.

모멘텀 $\mu$는 여러 반복 이후에 업데이트의 크기를 $ \frac{1}{1 - \mu}$ 로 하여 곱하는 것이 효과적입니다. 따라서 $\mu$를 증가시키면 $\alpha$를 같이 줄여주는 것이 좋습니다. 그리고 반대의 경우도 마찬가지 입니다.

예를 들면, $\mu=0.9$를 사용할 때에는 업데이트 크기를 $\frac{1}{1 - 0.9} = 10$로 하는 것이 효과적입니다. 모멘텀은 $\mu=0.99$로 사용할 경우 업데이트 크기를 100으로 하는 것이 좋으며, 이에 따라 $\alpha$(base_lr)을 10 단위로 줄여주는 것이 좋습니다.

위의 세팅들은 단지 가이드라인일 뿐이며, 모든 경우에 대해서 최적이라고 결코 이야기할 수 없습니다. 심지어 잘 동작하지 않을 수도 있습니다. 학습이 발산해버리면, 그러니까 출력이나 loss 값이 매우 커지거나 NaN, inf가 되어버릴 경우, base_lr을 줄이고 (base_lr: 0.001) 다시 시도해보아야 합니다. 적절한 base_lr값을 찾을 때 까지 이를 반복해보세요.

AdaDelta

AdaDelta(type: "AdaDelta")는 "강인한 학습률 방법" 입니다. 이는 SGD와 마찬가지로 그래디언트 기반의 최적화 방법입니다. 업데이트 수식은 아래와 같습니다.

$$ \begin{align} (v_t)_i &= \frac{\operatorname{RMS}((v_{t-1})_i)}{\operatorname{RMS}\left( \nabla L(W_t) \right)_{i}} \left( \nabla L(W_{t'}) \right)_i \\ \operatorname{RMS}\left( \nabla L(W_t) \right)_{i} &= \sqrt{E[g^2] + \varepsilon} \\ E[g^2]_t &= \delta{E[g^2]_{t-1} } + (1-\delta)g_{t}^2 \end{align} $$

$$ (W_{t+1})_i = (W_t)_i - \alpha (v_t)_i $$

AdaGrad

Adaptive Gradient (type: "AdaGrad")는 SGD와 같은 그래디언트 기반의 최적화 방법으로 Duchi의 말에 따르면 "예측할 수는 있으나 거의 특징이 보이지 않는 상황의 모래 속에서 바늘을 찾는 것"과 같은 시도를 하는 방법입니다. 이전까지의 모든 반복 $t' \in \{1, 2, ..., t\}$에서 비롯된 업데이트 정보($\left( \nabla L(W) \right)_{t'}$)를 이용하여 아래 수식을 이용하여 가중치 $W$의 각 컴포넌트 $i$를 업데이트하게 됩니다.

$$ (W_{t+1})_i = (W_t)_i - \alpha \frac{\left( \nabla L(W_t) \right)_{i}}{ \sqrt{\sum_{t'=1}^{t} \left( \nabla L(W_{t'}) \right)_i^2} } $$

AdaGrad는 가중치 $W \in \mathcal{R}^d$의 계층적 그래디언트 정보를 저장하여야 합니다. 각각의 정보를 그대로 저장한다면 $\mathcal{O}(dt)$의 공간이 필요하지만 실제로 Caffe에서는 $\mathcal{O}(d)$수준의 추가적인 공간만이 필요합니다.

Adam

Kingma가 제안한 Adam (type: "Adam")은 SGD와 같은 그래디언트 기반의 방법으로, adaptive moment estimation($m_t, v_t$)를 포함하며 일반화된 AdaGrad로 생각할 수 있습니다. 갱신 수식은 아래와 같습니다.

$$ (m_t)_i = \beta_1 (m_{t-1})_i + (1-\beta_1)(\nabla L(W_t))_i,\\ (v_t)_i = \beta_2 (v_{t-1})_i + (1-\beta_2)(\nabla L(W_t))_i^2 $$

$$ (W_{t+1})_i = (W_t)_i - \alpha \frac{\sqrt{1-(\beta_2)_i^t}}{1-(\beta_1)_i^t}\frac{(m_t)_i}{\sqrt{(v_t)_i}+\varepsilon} $$

Kingma는 $\beta_1 = 0.9, \beta_2 = 0.999, \epsilon = 10^{-8}$를 기본 값으로 사용할 것을 제안하였습니다. Caffe는 이들 값을 각각 momentum, momentum2, delta의 이름으로 사용합니다.

NAG

Nesterov의 accelerated gradient (type: "Nesterov")는 컨백스 최적화를 위한 최선의 방법으로 Nesterov가 제안한 것으로 $\mathcal{O}(1/t)$보다 더 빠른 $\mathcal{O}(1/t^2)$의 수렴률을 달성한 방법입니다. 이를 달성하기 위해 필요한 가정, 즉 스무스하지 않고, 컨벡스하지 않기 때문에 Caffe를 이용한 심층 네트워크에 맞지 않더라도 실제로 NAG를 사용해보면 심층 학습 구조의 최적화에도 아주 효과적인 방법입니다. 이는 심층 Sutskever의 MNIST autoencoder에 잘 나타나 있습니다.

가중치 업데이트 수식은 SGD의 그것과 매우 비슷합니다.

$$ V_{t+1} = \mu V_t - \alpha \nabla L(W_t + \mu V_t) $$
$$ W_{t+1} = W_t + V_{t+1} $$

SGD와의 차이점은 에러의 그래디언트 $\nabla L(W)$를 계산할 때의 가중치 세팅 $W$입니다. NAG에서는 모멘텀이 더해진 가중치 $\nabla L(W_t + \mu V_t)$의 그래디언트를 사용합니다. SGD에서는 단순히 현재 가중치의 그래디언트 $\nabla L(W_t)$를 사용하고 있었습니다.

RMSprop

RMSprop (type: "RMSProp")는 Coursera 강의에서 Tieleman가 제안한 것으로 SGD와 같이 그래디언트 기반의 방법입니다. 업데이트 수식은 아래와 같습니다.

$$ \operatorname{MS}((W_t)_i)= \delta\operatorname{MS}((W_{t-1})_i)+ (1-\delta)(\nabla L(W_t))_i^2 \\ (W_{t+1})_i= (W_{t})_i -\alpha\frac{(\nabla L(W_t))_i}{\sqrt{\operatorname{MS}((W_t)_i)}} $$

$\delta$(rms_decay)의 기반 값은 $\delta=0.99$입니다.

Scaffolding

Solver scaffolding은 Solver::Presolve()에서 최적화 알고리즘을 준비하고 model이 학습할 수 있도록 초기화합니다.

Net 초기화

Loss

완료

Updating Parameters

실제 가중치 업데이트는 solver에 의해 만들어진 다음 Solver::ComputeUpdateValue()에서 net 파라미터로 적용됩니다. ComputeUpdateValue 메서드는 weight decay($r(W)$)와 통합되어 각 네트워크의 가중치에 대하여 최종 그래디언트를 얻기 위한 가중치 그래디언트(현재는 에러의 그래디언트)로 변환됩니다. 다음 그 그래디언트는 학습률 $\alpha$로 스케일링되어 뺄셈을 위해 Blob의 diff 필드의 각 파라미터에 저장됩니다. 마지막으로 각 파라미터 blob의 Blob::Update 메서드를 실행하여 최종 업데이트(data에서 diff를 빼는 작업)를 수행합니다.

Snapshotting and Resuming

Solver는 훈련 중 가중치와 그것의 상태를 Solver::Snapshot()Solver::SnapshotSOlverState()를 사용하여 스냅샷을 저장해놓습니다. 가중치 스냅샷은 정해진 학습된 model을 저장하는 반면, solver 스냅샷은 주어진 시점부터 이어서 다시 학습할 수 있습니다. 훈련은 Solver::Restore()Solver::RestoreSolverState()에 의해 재개될 수 있습니다.

가중치는 확장자 없이 저장되지만 solver 상태는 .solverstate 확장자가 붙어 저장됩니다. 두 파일일 반복의 숫자에 따라 모두 _iter_N라는 이름이 붙을 것입니다.

스냅샷은 solver를 정의하는 prototxt에 다음과 같이 설정합니다.


Tags:
Add a Comment Trackback