梯度更新的最终目的是为了“到山最底端”,梯度更新算法优化的目的是“最稳最快的 到山最底端”。
0.SGD
SGD是非常好用,经典的梯度更新算法。算法思路比较简单,直接上代码。
def sgd(w, dw, config=None):
"""
单纯的sgd实现
"""
if config is None: config = {}
config.setdefault('learning_rate', 1e-2)
w -= config['learning_rate'] * dw
return w, config
虽然sgd简单有效,但是也有问题,比如,sgd收敛的图像可能是下图这样的。
翻译一下: 小人P在从R(小红点)点去往山的最低处F(笑脸)点时,最近的路线方向应该是R->F的直线方向,但是现在P听从SGD的建议,就往其他各种方向走来走去,但是大方向还是R->F的方向。
解决办法?下面有。
1.Momentum
通过图1.1可知,所有方向上的loss值都在来回震荡。如果给震荡一个“摩擦力”,那么慢慢的震荡就会终止。于是有了momentum。python代码是这样的:
def sgd_momentum(w, dw, config=None):
"""
带动量的sgd实现
"""
if config is None: config = {}
learning_rate=config.setdefault('learning_rate', 1e-2)
mu=config.setdefault('momentum', 0.9)
v = config.get('velocity', np.zeros_like(w))
next_w = None
v = mu * v - learning_rate * dw
next_w = w + v
config['velocity'] = v
return next_w, config
2.Nesterov Momentum
这个是Momentum的升级版本,因为有了“预测“功能,所以损失函数收敛更快一些。
def sgd_nesterov_momentum(w, dw, config=None):
if config is None:
config = {}
learning_rate = config.get('learning_rate', 1e-2)
mu = config.get('momentum', 0.9)
v = config.get('velocity', np.zeros_like(w))
v_pre = v
v = -dw * learning_rate + mu * v
v = v + mu * (v - v_pre)
next_w = w + v
config['velocity'] = v
return next_w,config
3. AdaGrad
def adagrad(w, dw, config=None):
if config is None: config = {}
learning_rate=config.get('learning_rate', 1e-2)
epsilon=config.get('epsilon', 1e-8)
config.setdefault('cache', np.zeros_like(w))
cache=np.zeros_like(w)
cache += dw ** 2
next_w = w - learning_rate * dw / (np.sqrt(cache) + epsilon)
config['cache'] = cache
return next_w, config
翻译一下:相当于使得每一个方向的梯度都有了自己的learning_rate,而且会让梯度较大的方向上梯度更新越来越慢,梯度较小的方向的梯度更新也会越来越慢,但变慢的速率明显小于梯度大的方向上的。“稳当地下到山的最低处“。
效果:抑制大梯度方向上的更新,扩大小梯度方向上的更新,各个方向上的梯度更新趋向与一致。
缺点:因为cache越来越大,就会导致dw/( cache+epsilon)=0,梯度更新消失。
4.RMSProp
RMSProp是AdaGrad的升级版本,它使用衰减系数解决了Adagrad后面导致梯度更新变成0而导致学习终止的问题。
def rmsprop(w, dw, config=None):
if config is None: config = {}
learning_rate = config.get('learning_rate', 1e-2)
decay_rate = config.get('decay_rate', 0.99)
epsilon = config.get('epsilon', 1e-8)
cache = config.get('cache', np.zeros_like(w))
cache = decay_rate * cache + (1 - decay_rate) * dw ** 2
next_w = w - learning_rate * dw / (np.sqrt(cache) + epsilon)
config['cache'] = cache
return next_w, config
5. Adam
Adam是集大成者,对于上面RMSProp和Nesterov Momentum取其精华。
def adam(w, dw, config=None):
"""
config format:
- learning_rate: Scalar learning rate.
- beta1: Decay rate for moving average of first moment of gradient.
- beta2: Decay rate for moving average of second moment of gradient.
- epsilon: Small scalar used for smoothing to avoid dividing by zero.
- m: Moving average of gradient.
- v: Moving average of squared gradient.
- t: Iteration number.
"""
if config is None: config = {}
config.setdefault('learning_rate', 1e-3)
config.setdefault('beta1', 0.9)
config.setdefault('beta2', 0.999)
config.setdefault('epsilon', 1e-8)
config.setdefault('m', np.zeros_like(w))
config.setdefault('v', np.zeros_like(w))
config.setdefault('t', 0)
m = config['m']
v = config['v']
t = config['t'] + 1
beta1 = config['beta1']
beta2 = config['beta2']
epsilon = config['epsilon']
learning_rate = config['learning_rate']
m = beta1 * m + (1 - beta1) * dw
v = beta2 * v + (1 - beta2) * (dw ** 2)
mb = m / (1 - beta1 ** t)
vb = v / (1 - beta2 ** t)
next_w = w - learning_rate * mb / (np.sqrt(vb) + epsilon)
config['m'] = m
config['v'] = v
config['t'] = t
return next_w, config
参考资料:
深度学习与计算机视觉 李飞飞 系列课程