📝个人主页🌹:Eternity._
🌹🌹期待您的关注 🌹🌹
文献参考
Learning representations by back-propagating errors
《Learning representations by back-propagating errors》这篇论文是神经网络和机器学习领域的开创性工作,由David E. Rumelhart, Geoffrey E. Hinton, 和 Ronald J. Williams于1986年发表。这篇论文的主要内容是介绍了反向传播算法(Backpropagation),这是一种用于训练多层神经网络的高效方法。
反向传播算法的核心思想是利用链式法则来计算神经网络中每个权重参数的梯度,即计算损失函数对每个权重的影响。这些梯度随后用于通过梯度下降法更新网络中的权重,目的是最小化网络的预测误差。
论文的主要贡献包括:
反向传播算法的描述:详细阐述了如何计算多层网络中每个权重的误差梯度。这个过程涉及到从输出层开始,逐层向后传播误差信号,直到达到输入层。
误差梯度的计算:论文解释了如何利用误差梯度来调整网络中的权重,以便减少网络输出和目标值之间的差异。
网络结构的讨论:论文讨论了不同类型的网络结构,包括前馈网络和反馈(递归)网络,并探讨了它们在不同任务中的应用。
学习表示的选择:论文讨论了学习表示的重要性,即网络如何通过学习输入数据的内在特征来提高性能。
实验结果:提供了使用反向传播算法训练网络的实验结果,展示了该算法在语音识别和手写字符识别等任务上的有效性。
反向传播算法的提出对深度学习的发展产生了深远的影响,它使得研究人员能够训练具有大量参数的复杂神经网络,这在之前是不可能的。这篇论文因此被认为是深度学习领域的里程碑之一。
概述
本文将细致阐述一个基础神经网络模型从输入到预测的全过程,包括其前向计算、误差回传以及参数调优等环节,并通过一个手写数字辨识的实例,分别运用纯Python编程和PyTorch框架来具体实践,旨在让读者深切体会到神经网络参数迭代优化的内在机制。
这些内容植根于神经网络与机器学习的核心理论——前向传播与反向传播,它们是当今绝大多数深度学习模型训练不可或缺的基石。通过这一系列的讲解与实践,我们将一同揭开深度学习模型训练的神秘面纱,助力大家更深入地把握这一领域的精髓。文章末尾,我们将提供完整的数据集、实现代码以及配套的PPT讲解材料。
引言
多层感知机(Multilayer Perceptron,简称MLP)是神经网络的一种。MLP是一种前馈神经网络,它包含一个或多个隐藏层,以及非线性激活函数,这使得MLP能够学习和模拟复杂的非线性关系。MLP是最基础也是最广泛研究的神经网络类型之一,本文将以一个MLP模型来展开。
MLP的结构通常如下:
输入层:接收外部输入数据。
隐藏层:一个或多个隐藏层,每层包含多个神经元。隐藏层负责从输入数据中提取特征并进行初步的非线性变换。
输出层:输出网络的预测结果,对于分类问题,输出层通常使用softmax激活函数进行多类分类。
MLP的训练过程通常包括以下几个步骤:
前向传播:输入数据通过网络,通过每个神经元的加权和和激活函数,最终得到输出。
计算损失:使用损失函数(如均方误差、交叉熵等)计算网络输出与真实标签之间的差异。
反向传播:根据损失函数的梯度,计算每一层的权重对损失的贡献,即梯度。
权重更新:使用梯度下降或其他优化算法(如Adam、RMSprop等)根据梯度更新网络的权重和偏置。
MLP在许多领域都有应用,包括图像识别、语音识别、自然语言处理、游戏AI等。随着深度学习的发展,MLP作为深度神经网络的基础,其结构和训练方法也在不断地被改进和优化。
实际上,几乎所有的深度学习模型中都会有MLP的身影,相当于深度学习模型的骨架,特别是在深度学习模型中最后一步,通常会接个MLP来使得输出的维度符合我们任务的需求,例如我们当前需要要对手写数字识别,那就是一个10分类问题,最后输出可以通过接一个MLP变成10维,每一维代表一个分类,从而顺利地使模型适配我们的任务。
本文所涉及的所有资源的获取方式:这里
神经网络公式推导
参数定义
假设我们有这么一个神经网络,由输入层、一层隐藏层、输出层构成:
(这里为了方便,不考虑偏置bias)
Simoid激活函数如下:
前向传播(forward)
首先,我们可以试着表示一下
y
1
y_1
y1
如模型图所示可以表示为:
那么我要表示 y j y_j yj呢?
其中j=1时,就是 y 1 y_1 y1的表示,j=m时,就是 y m y_m ym的表示。
同理我们可以得到:
o
k
o_k
ok表示输出层第k个神经元的预测值,这就是我们需要的输出。
至此,正向传播完毕。
反向传播(backward)
光顺着路径前行时,我们只能获取到模型给出的预估结果,而无法对模型内部的参数进行任何调整。换句话说,在模型正向运行的过程中,它的内部设置是保持不变的。
但是,当我们拿到了模型给出的预估结果,并且还拥有与之对应的实际值时,就能够利用这两者之间的差距来反向调整模型,使其更加精准。
那么,具体该如何操作呢?
首先,我们需要明确这个差距,也就是预估结果与实际值之间的差异程度,这个差异将作为我们调整模型参数的依据和指导。
为了量化这个差异,我们可以采用一个简单的方法:计算预估值与实际值之间差的平方,作为损失函数。这样,我们就能够清晰地知道模型在哪些方面存在不足,并据此对模型进行有针对性的优化。
注意,这里只是更新输出层第k个神经元所反馈的误差。
隐藏层和输出层的权重更新
首先根据已知如下:
输出层预测值
o
k
o_k
ok
激活函数Sigmoid
那我们可以试着展开一下
E
k
E_k
Ek
因为我们现在需要更新的是
w
j
k
w_{jk}
wjk,因此展开到
w
j
k
w_{jk}
wjk我们就能有一个比较形象的认识了。
根据梯度下降法可得,我们现在只需要求出
即可通过
来更新我们隐藏层和输出层的权重了。
那么如何计算呢?
直接求导可能有点混乱,利用复合函数求导的方法,我们可以根据链式法则将表达式展开如下:
接下来我们分别求出:
以及
就可以了。
我们先给出激活函数的导数推导过程:
就是使用复合函数除的求导法则进行求导。我们可以发现sigmoid函数求导之后还是挺好看的。
接下来就是计算两个导数即可。
首先:
一眼就能看出来了吧。
就是别忘了里面的-ok也要导,负号别漏了
然后是
这个可能会有点困难,但是仔细看看,发现还是很简单的。
首先
(链式求导法)
因此:
那么这个结果计算起来就比较简单了。
既然如此,将结果拼起来就是我们要求的结果了:
其中:
全是已知的,不就可以更新参数了嘛
因此,加个学习率这层权重更新推导就大功告成了。
输入层和隐藏层的权重更新
如果上面的推导看懂了,下面的推导就非常简单了,无非就是多展开一级,多求一次导数而已。
首先(前面已经推到过了)
那么我们可以将误差再展开一级(接着链导下去):
那么下面这个就非常直观了
同样的,我们也分别求出三次的导数,最后拼起来就行了。
至此分别求出来了,拼起来就是我们要的结果了:
通过观察,里面全是已知的变量
那么更新公式也就有了:
至此我们公式推导就完成了。
数据集介绍
实验数据就是mnist手写数据集
第一列为label,表示这个图片是什么数字
后面都为图片的像素值,表示图片的数据
模型的输入就是像素值,输出就是预测值,即通过像素预测出是什么数字。
核心代码
其中比较关键的就是那两个参数的更新公式。
隐藏层和输出层的权重更新:
输入层和隐藏层的权重更新:
数据集+python手写代码+pytorch代码+ppt都在附件里哦
运行结果
pytorch结果:
python手写结果:
总结
感觉从推导到代码实现也是一个反复的过程,从推导发现代码写错了,写不出代码了就要去看看推导的过程,这个过程让我对反向传播有了较全面的理解。
我们发现,手写代码运行时间要一分多钟而pytorch其实只要10s不到,毕竟框架,底层优化很多,用起来肯定用框架。
以及二者准确率有一些差距,可能是因为pytorch里使用了交叉熵损失函数,比较适合分类任务;手写的并没有分batch,而是所有数据直接更新参数,但是pytorch里分了batch,分batch能够使得模型训练速度加快(并行允许),也使得模型参数更新的比较平稳。
编程未来,从这里启航!解锁无限创意,让每一行代码都成为你通往成功的阶梯,帮助更多人欣赏与学习!
更多内容详见:这里