现在开始讲解反向传播的代码,如果对反向传播不太理解可以参考一下博客里面的讲解;MSE是真实值与预测值的差值的平方然后求和平均,通过平方的形式便于求导,所以常被用作线性回归的损失函数。
backward_network(net)函数
void backward_network(network *netp)
{
#ifdef GPU
if(netp->gpu_index >= 0){
backward_network_gpu(netp);
return;
}
#endif
network net = *netp;
int i;
network orig = net;
//这里与前向不同,是从最后一层传递到第一层
for(i = net.n-1; i >= 0; --i){
layer l = net.layers[i];
if(l.stopbackward) break;//若是不需要反向传播则不传播此层
if(i == 0){
net = orig;
}else{
layer prev = net.layers[i-1];//将前面一层赋给prev
net.input = prev.output;//令网络的输入指向此层前面一层的输出内存空间
//这里注意,因为delta是指针变量,对net.delta做修改,就相当与对prev层的delta做了修改
net.delta = prev.delta;//将网络层的delta指向前一层的delta
}
net.index = i;//获取此层的索引
l.backward(l, net);//进行反向传播
}
}
1.反向传播检测层backward_detection_layer
void backward_detection_layer(const detection_layer l, network net)
{
//给net.delta赋值,l.delta存放的是预测值与真实值的差,net.delta则是前一层的
axpy_cpu(l.batch*l.inputs, 1, l.delta, 1, net.delta, 1);
}
//axpy函数:y += a * x;将此层的误差传给上一层
void axpy_cpu(int N, float ALPHA, float *X, int INCX, float *Y, int INCY)
{
int i;
for(i = 0; i < N; ++i) Y[i*INCY] += ALPHA*X[i*INCX];//将检测层的误差依次传给上一层的delta,上一层在每一次接收反向传播误差时都会与自身的误差相加,这是为了攒够一个batch,然后更新权重,一个batch=sub*min_batch;
}
2、反向传播全连接层
void backward_connected_layer(layer l, network net)
{
// gradient_array()函数主要完成激活函数对加权输入的导数,并乘以之前得到的l.delta,得到当前层最终的l.delta(误差函数对加权输入的导数)
//就是将layer的输出图像,输入到相应的梯度下降算法(计算每一个元素对应激活函数的导数),最后将delta的每个元素乘以激活函数的导数,结果送到delta指向的内存中。
//不过这里选择的是linear类型y=x,求导后为常数1;与delta的每个元素相乘结果不变;求得公式里面的delta,最终求的权重误差只需要使用delta乘以前一层的输出即可,也就是此层的输入;
//之前delta中的值没有进行平方就是为了这里的计算方便,直接就是误差偏导了
gradient_array(l.output, l.outputs*l.batch, l.activation, l.delta);
/*
void gradient_array(const float *x, const int n, const ACTIVATION a, float *delta)
{
int i;
for(i = 0; i < n; ++i){
delta[i] *= gradient(x[i], a);//求得(truth-out)*(out的偏导)
}
}
*/
//全链接层没用到batch_normalize,这里不做介绍
if(l.batch_normalize){
backward_batchnorm_layer(l, net);
} else {
//计算当前全连接层的偏置的梯度值
// 误差函数对偏置的偏导数实际上就等于以上刚求完的l.delta, 因为一个batch中不只有一张图片,所有将进行效果叠加.
// 不同于卷积层每个卷积核采有一个偏置参数, 全连接层是每个输出元素就对应一个偏置参数,共有l.outputs个,
// 每次循环求完一张图片输出的偏置梯度值.
// 最终会把每一张图的偏置更新叠加,因此,最终l.bias_updates中每一个元素的值是batch中所有图片对应输出元素偏置更新值的叠加.
backward_bias(l.bias_updates, l.delta, l.batch, l.outputs, 1);
/*
void backward_bias(float *bias_updates, float *delta, int batch, int n, int size)
{
int i,b;
for(b = 0; b < batch; ++b){
for(i = 0; i < n; ++i){
bias_updates[i] += sum_array(delta+size*(i+b*n), size);
}
}
}
float sum_array(float *a, int n)
{
int i;
float sum = 0;
for(i = 0; i < n; ++i) sum += a[i];
return sum;
}
*/
}
int m = l.outputs;
int k = l.batch;
int n = l.inputs;
float *a = l.delta;
float *b = net.input;
float *c = l.weight_updates;
//获得这一层的权重误差并导入l.weight_updates中
gemm(1,0,m,n,k,1,a,m,b,n,1,c,n);
/*因为TA=1,TB=0,所以选择了gemm_tn
void gemm_tn(int M, int N, int K, float ALPHA,
float *A, int lda,
float *B, int ldb,
float *C, int ldc)
{
int i,j,k;
#pragma omp parallel for
for(i = 0; i < M; ++i){
for(k = 0; k < K; ++k){
register float A_PART = ALPHA*A[k*lda+i];
for(j = 0; j < N; ++j){
C[i*ldc+j] += A_PART*B[k*ldb+j];//这里就是将delta值与此层输出值的偏导进行相乘,因为根据加权公式求导结果就是上一层输出,也就是此层的输入,故这里就是与此层输入进行了相乘;
}
}
}
}
*/
m = l.batch;
k = l.outputs;
n = l.inputs;
a = l.delta;
b = l.weights;
c = net.delta;
//将此层的delta乘以此层的权重然后导入上一层的delta中
if(c) gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);
}
/*
void gemm_nn(int M, int N, int K, float ALPHA,
float *A, int lda,
float *B, int ldb,
float *C, int ldc)
{
int i,j,k;
#pragma omp parallel for
for(i = 0; i < M; ++i){
for(k = 0; k < K; ++k){
register float A_PART = ALPHA*A[i*lda+k];
for(j = 0; j < N; ++j){
//将此层delta值乘以此层的权重,然后导入上一层的delta中,就是下图第一个公式括号内容
//这里首先对上一层delta中的每个元素进行赋值,然后换一个E(out)继续遍历赋值累加,理解了公式很容易读懂;
C[i*ldc+j] += A_PART*B[k*ldb+j];
}
}
}
}
*/
3、反向传播dropout层
void backward_dropout_layer(dropout_layer l, network net)
{
int i;
if(!net.delta) return;//基本不会运行,没人会把他放在第一层后面
for(i = 0; i < l.batch * l.inputs; ++i){
float r = l.rand[i];//随机数
if(r < l.probability) net.delta[i] = 0;//随机将上一层的delta置为0
else net.delta[i] *= l.scale;//随机令上一层的delta乘以1./(1.-probability);
}
}
4、反向传播backward_local_layer
void backward_local_layer(local_layer l, network net)
{
int i, j;
int locations = l.out_w*l.out_h;
gradient_array(l.output, l.outputs*l.batch, l.activation, l.delta);//就是上图中的公式二中的前面两项,即公式3中的delta值
//更新此层的bias_updates
for(i = 0; i < l.batch; ++i){
axpy_cpu(l.outputs, 1, l.delta + i*l.outputs, 1, l.bias_updates, 1);
}
for(i = 0; i < l.batch; ++i){
float *input = net.input + i*l.w*l.h*l.c;
im2col_cpu(input, l.c, l.h, l.w,
l.size, l.stride, l.pad, net.workspace);
for(j = 0; j < locations; ++j){
float *a = l.delta + i*l.outputs + j;
float *b = net.workspace + j;
float *c = l.weight_updates + j*l.size*l.size*l.c*l.n;
int m = l.n;
int n = l.size*l.size*l.c;
int k = 1;
//获取权重误差并放入l.weight_updates中,以便之后的权重更新,注意这里是权重误差不是更新后的权重,权重更新会在更新网络中运行
gemm(0,1,m,n,k,1,a,locations,b,locations,1,c,n);
}
if(net.delta){
for(j = 0; j < locations; ++j){
float *a = l.weights + j*l.size*l.size*l.c*l.n;
float *b = l.delta + i*l.outputs + j;
float *c = net.workspace + j;
int m = l.size*l.size*l.c;
int n = 1;
int k = l.n;
//与之前一样将此层的delta乘以权重传给上一层的delta,从而更新上一层的误差项
gemm(1,0,m,n,k,1,a,m,b,locations,0,c,locations);
}
//转化存放格式,具体可以参考前向输入
col2im_cpu(net.workspace, l.c, l.h, l.w, l.size, l.stride, l.pad, net.delta+i*l.c*l.h*l.w);
}
}
}
5、反向传播卷积层
void backward_convolutional_layer(convolutional_layer l, network net)
{
int i, j;
int m = l.n/l.groups;
int n = l.size*l.size*l.c/l.groups;
int k = l.out_w*l.out_h;
//计算激活层梯度
gradient_array(l.output, l.outputs*l.batch, l.activation, l.delta);
if(l.batch_normalize){
backward_batchnorm_layer(l, net);
} else {
backward_bias(l.bias_updates, l.delta, l.batch, l.n, k);//更新bias_updates
}
for(i = 0; i < l.batch; ++i){
for(j = 0; j < l.groups; ++j){
float *a = l.delta + (i*l.groups + j)*m*k;
float *b = net.workspace;
float *c = l.weight_updates + j*l.nweights/l.groups;
float *im = net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w;
float *imd = net.delta + (i*l.groups + j)*l.c/l.groups*l.h*l.w;
if(l.size == 1){
b = im;
} else {
im2col_cpu(im, l.c/l.groups, l.h, l.w,
l.size, l.stride, l.pad, b);
}
//获取权重误差放入weight_updates
gemm(0,1,m,n,k,1,a,k,b,k,1,c,n);
if (net.delta) {
a = l.weights + j*l.nweights/l.groups;
b = l.delta + (i*l.groups + j)*m*k;
c = net.workspace;
if (l.size == 1) {
c = imd;
}
//得到上一层的delta值
gemm(1,0,n,k,m,1,a,n,b,k,0,c,k);
if (l.size != 1) {
col2im_cpu(net.workspace, l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, imd);
}
}
}
}
}
6、反向传播最大池化层
void backward_maxpool_layer(const maxpool_layer l, network net)
{
int i;
int h = l.out_h;
int w = l.out_w;
int c = l.c;
for(i = 0; i < h*w*c*l.batch; ++i){
//l.indexes存储的是前一层最大值的坐标
int index = l.indexes[i];
net.delta[index] += l.delta[i];
}
}