Adam Optimizer

Adam Optimizer(Adaptive Moment estimation) (ref. 2015 ICL “Adam: A Method for Stochastic Optimization)는 목적함수 \(f\)의 최솟값을 찾는 알고리즘으로 Momentum과 RMSProp의 기본 아이디어를 합친 알고리즘이다. Momentum term에 RMSProp에서 등장했던 지수이동평균을 적용하고 RMSProp과 같은 방식으로 변수들이 update하는 방향과 크기는 Adaptive 벡터에 의해서 결정된다(소득 재분배). 알고리즘의 형태는 조금 복잡해보이지만 두 optimizer를 적절하게 조화시킨 형태다(optimizer의 완결판).

Adam Optimizer는 다음과 같이 목적함수 \(f\)의 최솟값을 찾는다.

  • 초기값 \(x_0\)와 적당한 Learning rate \(\alpha\), \(\beta_1\), \(\beta_2\), \(\varepsilon\) 설정
    • \(\beta_1\) : momentum decay rate(default : 0.9)
    • \(\beta_2\) : adaptive term decay rate(default : 0.999)
  • \(n\geq 0\)인 정수에 대해서 momentun term \(a_{n+1}\)과 adaptive term \(b_{n+1}\)을 다음과 같이 정의한다(\(\odot\)는 element wise 곱, 참고).
\[a_{n+1} := \beta_1 \cdot a_n + (1-\beta_1)\cdot \nabla f(x_n), \quad a_0 := 0\] \[b_{n+1} := \beta_2 \cdot b_n +(1-\beta_2) \cdot \nabla f(x_n)\odot \nabla f(x_n) , \quad b_0 := 0\]

여기서 \(a_{n+1}\)을 정의할 때 기존의 Momentum 알고리즘과 달리 지수이동평균을 사용하기 때문에 \(\nabla f\)의 계수(\(1 - \beta_1\))가 작은 값을 갖는다. 그래서 Adam Optimizer는 초기 update 속도를 보정(올리기)하기 위해서 상수 \(\widehat{\alpha}_{n+1}\), \(n\geq 0\)를 다음과 같이 정의하여 변수를 update하는데 사용한다. Momentum term \(a_{n}\)과 adaptive term \(b_n\)은 모두 벡터다.

\[\widehat{\alpha}_{n+1} := \alpha \cdot\frac{\sqrt{1-(\beta_2)^{n+1}}}{1-(\beta_1)^{n+1}}\]

\(n>>1\)인 경우 \(\widehat{\alpha}_n\)은 \(\alpha\)로 수렴한다. 마지막으로 변수 \(x_{n+1}\)는 다음과 같이 정의한다.

\[x_{n+1} := x_n - \frac{\widehat{\alpha}_{n+1}}{\sqrt{b_{n+1}}+\varepsilon} \odot a_{n+1}\]

\(\varepsilon\)은 분모에 0이 되는 것을 방지하는 수로 보통 (1e-8) 정도의 작은 값을 대입한다. Adam Optimizer의 정의로 부터 Momentum Optimizer와 RMSProp Optimizer의 장점들을 모두 기대할 수 있다.


방정식 \(2\cdot x=10\) 의 근을 Adam Optimizer 알고리즘을 이용해서 찾아보자.

  • 목적함수 \(f(x):=(2x-10)^2\) 정의
  • 초기값 \(x_0 = 0\), Learning rate \(\alpha=0.5\), \(\beta_1=0.9\), \(\beta_2=0.999\), \(\varepsilon = 10^{-8}\)로 설정

\(f'(x) = 4(2x-10)\)이므로

\[a_1 = \beta_1 \cdot a_0 + (1-\beta_1) \cdot f'(x_0) = -4,\] \[b_1 = \beta_2 \cdot b_0 + (1-\beta_2) \cdot (f'(x_0))^2 = 1.6,\] \[\widehat{\alpha}_1= \alpha \cdot \frac{\sqrt{1-\beta_2}}{1-\beta_1} = 0.0158113\]

이고 \(x_1\)은 다음과 같이 얻을 수 있다.

\[x_1 = x_0 - \frac{\widehat{\alpha}_1}{\sqrt{b_1}+\varepsilon} \cdot a_1=0.5.\]

같은 방법으로 작업을 반복적으로 수행하면 \(x_n\)의 수열을 다음과 같이 얻을 수 있다.

\[(x_n) = (0, 0.5, 0.997937, 1.49206, 1.9803, 2.46017, ...)\]

\(x_n\)이 5로 수렴하는 것을 확인 할 수 있고 초기에 업데이트 되는 속도가 Gradient Descent에 비해 느리지만 주목할 부분이 Gradient Descent에서 learning rate=0.5 인 경우 발산했지만 Adam Optimizer는 발산하지 않고 수렴한다.

부연 설명 : 위 그림에서 보면 시행 횟수가 16회 정도 되었을 때 \(x_n\)이 우리가 원하는 정답값 5를 지나친다. 만약에 Gradient Descent Optimizer를 사용했다면 \(x_n\)값이 5보다 작다가 5를 넘어가게 되면 미분계수의 부호가 음수에서 양수로 변경이 되어서 바로 돌아와야 하는데 관성(Momentum) 효과, 즉 이동하고 있던 이동량을 기억하고 있기 때문에 돌아오지 않고 정답 5에서 멀어지고 있는 것이다. 이 같은 현상이 35번 째에서도 발생한다.


TensorFlow code

\(\beta_1\), \(\beta_2\), \(\varepsilon\)을 따로 지정하지 않으면 default값으로 사용

# 방정식 2*x = 10 을 만족하는 x 찾기
# x 초깃값 = 0

import tensorflow as tf
import matplotlib.pyplot as plt

%matplotlib inline


X = tf.Variable(0.)
Y = tf.constant(10.)
H = 2 * X

# learning rate = 0.5
loss = tf.square(H-Y)
optimize = tf.train.AdamOptimizer(0.5).minimize(loss)


sess = tf.Session()
sess.run(tf.global_variables_initializer())
sequence = []

for i in range(100):
    #print("x_%i = %s" %(+i, sess.run(X)))
    sess.run(optimize)
    sequence.append(sess.run(X))

plt.suptitle("Sequence of x", fontsize=20)
plt.ylabel("x value")
plt.xlabel("Steps")
plt.plot(sequence, ".")