【AlexeyAB DarkNet框架解析】四,网络的前向传播和反向传播介绍以及layer的详细解析

这个AlexeyAB DarkNet框架解析在AlexeyAB的DarkNet源码上会做大量注释,我克隆该工程然后添加注释,注释版DarkNet工程地址为:https://github.com/GiantPandaCV/darknet

前言

前面我们已经成功的获取了目标检测的网络结构(cfg文件的内容),并将网络保存在了一个network结构体中,然后我们还分析了数据加载方式。现在数据和网络结构都有了,接下来就是开始训练/测试的过程了,这个过程主要调用的是network的前向传播和反向传播函数,而network的前向传播和反向传播又可以细分为每一个layer的前向传播和反向传播,今天我们来看一下网络的前向传播和反向传播以及layer是如何定义的。

网络的前向传播和反向传播

网络的前向传播函数在src/network.c中实现,代码如下:

/*
** 前向计算网络net每一层的输出
** state用来标记当前网络的状态,
** 遍历net的每一层网络,从第0层到最后一层,逐层计算每层的输出
*/
void forward_network(network net, network_state state)
{
	// 网络的工作空间, 指的是所有层中占用运算空间最大的那个层的 workspace_size,
    // 因为实际上在 GPU 或 CPU 中某个时刻只有一个层在做前向或反向运算
    state.workspace = net.workspace;
    int i;
	// 遍历所有层,从第一层到最后一层,逐层进行前向传播,网络共有net.n层
    for(i = 0; i < net.n; ++i){
		// 当前正在进行第i层的处理
        state.index = i;
		// 获取当前层
        layer l = net.layers[i];
		// 如果当前层的l.delta已经动态分配了内存,则调用fill_cpu()函数将其所有元素初始化为0
        if(l.delta && state.train){
			// 第一个参数为l.delta的元素个数,第二个参数为初始化值,为0
            scal_cpu(l.outputs * l.batch, 0, l.delta, 1);
        }
        //double time = get_time_point();
		// 前向传播: 完成当前层前向推理
        l.forward(l, state);
		// 完成某一层的推理时,置网络的输入为当前层的输出(这将成为下一层网络的输入),注意此处更改的是state,而非原始的net
        //printf("%d - Predicted in %lf milli-seconds.\n", i, ((double)get_time_point() - time) / 1000);
        state.input = l.output;
    }
}

网络的反向传播函数也在src/network.c中实现,代码如下:

/*
** 反向计算网络net每一层的梯度图,并进而计算每一层的权重、偏置更新值,最后完成每一层权重与偏置更新
** 流程: 遍历net的每一层网络,从最后一层到第一层(此处所指的第一层不是指输入层,而是与输入层直接相连的第一层隐含层)进行反向传播
*/
void backward_network(network net, network_state state)
{
    int i;
	// 在进行反向传播之前先保存一下原来的net信息
    float *original_input = state.input;
    float *original_delta = state.delta;
    state.workspace = net.workspace;
    for(i = net.n-1; i >= 0; --i){
		// 标志参数,当前网络的活跃层
        state.index = i;
		// i = 0时,也即已经到了网络的第1层(或者说第0层,看个人习惯了~)了,
        // 就是直接与输入层相连的第一层隐含层(注意不是输入层,我理解的输入层就是指输入的图像数据,
        // 严格来说,输入层不算一层网络,因为输入层没有训练参数,也没有激活函数),这个时候,不需要else中的赋值,1)对于第1层来说,其前面已经没有网络层了(输入层不算),
        // 因此没有必要再计算前一层的参数,故没有必要在获取上一层;2)第一层的输入就是图像输入,也即整个net最原始的输入,在开始进行反向传播之前,已经用original_*变量保存了
        // 最为原始的net,所以net.input就是第一层的输入,不需要获取上一层的输出作为当前层的输入;3)同1),第一层之前已经没有层了,
        // 也就不需要计算上一层的delta,即不需要再将net.delta链接到prev.delta,此时进入到l.backward()中后,net.delta就是NULL(可以参看darknet.h中关于delta
        // 的注释),也就不会再计算上一层的敏感度了(比如卷积神经网络中的backward_convolutional_layer()函数)
        // 这几行代码就是给net.input和net.delta赋值
        if(i == 0){
            state.input = original_input;
            state.delta = original_delta;
        }else{
            layer prev = net.layers[i-1];
            state.input = prev.output;
            state.delta = prev.delta;
        }
        layer l = net.layers[i];
        if (l.stopbackward) break;
        if (l.onlyforward) continue;
		// 反向计算第i层的敏感度图、权重及偏置更新值,并更新权重、偏置(同时会计算上一层的敏感度图,
        // 存储在net.delta中,但是还差一个环节:乘上上一层输出对加权输入的导数,也即上一层激活函数对加权输入的导数)
        l.backward(l, state);
    }
}

layer的解释

在将具体某个层如卷积层的前向传播和反向传播之前,需要先来看一下layer是怎么定义的,因为网络的前向传播和反向传播实际上就是各个网络层(layer)的前向传播和反向传播,这部分加好注释的代码(在src/darknet.h中)如下:

//定义layer
struct layer {
    LAYER_TYPE type; // 网络层的类型,枚举类型,取值比如DROPOUT,CONVOLUTIONAL,MAXPOOL分别表示dropout层,卷积层,最大池化层,可参见LAYER_TYPE枚举类型的定义
    ACTIVATION activation; //激活函数类型,枚举类型
    COST_TYPE cost_type; //损失函数类型,枚举类型
    void(*forward)   (struct layer, struct network_state);
    void(*backward)  (struct layer, struct network_state);
    void(*update)    (struct layer, int, float, float, float);
    void(*forward_gpu)   (struct layer, struct network_state);
    void(*backward_gpu)  (struct layer, struct network_state);
    void(*update_gpu)    (struct layer, int, float, float, float);
    layer *share_layer;
    int train;
    int avgpool;
    int batch_normalize; // 是否进行BN,如果进行BN,则值为1
    int shortcut;
    int batch; // 一个batch中含有的图片张数,等于net.batch,详细可以参考network.h中的注释,一般在构建具体网络层时赋值(比如make_maxpool_layer()中)
    int forced;
    int flipped;
    int inputs; // 一张输入图片所含的元素个数(一般在各网络层构建函数中赋值,比如make_connected_layer()),第一层的值等于l.h*l.w*l.c,
                // 之后的每一层都是由上一层的输出自动推算得到的(参见parse_network_cfg(),在构建每一层后,会更新params.inputs为上一层的l.outputs)
    int outputs; // 该层对应一张输入图片的输出元素个数(一般在各网络层构建函数中赋值,比如make_connected_layer())
                 // 对于一些网络,可由输入图片的尺寸及相关参数计算出,比如卷积层,可以通过输入尺寸以及步长、核大小计算出;
                 // 对于另一些尺寸,则需要通过网络配置文件指定,如未指定,取默认值1,比如全连接层(见parse_connected()函数)
    int nweights;
    int nbiases;
    int extra;
    int truths;  // < 根据region_layer.c判断,这个变量表示一张图片含有的真实值的个数,对于检测模型来说,一个真实的标签含有5个值,
                // 包括类型对应的编号以及定位矩形框用到的w,h,x,y四个参数,且在darknet中固定每张图片最大处理30个矩形框,(可查看max_boxes参数),
                // 因此,在region_layer.c的make_region_layer()函数中赋值为30*5.
    int h, w, c;  // 该层输入的图片的宽,高,通道数(一般在各网络层构建函数中赋值,比如make_connected_layer())
    int out_h, out_w, out_c;// 该层输出图片的高、宽、通道数(一般在各网络层构建函数中赋值,比如make_connected_layer())
    int n; // 对于卷积层,该参数表示卷积核个数,等于out_c,其值由网络配置文件指定;对于region_layer层,该参数等于配置文件中的num值
           // (该参数通过make_region_layer()函数赋值,在parser.c中调用的make_region_layer()函数),
           // 可以在darknet/cfg文件夹下执行命令:grep num *.cfg便可以搜索出所有设置了num参数的网络,这里面包括yolo.cfg等,其值有
           // 设定为3,5,2的,该参数就是Yolo论文中的B,也就是一个cell中预测多少个box。
    int max_boxes; // 每张图片最多含有的标签矩形框数(参看:data.c中的load_data_detection(),其输入参数boxes就是指这个参数),
                   // 什么意思呢?就是每张图片中最多打了max_boxes个标签物体,模型预测过程中,可能会预测出很多的物体,但实际上,
                   // 图片中打上标签的真正存在的物体最多就max_boxes个,预测多出来的肯定存在false positive,需要滤出与筛选,
                   // 可参看region_layer.c中forward_region_layer()函数的第二个for循环中的注释
    int groups; // 应该是控制组卷积的组数,类似于caffe的group参数
    int group_id;
    int size; // 核尺寸(比如卷积核,池化核等)
    int side;
    int stride; // 滑动步长,如卷积核的滑动步长
    int stride_x;
    int stride_y;
    int dilation; //空洞卷积参数
    int antialiasing;
    int maxpool_depth;
    int out_channels; // 输出通道数
    int reverse;
    int flatten;
    int spatial;
    int pad; // 该层对输入数据四周的补0长度(现在发现在卷积层,最大池化层中有用到该参数),一般在构建具体网络层时赋值(比如make_maxpool_layer()中)
    int sqrt;
    int flip;
    int index;
    int scale_wh;
    int binary;
    int xnor;
    int peephole;
    int use_bin_output;
    int keep_delta_gpu;
    int optimized_memory;
    int steps;
    int state_constrain;
    int hidden;
    int truth;
    float smooth;
    float dot;
    int deform;
    int sway;
    int rotate;
    int stretch;
    int stretch_sway;
    float angle;
    float jitter;
    float saturation;
    float exposure;
    float shift;
    float ratio;
    float learning_rate_scale;
    float clip;
    int focal_loss;
    float *classes_multipliers;
    float label_smooth_eps;
    int noloss;
    int softmax;
    int classes; // 物体类别种数,一个训练好的网络,只能检测指定所有物体类别中的物体,比如yolo9000.cfg,设置该值为9418,
                 // 也就是该网络训练好了之后可以检测9418种物体。该参数由网络配置文件指定。目前在作者给的例子中,
                 // 有设置该值的配置文件大都是检测模型,纯识别的网络模型没有设置该值,我想是因为检测模型输出的一般会为各个类别的概率,
                 // 所以需要知道这个种类数目,而识别的话,不需要知道某个物体属于这些所有类的具体概率,因此可以不知道。
    int coords; // 这个参数一般用在检测模型中,且不是所有层都有这个参数,一般在检测模型最后一层有,比如region_layer层,该参数的含义
                // 是定位一个物体所需的参数个数,一般为4个,包括物体所在矩形框中心坐标x,y两个参数以及矩形框长宽w,h两个参数,
                // 可以在darknet/cfg文件夹下,执行grep coords *.cfg,会搜索出所有使用该参数的模型,并可看到该值都设置为4
    int background;
    int rescore;
    int objectness;
    int does_cost;
    int joint;
    int noadjust;
    int reorg;
    int log;
    int tanh;
    int *mask;
    int total;
    float bflops;

    int adam;
    float B1;
    float B2;
    float eps;

    int t;

    float alpha;
    float beta;
    float kappa;

    float coord_scale;
    float object_scale;
    float noobject_scale;
    float mask_scale;
    float class_scale;
    int bias_match;
    float random;
    float ignore_thresh;
    float truth_thresh;
    float iou_thresh;
    float thresh;
    float focus;
    int classfix;
    int absolute;
    int assisted_excitation;

    int onlyforward; // 标志参数,当值为1那么当前层只执行前向传播
    int stopbackward;  // 标志参数,用来强制停止反向传播过程(值为1则停止反向传播),参看network.c中的backward_network()函数
    int dontload;
    int dontsave;
    int dontloadscales;
    int numload;

    float temperature; // 温度参数,softmax层特有参数,在parse_softmax()函数中赋值,由网络配置文件指定,如果未指定,则使用默认值1(见parse_softmax())
    float probability; // dropout概率,即舍弃概率,相应的1-probability为保留概率(具体的使用可参见forward_dropout_layer()),在make_dropout_layer()中赋值,
						// 其值由网络配置文件指定,如果网络配置文件未指定,则取默认值0.5(见parse_dropout())
    float dropblock_size_rel;
    int dropblock_size_abs;
    int dropblock;
    float scale; // 在dropout层中,该变量是一个比例因子,取值为保留概率的倒数(darknet实现用的是inverted dropout),用于缩放输入元素的值
                       // (在网上随便搜索关于dropout的博客,都会提到inverted dropout),在make_dropout_layer()函数中赋值

    char  * cweights;
    int   * indexes; // 维度为l.out_h * l.out_w * l.out_c * l.batch,可知包含整个batch输入图片的输出,一般在构建具体网络层时动态分配内存(比如make_maxpool_layer()中)。
                     // 目前仅发现其用在在最大池化层中。该变量存储的是索引值,并与当前层所有输出元素一一对应,表示当前层每个输出元素的值是上一层输出中的哪一个元素值(存储的索引值是
                     // 在上一层所有输出元素(包含整个batch)中的索引),因为对于最大池化层,每一个输出元素的值实际是上一层输出(也即当前层输入)某个池化区域中的最大元素值,indexes就是记录
                     // 这些局部最大元素值在上一层所有输出元素中的总索引。记录这些值有什么用吗?当然有,用于反向传播过程计算上一层敏感度值,详见backward_maxpool_layer()以及forward_maxpool_layer()函数。
    int   * input_layers; //这个层有哪些输入层
    int   * input_sizes; // 输入层的尺寸
    float **layers_output; //产生的一系列输出层
    float **layers_delta;
    WEIGHTS_TYPE_T weights_type;
    WEIGHTS_NORMALIZATION_T weights_normalizion;
	/*
     * 这个参数用的不多,仅在region_layer.c中使用,该参数的作用是用于不同数据集间类别编号的转换,更为具体的,
     * 是coco数据集中80类物体编号与联合数据集中9000+物体类别编号之间的转换,可以对比查看data/coco.names与
     * data/9k.names以及data/coco9k.map三个文件(旧版的darknet可能没有,新版的darknet才有coco9k.map这个文件),
     * 可以发现,coco.names中每一个物体类别都可以在9k.names中找到,且coco.names中每个物体类别名称在9k.names
     * 中所在的行数就是coco9k.map中的编号值(减了1,因为在程序数组中编号从0开始),也就是这个map将coco数据集中
     * 的类别编号映射到联和数据集9k中的类别编号(这个9k数据集是一个联和多个数据集的大数集,其名称分类被层级划分,
     * )(注意两个文件中物体的类别名称大部分都相同,有小部分存在小差异,虽然有差异,但只是两个数据集中使用的名称有所差异而已,
     * 对应的物体是一样的,比如在coco.names中摩托车的名称为motorbike,在联合数据集9k.names,其名称为motorcycle).
    */
    int   * map;
    int   * counts;
    float ** sums;
	// 这个参数目前只发现用在dropout层,用于存储一些列的随机数,这些随机数与dropout层的输入元素一一对应,维度为l.batch*l.inputs(包含整个batch的),在make_dropout_layer()函数中用calloc动态分配内存,
    // 并在前向传播函数forward_dropout_layer()函数中逐元素赋值。里面存储的随机数满足0~1均匀分布,干什么用呢?用于决定该输入元素的去留,
    // 我们知道dropout层就完成一个事:按照一定概率舍弃输入神经元(所谓舍弃就是置该输入的值为0),rand中存储的值就是如果小于l.probability,则舍弃该输入神经元(详见:forward_dropout_layer())。
    // 为什么要保留这些随机数呢?和最大池化层中的l.indexes类似,在反向传播函数backward_dropout_layer()中用来指示计算上一层的敏感度值,因为dropout舍弃了一些输入,
    // 这些输入(dropout层的输入,上一层的输出)对应的敏感度值可以置为0,而那些没有舍弃的输入,才有必要由当前dropout层反向传播过去。
    float * rand;
    float * cost;
    float * state;
    float * prev_state;
    float * forgot_state;
    float * forgot_delta;
    float * state_delta;
    float * combine_cpu;
    float * combine_delta_cpu;

    float *concat;
    float *concat_delta;

    float *binary_weights;

    float *biases;  // 当前层所有偏置,对于卷积层,维度l.n,每个卷积核有一个偏置;对于全连接层,维度等于单张输入图片对应的元素个数即outputs,一般在各网络构建函数中动态分配内存(比如make_connected_layer()
    float *bias_updates; // 当前层所有偏置更新值,对于卷积层,维度l.n,每个卷积核有一个偏置;对于全连接层,维度为outputs。所谓权重系数更新值,就是梯度下降中与步长相乘的那项,也即误差对偏置的导数,
                          // 一般在各网络构建函数中动态分配内存(比如make_connected_layer())

    float *scales;
    float *scale_updates;

    float *weights; //当前层所有权重系数(连接当前层和上一层的系数,但记在当前层上),对于卷积层,维度为l.n*l.c*l.size*l.size,即卷积核个数乘以卷积核尺寸再乘以输入通道数(各个通道上的权重系数独立不一样);
                     // 对于全连接层,维度为单张图片输入与输出元素个数之积inputs*outputs,一般在各网络构建函数中动态分配内存(比如make_connected_layer())
    float *weight_updates;// 当前层所有权重系数更新值,对于卷积层维度为l.n*l.c*l.size*l.size;对于全连接层,维度为单张图片输入与输出元素个数之积inputs*outputs,
                            // 所谓权重系数更新值,就是梯度下降中与步长相乘的那项,也即误差对权重的导数,一般在各网络构建函数中动态分配内存(比如make_connected_layer()

    float scale_x_y;
    float max_delta;
    float uc_normalizer;
    float iou_normalizer;
    float cls_normalizer;
    IOU_LOSS iou_loss;
    NMS_KIND nms_kind;
    float beta_nms;
    YOLO_POINT yolo_point;

    char *align_bit_weights_gpu;
    float *mean_arr_gpu;
    float *align_workspace_gpu;
    float *transposed_align_workspace_gpu;
    int align_workspace_size;

    char *align_bit_weights;
    float *mean_arr;
    int align_bit_weights_size;
    int lda_align;
    int new_lda;
    int bit_align;

    float *col_image;
    float * delta; // 存储每一层的敏感度图:包含所有输出元素的敏感度值(整个batch所有图片)。所谓敏感度,即误差函数关于当前层每个加权输入的导数值,
                   // 关于敏感度图这个名称,其实就是梯度,可以参考https://www.zybuluo.com/hanbingtao/note/485480。
                   // 元素个数为l.batch * l.outputs(其中l.outputs = l.out_h * l.out_w * l.out_c),
                   // 对于卷积神经网络,在make_convolutional_layer()动态分配内存,按行存储,可视为l.batch行,l.outputs列,
                   // 即batch中每一张图片,对应l.delta中的一行,而这一行,又可以视作有l.out_c行,l.out_h*l.out_c列,
                   // 其中每小行对应一张输入图片的一张输出特征图的敏感度。一般在构建具体网络层时动态分配内存(比如make_maxpool_layer()中)。
    float * output; // 存储该层所有的输出,维度为l.out_h * l.out_w * l.out_c * l.batch,可知包含整个batch输入图片的输出,一般在构建具体网络层时动态分配内存(比如make_maxpool_layer()中)。
                    // 按行存储:每张图片按行铺排成一大行,图片间再并成一行。
    float * activation_input;
    int delta_pinned;
    int output_pinned;
    float * loss;
    float * squared;
    float * norms;

    float * spatial_mean;
    float * mean;
    float * variance;

    float * mean_delta;
    float * variance_delta;

    float * rolling_mean;
    float * rolling_variance;

    float * x;
    float * x_norm;

    float * m;
    float * v;

    float * bias_m;
    float * bias_v;
    float * scale_m;
    float * scale_v;


    float *z_cpu;
    float *r_cpu;
    float *h_cpu;
    float *stored_h_cpu;
    float * prev_state_cpu;

    float *temp_cpu;
    float *temp2_cpu;
    float *temp3_cpu;

    float *dh_cpu;
    float *hh_cpu;
    float *prev_cell_cpu;
    float *cell_cpu;
    float *f_cpu;
    float *i_cpu;
    float *g_cpu;
    float *o_cpu;
    float *c_cpu;
    float *stored_c_cpu;
    float *dc_cpu;

    float *binary_input;
    uint32_t *bin_re_packed_input;
    char *t_bit_input;

    struct layer *input_layer;
    struct layer *self_layer;
    struct layer *output_layer;

    struct layer *reset_layer;
    struct layer *update_layer;
    struct layer *state_layer;

    struct layer *input_gate_layer;
    struct layer *state_gate_layer;
    struct layer *input_save_layer;
    struct layer *state_save_layer;
    struct layer *input_state_layer;
    struct layer *state_state_layer;

    struct layer *input_z_layer;
    struct layer *state_z_layer;

    struct layer *input_r_layer;
    struct layer *state_r_layer;

    struct layer *input_h_layer;
    struct layer *state_h_layer;

    struct layer *wz;
    struct layer *uz;
    struct layer *wr;
    struct layer *ur;
    struct layer *wh;
    struct layer *uh;
    struct layer *uo;
    struct layer *wo;
    struct layer *vo;
    struct layer *uf;
    struct layer *wf;
    struct layer *vf;
    struct layer *ui;
    struct layer *wi;
    struct layer *vi;
    struct layer *ug;
    struct layer *wg;
 
    tree *softmax_tree; // softmax层用到的一个参数,不过这个参数似乎并不常见,很多用到softmax层的网络并没用使用这个参数,目前仅发现darknet9000.cfg中使用了该参数,如果未用到该参数,其值为NULL,如果用到了则会在parse_softmax()中赋值,
                       // 目前个人的初步猜测是利用该参数来组织标签数据,以方便访问

    size_t workspace_size; // net.workspace的元素个数,为所有层中最大的l.out_h*l.out_w*l.size*l.size*l.c,(在make_convolutional_layer()计算得到workspace_size的大小,在parse_network_cfg()中动态分配内存,此值对应未使用gpu时的情况
 
#ifdef GPU
    int *indexes_gpu;

    float *z_gpu;
    float *r_gpu;
    float *h_gpu;
    float *stored_h_gpu;

    float *temp_gpu;
    float *temp2_gpu;
    float *temp3_gpu;

    float *dh_gpu;
    float *hh_gpu;
    float *prev_cell_gpu;
    float *prev_state_gpu;
    float *last_prev_state_gpu;
    float *last_prev_cell_gpu;
    float *cell_gpu;
    float *f_gpu;
    float *i_gpu;
    float *g_gpu;
    float *o_gpu;
    float *c_gpu;
    float *stored_c_gpu;
    float *dc_gpu;

    // adam
    float *m_gpu;
    float *v_gpu;
    float *bias_m_gpu;
    float *scale_m_gpu;
    float *bias_v_gpu;
    float *scale_v_gpu;

    float * combine_gpu;
    float * combine_delta_gpu;

    float * forgot_state_gpu;
    float * forgot_delta_gpu;
    float * state_gpu;
    float * state_delta_gpu;
    float * gate_gpu;
    float * gate_delta_gpu;
    float * save_gpu;
    float * save_delta_gpu;
    float * concat_gpu;
    float * concat_delta_gpu;

    float *binary_input_gpu;
    float *binary_weights_gpu;
    float *bin_conv_shortcut_in_gpu;
    float *bin_conv_shortcut_out_gpu;

    float * mean_gpu;
    float * variance_gpu;

    float * rolling_mean_gpu;
    float * rolling_variance_gpu;

    float * variance_delta_gpu;
    float * mean_delta_gpu;

    float * col_image_gpu;

    float * x_gpu;
    float * x_norm_gpu;
    float * weights_gpu;
    float * weight_updates_gpu;
    float * weight_deform_gpu;
    float * weight_change_gpu;

    float * weights_gpu16;
    float * weight_updates_gpu16;

    float * biases_gpu;
    float * bias_updates_gpu;
    float * bias_change_gpu;

    float * scales_gpu;
    float * scale_updates_gpu;
    float * scale_change_gpu;

    float * input_antialiasing_gpu;
    float * output_gpu;
    float * activation_input_gpu;
    float * loss_gpu;
    float * delta_gpu;
    float * rand_gpu;
    float * squared_gpu;
    float * norms_gpu;

    float *gt_gpu;
    float *a_avg_gpu;

    int *input_sizes_gpu;
    float **layers_output_gpu;
    float **layers_delta_gpu;
#ifdef CUDNN
    cudnnTensorDescriptor_t srcTensorDesc, dstTensorDesc;
    cudnnTensorDescriptor_t srcTensorDesc16, dstTensorDesc16;
    cudnnTensorDescriptor_t dsrcTensorDesc, ddstTensorDesc;
    cudnnTensorDescriptor_t dsrcTensorDesc16, ddstTensorDesc16;
    cudnnTensorDescriptor_t normTensorDesc, normDstTensorDesc, normDstTensorDescF16;
    cudnnFilterDescriptor_t weightDesc, weightDesc16;
    cudnnFilterDescriptor_t dweightDesc, dweightDesc16;
    cudnnConvolutionDescriptor_t convDesc;
    cudnnConvolutionFwdAlgo_t fw_algo, fw_algo16;
    cudnnConvolutionBwdDataAlgo_t bd_algo, bd_algo16;
    cudnnConvolutionBwdFilterAlgo_t bf_algo, bf_algo16;
    cudnnPoolingDescriptor_t poolingDesc;
#endif  // CUDNN
#endif  // GPU
};

后记

由于篇幅原因就讲到这里,明天开始分析卷积层的前向传播和反向传播的代码和详细过程,同时包括im2col以及col2im等算法。

欢迎关注GiantPandaCV, 在这里你将看到独家的深度学习分享,坚持原创,每天分享我们学习到的新鲜知识。( • ̀ω•́ )✧

下一篇
举报

扫码关注云+社区

领取腾讯云代金券