Caffe中的SGD的变种优化算法(2)
接上回,我们继续来看优化的算法。
Adam
关于adam的算法,我觉得它的复杂程度比前面的算法更高,但是它还是主要使用了和计算动量相同的方法,并把这种方法应用到梯度的一阶总量和二阶总量上:
它的代码如下所示:
def adam(x_start, step, g, beta1 = 0.9, beta2 = 0.999,delta=1e-8):
x = np.array(x_start, dtype='float64')
sum_m = np.zeros_like(x)
sum_v = np.zeros_like(x)
passing_dot = [x.copy()]
for i in range(50):
grad = g(x)
sum_m = beta1 * sum_m + (1 - beta1) * grad
sum_v = beta2 * sum_v + (1 - beta2) * grad * grad
correction = np.sqrt(1 - beta2 ** i) / (1 - beta1 ** i)
x -= step * correction * sum_m / (np.sqrt(sum_v) + delta)
passing_dot.append(x.copy())
if abs(sum(grad)) < 1e-6:
break;
return x, passing_dot
前面也说过,实际上这些算法背后都有些原理的,但是这些原理在我们看来可能都比较抽象,所以再我们看完实验再看原理可能会更有感觉。下面我们看看它的表现:
res, x_arr = adam([5, 5], 0.1, g)
contour(X,Y,Z, x_arr)
好吧,这一回它是唯一一个走得有点过头的算法,不过最后还是走上了正道。
这样除了Nesterov算法之外,我们把其他所有的算法都展示出来了。前面这个实验中,我们希望检验算法的“拐弯”能力。也就是说一开始算法冲着一个局部最优点而来,什么时候它会发现这是个骗局并掉头朝向真正的最优值呢?现在每一个算法都给我们交出了答案。仔细看来每个算法的表示还是不太一样的。有的算法比较灵敏,刚发现不对就调头逃跑,有的算法则是冲过了头才转过身来。有的算法做到这一切十分容易,并不需要很大的Learning rate,有的算法则需要强大的力量才能转过来,否则就会行动缓慢。
好了,各位童鞋,转弯赛到此结束,下面我们进入下一个比赛——爬坡赛。爬坡赛的比赛规则是,所有算法使用同样的参数,从鞍点附近出发,经过50轮迭代,看看它能走到哪里。
爬坡赛
首先是我们的老朋友梯度下降法:
res, x_arr = gd([-0.23,0.0], 0.0008, g)
contour(X,Y,Z, x_arr)
我们的老朋友在爬坡赛看来是要叫白卷了,说明它虽然很轻盈,但是动力不是很足啊。
下面是动量算法:
res, x_arr = momentum([-0.23,0], 5e-4, g)
contour(X,Y,Z, x_arr)
比梯度下降好一点,但是好的有限。
然后是Adagrad:
res, x_arr = adagrad([-0.23, 0], 1.3, g)
contour(X,Y,Z, x_arr)
好吧,表现得比转弯赛还好,省去夸赞的词语了。
下面是Rmsprop:
res, x_arr = rmsprop([-0.23, 0], 0.3, g)
contour(X,Y,Z, x_arr)
同样完成的不错!
下面我们再看AdaDelta:
res, x_arr = adadelta([-0.23, 0], 0.4, g)
contour(X,Y,Z, x_arr)
好吧,直接一骑绝尘而去了。
最后是我们今天登场的Adam:
res, x_arr = adam([-0.23, 0], 0.1, g)
contour(X,Y,Z, x_arr)
同样是跑出了我们原始规划的区域。
总结
经过两轮的比赛——转弯赛和爬坡赛,我们测试了几种算法在同一配置上的实力。测试的目标不是为了比较哪个算法更厉害,而是展示另外一个实力——哪个算法表现更稳定。
因为我们在实际使用过程中,learning rate是相对固定的,在优化过程中我们无法预料自己将会碰上什么样的情景,那么我们肯定希望一个算法又能转弯又能爬坡。如果一个算法转弯厉害但是爬坡比较弱,那一旦遇上爬坡它就会走得很慢,反之也是如此。
所以从刚才的表现来看,我们的新算法似乎表现更为平稳,现在也确实有越来越多的人抛弃了一些经典的算法,转而投入这些新算法的怀抱,而这些新算法也足够争气,拿出了它们的实力。关于算法性能的测试一直在进行,这些算法还需要经历更多的考验!