至于实现BP的理论和推导,cjwdeq同学已经讲的非常清楚了。既然答题组的大大们总说要发扬“左手代码,右手公式”的精神,我就结合caffe的源码讲讲具体反向传播是怎么实现的。先从简单的全连接层入手:
打开Inner_product_layer.cpp,里面的Backward_cpu函数实现了反向传播的过程。(如果使用的是GPU,则会调用Inner_product_layer.cu文件里的Backward_gpu函数,实现过程是类似的)
先通过LayerSetUp函数明确几个变量:
N_ = num_output
K_ = bottom[0]->count(axis)
M_ = bottom[0]->count(0, axis)
N_表示输出的特征维数,即输出的神经元的个数
K_表示输入的样本的特征维数,即输入的神经元的个数
M_表示样本个数
因此全连接层的轮樱W维数就是N_×K_,b维数就是N_×1
weight_shape[0] = N_
weight_shape[1] = K_
vector<历握int>bias_shape(1, N_)
this->blobs_[1].reset(new Blob<Dtype>(bias_shape))
下面一行一行看Backward_cpu函数的代码,整个更新过程大概可以分成三步:(顺便盗几个cjwdeq同学贴的公式,哈哈)
1.
caffe_cpu_gemm<Dtype>(CblasTrans, CblasNoTrans, N_, K_, M_, (Dtype)1.,
top_diff, bottom_data, (Dtype)0., this->blobs_[0]->mutable_cpu_diff())
这一句是为了计算dW,对应公式就是
1.jpg
其中的bottom_data对应的是a,即输入的神经元激活值,维数为K_×N_,top_diff对应的是delta,维数是M_×N_,而caffe_cpu_gemm函数是对blas中的函数进行封装,实现了一个N_×M_的矩阵与一个M_×K_的矩阵相乘(注意此处乘肢桐庆之前对top_diff进行了转置)。相乘得到的结果保存于blobs_[0]->mutable_cpu_diff(),对应dW。
2.
caffe_cpu_gemv<Dtype>(CblasTrans, M_, N_, (Dtype)1., top_diff,
bias_multiplier_.cpu_data(), (Dtype)0.,
this->blobs_[1]->mutable_cpu_diff())
这一句是为了计算db,对应公式为
2.jpg
caffe_cpu_gemv函数实现了一个M_×N_的矩阵与N_×1的向量进行乘积,其实主要实现的是对delta进行了一下转置,就得到了db的值,保存于blobs_[1]->mutable_cpu_diff()中。此处的与bias_multiplier_.cpu_data()相乘是实现对M_个样本求和,bias_multiplier_.cpu_data()是全1向量,从公式上看应该是取平均的,但是从loss传过来时已经取过平均了,此处直接求和即可。(感谢@孙琳钧和@辛淼同学的提醒)
3.
caffe_cpu_gemm<Dtype>(CblasNoTrans, CblasNoTrans, M_, K_, N_, (Dtype)1.,
top_diff, this->blobs_[0]->cpu_data(), (Dtype)0.,
bottom[0]->mutable_cpu_diff())
这一句是为了利用后面层传过来的delta_l+1计算本层的delta_l,对应公式为
3.jpg
主要Inner_product层里面并没有激活函数,因此没有乘f’,与f’的相乘写在ReLU层的Backward函数里了,因此这一句里只有W和delta_l+1相乘。blobs_[0]->cpu_data()对应W,维度是N_×K_,bottom[0]->mutable_cpu_diff()是本层的delta_l,维度是M_×K_。
写了这么多,Backward_cpu函数终于结束了。但是更新其实没结束,我最初看源码时就觉得奇怪,因为Backward_cpu函数里只计算了dW,db,delta,并没有对W和b进行更新呀?后来才发现,其实caffe里的反向传播过程只是计算每层的梯度的导,把所有层都计算完之后,在solver.cpp里面统一对整个网络进行了更新。具体是在step函数里先通过ComputeUpdateValue把learning rate、momentum、weight_decay什么的都算好,然后调用了Net.cpp的update函数逐层更新,对应公式就是:
4.jpg
虽然每个人工神经元很简单,但是只要把多个人工
神经元按一定方式连接起来就构成了一个能处理复杂信息的神经网络。采用BP算法的多层前馈网络是目前应用最广泛的神经网络,称之为BP神经网络。它的最大功能就是能映射复杂的非线性函数关系。
对于已知的模型空间和数据空间,我们知道某个模型和他对应的数据,但是无法写出它们之间的函数关系式,但是如果有大量的一一对应的模型和数据样本集合,利用BP神经网络可以模拟(映射)它们之间的函数关系。
一个三层BP网络如图8.11所示,分为输入层、隐层、输出层。它是最常用的BP网络。理论分析证明三层网络已经能够表达任意复杂的连续函数关系了。只有在映射不连续函数时(如锯齿波)才需要两个隐层[8]。
图8.11中,X=(x1,…,xi,…,xn)T为输入向量,如加入x0=-1,可以为隐层神经元引入阀值隐层输出向量为:Y=(y1,…,yi,…,ym)T,如加入y0=-1,可以为输出层神经元引入阀值输出层输出向量为:O=(o1,…,oi,…,ol)T输入层到隐层之间的权值矩阵用V表示,V=(V1,…,Vj,…,Vl)T,其中列向量Vj表示隐层第j个神经元的权值向量隐层到输出层之间的权值矩阵用W表示,W=(W1,…,Wk,…,Wl)T,
其中列向量Wk表示输出层第k个神经元的权值向量。
图8.11 三层BP网络[8]
BP算法的基本思想是:预先给定一一对应的输入输出样本集。学习过程由信号的正向传播与误差的反向传播两个过程组成。正向传播时,输入样本从输入层传入,经过各隐层逐层处理后,传向输出层。若输出层的实际输出与期望的输出(教师信号)不符,则转入误差的反向传播。将输出误差以某种形式通过隐层向输入层逐层反传,并将误差分摊给各层的所有神经元,获得各层的误差信号,用它们可以对各层的神经元的权值进行调整(关于如镇岁何修改权值参见韩立群著作御兆睁[8]),循环不断地利用输入输出样本集进行权值调整,以使所有输入样本的输出误差都减小到满意的精度。这个过程就称为网络的学习训练过程。当网络训练完毕后,它相当于映射(表达)了输入输出样本之间的函数关系。
在地球物理勘探中,正演过程可以表示为如下函数:
d=f(m) (8.31)
它的反函数为
m=f-1(d) (8.32)
如果能够获得这个反函数,那么就解决了反演问题。一般来说,难以写出这个反函数,但是我们可以用BP神经网络来映射这个反函数m=f-1(d)。对于地球物理反问题,如果把观测数据当作输入数据,模型参数当作输出数据,事先在模型空间随机产生大量样本进行正演计算,获得对应的观测数据样本,利用它们对BP网络进行训练,则训练好的网络就相当于是地球物理数据方程的反函数。可以用它进行反演,输入观测数据,网络就会输出它所对应的模型。猜如
BP神经网络在能够进行反演之前需要进行学习训练。训练需要大量的样本,产生这些样本需要大量的正演计算,此外在学习训练过程也需要大量的时间。但是BP神经网络一旦训练完毕,在反演中的计算时间可以忽略。
要想使BP神经网络比较好地映射函数关系,需要有全面代表性的样本,但是由于模型空间的无限性,难以获得全面代表性的样本集合。用这样的样本训练出来的BP网络,只能反映样本所在的较小范围数据空间和较小范围模型空间的函数关系。对于超出它们的观测数据就无法正确反演。目前BP神经网络在一维反演有较多应用,在二维、三维反演应用较少,原因就是难以产生全面代表性的样本空间。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)