写在前面 说实话设计优化算法和解释优化算法的合理性实际上是一个不太容易的事情。这里我们主要关注算法的实际效果,因此我们对于算法的理论推导就暂时放在一边了。而关于理论,我们只简单讲两句,不做过多的深入,重点是看算法在我们设计的场景下的表现。 非凸函数 之前在我们的算法演示中,我们展示的函数都是凸函数。凸函数有哪些好处,谁来讲一讲? 图片源自网络金坷垃(侵删)
而下面我们将展示一个函数,这个函数可没有这么多好的性质了: 在等高线图上,它的样子是这样的: 这里的配色采用绘图中的经典彩虹色,其中颜色越靠近蓝色表示函数值越大,颜色越靠近红色表示函数值越小。这和我们的直觉是相符的,一三象限的数字大,二四象限的数字小。 我们的第一个尝试就是从一个比较正常的位置出发——(5,5)点,看看各种算法的效果 在这个新的环境下,我们的老算法——梯度下降,动量,Nesterov算法会不会焕发第二春呢? 有请老队员上场 首先是梯度下降法,由于前面我们已经贴过代码了,这里我们就不再贴代码了,直接上结果: res, x_arr = gd([5,5], 0.0008, g) contour(X,Y,Z, x_arr) 为了保证算法经过50轮迭代不会超出我们限定的范围,我们调整了它的learning rate。可以看出算法在迭代过程中先是冲向了局部最优点,也就是我们常说的鞍点,然后发现不对,调转头冲向了真正的优化点,祝它一路走好…… 然后是动量算法: res, x_arr = momentum([5, 5], 3e-4, g) contour(X,Y,Z, x_arr) 一切顺利!不同的演员同样的剧本,演出来的效果还是不错的。 好了,到这里我们的老队员登场完毕,下面看看新队员了。 Adagrad Adagrad是一种自适应的梯度下降方法,它希望根据梯度的更新量调节梯度实际的影响值。如果一个变量的梯度更新量比较大,那么再后面的更新过程中我们会适当减少它的更新量,如果更新量比较小,那么我们可以适当地增加它的更新量。 它的参数更新公式如下所示: 它的核心代码如下所示: def adagrad(x_start, step, g, delta=1e-8): x = np.array(x_start, dtype='float64') sum_grad = np.zeros_like(x) for i in range(50): grad = g(x) sum_grad += grad * grad x -= step * grad / (np.sqrt(sum_grad) + delta) if abs(sum(grad)) < 1e-6: break; return x 我们直接来看看它的表现: res, x_arr = adagrad([5, 5], 1.3, g) contour(X,Y,Z, x_arr) 看上去和前面的算法没什么差别嘛…… Rmsprop 前面的Adagrad有一个很大的问题,那就是随着优化的进行,更新公式的分母项会变得越来越大。所以理论上更新量会越来越小,下面的算法Rmsprop就试图解决这个问题。在它的算法中,分母的梯度平方和不再随优化而递增,而是做加权平均: 它的代码如下所示: def rmsprop(x_start, step, g, rms_decay = 0.9, delta=1e-8): x = np.array(x_start, dtype='float64') sum_grad = np.zeros_like(x) passing_dot = [x.copy()] for i in range(50): grad = g(x) sum_grad = rms_decay * sum_grad + (1 - rms_decay) * grad * grad x -= step * grad / (np.sqrt(sum_grad) + delta) passing_dot.append(x.copy()) if abs(sum(grad)) < 1e-6: break; return x, passing_dot 我们同样来看看它的表现: res, x_arr = rmsprop([5, 5], 0.3, g) contour(X,Y,Z, x_arr) 看上去和前面的表现还是差不多的。 AdaDelta Rmsprop已经很好地解决了一部分的问题,但是在AdaDelta的眼中似乎还不够。在介绍它的论文中,作者详细阐述了Adagrad算法中得到的参数更新量的“单位”已经不太对了,于是给出了一堆公式用以证明我们需要增加一些计算来使得计算单位恢复正常,于是就有了下面的公式: 具体的代码如下: def adadelta(x_start, step, g, momentum = 0.9, delta=1e-1): x = np.array(x_start, dtype='float64') sum_grad = np.zeros_like(x) sum_diff = np.zeros_like(x) passing_dot = [x.copy()] for i in range(50): grad = g(x) sum_grad = momentum * sum_grad + (1 - momentum) * grad * grad diff = np.sqrt((sum_diff + delta) / (sum_grad + delta)) * grad x -= step * diff sum_diff = momentum * sum_diff + (1 - momentum) * (diff * diff) passing_dot.append(x.copy()) if abs(sum(grad)) < 1e-6: break; return x, passing_dot 它的表现如下: |
|