所有人都在说AI、CNN、RNN、GAN好像所有人都会TensorFlow、Torch、Keras、MxNet.....的时候我还在写这种东西,应该会被鄙视的吧。
绝大部分的机器学习算法最后都是最优化一个目标函数,而梯度下降算法是寻找一个函数局部最优解的有效方法。我做了个例子,用Softmax实现mnist上的分类器(手写数字识别),具体代码参见 GitHub .Softmax 的详细介绍可以参照 ufldl 使用交叉熵作为误差函数Softmax 的目标函数可以用下图中的公式表示。
其中$$m$$ 是训练样本数量,$$k$$ 是分类数,$$/theta$$ 是要求的参数。 我对误差函数直观的理解就是:模型对所有样本的预测结果与真实结果之间的差异的期望,预测和真实结果之间的差异用交叉熵来计算的。如果把负号放到求和符号的最里面,第二个求和符号计算的是交叉熵,第一个求和符号再乘以1/m则计算的是期望。如果换做是二次误差函数只是把预测和真实之间的差异的度量方式改了。
理论上来说误差越小越好(不考虑过拟合),如何求函数的最小值呢?首先想到的是直接求解,通常这是行不同的,对于计算机来说方便的一种方法就是遍历,我可以遍历所有的可能取值然后选最小的,在这里的$$/theta$$ 是一个784X10 的参数矩阵,要遍历所有可能的实数自然不行。
梯度下降算法给出了一种搜索方法,首先随机初始化$$/theta$$ 然后沿着梯度方向(求极大值)和梯度相反的方向(求极小值,也就是梯度下降方向)来寻找参数。那为何梯度方向是函数变化最大的方向呢?这要扯到方向导数了。对于三维空间来说,偏导数是函数沿坐标轴方向上的变化率,则方向导数是函数沿着任意方向的变化率。方向导数是可以通过偏导数和方向余弦计算的到的。
梯度是偏导数组成的向量,比如$$f(x,y,z)$$ 在点$$P(x_0, y_0, z_0)$$的梯度为向量$$(f_x(P) , f_y(P) , f_z(P))$$ 梯度本身是向量所以才有所谓的梯度下降或上升方向这种说法。从方向导数和梯度直接的关系可以推出梯度方向是函数增长最快的方向,其实这种问题可以不纠结,因为和代码实现关系不大。
知道了梯度反方向是函数下降最快的方向,所以可以每次都在参数上减去对应的偏导数。 如何计算偏导数呢?对上图中的$$J(/theta)$$ 求偏导数,根据求导法则我们可以把求导放到第一个求和符号的里面,或者直接把第一个求和符号拆开,整个等式的右边就会是很多加号相连的,所以代码实现的时候可以求每一个样本的那部分再加总。如过理解这个上面说的这几句,对于随机梯度下降,或只使用少量样本的批量梯度下降为什么work其实也相对比较容易了。(体会:深入理解公式是代码实现的前提)
上面知道求解最优问题的时候,如果使用梯度下降,需要计算函数的梯度,反向传播算法解释计算神经网络中误差函数梯度的一种方法。 在网络(这里指的是多次全连接网络)中上一层的输出是下一次的输入,导致这个求导过程及其复杂,聪明的人想出了一个求导方法即反向传播,至于怎么想出来的 Michael Nielsen 写的 书 里说**It's just a lot of hard work ** ,这里写的也参考这本说的 第二部分
反向传播的大体思路就是找到一个中间变量,通过链式法则来求解误差对参数的偏导数。书里总结了四个公式我觉得真的是太好了,书里证明了前两个公式,后两个看完应该也能证了。
公式里的$$ Z^l$$ 是第l 层激活函数的输入(就是wx+b之后的结果,这样应该比较清楚),$$/delta^l$$是误差函数对$$z^l$$的偏导。 第一个公式是误差函数对最后一层的z的偏导$$/Delta C$$是误差函数,对最后一层激活函数的输出的偏导,$$/odot$$后面部分是激活函数对z求导。
第二个公式是$$/delta^l$$ 在各层之间的递推关系 第三个和第四个公式是我们最终要计算的。怎么得到第三个和第四个公式其实是比较简单了,方法就是把误差函数C对w和b的求导拆成C对z 的导数乘z对w或b 的导数(连上法则)。
如果真的理解了,想想下面的问题。当网络使用不同的误差函数,或激活函数,会影响到公式的哪些地方?,代码实现上会有哪里差异?
误差函数的变化只会影响第一个公式,也就是最后一层误差的部分,激活函数的变化为影响第一和第二个公式 我读了很多对反向传播算法原理解释的文章,我发现搞懂这四个公式是对代码实现和原理理解最好的方法,当然你或许会有其他的方法。 另外作者的代码实现也非常的好,我也写了自己的版本,虽然看过作者的代码,自己重写的时候还是遇到很多问题,调了好久。