## BP神经网络识别手写字体
### 导言
1. **问题描述:**
  本次实验所要解决的问题是使用人工神经网络实现识别手写字体。实验采用MINST手写字符集作为识别对象。其中60000张作为训练集,剩余10000张作为测试集。实验采用python语言进行编程,使用到一些python的第三方库。使用的神经网络模型为BP神经网络,这是一种按照误差逆向传播算法训练的多层前馈神经网络。而其逆向传播过程使用了小批量梯度下降法(MBGD)。本次实验中使用的是含隐藏层的784\*30\*10的网络模型,在此模型下,学习率为3的情况下,大概30次学习后可以取得95%的正确率。其余结果详解结果分析。
2. **背景介绍:**
  **1)识别手写字体**:字符识别是图像识别领域中的一个非常活跃的分支,一方面是由于问题本身的难度使之成为一个极具挑战性的课题,另一方面,是因为字符识别不是一门孤立的应用技术,其中包含了模式识别领域的其它分支都会遇到的一些基本的、共性的问题。也正是由于字符识别技术的飞速发展,才促使模式识别和图像分析发展成为一个成熟的科学领域。
  **2)人工神经网络**:人工神经网络是20世纪80 年代以来人工智能领域兴起的研究热点。它从信息处理角度对人脑神经元网络进行抽象, 建立某种简单模型,按不同的连接方式组成不同的网络。神经网络是一种运算模型,由大量的节点(或称神经元)之间相互联接构成。每个节点代表一种特定的输出函数,称为激励函数。每两个节点间的连接都代表一个对于通过该连接信号的加权值,称之为权重,这相当于人工神经网络的记忆。网络的输出则因网络的连接方式,权重值和激励函数的不同而不同。而网络自身通常都是对自然界某种算法或者函数的逼近,也可能是对一种逻辑策略的表达。人工神经网络是由大量处理单元互联组成的非线性、自适应信息处理系统。它是在现代神经科学研究成果的基础上提出的,试图通过模拟大脑神经网络处理、记忆信息的方式进行信息处理。
  **3)BP神经网络**:BP神经网络是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。BP神经网络模型拓扑结构包括输入层(input)、隐层(hide layer)和输出层(output layer)。
  **4)梯度下降法**:梯度下降法是一个最优化算法,通常也称为最速下降法。梯度下降法的计算过程就是沿梯度下降的方向求解极小值(也可以沿梯度上升方向求解极大值)。梯度下降法又有批量梯度下降法BGD、随机梯度下降法SGD、小批量梯度下降法MBGD。
### 实验过程
1. **算法理论部分**
  在本次实验中的神经网络,我们对节点的激励函数采用常用的**sigmoid函数**。这个函数如下所示:

  它的导数可求,且可以表示为如下所示:

  我们使用误差函数来描述正确结果与网络输出的差值,并对这个误差函数求偏导作为每一个神经网络中更新权重以及偏移量的依据。这里使用如下的误差函数:

  其中T为正确结果,O为神经网络输出的结果。
  假设BP神经网络模型如下所示:

  给定隐藏层或输出层的单元 j,单位j的净输入Ij为:

  wij是从上一层单元i到单元j的连接权重;,Oi是上一层单元i的输出,θj是j单元的偏置.
  给定单元j的输入Ij, 则单位j的输出Oj的公式如下,即使用sigmoid函数作为激励函数.

  为了调整权重 wjk,我们首先计算在E关于wjk的偏导数,这里可以使用求偏导的链式法则。然后利用这个值修正权重。

  同样的,如果要调整偏移量,也是计算误差函数E关于其的偏导数。
  最终,由计算可得,对于输出层的单元k,误差梯度可表示如下:

  隐藏层单元 j 的误差梯度为:

  而权重和偏移量的更新可表示如下:

  根据以上三个公式,即可反向传播误差,结合梯度下降算法,修正神经网络的权重和偏移量。
  对于梯度下降算法,导言中有所提及,这里我们使用的是小批量梯度下降算法(MBGD)。MBGD在每次更新参数时使用b个样本(b一般为10),求得这b个样本的误差平均值之后,更新一次权重和偏移量。它的伪代码表现如下:

2. **代码实现部分**
  代码实现部分的流程如下所示。

**Main函数部分:**
  Main函数如下所示。
```python
train_images = decode_idx3_ubyte(train_images_idx3_ubyte_file)
train_labels = decode_idx1_ubyte(train_labels_idx1_ubyte_file)
```
```python
test_images = decode_idx3_ubyte(test_images_idx3_ubyte_file)
test_labels = decode_idx1_ubyte(test_labels_idx1_ubyte_file)
trainingimages = [(im / 255).reshape(1, 784) for im in train_images] # 归一化
traininglabels = [vectorized_result(int(i)) for i in train_labels]
testimages = [(im / 255).reshape(1, 784) for im in test_images]
testlabels = [l for l in test_labels]
net = NueraLNet([28 * 28, 30, 10])
net.train_net(trainingimages, traininglabels, 30, 3, 10, testimages, testlabels)
net.save_training()
net.read_training()
net.test_net(testimages, testlabels)
print("end")
```
  首先是从字符集中读取出训练集和验证集的图片及其数字的数据,以list的形式存储。而对于每一个图片则是为numpy数组,对于每一个标签则为浮点数。由于读取出来的是像素的灰度值,我们需要将其归一化才能使用,并将其每一张图片重构为1\*784的数组。而对于训练集的标签,我们还需要为其每一个标签重新构造1\*10的数组,其中下标为标签值的地方值为1,原因是为了与我们神经网络10的输出节点相对应。读取字符集以及转化标签的函数均为自定义函数,由于与神经网络无关,这里不作解释。
  接着,我们声明了一个神经网络对象,这个对象的类是我们自定义的。初始化是一个列表,表明了每一层各有多少个节点�