【python】三层BP神经网络推导MNIST优化效果对比损失函数对比

article/2025/8/23 19:54:23

一、概述

本文的推导参见西瓜书P102~P103,代码参见该网址。主要实现了利用三层神经网络进行手写数字的识别。

二、理论推导

1、参数定义

三层神经网络只有一层隐藏层。参数如下:

x输入层输入
v输入层与隐藏层间的权值
α隐藏层输入
b

                           隐藏层输出

                      w

隐藏层与输出层间的权值
\beta输出层输入
\hat y输出层输出

参数关系如下:

\alpha_h= \sum_{i=1}^{d}{v_{ih}x_i}

b_h=f(\alpha_h)

\beta_j=\sum^{q}_{h=1}{w_{hj}b_h}

\hat y=f(\beta_j)

上述等式中fx为激活函数。西瓜书默认激活函数为sigmoid,损失函数为均方根,本文以此为前提进行推导。

2、推导

设损失函数为E,则其公式如下:

E=\frac{1}{2}\sum^{l}_{j=1}({y_j-\hat y_j})^2

w_{hj}求偏导如下,这愚蠢的CSDN不支持多行公式编辑,所以只好手写了:

于是我们就得到了w_{hj}的更新公式:

\Delta w_{hj}=\eta(y_j-\hat y_j)\hat y_j(1-\hat y_j)b_h

同样的,对v_{ih}求偏导如下:

于是我们就得到了v_{ih}的更新公式:

\Delta v_{ih}=\eta x_i b_h(1-b_h)\sum^l_{j=1}((y_j-\hat y_j)\hat y_j(1-\hat y_j)w_{hj})

三、代码实现

优化方法选择SGD。

1、数据集初始化

MNIST数据集可以在TensorFlow中下载到:

import tensorflow.examples.tutorials.mnist.input_data as input_data
mnist = input_data.read_data_sets("./MNIST_data/", one_hot=True)

mnist对象中就存储着所有的数据,其中,mnist.train.images为50000*784的二维array;储存着训练集的输入,每一行储存着784个像素;mnist.train.labels为50000*10的二维array;储存着训练集的标记,每一行中为1的列对应着该行的label。

2、初始化神经网络类

class NeuralNet:def __init__(self,InputNum,HiddenNum,OutputNum,LearnRate):self.InNum=InputNum#输入层节点数self.HiNum=HiddenNum#隐藏层节点数self.OuNum=OutputNum#输出层节点数self.LeRate=LearnRate#学习率self.MatrixInputHidden=np.random.normal(0.0,pow(self.HiNum,-0.5),(self.HiNum,self.InNum))#输入层和隐藏层之间的矩阵,100*784self.MatrixHiddenOutput=np.random.normal(0.0,pow(self.OuNum,-0.5),(self.OuNum,self.HiNum))#隐藏层和输出层之间的矩阵,10*100self.activation_function=lambda x:1/(1+np.exp(-x))

由1可知,输入层节点数为784,隐藏层节点数暂定为100,输出层节点数为10,学习率暂定为10。

则输入层和隐藏层之间的系数矩阵MatrixInputHidden应该是一个100*784的矩阵。隐藏层和输出层之间的系数矩阵MatrixHiddenOutput应该是一个10*100的矩阵。将其初始化为均值为0的正态分布。

3、训练函数:

def train(self,TrainData,TargetData):#784*1;10*1HiddenZ=self.activation_function(np.dot(self.MatrixInputHidden,TrainData))#TrainData中,每列是一个样本,HiddenZ中,每列是一个样本的隐藏层输出OutputZ=self.activation_function(np.dot(self.MatrixHiddenOutput,HiddenZ))#OutputZ中,每列是一个样本的输出层输出ErrorOutput=TargetData-OutputZ#输出层误差ErrorHidden=np.dot(self.MatrixHiddenOutput.T,ErrorOutput)#隐藏层误差DeltaMatrixHiddenOutput=self.LeRate*np.dot((ErrorOutput*OutputZ*(1-OutputZ))[:, None],HiddenZ[None,:])DeltaMatrixInputHidden=self.LeRate*np.dot((ErrorHidden*HiddenZ*(1-HiddenZ))[:, None],TrainData[None,:])self.MatrixHiddenOutput+=DeltaMatrixHiddenOutputself.MatrixInputHidden+=DeltaMatrixInputHidden

训练函数的功能是参数优化。

输入一个784*1的向量和一个10*1的向量,分别是一个样本的输入和标记,对输入向量进行计算可以得到一个预测向量。通过预测向量和标记的差值可以得到损失值,从而可以对参数进行修改。

这里较难的一点是将如下两个公式矩阵化:

\Delta w_{hj}=\eta(y_j-\hat y_j)\hat y_j(1-\hat y_j)b_h

\Delta v_{ih}=\eta x_i b_h(1-b_h)\sum^l_{j=1}((y_j-\hat y_j)\hat y_j(1-\hat y_j)w_{hj})

因为参数是以矩阵的形式存储的,所以,对参数的更新也应该以矩阵的形式。也就是说,我们需要计算出\Delta w_{hj}矩阵和\Delta v_{ih}矩阵。首先计算前者。

\Delta w_{hj}矩阵应该是一个10*100的矩阵,我们观察公式可知,y_j对应一个y向量,\hat y_j对应一个\hat y向量,都是10*1的向量;b_h对应b向量,是100*1的向量——所以我们要得到一个10*100的向量,需要用y向量乘以b向量的转置,即10*1的向量乘以1*100的向量,即可得到10*100的矩阵。

这对应如下一行代码:

ErrorOutput=TargetData-OutputZ#输出层误差
DeltaMatrixHiddenOutput=self.LeRate*np.dot((ErrorOutput*OutputZ*(1-OutputZ))[:, None],HiddenZ[None,:])

注意,向量在python是没有转置的,将其转置需要用[None,:]来进行。

然后计算后者。

\Delta v_{ih}矩阵应该是一个100*784的矩阵,我们观察公式可知,x_i对应一个x向量,是784*1的向量;b_h(1-b_h)是对应元素相乘,是一个100*1的向量;y_j对应一个y向量,\hat y_j对应一个\hat y向量,都是10*1的向量;w_{hj}对应w矩阵,是一个10*100的矩阵。这就有点复杂了——向量乘矩阵,如何安排其顺序才能得到正确的结果呢?

先看累加部分,将其向量化后应该是w在前,y在后;又由于w是一个10*100的矩阵,与10*1不能直接相乘,因此要将其转置,结果是一个100*1的向量。

现在化简了一部分,相乘的矩阵减少为三个:x:784*1;b:100*1;求和部分:100*1。

很明显了,要得到一个100*784的矩阵,需要100*1的向量乘以一个1*784的向量。

我们观察这两个公式的向量化,都是一个向量a乘以一个向量b的转置。其中,向量b的转置对应前一层的输出,向量a对应什么不容易看出来。

不如重新写一下上面的公式吧:

\Delta w_{hj}=\eta b_h\hat y_j(1-\hat y_j)(y_j-\hat y_j)

\Delta v_{ih}=\eta x_i b_h(1-b_h)\sum^l_{j=1}((y_j-\hat y_j)\hat y_j(1-\hat y_j)w_{hj})

上下对应一下:上面的b_h对应下面的x_i,上面的\hat y_j(1-\hat y_j)对应下面的b_h(1-b_h);因为其实际意义相同,b_hx_i都是前一层的输出,\hat y_jb_h都是后一层的输出。那么问题来了:(y_j-\hat y_j)\sum^l_{j=1}((y_j-\hat y_j)\hat y_j(1-\hat y_j)w_{hj})对应,这俩玩意实际意义有什么相同的?

(y_j-\hat y_j)可以看做是输出层的误差,\sum^l_{j=1}((y_j-\hat y_j)\hat y_j(1-\hat y_j)w_{hj})也就可以视作是隐藏层的误差。我们将神经网络视为是无方向的——那么将原来的输出层作为输入层,原来的输出误差视作输入,那么这个输入在隐藏层上的输出就可以看做是隐藏层的误差。当然要作为输入还需要一些处理:需要“输入*激活函数的导数*系数”从而得到隐藏层的误差。

这也是反向传播的名字由来,反向传播,传播的就是误差。这对应以下两行代码:

ErrorHidden=np.dot(self.MatrixHiddenOutput.T,ErrorOutput*OutputZ*(1-OutputZ))#隐藏层误差
DeltaMatrixInputHidden=self.LeRate*np.dot((ErrorHidden*HiddenZ*(1-HiddenZ))[:, None],TrainData[None,:])

4、预测函数

预测函数就是神经网络实际使用的时候调用的函数,给一个输入,返回一个输出,实际上就从训练函数里面摘出来两行就好了:

def query(self,TrainData):HiddenZ=self.activation_function(np.dot(self.MatrixInputHidden,TrainData))#TrainData中,每列是一个样本,HiddenZ中,每列是一个样本的隐藏层输出OutputZ=self.activation_function(np.dot(self.MatrixHiddenOutput,HiddenZ))#OutputZ中,每列是一个样本的输出层输出return OutputZ

5、主函数

if __name__ == "__main__":mnist = input_data.read_data_sets("./MNIST_data/", one_hot=True)InputNum=mnist.train.images.shape[1]HiddenNum=100OutputNum=mnist.train.labels.shape[1]LearnRate=0.2epoch=50nn=NeuralNet(InputNum,HiddenNum,OutputNum,LearnRate)accu=[]for e in range(epoch):for i in range(mnist.train.images.shape[0]):nn.train(mnist.train.images[i].T,mnist.train.labels[i].T)print("已训练"+str(e+1)+"次")num=0for i in range(mnist.test.images.shape[0]):res=nn.query(mnist.test.images[i].T)label=np.argmax(res)correct_label=np.argmax(mnist.test.labels[i])if(label==correct_label):num=num+1print("第"+str(e+1)+"次训练后预测正确率为:"+str(num/mnist.test.images.shape[0]))accu.append(num/mnist.test.images.shape[0])plt.figure() #创建绘图对象  plt.plot(accu,"b--",linewidth=1) #在当前绘图对象绘图(X轴,Y轴,蓝色虚线,线宽度)  plt.xlabel("epoch") #X轴标签  plt.ylabel("Accuracy")  #Y轴标签  plt.show()

我们先给神经网络初始化——三层节点数、学习率和迭代优化步数,然后进行训练。

由于我们使用了SGD,即Stochastic Gradient Descent,随机梯度下降,每次选择一个样本计算误差,然后进行优化,这样总计50000个样本,一次迭代可以优化50000次。每次输入一个向量即可。

效果如下:

可以看出,在第一次的50000个样本优化之后,总的准确率已经达到了0.9463,在之后准确率不断上升,但上升速度不断变慢。由于SGD并不会每次选择最优的梯度进行优化,因此可能出现准确率变少的情况,但是总体趋势仍是向上的。

四、不同的优化方法对比

在三中,我们选择使用SGD,CostFunction选择使用均方根误差(Quadratic cost)。接下来我们将选择batch GD、mini batchGD进行优化。同时选择交叉熵函数(Cross-entropy cost)作为对比。

1、SGD改为batchGD

这俩优化方法的区别很明显:

SGD是选择一个样本,求误差,对这个误差求偏导,更新参数;batchGD则是选择所有样本,求误差和,对误差和求偏导,然后除以总的样本数量,更新参数。更改后的train函数代码如下:

def train(self,TrainData,TargetData):#784*50000;10*50000;Batch GDHiddenZ=self.activation_function(np.dot(self.MatrixInputHidden,TrainData))#100*50000OutputZ=self.activation_function(np.dot(self.MatrixHiddenOutput,HiddenZ))#10*50000ErrorOutput=TargetData-OutputZ#输出层误差,10*50000ErrorHidden=np.dot(self.MatrixHiddenOutput.T,ErrorOutput*OutputZ*(1-OutputZ))#隐藏层误差,100*50000DeltaMatrixHiddenOutput=self.LeRate*(np.dot((ErrorOutput*OutputZ*(1-OutputZ)),HiddenZ.T)/TrainData.shape[1])DeltaMatrixInputHidden=self.LeRate*(np.dot((ErrorHidden*HiddenZ*(1-HiddenZ)),TrainData.T)/TrainData.shape[1])self.MatrixHiddenOutput+=DeltaMatrixHiddenOutputself.MatrixInputHidden+=DeltaMatrixInputHidden

主函数调用如下:

if __name__ == "__main__":mnist = input_data.read_data_sets("./MNIST_data/", one_hot=True)InputNum=mnist.train.images.shape[1]HiddenNum=100OutputNum=mnist.train.labels.shape[1]LearnRate=0.2epoch=500nn=NeuralNet(InputNum,HiddenNum,OutputNum,LearnRate)accu=[]for e in range(epoch):nn.train(mnist.train.images.T,mnist.train.labels.T)print("已训练"+str(e+1)+"次")num=0for i in range(mnist.test.images.shape[0]):res=nn.query(mnist.test.images[i].T)label=np.argmax(res)correct_label=np.argmax(mnist.test.labels[i])if(label==correct_label):num=num+1print("第"+str(e+1)+"次训练后预测正确率为:"+str(num/mnist.test.images.shape[0]))accu.append(num/mnist.test.images.shape[0])plt.figure() #创建绘图对象  plt.plot(accu,"b--",linewidth=1) #在当前绘图对象绘图(X轴,Y轴,蓝色虚线,线宽度)  plt.xlabel("epoch") #X轴标签  plt.ylabel("Accuracy")  #Y轴标签  plt.show()

效果如下:

由此,batchGD和SGD的区别在于:batchGD每次迭代的方向都是对的,就是幅度可能有区别;SGD的方向可能错误,幅度也有区别。由于用的是我自己的小破电脑跑的,因此只迭代了五百次,最终准确率为85.64%。与SGD的97.7%还有较大差距。但是想到SGD即使只迭代一次也优化了50000次,而batchGD只优化了500次,这个准确率也是可以接受的。

2、SGD改为mini batch GD

mini batch GD和batchGD使用的train函数是一样的,只不过是每次训练使用的样本数量不同,后者使用所有50000个样本,前者我选择使用50个样本。调用函数如下:

if __name__ == "__main__":mnist = input_data.read_data_sets("./MNIST_data/", one_hot=True)InputNum=mnist.train.images.shape[1]HiddenNum=100OutputNum=mnist.train.labels.shape[1]LearnRate=0.2epoch=100nn=NeuralNet(InputNum,HiddenNum,OutputNum,LearnRate)accu=[]for e in range(epoch):for i in range(0,mnist.train.images.shape[0],50):nn.train(mnist.train.images[i:i+50].T,mnist.train.labels[i:i+50].T)print("已训练"+str(e+1)+"次")num=0for i in range(mnist.test.images.shape[0]):res=nn.query(mnist.test.images[i].T)label=np.argmax(res)correct_label=np.argmax(mnist.test.labels[i])if(label==correct_label):num=num+1print("第"+str(e+1)+"次训练后预测正确率为:"+str(num/mnist.test.images.shape[0]))accu.append(num/mnist.test.images.shape[0])plt.figure() #创建绘图对象  plt.plot(accu,"b--",linewidth=1) #在当前绘图对象绘图(X轴,Y轴,蓝色虚线,线宽度)  plt.xlabel("epoch") #X轴标签  plt.ylabel("Accuracy")  #Y轴标签  plt.show()

效果如下:

效果好的出奇——没有方向相反的情况,一直是增加,而且仅100次优化离SGD的最佳值差距已很小。我不禁怀疑我的batchGD是不是写错了。这被另外俩完爆。你说这个batchGD,它用内存有多,计算又慢,唯一的优点是收敛稳定,这优点人家minibatchGD也有,还比你用内存少,还比你快。人比人气死人。

五、不同的CostFunction对比

这里我们选择使用Cross-entropy cost,对于多分类问题,其公式如下:

E=-\frac{1}{n}\sum_{x}\sum_{j}({y_jln\hat y_j}+(1-y_j)ln(1-\hat y_j))^2

w_{hj}求偏导如下:

于是我们可以得到更新的公式如下:

\Delta w_{hj}=-\eta(y_j-\hat y_j)b_h

同理,对v_{ih}求偏导如下:

于是我们可以得到更新公式如下:

\Delta v_{ih}=-\eta x_i b_h(1-b_h)\sum^l_{j=1}((y_j-\hat y_j)w_{hj})

可以看出,交叉熵函数就是比均方根函数少了一个系数罢了。因此train函数也很容易更改:

def train(self,TrainData,TargetData):#784*1;10*1;SGD;CrossEntropyHiddenZ=self.activation_function(np.dot(self.MatrixInputHidden,TrainData))#TrainData中,每列是一个样本,HiddenZ中,每列是一个样本的隐藏层输出OutputZ=self.activation_function(np.dot(self.MatrixHiddenOutput,HiddenZ))#OutputZ中,每列是一个样本的输出层输出ErrorOutput=TargetData-OutputZ#输出层误差ErrorHidden=np.dot(self.MatrixHiddenOutput.T,ErrorOutput)#隐藏层误差DeltaMatrixHiddenOutput=self.LeRate*np.dot((ErrorOutput)[:, None],HiddenZ[None,:])DeltaMatrixInputHidden=self.LeRate*np.dot((ErrorHidden*HiddenZ*(1-HiddenZ))[:, None],TrainData[None,:])self.MatrixHiddenOutput+=DeltaMatrixHiddenOutputself.MatrixInputHidden+=DeltaMatrixInputHidden

效果如下:

与均方根误差相比,交叉熵的特点为波动更大——恕我直言,我也就看出来这个了。

六、总结

本文实现了使用三层BP网络对手写数字进行识别。并实现了三种不同的优化方法和两种不同的损失函数。

PS:这篇文章对于矩阵求导的讲解很不错,与我自己将公式矩阵化的思路类似。

 

 

 


http://chatgpt.dhexx.cn/article/hUsQh8SC.shtml

相关文章

BP神经网络隐藏层的作用,bp神经网络输出层函数

matlab bp神经网络只能输入一组预测一组吗 可以用前面很多数据建立网络后预测下几组数据吗 当然可以,这就是输入模式和输出模式的制定,可以自由设置,多输入多输出。但要注意如果预测多组,输入必须要提供足够的信息拥有足够多维。…

作业2:BP 算法实验报告

作业2:BP 算法实验报告 1. 算法介绍 BP 算法全称叫做误差反向传播(error Back Propagation, 或者叫作误差逆传播)算法。现实任务中使用神经网络时,大多是在是使用 BP 算法进行训练。BP 算法不仅可以用户多层前馈神经…

企业三层架构、冗余、STP生成树协议总结

总结 1.企业三层架构 2. 冗余(线路冗余+设备冗余) 3. STP生成树协议:IEEE802.1D,PVST+,802.1W,RSTP(rpvst),802.1S(MST) 企业三层架构(内网结构) 接入层: 常使用二层交换机,就近提供接口密度,用于用户的接入 汇聚层(分布层): 集合所有接入层流量(星型),…

利用BP神经网络 设计一个三层神经网络解决手写数字的识别问题

文章目录 1. 题目描述2. 求解原理(1)算法模型(2)算法原理 3.编程实现(1)环境说明(2)实验方案(3)Python实现 1. 题目描述 设计一个三层神经网络解决手写数字的…

模式识别八--三层BP神经网络的设计与实现

文章转自:http://www.kancloud.cn/digest/prandmethod/102850 本文的目的是学习和掌握BP神经网络的原理及其学习算法。在MATLAB平台上编程构造一个3-3-1型的singmoid人工神经网络,并使用随机反向传播算法和成批反向传播算法来训练这个网络&#xff0c…

多层前馈神经网络及BP算法

一.多层前馈神经网络 首先说下多层前馈神经网络,BP算法,BP神经网络之间的关系。多层前馈[multilayer feed-forward]神经网络由一个输入层、一个或多个隐藏层和一个输出层组成,后向传播[BP]算法在多层前馈神经网络上面进行学习,采用…

bp神经网络的拓扑结构,bp神经网络模型结构图

试画出BP神经网络结构输入层3节点,隐层5节点,输出层2节点 BP(BackPropagation)神经网络是1986年由Rumelhart和McCelland为首的科学家小组提出,是一种按误差逆传播算法训练的多层前馈网络,是目前应用最广泛的神经网络模型之一。 …

人工神经网络的拓扑结构,三层神经网络结构图

人工神经网络的基本组成是什么啊 谷歌人工智能写作项目:小发猫 神经网络BP模型 一、BP模型概述误差逆传播(ErrorBack-Propagation)神经网络模型简称为BP(Back-Propagation)网络模型常见的神经网络结构。 PallWerbas博士于1974年在他的博士论文中提出了误差逆传播…

模式识别:三层BP神经网络的设计与实现

本文的目的是学习和掌握BP神经网络的原理及其学习算法。在MATLAB平台上编程构造一个3-3-1型的singmoid人工神经网络,并使用随机反向传播算法和成批反向传播算法来训练这个网络,这里设置不同的初始权值,研究算法的学习曲线和训练误差。有了以上…

数学建模常用模型21:BP神经网络(三层)

给大家安利一款朋友开发的自研国产数据分析基础工具,一键式自动分析,自动生成分析模板,5分钟掌握主流61个统计类数学模型(几乎涵盖SPSS绝大部分功能),以及23个有监督机器学习(包括随机森林&…

三层神经网络的BP(BackPropagation)算法推导

BP算法: 利用的是sigmoid函数来搭建的一个三层神经网络。假设其拓扑结构如图1-1所示。 图1-1 三层神经网络 该网络的运行流程为:当输入一个样例后,获得该样例的特征向量,再根据权向量得到感知机的输入值,然后使用si…

多层神经网络BP算法解释

# 多层神经网络BP算法解释 ## 前向传播 *** * 该项目采用反向传播算法描述了多层神经网络的教学过程。 为了说明这个过程,使用了具有两个输入和一个输出的三层神经网络,如下图所示: 每个神经元由两个单元组成。 第一单元添加权重系数和输入信号的乘积。 …

python实现神经网络_Python实现的三层BP神经网络算法示例

本文实例讲述了Python实现的三层BP神经网络算法。分享给大家供大家参考,具体如下: 这是一个非常漂亮的三层反向传播神经网络的python实现,下一步我准备试着将其修改为多层BP神经网络。 下面是运行演示函数的截图,你会发现预测的…

BP神经网络(3层)

终于放假啦,于是就想着把前一段时间学习的知识给整理一下,希望能够对小伙伴们有所帮助。 数学原理 BP(Back Propagation)(Rummelhart D, McClelland J.,1986)神经网络是一种按照误差逆向传播算法训练的多层前馈神经网络。 三层BP神经网络的组成主要…

基于三层BP神经网络的人脸识别

实验四、基于三层BP神经网络的人脸识别 一、 实验要求 采用三层前馈BP神经网络实现标准人脸YALE数据库的识别,编程语言为C系列语言。 二、BP神经网络的结构和学习算法 实验中建议采用如下最简单的三层BP神经网络,输入层为 ,有n个神经元节点&a…

神经网络笔记1-三层BP神经网络

神经网络笔记1-三层BP神经网络 神经网络性质简介信息正向传输预期神经网络的获得误差反向更新(输出层→隐藏层)误差反向更新(隐藏层→输入层) 伪代码实现训练函数测试函数,用训练好的神经网络预测 手写数字识别举例识别…

BP神经网络的梯度公式推导(三层结构)

本站原创文章,转载请说明来自《老饼讲解-BP神经网络》bp.bbbdata.com 目录 一. 推导目标 1.1 梯度公式目标 1.2 本文梯度公式目标 二. 网络表达式梳理 2.1 梳理三层BP神经网络的网络表达式 三. 三层BP神经网络梯度推导过程 3.1 简化推导目标 3.2 输出层权重…

三层架构的搭建

目录 实验拓扑: 实验要求: 使用的技术有: ospf技术、NAT技术、静态、DHCP、VRRP 、STP、 VLAN、TRUNK 、 access接口模式、 以太网中继Eth-Trunk(华为) 技术、vlan管理技术 实验思路: 实验过程: IP地址的配置&am…

tfs路径映射已存在

环境:VS2019、Win10 最近公司服务器更换,想要将tfs项目映射到原来的工作区,提示“tfs-路径 XXX 已在工作区 XXX;XXX 中映射”,通过网络查找资料得知,需要修改Team Foundation的配置文件: 1.找到目录&#x…

TFS2012 权限设置

环境: TFS2012 win7 1.安装好TFS 2.创建系统用户组: TFSAdmins、TFSDevs、TFSUsers 分别为TFS管理人员组、TFS开发人员组、TFS普通用户组。 如下图: 这样我们在TFS的权限设置里,都只针对这三个组设权限,不会具体到…