反向网络函数backward_network(net)

29 篇文章 1 订阅
27 篇文章 2 订阅

现在开始讲解反向传播的代码,如果对反向传播不太理解可以参考一下博客里面的讲解;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];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值