1.引言
本文构建的全连接神经网络模型结构图如上。其中中间隐藏层的数量以及各层(输入层、隐藏层、输出层)的神经单元数量均可 自由设置,本文构造的神经网络并不是专门为识别手写数字而写死的,而是可以根据 任务的需要,自由改变神经网络的参数(如层数、神经单元数、学习率、学习率衰减值 等)。在本文里以识别手写数字为例,输出层的神经单元数为 9,结构图为了能够表示得更清晰(使其看 起来不混乱),遂只绘制了 3 个神经单元。
本文已将神经网络模型程序封装成类,神经网络的各参数为类中属性,神经网络的生成、训练、预测、保存、评估等为类中方法,以便可以更加快捷地调用,同时降低代码冗余、提高代码可读性。
本文作者:A WHU SIM Student
目录
1.引言
2.涉及的神经网络知识原理
2.1神经网络思想
2.2激活函数
2.2.1sigmoid激活函数
2.2.2softmax激活函数
2.3交叉熵损失
2.4小批量梯度下降法
2.5误差反向传播法
2.6手写数字数据集
2.7Python面向对象编程
3.详细计算推导
3.1各符号解释
3.2前向传播部分
3.3小批量梯度下降部分
3.4误差反向传播部分
4.代码实现
4.1本文神经网络UML类图如下:
4.2用到的第三方库有:
4.3模型包含的属性与方法部分代码如下:
4.4模型功能:
4.5模型训练与预测中的部分截图:
4.6包含6万条手写数据的数据集+本文章构建的全连接神经网络模型+训练好的准确率为93.21%的全连接神经网络模型:全连接神经网络
2.涉及的神经网络知识原理
2.1神经网络思想
人工神经网络(artificial neural network,ANN)是一种模仿生物神经网络的结构和功能的数学模型或计算模型。神经网络由大量的人工神经元联结进行计算。大多数情况下人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统。现代神经网络是一种非线性统计性数据建模工具,常用来对输入和输出间复杂的关系进行建模,或用来探索数据的模式。
生物神经网络的主要工作原理如下:
①神经元形成网络。
②对于从其他多个神经元传递过来的信号,如果它们的和不超过某个固定大小的值(阈值),则神经元不做出任何反应。
③对于从其他多个神经元传递过来的信号,如果它们的和超过某个固定大小的值(阈值),则神经元做出反应(称为点火),向另外的神经元传递固定强度的信号。
④在②和③中,从多个神经元传递过来的信号之和中,每个信号对应的权重不一样。
将神经元的工作在数学上抽象化,并以其为单位人工地形成网络,这样的人工网络就是神经网络。将构成大脑的神经元的集合体抽象为数学模型,这就是神经网络的出发点。
单个人工神经单元结构如下图:
多个神经单元组成的神经网络如下图:
2.2激活函数
激活函数对于ANN学习和理解真正复杂的东西很重要。它们的主要目的是将ANN中节点的输入信号转换为输出信号,此输出信号将作为下一层的输入。常用激活函数有:单位阶跃激活函数、Sigmoid激活函数、tanh双曲正切激活函数、ReLU整流线性单元激活函数、softmax激活函数。本文使用的是sigmoid(中间层使用)和softmax激活函数(输出层使用)。
2.2.1sigmoid激活函数
Sigmoid函数是一个有着优美S形曲线的数学函数,在逻辑回归、人工神经网络中有着广泛的应用。
公式:
导数:
图像:
优点:平滑、易于求导。
缺点:激活函数计算量大(在正向传播和反向传播中都包含幂运算和除法);Sigmoid导数取值范围是[0, 0.25],由于神经网络反向传播时的“链式反应”,很容易就会出现梯度消失的情况。例如对于一个10层的网络, 根据 0.25^10 ≈ 0.000000954,第10层的误差相对第一层卷积的参数 W1 的梯度将是一个非常小的值,这就是所谓的“梯度消失”;Sigmoid的输出不是0均值(即zero-centered);这会导致后一层的神经元将得到上一层输出的非0均值的信号作为输入,随着网络的加深,会改变数据的原始分布。
2.2.2softmax激活函数
softmax用于多分类过程中,它将多个神经元的输出,映射到(0,1)区间内,可以看成概率来理解,从而来进行多分类,其多用于神经网络的最后一次层。
公式:
导数:
对 softmax 导数,就是求第 i 项的输出对第 j 项输入的偏导:
当i=j时:
当i != j时:
优点:softmax 是用于多类分类问题的激活函数,在多类分类问题中,超过两个类标签则需要类成员关系。对于长度为 K 的任意实向量,softmax 可以将其压缩为长度为 K,值在(0,1)范围内,并且向量中元素的总和为 1 的实向量。
缺点:在零点不可微,负输入的梯度为零,这意味着对于该区域的激活,权重不会在反向传播期间更新,因此会产生永不激活的死亡神经元。
2.3交叉熵损失
交叉熵是信息论中的概念,最初用于估算平均编码长度。给定两个概率分布p和q,通过q来表示p的交叉熵为:
交叉熵损失(Cross-entropy cost)是用来衡量深度神经网络(DNN)预测的概率分布与实际概率分布的差异的一种方式。它刻画的是实际输出(概率)与期望输出(概率)的距离,即交叉熵损失的值越小,两个概率分布就越接近。与平方损失相比,它能更有效地促进训练全连接神经网络。若𝒚𝒊为标签值, 𝑦𝑖′为预值测,则交叉熵损失为:
2.4小批量梯度下降法
梯度下降法基于的思想是:要找到某函数的最小值,最好的方法是沿着该函数的梯度方向的反方向探寻。如果梯度记为 ∇,则函数 f(x,y)的梯度由下式表示:
这个梯度意味着要沿 x 的方向移动 -∂ f ( x,y ) /∂ x,沿 y 的方向移动- ∂ f ( x ,y )/ ∂ y。其中,函数 f(x,y)必须要在待计算的点上有定义并且可微。梯度下降算法到达每个点后都会重新估计移动的方向。从 P0 开始,计算完该点的梯度,函数就根据梯度移动到下一点 P1。在 P1 点,梯度再次被重新计算,并沿新的梯度方向移动到 P2。如此循环迭代,直到满足条件。迭代的过程中,梯度算子总是保证我们能选取到最佳的移动方向。偏导数确定了移动的方向,再由学习率α确定移动步长,用向量来表示的话,梯度下降算法的迭代公式如下:
小批量梯度下降(Mini-Batch Gradient Descent),是对全批量梯度下降以及随机梯度下降的一个折中办法。其思想是:每次迭代使用多个小批量的样本来对参数进行更新,每个小批量中含有“batchSize”个样本。其优点是每次使用一个小批量样本可以大大减小参数收敛所需要的迭代次数,同时可以使收敛到的结果更加接近梯度下降的效果。在一定范围内,一般来说“batchSize”越大,其确定的下降方向越准,引起训练震荡越小。
2.5误差反向传播法
误差反向传播算法(back propagation,简称BP模型)是1986年由Rumelhart和McClelland为首的科学家提出的概念,是一种按照误差逆向传播来训练多层前馈神经网络的算法。其系统的解决了多层神经网络隐含层连接权学习问题,人们把采用这种算法进行误差校正的多层前馈网络称为BP网。在实验过程中将会给出反向传播计算的详细过程。
2.6手写数字数据集
手写数字数据格式有两种形式:
①MNIST数据集,是由0到9的数字图像构成。其中训练图像有6万张,测试图像有1万张,手写数字由250个不同的人手写而成。每一张图片都有对应的标签数字,图像像素均为28*28。图像并不以图像文件形式存储,而是由28*28的数组表示,数组中的每个元素与每个像素相对应,通过matplotlib可以将数组表示的图像显示出来。用于训练与分类时,需要将28*28的数组转换成1*784的向量。
②另一个数字数据集是以文本形式保存的,每一个数字保存在一个txt文本中,数字通过只包含0与1的32*32的数组表示,如数字“4”的表示如下图:
本文使用的数据集为①中数据集。
2.7Python面向对象编程
面向对象(英文是Object Oriented,缩写为OO)面向对象是一种软件开发方法,一种编程方式。通常情况下,我们把对象分为两个部分:静态部分和动态部分。其中静态部分我们称为“属性”,任何的对象都有自己的属性,是客观存在的,如人的性别高矮胖瘦等属性;动态部分是指对象的行为,如人的走路,吃饭,睡觉等可以执行的动作行为。在Python中,类是封装对象的属性和行为的载体。封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而讲对象的属性和行为封装起来的载体就是类。封装就是隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别,将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。
本文将神经网络封装成类,神经网络的各参数为类中属性,神经网络的生成、训练、预测、保存、评估等为类中方法,以便可以更加快捷地调用,同时降低代码冗余、提高代码可读性。
3.详细计算推导
3.1各符号解释
3.2前向传播部分
3.3小批量梯度下降部分
3.4误差反向传播部分
4.代码实现
4.1本文神经网络UML类图如下:
4.2用到的第三方库有:
- import
- import
- import numpy as
- from matplotlib import pyplot as
- import pandas as
- import prettytable as pt # 用于绘制表格
- import pickle # 用于保存类实例
4.3模型包含的属性与方法部分代码如下:
class DNN:
def __init__(self): #初始化模型
self.a = {} # 用于存放各层各神经单元输出的字典
self.z = {} # 用于存放各层各神经单元输入的字典
self.w = {} # 用于存放各层之间w参数的字典
self.b = {} # 用于存放各层各神经单元偏置的字典
self.sigma = {} # 用于存放反向传播时各层各神经单元sigma值的字典
self.hiddenLayersNum = None # 用于记录隐藏层层数,便于计算出输出层的索引
# self.y = None # 用于保存经过softmax函数激活后的输出的numpy列向量
self.lr = 0.01 # 设置初始学习率,默认值为0.01
self.decay = 0 # 设置每轮迭代学习率衰减值,默认为0
self.wDeriv = {} # 记录 Li 对 w 的偏导的累加和
self.bDeriv = {} # 记录 Li 对 b 的偏导的累加和
self.parameterRecord = {} # 用于记录某些指定参数在每轮迭代后的值,便于绘制参数收敛曲线
self.y_Matrix = None # 待预测的样本的类别矩阵
self.y_predict_Matrix = [] # 用于保存模型预测结果的矩阵
self.y_DataFrame = None
self.y_predict_DataFrame = None
# 添加输入层的方法,参数layer为列表,其列表元素只有一个,用于指定输入层的神经单元个数
def addInputLayer(self, layer):
# 添加隐藏层的方法,参数layer为列表,其列表元素个数代表添加的隐藏层层数,每一个元素指定其对应的隐藏层所含的神经单元个数
def addHiddenLayer(self, layer):
# 添加输出层,参数layer为列表,其元素只有一个,代表输出层神经单元个数(即分类类别个数)
def addOutputLayer(self, layer):
# 用于查看各层详情
def summary(self):
# sigmoid函数,输入列向量,返回计算后的列向量
def sigmoid(self, vector):
# softmax激活函数,输入列向量,返回计算后的列向量
def softmax(self, vector):
# 前向传播,参数 X_vector 为某样本特征向量,返回 y_predict_vector 该样本预测类别向量
def forwardPropagation(self, X_vector):
# 计算多分类交叉熵损失
def categoricalCrossentropyLoss(self, y_vector,y_predict_vector):
# 反向传播, 参数为该样本真实类别标签 y_vector.shape = (1, 10) ,用于计算输出层的sigma值
def backPropagation(self, y_vector):
def judge(self, y_vector, y_predict_vector):
# 模型训练
'''
参数 X_Matrix 为训练样本特征值矩阵(每一行为一个样本)
y_Matrix为训练样本类别标签矩阵(每一行为一个样本)
epochs为迭代轮数(默认为20轮)
batchSize为使用小批量梯度下降算法时每个小批量的样本数目
parameterList 描述需要记录的参数,如[("w",2,4,3), ("b", 2, 1)] 表示需要记录 第1层第3个神经元到第2层第4个神经元的权重w 和 第2层第1个神经元的偏置b
'''
# ,,epochs为迭代轮数(默认为20轮),
def fit(self, X_Matrix, y_Matrix, epochs=20, batchSize=30, parameterList = None):
# 根据self.parameterRecord绘制参数收敛曲线的函数
def drawConvergenceCurve(self):
# 模型预测
def predict(self, X_Matrix):
# 模型评估
def modelEvaluate(self, y_Matrix):
# 初始化交叉熵损失
L = 0.0
# 初始化准确率
accuracy = 0
# 保存模型 实例方法
def saveModel(self, modelFileUrl):
f = open(modelFileUrl, 'wb')
@classmethod # 类方法,用于调用模型
def importModel(cls, modelFileUrl):
f = open(modelFileUrl, 'rb')
# 绘制手写数字
def drawDigits(self, X_vector, y_vector):
plt.imshow(X_vector.reshape(28, 28)) # cmap="gray"
@classmethod # 类方法,用于转换数据格式,输入的是pandas数组,返回numpy矩阵,以便于神经网络的计算
def dataFormatConversion(cls, X_DataFrame,y_DataFrame, classNum):
# 将y_DataFrame由列向量转为行向量,
4.4模型功能:
1.任意设置输入层、隐藏层、输出层中各层的神经单元个数;
2.任意设置隐藏层层数;任意设置学习率、学习率衰减值,从而满足不同的学习率衰减策略;
3.任意设置迭代轮数;
4.任意设置小批量中的样本数;
5.能够根据给定的数组还原手写数字图像;
6.能够绘制指定参数的迭代收敛曲线。
7.能够保存训练好的模型(保存在txt文件中),这样就可以只训练一次,下次要用时直接调用训练好的模型,而不需要再次训练。
4.5模型训练与预测中的部分截图:
经过一天的等待,最终模型对2万个训练数据的预测准确率为0.9321,交叉熵损失为1.5233。
绘制参数收敛曲线,其中 的500轮迭代的收敛曲线如下图:
模型绘制的数据集中的第一个数字“5”: