前言

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*
** 前向计算网络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中实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/*
** 反向计算网络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中)如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
//定义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
};