话不多说直接上参考书籍:http://neuralnetworksanddeeplearning.com/index.html。当然中文版的百度很容得到。

一、初学神经网络的体会

正如书中作者说的神经网络可以被称作最美的编程范式之一,神经网络将我们需要解决的复杂问题,比如手写字体分类,简化成一个个简单的步骤,而本人无需了解内部的具体结构参数变化等。关于神经网络已经有很多实用的库,使用这些库可以很快的解决问题。但是不满足于使用别人造好的轮子,也为了了解几年内都不会过时的原理,仅仅学习程序库是远远不够的,还是需要自己多花一花时间来了解了解轮子是怎么造出来的。在看这本书的过程中,有一些比较简单的数学推导需要自己去实现,既复习了已经落下很久的高数线代,也能更深入得了解模型背后的数学原理,这对于改进算法了解算法是至关重要的。

二、谈谈神经元

书中作者主要介绍了感知器和S型神经元(本质上就是大家常说的Logistic函数),这两个比较简单,与大家学过的与非门比较类似,不过本质区别在于我们可以设计学习算法来自动更新权重和偏置从而得到不同的输出,学习的本质在于自动更新权值和偏置从而达到令人满意的效果。

三、梯度下降算法和反向传播算法的数学推导

梯度下降算法

一般而言对于网络的输出的结果与实际偏差会选择最小均方值误差来衡量,当然在这里没必要去定义一个所谓的最小的阈值,因为对于分类问题来说误差当然越小越好,没有最好;而且目前来说实际应用的分类器的识别效果很难做到100%甚至是95%以上。这也设计到网络的优化问题,这里我们就定义输出层输出值a与期望输出y的代价函数如下:

训练神经网络的目的是不断减小代价函数C的值,我们将采用梯度下降算法(GD)来达到这个目的。

我们假定每个输入值权重为W1j,W2j,Wkj....输出的偏置为Bj.其中k表示上一层神经元的第k神经元的输出,j表示下一层神经元的第j个输出。

所谓梯度下降,就是找到我们所关心的变量的最快的下降方向,在本研究中我们关心的是寻求更小的代价函数。当然我们求解最小值不是直接一步就可以得到,而是需要设定比较合适的梯度下降的步长。关于步长选取也是一门学问,这里不多做深究。下面我们直接给出梯度下降的更新规则(η表示步长,在机器学习中我们称之为学习速率):

上面这个公式可能看起来很简单,但是在编程实现时可能会遇到比较多的困难,这一点在后面再谈。下面我们把重点放在计算梯度的计算复杂度上,这是应用梯度下降算法的主要挑战,试想如果我们对每个权重和偏置按照上述公式来计算偏导将是一大笔计算开销,例如在MNIST数据训练中输入数据是50000个手写字体图片,每个图片是一个28X28的值域为(0~1)灰度数据,需要我们设计一个输入层有784个神经元,输出层为10的神经网络,每个神经元有各自的权重和偏置,一次迭代总共需要重复50000次这样的操作,多次迭代计算量剧增。这样会使学习速度显著下降。

好在我们不需要在一次迭代中输入全部的50000组数据,而是可以随机的选取其中的训练数据,这就是随机梯度下降算法(SGD)。通过随机选取训练数据来计算代价函数的梯度从而实现神经元的权重和偏置快速更新。SGD的基本思想是我们可以每次随机的选取样本量为m的训练数据,假设m足够大,我们期望这些样本计算的到的代价函数的平均梯度等于整个训练数据代价函数的平均梯度,因此我们可以将更新规则演变如下:

这就是我们在本次实践中需要用的梯度下降算法,原理上也不难理解,主要是在代码实现上如何与神经网络的结构结合起来是一个难点,这是我在学习中遇到的比较大的困难。

反向传播算法

反向传播传播的是什么?答:传播的是误差。可以这么理解,从输入层到隐藏层再到输出层,神经元的输出不断前向传播,我们可以定义神经元的输出是代价函数,那么输出层的误差就是我们定义的代价函数。但是,各个隐藏层的误差是什么呢?我们能否通过输出层的误差反向推导出各个隐藏层的误差呢?答案是肯定的,因为我们可以让误差反向传播,就如同一个反过来的神经元,输入是第L+1层的误差,输出是第L层的误差,这样不断向前传播直到输入层,从而实现神经元权重和偏置的更新。

关于反向传播有以下四个公式:

关于公式符号的意义和前面两个公式的推导大家可以看书,值得注意的是这里的误差δ定义的是关于z的函数。书中没有给后面两个公式的推导,下面给出:

 

公式推导的基本原理是链式求导法则,然后引用中间变量a和z。

下面直接摘录书籍中关于算法的原理伪代码:

四、代码实现

首先谈谈遇到的问题:

①不同的随机训练训练集是否可以使用同一代价函数?

这里我们给出假设:代价函数可以被写成在每一个训练样本上的代价函数的均值。

②如何设计代码结构:

这里主要参照了作者给出的代码,然后对代码逻辑进行归纳。

大致的逻辑是:数据导入——>数据规整使其适应于神经网络输入输出结构——>随机选取训练集——>训练集梯度下降算法——>训练集反向传播算法——>输出训练效果并迭代

③关于作者给的代码遇到的问题。

作者给出的代码是基于python2.7的,这里需要在代码中做出微小的改动:

需要将cPickle包改成pickle包;

需将zip对象转换为list才能对数据进行循环(转为为可迭代对象);

将 print 'Epoch {0}: {1} / {2'.format(j, self.evaluate(test_data), n_test)改写成print('Epoch {0}:{1}/{2}'.format(i,self.evaluate(test_data),n_test));

class内部函数调用,需要手动在调用函数前加上self,例如self.sigmoid()等等等

 

可以看出,该算法准确率基本稳定在95%附近,效果还不错。最主要的是除此迭代就到了90%的准确率,说明收敛速度还是很理想的。第一轮仿造车轮结束。