forward_network(net)函数

void forward_network(network *netp)
{#ifdef GPU//定义GPU使用下面代码if(netp->gpu_index >= 0){forward_network_gpu(netp);   //前向网络训练return;}
#endifnetwork net = *netp;//使用CPU是使用下面代码训练int i;for(i = 0; i < net.n; ++i){//遍历所有网络层进行训练net.index = i;layer l = net.layers[i];//通过目录找寻每一层网络if(l.delta){fill_cpu(l.outputs * l.batch, 0, l.delta, 1);/*void fill_cpu(int N, float ALPHA, float *X, int INCX){int i;for(i = 0; i < N; ++i) X[i*INCX] = ALPHA;初始化l.delta中的值为0,l.delta内存大小为l.outputs * l.batch}*/}l.forward(l, net);//CPU前向网络训练net.input = l.output;if(l.truth) {net.truth = l.output;}}calc_network_cost(netp);
}

forward_network_gpu(netp);函数
GPU运行

void forward_network_gpu(network *netp)
{network net = *netp;//将指针netp指向内存的内容深拷贝到结构体net中去cuda_set_device(net.gpu_index);//配置cudacuda_push_array(net.input_gpu, net.input, net.inputs*net.batch);//将每次的min_batch训练数据导入GPU训练网络中去,即net.inputs*net.batch=602112 x min_batch;/* void cuda_push_array(float *x_gpu, float *x, size_t n){size_t size = sizeof(float)*n;cudaError_t status = cudaMemcpy(x_gpu, x, size, cudaMemcpyHostToDevice);check_error(status);}*/if(net.truth){cuda_push_array(net.truth_gpu, net.truth, net.truths*net.batch);//将与之对应的标签导入GPU训练网络,这里的net.truths表示的是一幅图像上所有网格每个网格对应一个框且对应一个类别概率,即7*7*(4+1+20)=1225}int i;for(i = 0; i < net.n; ++i){//遍历所有网络层进行训练net.index = i;//网络层目录layer l = net.layers[i];//通过目录找寻每一层网络if(l.delta_gpu){fill_gpu(l.outputs * l.batch, 0, l.delta_gpu, 1);//初始化GPU网络每一层输出的 l.delta_gpu为0;功能与fill_cpu一样l.delta内存大小为l.outputs * l.batch}l.forward_gpu(l, net);//GPU网络训练,对应了每一层网络的前向训练函数入口net.input_gpu = l.output_gpu;net.input = l.output;if(l.truth) {net.truth_gpu = l.output_gpu;net.truth = l.output;}}pull_network_output(netp);calc_network_cost(netp);
}

1.前向卷积网络函数

GPU运行

void forward_convolutional_layer_gpu(convolutional_layer l, network net)
{fill_gpu(l.outputs*l.batch, 0, l.output_gpu, 1);//初始化GPU网络每一层输出的 l.output_gpu为0;l.output = calloc(l.batch*l.outputs, sizeof(float));if(l.binary){//是否进行权重二值化处理,这里若是加载了预训练模型一般不用//这里的功能与CPU中的函数功能一样,为了方便理解,通过CPU下的函数讲解binarize_weights_gpu(l.weights_gpu, l.n, l.c/l.groups*l.size*l.size, l.binary_weights_gpu);swap_binary(&l);//交换l->weights与l->binary_weights的指针指向}if(l.xnor){//是否对权重以及输入进行二值化binarize_weights_gpu(l.weights_gpu, l.n, l.c/l.groups*l.size*l.size, l.binary_weights_gpu);//同上swap_binary(&l);//同上binarize_gpu(net.input_gpu, l.c*l.h*l.w*l.batch, l.binary_input_gpu);//为了方便还是讲解CPU函数,与GPU功能一样net.input_gpu = l.binary_input_gpu;//令net.input_gpu与l.binary_input_gpu指向同一内存地址}#ifdef CUDNN//用于加速cuda训练的,这里不再细讲,有兴趣的可以学习一下cuda编程float one = 1;cudnnConvolutionForward(cudnn_handle(),&one,l.srcTensorDesc,net.input_gpu,l.weightDesc,l.weights_gpu,l.convDesc,l.fw_algo,net.workspace,l.workspace_size,&one,l.dstTensorDesc,l.output_gpu);#else//若是未使用CUDNN则运行下面代码,其实就是卷积操作,可以参照卷积公式来看,理解卷积运行过程int i, j;int m = l.n/l.groups;int k = l.size*l.size*l.c/l.groups;int n = l.out_w*l.out_h;for(i = 0; i < l.batch; ++i){//遍历min_batch中的每一幅图像for(j = 0; j < l.groups; ++j){//遍历groupsfloat *a = l.weights_gpu + j*l.nweights/l.groups;//l.nweights = c/groups*n*size*size;其实就是令指针指向下一幅图像的权重元素float *b = net.workspace;//指向state.workspace这个工作空间,也就是把原始数据变成行向量放到工作空间里,然后进行卷积计算;l.out_h*l.out_w*l.size*l.size*l.c/l.groups*sizeof(float)float *c = l.output_gpu + (i*l.groups + j)*n*m;//指向输出的下一幅图像特征图元素float *im = net.input_gpu + (i*l.groups + j)*l.c/l.groups*l.h*l.w;//指向输入的下一幅图像元素if (l.size == 1){//如果卷积核尺寸为1,则图像不变b = im;} else {im2col_gpu(im, l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, b);//完成卷积操作}gemm_gpu(0,0,m,n,k,1,a,k,b,n,1,c,n);}}
#endifif (l.batch_normalize) {//是否进行批标准化处理forward_batchnorm_layer_gpu(l, net);//批标准化处理,具体函数不再赘述,可以参照公式查看} else {add_bias_gpu(l.output_gpu, l.biases_gpu, l.batch, l.n, l.out_w*l.out_h);}activate_array_gpu(l.output_gpu, l.outputs*l.batch, l.activation);//激活函数if(l.binary || l.xnor) swap_binary(&l);
}

CPU运行的程序

void forward_convolutional_layer(convolutional_layer l, network net)
{int i, j;fill_cpu(l.outputs*l.batch, 0, l.output, 1);/*void fill_cpu(int N, float ALPHA, float *X, int INCX){int i;for(i = 0; i < N; ++i) X[i*INCX] = ALPHA;初始化l.output中的值为0}*/if(l.xnor){//是否对权重以及输入进行二值化binarize_weights(l.weights, l.n, l.c/l.groups*l.size*l.size, l.binary_weights);/*void binarize_weights(float *weights, int n, int size, float *binary){int i, f;for(f = 0; f < n; ++f){//遍历所有输出通道,即输出特征图数float mean = 0;for(i = 0; i < size; ++i){//遍历特征图上的所有权重元素值mean += fabs(weights[f*size + i]);将特征图上的权重元素值的绝对值相加}mean = mean / size;//求得特征图上所有权重元素值的均值for(i = 0; i < size; ++i){binary[f*size + i] = (weights[f*size + i] > 0) ? mean : -mean;将权重值依次赋到binary中,当权重值大于0时赋值为mean,若小于0则赋-mean;将权重归一到了(-mean,mean)的范围内}}}*/swap_binary(&l);/*void swap_binary(convolutional_layer *l){float *swap = l->weights;定义指针swap并令其与l->weights指向同一内存地址l->weights = l->binary_weights;令l->weights指向l->binary_weights指向的内存地址l->binary_weights = swap;令l->binary_weights指向swap指向的内存地址,因为之前定义了swap的指向与l->weights相同,故l->binary_weights指向了之前l->weights指向的内存地址,两个指针完成了指向互换;#ifdef GPU同上swap = l->weights_gpu;l->weights_gpu = l->binary_weights_gpu;l->binary_weights_gpu = swap;#endif}*/binarize_cpu(net.input, l.c*l.h*l.w*l.batch, l.binary_input);/*void binarize_cpu(float *input, int n, float *binary){int i;for(i = 0; i < n; ++i){对输入进行归一化处理,使其取值范围在(-1,1)内;binary[i] = (input[i] > 0) ? 1 : -1;}}*/net.input = l.binary_input;//令指针指向input指向binary_input指向的内存单元}int m = l.n/l.groups;//输出通道数int k = l.size*l.size*l.c/l.groups;//每个卷积核的参数数量int n = l.out_w*l.out_h;//输出特征图的大小for(i = 0; i < l.batch; ++i){//遍历每幅图像for(j = 0; j < l.groups; ++j){float *a = l.weights + j*l.nweights/l.groups;//指向图像的权重,l.weights指向的空间大小为c/groups*n*size*size,l.nweights = c/groups*n*size*size;通过循环正好令其指向下一幅图像的权重float *b = net.workspace;//用来保存卷积核每个元素所对应的原始图像的元素值,一个卷积元素对应的输入图像元素值为out_h*out_w,而卷积核有size*l.size*l.c个元素,故对应out_h*out_w*size*size*c个元素;float *c = l.output + (i*l.groups + j)*n*m;//指向output指向的内存,通过循环可以令其指向下一幅图像的输出特征图空间float *im =  net.input + (i*l.groups + j)*l.c/l.groups*l.h*l.w;//指向input指向的内存,通过循环可以令其指向下一幅输入图像的空间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);}gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);//卷积操作,最后将卷积结果保存到了c;}}if(l.batch_normalize){//批标准化forward_batchnorm_layer(l, net);} else {add_bias(l.output, l.biases, l.batch, l.n, l.out_h*l.out_w);//加上偏置项}activate_array(l.output, l.outputs*l.batch, l.activation);//激活函数if(l.binary || l.xnor) swap_binary(&l);
}

(1)im2col_gpu(im, l.c/l.groups, l.h, l.w, l.size, l.stride, l.pad, b);卷积重要函数可以参考博客来进行理解


void im2col_cpu(float* data_im,int channels,  int height,  int width,int ksize,  int stride, int    pad, float* data_col)
{int c,h,w;int height_col = (height + 2*pad - ksize) / stride + 1;//卷积后的特征图高int width_col = (width + 2*pad - ksize) / stride + 1;//卷积后的特征图宽int channels_col = channels * ksize * ksize;//每个卷积核的参数个数for (c = 0; c < channels_col; ++c) {//遍历卷积核的每个参数int w_offset = c % ksize;//卷积核的行坐标例如(0,1,2,0,1,2,0,1,2,......)int h_offset = (c / ksize) % ksize;//卷积核的列坐标,例如(0,0,0,1,1,1,2,2,2,......)int c_im = c / ksize / ksize;//输入图像的第几个通道//这两层循环是用卷积核把图像遍历一遍for (h = 0; h < height_col; ++h) {for (w = 0; w < width_col; ++w) {//表示第c_im个卷积核上坐标为(h_offset,w_offset)的元素对应输入图像中元素的坐标(im_row,im_col);int im_row = h_offset + h * stride;//每个卷积核元素对图像进行卷积时图像元素的行坐标int im_col = w_offset + w * stride;//每个卷积核元素对图像进行卷积时图像元素的列坐标//因为图像时按照一维格式保存的,这里为了便于对应原始图像元素,也使用了一维格式,最大目录为l.out_h*l.out_w*l.size*l.size*l.cint col_index = (c * height_col + h) * width_col + w;//将该目录下的所对应的原始图像元素导入到data_col内存中;其实存放还是很有规律的,首先存放第一个卷积核的第一个元素所对应的原始图像元素,又因为stride=2,故其对应元素的数量的height_col*width_col;之后依次存放。data_col[col_index] = im2col_get_pixel(data_im, height, width, channels,im_row, im_col, c_im, pad);}}}
}float im2col_get_pixel(float *im, int height, int width, int channels,int row, int col, int channel, int pad)
{//行列坐标目录都减去pad值,去除不能卷积的边界row -= pad;col -= pad;if (row < 0 || col < 0 ||row >= height || col >= width) return 0;//防止越界return im[col + width*(row + height*channel)];//返回卷积核每个元素所对应的原始图像元素
}

下面的图来帮助理解下im2col_cpu()这个函数,为了方便理解,这里假设图像尺寸是5*5, stride=2,kernel_size=3

其实就是根据卷积核每个元素对应输入图像元素值放入了一个暂存内存中,因为输入图像和卷积核在内存中存放都是一维连续存放的,所以需要通过上面的操作来找到各自的位置坐标,float *b指向state.workspace这个工作空间,也就是把原始数据变成行向量放到工作空间里,然后进行卷积计算;

(2)gemm(0,0,m,n,k,1,a,k,b,n,1,c,n);

void gemm(int TA, int TB, int M, int N, int K, float ALPHA, //过度函数float *A, int lda, float *B, int ldb,float BETA,float *C, int ldc)
{gemm_cpu( TA,  TB,  M, N, K, ALPHA,A,lda, B, ldb,BETA,C,ldc);
}void gemm_cpu(int TA, int TB, int M, int N, int K, float ALPHA, float *A, int lda, float *B, int ldb,float BETA,float *C, int ldc)
{//printf("cpu: %d %d %d %d %d %f %d %d %f %d\n",TA, TB, M, N, K, ALPHA, lda, ldb, BETA, ldc);int i, j;for(i = 0; i < M; ++i){//M就是输出通道的数量,即输出特征图数,这里将遍历每幅特征图for(j = 0; j < N; ++j){//N就是输出特征图的参数数量,这里将遍历特征图上的每一个参数C[i*ldc + j] *= BETA;//初始化每个通道特征图上的每个元素;这里由于BETA=1,故初始化为1;}}/*根据指定的TA和TB来选择不同的矩阵乘法方法:当TA=0,TB=0时,我们进行的是C = ALPHA * A * B + BETA * C操作当TA=1,TB=0时,进行的是C = ALPHA * A' * B + BETA * C操作当TA=0,TB=1时,进行的是C = ALPHA * A * B' + BETA * C操作当TA=1,TB=1时,进行的时C = ALPHA * A' * B' + BETA * C操作*/if(!TA && !TB)gemm_nn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);//卷积操作else if(TA && !TB)gemm_tn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);else if(!TA && TB)gemm_nt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);elsegemm_tt(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);
}

**(3)gemm_nn(M, N, K, ALPHA,A,lda, B, ldb,C,ldc);卷积函数 **

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循环将被多线程执行,另外每次循环之间不能有关系。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){//遍历每个输出特征图的元素C[i*ldc+j] += A_PART*B[k*ldb+j];//令与给通道对应的卷积核内的元素依次与其对应位置的图像像素卷积,然后将结果赋给对应位置的输出特征图;}}}
}

这个函数的将卷积后的值放入c所指向的内存中(最终生成number of kernel个2*2的feature map) ,下图只是当只有一个输出特征图的时候;函数结束后,开始循环每一个batch,卷积计算结果依次向后放

(4)forward_batchnorm_layer(l, net);

void forward_batchnorm_layer(layer l, network net)
{if(l.type == BATCHNORM) copy_cpu(l.outputs*l.batch, net.input, 1, l.output, 1);copy_cpu(l.outputs*l.batch, l.output, 1, l.x, 1);if(net.train){mean_cpu(l.output, l.batch, l.out_c, l.out_h*l.out_w, l.mean);variance_cpu(l.output, l.mean, l.batch, l.out_c, l.out_h*l.out_w, l.variance);scal_cpu(l.out_c, .99, l.rolling_mean, 1);axpy_cpu(l.out_c, .01, l.mean, 1, l.rolling_mean, 1);scal_cpu(l.out_c, .99, l.rolling_variance, 1);axpy_cpu(l.out_c, .01, l.variance, 1, l.rolling_variance, 1);normalize_cpu(l.output, l.mean, l.variance, l.batch, l.out_c, l.out_h*l.out_w);   copy_cpu(l.outputs*l.batch, l.output, 1, l.x_norm, 1);} else {normalize_cpu(l.output, l.rolling_mean, l.rolling_variance, l.batch, l.out_c, l.out_h*l.out_w);}scale_bias(l.output, l.scales, l.batch, l.out_c, l.out_h*l.out_w);add_bias(l.output, l.biases, l.batch, l.out_c, l.out_h*l.out_w);
}

对应公式即可理解函数

//blas.c
//计算均值
void mean_cpu(float *x, int batch, int filters, int spatial, float *mean)
{float scale = 1./(batch * spatial);int i,j,k;//注意,这里的均值是不同batch的同一维度的feature的均值for(i = 0; i < filters; ++i){mean[i] = 0;for(j = 0; j < batch; ++j){for(k = 0; k < spatial; ++k){int index = j*filters*spatial + i*spatial + k;mean[i] += x[index];}}mean[i] *= scale;}
}
//计算方差
void variance_cpu(float *x, float *mean, int batch, int filters, int spatial, float *variance)
{float scale = 1./(batch * spatial - 1);int i,j,k;for(i = 0; i < filters; ++i){variance[i] = 0;for(j = 0; j < batch; ++j){for(k = 0; k < spatial; ++k){int index = j*filters*spatial + i*spatial + k;variance[i] += pow((x[index] - mean[i]), 2);}}variance[i] *= scale;}
}
//归一化
void normalize_cpu(float *x, float *mean, float *variance, int batch, int filters, int spatial)
{int b, f, i;for(b = 0; b < batch; ++b){for(f = 0; f < filters; ++f){for(i = 0; i < spatial; ++i){int index = b*filters*spatial + f*spatial + i;//公式中的ε=.000001fx[index] = (x[index] - mean[f])/(sqrt(variance[f]) + .000001f);}}}
}//convolutional_layer.c
void scale_bias(float *output, float *scales, int batch, int n, int size)
{int i,j,b;for(b = 0; b < batch; ++b){for(i = 0; i < n; ++i){for(j = 0; j < size; ++j){//scales就是创建convolutional_layer时分配的l.scales,值全是1output[(b*n + i)*size + j] *= scales[i];}}}
}

2.最大池化层前向网络函数

void forward_maxpool_layer(const maxpool_layer l, network net)
{int b,i,j,k,m,n;int w_offset = -l.pad/2;int h_offset = -l.pad/2;int h = l.out_h;int w = l.out_w;int c = l.c;for(b = 0; b < l.batch; ++b){//遍历batch中的每个特征图for(k = 0; k < c; ++k){//遍历所有的特征图//注意这里的h和w是maxpooling层输出的高度和宽度for(i = 0; i < h; ++i){for(j = 0; j < w; ++j){int out_index = j + w*(i + h*(k + c*b));//因为内存是一维连续存放,所以需要定位输出特征图元素位置float max = -FLT_MAX;int max_i = -1;//寻找最大值//遍历池化滤波器for(n = 0; n < l.size; ++n){for(m = 0; m < l.size; ++m){//获取当前滤波器对应的输入特征图元素位置int cur_h = h_offset + i*l.stride + n;int cur_w = w_offset + j*l.stride + m;//转化为一维连续存放格式坐标int index = cur_w + l.w*(cur_h + l.h*(k + b*l.c));int valid = (cur_h >= 0 && cur_h < l.h &&cur_w >= 0 && cur_w < l.w);//开始寻找一个滤波器对应元素中的最大值float val = (valid != 0) ? net.input[index] : -FLT_MAX;max_i = (val > max) ? index : max_i;max   = (val > max) ? val   : max;}}//使用最大值替代之前的值l.output[out_index] = max;l.indexes[out_index] = max_i;}}}}
}

3.定位层前向网络函数

void forward_local_layer(const local_layer l, network net)
{//输出后的特征图高宽int out_h = local_out_height(l);int out_w = local_out_width(l);/*out_h= (h + 2*pad - ksize) / stride + 1;int local_out_height(local_layer l){int h = l.h;//输入特征图高if (!l.pad) h -= l.size;//判断pad是否为0,若是为0则令h=h-l.size,否则h=h-1else h -= 1;return h/l.stride + 1;//返回}int local_out_width(local_layer l){int w = l.w;if (!l.pad) w -= l.size;else w -= 1;return w/l.stride + 1;}*/int i, j;int locations = out_h * out_w;//输出特征图的尺寸//将一个batch中的l.biases中的值复制到l.output的内存中for(i = 0; i < l.batch; ++i){copy_cpu(l.outputs, l.biases, 1, l.output + i*l.outputs, 1);}/*void copy_cpu(int N, float *X, int INCX, float *Y, int INCY){int i;for(i = 0; i < N; ++i) Y[i*INCY] = X[i*INCX];}*/for(i = 0; i < l.batch; ++i){//将输入特种图数据导入到net.workspace中去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);//对输入图片进行重排, 重排后的大小[c*size*size, out_h*out_w]float *output = l.output + i*l.outputs;//指向每个输出特征图的起始位置for(j = 0; j < locations; ++j){//遍历输出特征图的每一个元素;locations = out_h * out_w,即对不同特征图相同位置每个元素都进行local操作float *a = l.weights + j*l.size*l.size*l.c*l.n;//指向每个权重文件的起始位置,/*这里可以看出生成的每个特征图上的元素所对应的滤波器都是不同的,卷积时如果是生成一个特征图的话,只需要一个卷积核大小为l.size*l.size*l.c的卷积核即可,对于输出的同一个特征图上所有的元素其卷积核参数是相同的,输出n个特征图则需要n个卷积核;但是在定位层里面就不同了,对应输出的同一个特征图上每个元素的滤波器都是不同的,对与输出一个特征图时,需要out_h*out_w个滤波器,输出n个特征图则需要n*out_h*out_w个卷积核;*/float *b = net.workspace + j;float *c = output + j;int m = l.n;//输出通道数int n = 1;//因为每个输出特征图上的每个元素所对应的滤波器都不同,所以不能使用同一个滤波器进行遍历,这里的输出特征图元素已经由上面的循环进行了遍历选择,所以只需要直接赋给它选择的那个位置的元素即可;int k = l.size*l.size*l.c;//每个滤波器的参数数量gemm(0,0,m,n,k,1,a,k,b,locations,1,c,locations);//进行local操作}}activate_array(l.output, l.outputs*l.batch, l.activation);//激活函数
}

4.dropout前向网络函数

void forward_dropout_layer(dropout_layer l, network_state state)
{int i;//dropout层只在训练阶段有效if (!state.train) return;for(i = 0; i < l.batch * l.inputs; ++i){//产生0~1之间的随机数float r = rand_uniform(0, 1);l.rand[i] = r;//如果小于给定概率值,就把相应的输入项赋0if(r < l.probability) state.input[i] = 0;else state.input[i] *= l.scale;}
}

5.全连接层前向网络函数

void forward_connected_layer(connected_layer l, network_state state)
{int i;fill_cpu(l.outputs*l.batch, 0, l.output, 1);int m = l.batch;int k = l.inputs;int n = l.outputs;float *a = state.input;float *b = l.weights;float *c = l.output;//注意这里的TB=1了,所以调用了gemm_tn()这个函数,下面会有介绍gemm(0,1,m,n,k,1,a,k,b,k,1,c,n);if(l.batch_normalize){if(state.train){mean_cpu(l.output, l.batch, l.outputs, 1, l.mean);variance_cpu(l.output, l.mean, l.batch, l.outputs, 1, l.variance);//将l.rolling_mean所有值赋0.95(移动平均什么意思呢?自己百度吧,数据分析用的很多~)scal_cpu(l.outputs, .95, l.rolling_mean, 1);//将l.rolling_mean的值加上0.5*l.meanaxpy_cpu(l.outputs, .05, l.mean, 1, l.rolling_mean, 1);//将l.rolling_variance所有值赋0.95scal_cpu(l.outputs, .95, l.rolling_variance, 1);//将l.rolling_variance的值加上0.5*l.varianceaxpy_cpu(l.outputs, .05, l.variance, 1, l.rolling_variance, 1);//将l.output的值赋值到l.x,此时l.x是没有经过BN的copy_cpu(l.outputs*l.batch, l.output, 1, l.x, 1);//BNnormalize_cpu(l.output, l.mean, l.variance, l.batch, l.outputs, 1);//将l.output的值赋值到l.x_norm,此时l.x_norm是经过BN之后的数据copy_cpu(l.outputs*l.batch, l.output, 1, l.x_norm, 1);} else {normalize_cpu(l.output, l.rolling_mean, l.rolling_variance, l.batch, l.outputs, 1);}scale_bias(l.output, l.scales, l.batch, l.outputs, 1);}for(i = 0; i < l.batch; ++i){axpy_cpu(l.outputs, 1, l.biases, 1, l.output + i*l.outputs, 1);}//线性变换返回值不变activate_array(l.output, l.outputs*l.batch, l.activation);
}
//gemm.c
void gemm_nt(int M, int N, int K, float ALPHA, float *A, int lda, float *B, int ldb,float *C, int ldc)
{int i,j,k;//M=batch,每个样本有N(yolo.train.cfg中是1715=S×S×(B∗5+C))个输出for(i = 0; i < M; ++i){for(j = 0; j < N; ++j){register float sum = 0;//K是inputs,即输入个数,就是得到一个输出时遍历的权重,如下图所示for(k = 0; k < K; ++k){//输入项 和权重项对应相乘相加sum += ALPHA*A[i*lda+k]*B[j*ldb + k];}C[i*ldc+j] += sum;}}
}


如上图所示就是得到一个输出时进行的操作,而权重大小为 l.inputs* l.outputs,每次使用inputs个权重与输入进行计算得到一个输出,共有outputs个inputs的权重,故会得到outputs个输出值。这里没有使用偏置值,会在之后的计算中加入;

6.检测层前向网络函数

void forward_detection_layer(const detection_layer l, network net)
{int locations = l.side*l.side;int i,j;memcpy(l.output, net.input, l.outputs*l.batch*sizeof(float));//if(l.reorg) reorg(l.output, l.w*l.h, size*l.n, l.batch, 1);int b;//这里的softmax=0,所以最后竟然都没有softmax层……if (l.softmax){for(b = 0; b < l.batch; ++b){int index = b*l.inputs;for (i = 0; i < locations; ++i) {int offset = i*l.classes;softmax(l.output + index + offset, l.classes, 1, 1,l.output + index + offset);}}}//训练的时候才需要cost functionif(net.train){float avg_iou = 0;float avg_cat = 0;float avg_allcat = 0;float avg_obj = 0;float avg_anyobj = 0;int count = 0;*(l.cost) = 0;int size = l.inputs * l.batch;/*void *memset(void *s, int ch, size_t n);memset是计算机中C/C++语言函数。将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值*/memset(l.delta, 0, size * sizeof(float));//将l.delta指向的内存单元数据初始化为0,之后l.delta存放loss function的每一项,最后就是使用它来计算的损失值for (b = 0; b < l.batch; ++b){//遍历每一个图像int index = b*l.inputs;//输入图像目录,表示第几个图//locations = 7*7,49个grid cell,for (i = 0; i < locations; ++i) {//遍历每一个grid cell//真实标签索引,指向每个grid cell标签的第一个数,即置信度,其每个grid cell的标签存放格式为(1+20+4),即(置信度,类别概率,真实边界框位置),其中真实标签的置信度只有0和1,0代表该grid cell中没有目标对象,1代表存在目标对象;其类别概率也是hot格式的;int truth_index = (b*locations+i)*(1+l.coords+l.classes);//将真实标签中的置信度值赋给is_obj,使得is_obj判断该grid cell是否存在目标int is_obj = net.truth[truth_index];//遍历grid cell中的每一个预测框,论文中是2个,计算置信度的损失for (j = 0; j < l.n; ++j) {//p_index是预测值的置信度索引,每个网格预测l.n个框,这里l.n=3(cfg文件中的num值),论文中是2;预测标签的存放方式有些不同,在l.output内存中存放格式为(sxsxclass + sxsxnumx1+sxsxnumxcorrd),即(7x7x20+7x7x2x1+7x7x2x4)),将分类与位置预测分开保存了;故此索引表示该grid cell每个预测框的置信度索引int p_index = index + locations*l.classes+i*l.n + j;//注意这个l.delta,去格式大小都和l.output相同,其作用是为了保存损失值,之后会用于反向传播;当然在这个函数里面还有的用途就是用于最后的loss计算,这里没有进行平方,会在最后使用;l.delta[p_index]=l.noobject_scale*(0- l.output[p_index]);//对应论文公式,这里先假设B个框中都没有物体*(l.cost) += l.noobject_scale*pow(l.output[p_index], 2);avg_anyobj += l.output[p_index];//将所有预测框的预测置信度累加,方便一会计算预测置信度平均值}int best_index = -1;float best_iou = 0;float best_rmse = 20;//若是is_obj=0,则说明该grid cell没有目标,也就不需要计算其他损失了,直接继续下一个;if (!is_obj){continue;}int class_index = index + i*l.classes;//类别索引for(j = 0; j < l.classes; ++j) {//遍历每一种类别//计算类别的损失//继续保存类别误差,这里依然没有使用平方l.delta[class_index+j] = l.class_scale * (net.truth[truth_index+1+j] - l.output[class_index+j]);*(l.cost) += l.class_scale * pow(net.truth[truth_index+1+j] - l.output[class_index+j], 2);//和论文公式一样if(net.truth[truth_index + 1 + j]) avg_cat += l.output[class_index+j];//将真实类别的概率类别累加avg_allcat += l.output[class_index+j];//将所有的概率类别进行累加}//计算位置信息的损失box truth = float_to_box(net.truth + truth_index + 1 + l.classes, 1);//将真实标签位置坐标信息保存到truth/*box float_to_box(float *f, int stride){box b = {0};b.x = f[0];b.y = f[1*stride];b.w = f[2*stride];b.h = f[3*stride];return b;}*///将真实坐标进行转化,首先先了解标签坐标的格式,其(x,y,w,h)都是对原图进行了压缩,比如一幅(500, 403)的图像,其中一个框的位置坐标为(245.0 166.0 406.0 232.0),通过对其进行缩放可得(245/500,166/403,406/500,232/403),即(0.49 0.4119106699751861 0.812 0.575682382133995),之后装载时会将其中心坐标转化为相对于grid cell的位置;这里又除以l.side可能是为了令数据变得更小精度更高还有就是为了和预测通过grid建立联系;truth.x /= l.side;truth.y /= l.side;for(j = 0; j < l.n; ++j){//grid cell中每个预测边界框的位置信息索引int box_index = index + locations*(l.classes + l.n) + (i*l.n + j) * l.coords;//将预测位置信息保存到out;box out = float_to_box(l.output + box_index, 1);//将中心坐标转化为相对于grid cell的位置,找到与真实标签的关联out.x /= l.side;out.y /= l.side;//是否使用平方,可能是防止负值,这样预测值其实就是根号下的高和宽if (l.sqrt){out.w = out.w*out.w;out.h = out.h*out.h;}//计算iou的值float iou  = box_iou(out, truth);//iou = 0;//计算均方根误差(root-mean-square error)float rmse = box_rmse(out, truth);/*float box_rmse(box a, box b){return sqrt(pow(a.x-b.x, 2) + pow(a.y-b.y, 2) + pow(a.w-b.w, 2) + pow(a.h-b.h, 2));}*///选出iou最大或者均方根误差最小的那个框作为该grid cell的预测框if(best_iou > 0 || iou > 0){if(iou > best_iou){best_iou = iou;best_index = j;}}else{if(rmse < best_rmse){best_rmse = rmse;best_index = j;}}}//如果无法通过上面方法选择出结果则通过下面方法//强制确定一个最后预测框,默认为0,即默认随机if(l.forced){if(truth.w*truth.h < .1){best_index = 1;}else{best_index = 0;}}//随机确定一个最后预测框~if(l.random && *(net.seen) < 64000){best_index = rand()%l.n;}//网格中最终用来检测的那个预测框坐标索引int box_index = index + locations*(l.classes + l.n) + (i*l.n + best_index) * l.coords;//真实标签框坐标索引int tbox_index = truth_index + 1 + l.classes;//将最终确认负责grid cell的那个预测框的位置信息赋给out,前面的操作就是为了确定最终的负责grid cell的那个预测框;box out = float_to_box(l.output + box_index, 1);out.x /= l.side;out.y /= l.side;if (l.sqrt) {out.w = out.w*out.w;out.h = out.h*out.h;}//获取预测框与真实框交并比float iou  = box_iou(out, truth);//printf("%d,", best_index);//负责该grid的那个预测框的置信度索引int p_index = index + locations*l.classes + i*l.n + best_index;//之前计算的计算的没有目标的置信度损失是假设了所有的预测框都没有目标,现在需要减去含有目标的置信度损失值*(l.cost) -= l.noobject_scale * pow(l.output[p_index], 2);//计算含有目标的预测框置信度损失*(l.cost) += l.object_scale * pow(1-l.output[p_index], 2);avg_obj += l.output[p_index];//将所有的含有目标置信度相加//这里保存了含有目标的预测框置信度误差l.delta[p_index] = l.object_scale * (1.-l.output[p_index]);//暂理解为一个开关,非0时通过重打分来调整l.delta(预测值与真实值的差)if(l.rescore){//修改调整的误差值l.delta[p_index] = l.object_scale * (iou - l.output[p_index]);}//保存所有的定位误差l.delta[box_index+0] = l.coord_scale*(net.truth[tbox_index + 0] - l.output[box_index + 0]);l.delta[box_index+1] = l.coord_scale*(net.truth[tbox_index + 1] - l.output[box_index + 1]);l.delta[box_index+2] = l.coord_scale*(net.truth[tbox_index + 2] - l.output[box_index + 2]);l.delta[box_index+3] = l.coord_scale*(net.truth[tbox_index + 3] - l.output[box_index + 3]);//这里使用了公式中的内容,使用了根号计算if(l.sqrt){//修改保存的高宽误差,这里和论文中保持了一致,减少了小框的误差l.delta[box_index+2] = l.coord_scale*(sqrt(net.truth[tbox_index + 2]) - l.output[box_index + 2]);l.delta[box_index+3] = l.coord_scale*(sqrt(net.truth[tbox_index + 3]) - l.output[box_index + 3]);}//把iou作为损失,这包含了x,y,w,h四个参数,其实后来没用iou来计算损失,而是论文中给的公式*(l.cost) += pow(1-iou, 2);avg_iou += iou;++count;//目标对象计数}}if(0){//未使用float *costs = calloc(l.batch*locations*l.n, sizeof(float));for (b = 0; b < l.batch; ++b) {int index = b*l.inputs;for (i = 0; i < locations; ++i) {for (j = 0; j < l.n; ++j) {int p_index = index + locations*l.classes + i*l.n + j;costs[b*locations*l.n + i*l.n + j] = l.delta[p_index]*l.delta[p_index];}}}int indexes[100];top_k(costs, l.batch*locations*l.n, 100, indexes);float cutoff = costs[indexes[99]];for (b = 0; b < l.batch; ++b) {int index = b*l.inputs;for (i = 0; i < locations; ++i) {for (j = 0; j < l.n; ++j) {int p_index = index + locations*l.classes + i*l.n + j;if (l.delta[p_index]*l.delta[p_index] < cutoff) l.delta[p_index] = 0;}}}free(costs);//释放了costs,也就是说前面的计算没有用到,tmd搞了半天不用,作者很考验人的耐性}//前面的*(l.cost)其实可以注释掉了,因为前面都没用,到这里才计算loss,直接计算一个min_batch的损失值;不过使用delta保存的误差值计算与论文一样;*(l.cost) = pow(mag_array(l.delta, l.outputs * l.batch), 2);/*float mag_array(float *a, int n){int i;float sum = 0;for(i = 0; i < n; ++i){sum += a[i]*a[i];   //因为之前保存的误差都没有进行平方运算,这里补上}return sqrt(sum);//这里返回根号下的值,之后还要在平方,感觉多此一举;}*/printf("Detection Avg IOU: %f, Pos Cat: %f, All Cat: %f, Pos Obj: %f, Any Obj: %f, count: %d\n", avg_iou/count, avg_cat/count, avg_allcat/(count*l.classes), avg_obj/count, avg_anyobj/(l.batch*locations*l.n), count);//if(l.reorg) reorg(l.delta, l.w*l.h, size*l.n, l.batch, 0);}
}

前向传播网络函数forward_network(net)相关推荐

  1. (二十四) 手推BP神经网络的前向传播和反向传播

    手推BP神经网络的前向传播和反向传播 一.基本术语 1.监督学习与非监督学习 Supervised Learning有监督式学习: 输入的数据被称为训练数据,一个模型需要通过一个训练过程,在这个过程中 ...

  2. 深度学习(3)基础3 -- 前向传播与反向传播

    目录 一.前向传播 1.目的 2.前向传播过程 二.反向传播(梯度下降) 1.意义 2.目的 3.过程 4.门单元 5.更新权重计算 整体过程 前向传播:得到输出. 反向传播:更新权重. 最后调整权重 ...

  3. 【深度学习】(2) 数据加载,前向传播2,附python完整代码

    生成数据集: tf.data.Dataset.from_tensor_slices(tensor变量) 创建一个数据集,其元素是给定张量的切片 生成迭代器: next(iter()) next() 返 ...

  4. 【深度学习】(1) 前向传播,附python完整代码

    各位同学大家好,今天和大家分享一下TensorFlow2.0深度学习中前向传播的推导过程,使用系统自带的mnist数据集. 1. 数据获取 首先,我们导入需要用到的库文件和数据集.导入的x和y数据是数 ...

  5. 深度学习之核心要素:输入输出、目标函数、前向传播、后向传播、学习率、梯度下降

    深度学习之核心要素:输入输出.目标函数.前向传播.后向传播.学习率.梯度下降 目录 深度学习的学习过程 输入输出及隐层: 目标函数:

  6. RNN和LSTM的正向/前向传播-图示公式和代码

    本文先讲的基础版本的RNN,包含内部结构示意图,公式以及每一步的python代码实现.然后,拓展到LSTM的前向传播网络.结合图片+公式+可运行的代码,清晰充分明白RNN的前向传播网络的具体过程.完整 ...

  7. 前向传播和反向传播_深度学习的地基模块:模型、参数、非线性、前向传播、反向偏微分

    头条ID:钱多多先森,关注更多AI.CV.数码.个人理财领域知识,关注我,一起成长 在深度学习中,数据.模型.参数.非线性.前向传播预测.反向偏微分参数更新等等,都是该领域的基础内容.究竟他们最基础的 ...

  8. 深层神经网络中的前向传播

    深层神经网络 那么什么算是深度神经网络呢? 如图所示,我们说逻辑回归是一个浅层模型,单隐层神经网络是一个双层神经网络,注意我们数网络层数的时候一定不要把输入层数进去,只是数隐藏层的数量,以及输出层. ...

  9. 4.2 深层网络中的前向传播-深度学习-Stanford吴恩达教授

    ←上一篇 ↓↑ 下一篇→ 4.1 深层神经网络 回到目录 4.3 核对矩阵的维数 深层网络中的前向传播 (Forward Propagation in a Deep Network) 跟往常一样,我们 ...

最新文章

  1. apache 查看本地页面_GeoSever的页面实现
  2. 克鲁斯卡尔(并查集)hdu 1233
  3. echarts饼图扇区添加点击事件
  4. Spring Cloud【Finchley】实战-04将订单微服务与商品微服务分别拆分为多模块
  5. [云炬创业基础笔记]第五章创业机会评估测试4
  6. Python从键盘输入多行文本数据的方法
  7. 深入浅出React Native 1: 环境配置
  8. 前端解读面向切面编程(AOP)
  9. 1月22日发布!疑似渠道商泄露荣耀V40价格:3999元起?
  10. 【GPT-3】地表最强语言模型GPT-3的局限与出路
  11. 131 MySQL单表查询(重要)
  12. java实现验证码登录
  13. 微信开发者工具下载地址
  14. 读取头部姿态数据集300W_LP、AFLW2000、BIWI数据集中的真值
  15. IT大败局----第十章 企业公关的真谛
  16. flink on yarn使用第三方jars的方法如何查看进程所持有jar包
  17. 用批处理删除N天前或指定日期时间(前后)创建(或修改)的文件
  18. 慕尼黑工业大学开源含四季的数据集:用于自动驾驶的视觉长期定位
  19. 分享一套宾馆客房管理系统源码,功能完善,代码完整
  20. 【unity细节】关于资源商店(Package Maneger)无法下载资源问题的解决

热门文章

  1. 10个在线音乐分享中文网站推荐
  2. 多御安全浏览器新版下载 | 功能优秀性能出众
  3. 纬衡cad v2007 bt
  4. Task5.结构数组
  5. 2014款宝马I8插电式混合动力跑车将成为“蓝光”前灯的首秀:这是这一技术首次在欧洲汽车市场正式使用。
  6. 港湾设备石油设施锅炉地埋用无机富锌防腐底漆 漆膜干燥较快
  7. linux登陆域_Ubuntu 13.04 登录Windows域
  8. QQ登录界面功能测试条例
  9. 怎么看待美国化学品泄漏事故
  10. java ocsp校验_Nginx使用OCSP验证客户端证书