convolutional_layer layer =make_convolutional_layer(
batch,h,w,c,n,groups,size,stride,padding,activation, batch_normalize, binary, xnor, params.net->adam);函数主体
convolutional_layer make_convolutional_layer(int batch, int h, int w, int c, int n, int groups, int size, int stride, int padding, ACTIVATION activation, int batch_normalize, int binary, int xnor, int adam)
{
int i;
convolutional_layer l = {0};//定义convolutional_layer l并初始化为0
l.type = CONVOLUTIONAL;//类型为卷积
//设置参数
l.groups = groups;//分组数
l.h = h;
l.w = w;
l.c = c;
l.n = n;
l.binary = binary;
l.xnor = xnor;
l.batch = batch;
l.stride = stride;
l.size = size;
l.pad = padding;
l.batch_normalize = batch_normalize;
l.weights = calloc(c/groups*n*size*size, sizeof(float));//分配空间来保存权重参数,该卷积层总的权重元素(卷积核元素)个数=输入图像通道数 / 分组数*卷积核个数*卷积核尺寸
l.weight_updates = calloc(c/groups*n*size*size, sizeof(float));//分配空间来保存更新后的参数
l.biases = calloc(n, sizeof(float));//分配空间来保存bias参数,bias就是Wx+b中的b(上面的weights就是W),有多少个卷积核,就有多少个b(与W的个数一一对应,每个W的元素个数为c*size*size)
l.bias_updates = calloc(n, sizeof(float));//分配空间来保存更新后的bias参数
l.nweights = c/groups*n*size*size;//权重大小
l.nbiases = n;//bias大小
// float scale = 1./sqrt(size*size*c);
//参数初始化
// 初始化权重:缩放因子*标准正态分布随机数,缩放因子等于sqrt(2./(size*size*c)),随机初始化
// 此处初始化权重为正态分布,而在全连接层make_connected_layer()中初始化权重是均匀分布的。
//正态分布时的缩放因子scale计算公式就是sqrt(2./(卷积核宽*卷积核高*输入通道数))
// TODO:个人感觉,这里应该加一个if条件语句:if(weightfile),因为如果导入了预训练权重文件,就没有必要这样初始化了(事实上在detector.c的train_detector()函数中,
// 紧接着parse_network_cfg()函数之后,就添加了if(weightfile)语句判断是否导入权重系数文件,如果导入了权重系数文件,也许这里初始化的值也会覆盖掉,
// 总之这里的权重初始化的处理方式还是值得思考的,也许更好的方式是应该设置专门的函数进行权重的初始化,同时偏置也是,不过这里似乎没有考虑偏置的初始化,在make_connected_layer()中倒是有。。。)
float scale = sqrt(2./(size*size*c/l.groups))
//printf("convscale %f\n", scale);
//scale = .02;
//for(i = 0; i < c*n*size*size; ++i) l.weights[i] = scale*rand_uniform(-1, 1);
for(i = 0; i < l.nweights; ++i) l.weights[i] = scale*rand_normal();//初始化权重
//根据该层输入图像的尺寸、卷积核尺寸以及跨度计算输出特征图的宽度和高度
int out_w = convolutional_out_width(l);
int out_h = convolutional_out_height(l);
/*
int convolutional_out_height(convolutional_layer l)
{
return (l.h + 2*l.pad - l.size) / l.stride + 1;
}
int convolutional_out_width(convolutional_layer l)
{
return (l.w + 2*l.pad - l.size) / l.stride + 1;
}
*/
l.out_h = out_h;// 输出图像高度赋值
l.out_w = out_w;// 输出图像宽度赋值
l.out_c = n;//输出图像通道数(等于卷积核个数,有多少卷积核最终就得到多少张特征图,每张特征图是一个通道)
l.outputs = l.out_h * l.out_w * l.out_c;// 对应每张输入图片的所有输出特征图的总元素个数(每张输入图片会得到n也即l.out_c张特征图)
l.inputs = l.w * l.h * l.c;// mini-batch(batch/subdivisions)中每张输入图片的像素元素个数
l.output = calloc(l.batch*l.outputs, sizeof(float));//l.output为该层所有的输出(包括mini-batch所有输入图片的输出),为其分配空间
l.delta = calloc(l.batch*l.outputs, sizeof(float));//为所有的梯度分配内存
//核心代码,设置前行后向和更新的卷积网络,令函数指针 l.forward l.backward l.update都设置指向函数,这里只是将函数指针指向函数入口地址,并不会运行,会在之后加载模型和网络是使用
l.forward = forward_convolutional_layer;
l.backward = backward_convolutional_layer;
l.update = update_convolutional_layer;
if(binary){
l.binary_weights = calloc(l.nweights, sizeof(float));
l.cweights = calloc(l.nweights, sizeof(char));
l.scales = calloc(n, sizeof(float));
}
if(xnor){
l.binary_weights = calloc(l.nweights, sizeof(float));
l.binary_input = calloc(l.inputs*l.batch, sizeof(float));
}
if(batch_normalize)//为批标准化参数分配内存
{
l.scales = calloc(n, sizeof(float));
l.scale_updates = calloc(n, sizeof(float));
for(i = 0; i < n; ++i){
l.scales[i] = 1;//初始化缩放因子
}
l.mean = calloc(n, sizeof(float));//为每一个训练批次数据的均值分配空间
l.variance = calloc(n, sizeof(float));//为每一个训练批次数据的方差
l.mean_delta = calloc(n, sizeof(float));
l.variance_delta = calloc(n, sizeof(float));
l.rolling_mean = calloc(n, sizeof(float));
l.rolling_variance = calloc(n, sizeof(float));
l.x = calloc(l.batch*l.outputs, sizeof(float));
l.x_norm = calloc(l.batch*l.outputs, sizeof(float));
}
if(adam){
l.m = calloc(l.nweights, sizeof(float));
l.v = calloc(l.nweights, sizeof(float));
l.bias_m = calloc(n, sizeof(float));
l.scale_m = calloc(n, sizeof(float));
l.bias_v = calloc(n, sizeof(float));
l.scale_v = calloc(n, sizeof(float));
}
//这部分是使用GPU训练时使用的代码,若是不使用GPU可以不关注,因为不定义GPU,这部分代码不会运行
#ifdef GPU//若是使用定义了GPU,则会令l.forward_gpu等函数指针指向GPU函数
l.forward_gpu = forward_convolutional_layer_gpu;
l.backward_gpu = backward_convolutional_layer_gpu;
l.update_gpu = update_convolutional_layer_gpu;
if(gpu_index >= 0){//存在GPU则运行
if (adam) {
l.m_gpu = cuda_make_array(l.m, l.nweights);
l.v_gpu = cuda_make_array(l.v, l.nweights);
l.bias_m_gpu = cuda_make_array(l.bias_m, n);
l.bias_v_gpu = cuda_make_array(l.bias_v, n);
l.scale_m_gpu = cuda_make_array(l.scale_m, n);
l.scale_v_gpu = cuda_make_array(l.scale_v, n);
}
//这里的代码与上面的功能相同,但是在GPU上运行效率更高
l.weights_gpu = cuda_make_array(l.weights, l.nweights);
l.weight_updates_gpu = cuda_make_array(l.weight_updates, l.nweights);
l.biases_gpu = cuda_make_array(l.biases, n);
l.bias_updates_gpu = cuda_make_array(l.bias_updates, n);
l.delta_gpu = cuda_make_array(l.delta, l.batch*out_h*out_w*n);
l.output_gpu = cuda_make_array(l.output, l.batch*out_h*out_w*n);
if(binary){
l.binary_weights_gpu = cuda_make_array(l.weights, l.nweights);
}
if(xnor){
l.binary_weights_gpu = cuda_make_array(l.weights, l.nweights);
l.binary_input_gpu = cuda_make_array(0, l.inputs*l.batch);
}
if(batch_normalize){//批标准化
l.mean_gpu = cuda_make_array(l.mean, n);
l.variance_gpu = cuda_make_array(l.variance, n);
l.rolling_mean_gpu = cuda_make_array(l.mean, n);
l.rolling_variance_gpu = cuda_make_array(l.variance, n);
l.mean_delta_gpu = cuda_make_array(l.mean, n);
l.variance_delta_gpu = cuda_make_array(l.variance, n);
l.scales_gpu = cuda_make_array(l.scales, n);
l.scale_updates_gpu = cuda_make_array(l.scale_updates, n);
l.x_gpu = cuda_make_array(l.output, l.batch*out_h*out_w*n);
l.x_norm_gpu = cuda_make_array(l.output, l.batch*out_h*out_w*n);
}
//这部分代码只有在定义了CUDNN才会运行,其功能主要是加速训练
#ifdef CUDNN
cudnnCreateTensorDescriptor(&l.normTensorDesc);
cudnnCreateTensorDescriptor(&l.srcTensorDesc);
cudnnCreateTensorDescriptor(&l.dstTensorDesc);
cudnnCreateFilterDescriptor(&l.weightDesc);
cudnnCreateTensorDescriptor(&l.dsrcTensorDesc);
cudnnCreateTensorDescriptor(&l.ddstTensorDesc);
cudnnCreateFilterDescriptor(&l.dweightDesc);
cudnnCreateConvolutionDescriptor(&l.convDesc);
cudnn_convolutional_setup(&l);
#endif
}
#endif
/*
整个网络的工作空间,其元素个数为所有层中最大的l.workspace_size = l.out_h*l.out_w*l.size*l.size*l.c
(在make_convolutional_layer()计算得到workspace_size的大小,在parse_network_cfg()中动态分配内存,
此值对应未使用gpu时的情况),该变量貌似不轻易被释放内存,目前只发现在network.c的resize_network()函数对其进行了释放。net.workspace充当一个临时工作空间的作用,存储临时所需要的计算参数,比如每层单张图片重排后的结果
(这些参数马上就会参与卷积运算),一旦用完,就会被马上更新(因此该变量的值的更新频率比较大)
*/
l.workspace_size = get_workspace_size(l);//保存一个卷积核生成一个特征图计算量
l.activation = activation;//设置激活函数
fprintf(stderr, "conv %5d %2d x%2d /%2d %4d x%4d x%4d -> %4d x%4d x%4d %5.3f BFLOPs\n", n, size, size, stride, w, h, c, l.out_w, l.out_h, l.out_c, (2.0 * l.n * l.size*l.size*l.c/l.groups * l.out_h*l.out_w)/1000000000.);
return l;
}