文献链接:An overview of gradient descent optimization algorithms
我一直对于 gradient descent 的机制没有搞透,就很好奇这梯度到底怎么搞的。(我觉得关键是因为我高数学的太水,学梯度的时候没有用非数学的想法去理解它)
先又重复一遍老内容:
Batch Gradient Descent
也就是最朴素的GD,就真的很朴素:
θ
=
θ
−
η
∇
θ
J
(
θ
)
\theta~=~\theta~-\eta\nabla_{\theta}J(\theta)
θ = θ −η∇θJ(θ)
for i in range(nb_epochs):
params_grad = evaluate_gradient(loss_function, data, params)
params = params - learning_rate * params_grad
Stochastic Gradient Descent
这中方法每一次只用一个例子来更新,不像上面的把所有的training data 都计算一遍。所以就快很多,而且可以on-line学习。
θ = θ − η ∇ θ J ( θ ; x i ; y i ) \theta~=~\theta~-\eta\nabla_{\theta}J(\theta;~x^i;~y^i) θ = θ −η∇θJ(θ; xi; yi)
但还是,虽然每次只计算一个example,但是还是data里面的全部过一遍。
for i in range(nb_epochs):
np.random.shuffle(data)
for example in data:
params_grad = evaluate_gradient(loss_function, example, params)
params = params - learning_rate * params_grad
mini-batch Gradient Descent
这里就是我们非常常用的方法了,就是随机选一个batch,然后每次计算一个batch_size 的examples:
θ
=
θ
−
η
∇
θ
J
(
θ
;
x
(
i
:
i
+
n
)
;
y
(
i
:
i
+
n
)
)
\theta~=~\theta~-\eta\nabla_{\theta}J(\theta;~x^{(i:i+n)};~y^{(i:i+n)})
θ = θ −η∇θJ(θ; x(i:i+n); y(i:i+n))
for i in range(nb_epochs):
np.random.shuffle(data)
for batch in get_batches(data, batch_size=50):
params_grad = evaluate_gradient(loss_function, batch, params)
params = params - learning_rate * params_grad
其实SGD的方法就是mini-batch 的方法的一种batch size == 1 的特殊情况。
challenges
存在的问题就是:
- 我们需要选择一个合适的learning rate(这里用了 η \eta η来表示)
- learning rate最好能随着访问的次数而有所不同——经常访问的 η \eta η小,不经常访问的 η \eta η大。
- 我们需要避免被困在局部最优里,最坏的情况是被困在鞍点,鞍点的每个方向的gradient 都是0。
解决措施
我只看了四种常用的方法——Momentum, NAG, Adagrad, Adam
Momentum
这种比较简单,就是加一个momentum,给他一个下降的动力,使SGD加速。
v
t
=
γ
v
t
−
1
+
η
∇
θ
J
(
θ
)
v_t~=~\gamma v_{t-1}~+~\eta\nabla_{\theta}J(\theta)
vt = γvt−1 + η∇θJ(θ)
θ
=
θ
−
v
t
\theta ~=~\theta~-~v_t
θ = θ − vt
γ v t − 1 \gamma v_{t-1} γvt−1 就是加上的动量。 γ \gamma γ 一般设置为0.9。 v t v_t vt 是 θ \theta θ的更新量, v t − 1 v_{t-1} vt−1 是上一时刻的更新量,也就是说, t t t 时刻的更新量采用了一部分之前的更新量(也就是根据过去的经验)。
Nesterov accelerated gradient(NAG)
我们想要我们的参数更加超前一点,变得更加“先知”。举个形象的例子就是,我们给沿坡下降的小球加上动量之后,我们还希望它能够预测前面的情况,能够在到达最低点的时候不要再冲到另一边的坡上了。
所以,我们用来计算的梯度的参数 θ \theta θ,我们令他等于 ( θ − γ v t − 1 ) (\theta-\gamma v_{t-1}) (θ−γvt−1),用上一步得到的v来得到一个下一步 θ 的估计值。
v
t
=
γ
v
t
−
1
+
η
∇
θ
J
(
θ
−
γ
v
t
−
1
)
v_t~=~\gamma v_{t-1}~+~\eta\nabla_{\theta}J(\theta-\gamma v_{t-1})
vt = γvt−1 + η∇θJ(θ−γvt−1)
θ
=
θ
−
v
t
\theta ~=~\theta~-~v_t
θ = θ − vt
对比上面两种方法,如上图,非常形象。Momentum就是,先按照计算出的梯度方向前进一小部(第一个小的蓝箭头),然后根据过去积累的经验 vt-1方向前进一大步(大的蓝箭头)。
而NAG,是先按照过去积累的经验走一大步(棕色箭头),然后再根据现在计算出的方向加以校正(红色箭头)。就得到最终下面的绿色箭头。
Adagrad
我们还想每个参数单独更新,每个 time step 都单独更新每个参数。
g
t
,
i
=
∇
θ
J
(
θ
t
,
i
)
g_{t,i} = \nabla_{\theta}J(\theta_{t,i})
gt,i=∇θJ(θt,i)
θ
t
+
1
,
i
=
θ
t
,
i
−
η
⋅
g
t
,
i
\theta_{t+1,i}~=~\theta_{t,i}~-~\eta\cdot g_{t,i}
θt+1,i = θt,i − η⋅gt,i
然后如果我们想自动改变learning rate,可以这样:
g
t
,
i
=
∇
θ
J
(
θ
t
,
i
)
g_{t,i} = \nabla_{\theta}J(\theta_{t,i})
gt,i=∇θJ(θt,i)
θ
t
+
1
,
i
=
θ
t
,
i
−
η
G
t
,
i
i
+
ϵ
⋅
g
t
,
i
\theta_{t+1,i}~=~\theta_{t,i}~-~\frac{\eta}{\sqrt{G_{t,ii}+\epsilon}} \cdot g_{t,i}
θt+1,i = θt,i − Gt,ii+ϵη⋅gt,i
其中 G t G_{t} Gt 是一个矩阵,第i,i 个元素是关于 θi的所有得到的gradient 的平方的和。(从开始到时间t),所以 G t , i i G_{t,ii} Gt,ii就是对于θi的所有梯度的平方的和。
还有一种Adam
唉写8动了……