在darknet框架中(C语言实现),max pooling实现方式如下:
void forward_maxpool_layer(const maxpool_layer l, network net)
{
int b,i,j,k,m,n;
int w_offset = -l.pad;
int h_offset = -l.pad;
int h = l.out_h;
int w = l.out_w;
int c = l.c;
for(b = 0; b < l.batch; ++b){
for(k = 0; k < c; ++k){
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;
}
}
}
}
}
其中,函数的参数意义如下:
maxpool_layer:池化层(包含layer层的输出)
network:网络结构(包含layer层的输入)
下面逐步分析代码:
int w_offset = -l.pad; // max pooling层可能会进行padding,因此在后续计算坐标点时需要考虑偏移量
int h_offset = -l.pad;
int h = l.out_h; // 输出特征图的宽、高和通道数
int w = l.out_w;
int c = l.c;
for(b = 0; b < l.batch; ++b){
for(k = 0; k < c; ++k){
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;
max pooling layer输出的数据一般都是4D Tensor,维度为[batch, channels, height, width]。上述四个for循环就是为了遍历这个4D Tensor,确定输出特征图上每个点的值。由于是max操作,因此设置当前最大值为系统最小。
在C的代码中,首先要知道4D Tensor是如何存储的。其实是采用了一维数组来进行存储,并通过指针访问。
因此假设1D Tensor长度为length,将其分为batch份,表示每个图片所占的部分(或者说特征图),记为length_p。在每个length_p区域,又可以分成channels份,表示特征图每个channel所占的区域,记为length_c。在每个length_c区域,又可以分成h份,表示每个
单个特征图上每一行所占的区域,记为length_r。在每个length_r区域,又可以分为w份。

因此,4D Tensor的输出特征图上某个点在1D Tensor中的位置就是out_index 。
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;
}
}
这里两个循环是为了找到输出点out_index对应的输入特征图上的区域中的最大值,其中,输入特征图也是一个1D Tensor,因此输入位置由index计算得到。
l.output[out_index] = max;
l.indexes[out_index] = max_i;
最后对输出位置赋值即可。l.indexes记录了该点是从输入特征图上的哪个点计算得到的,在反向传播的时候使用。
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){
int index = l.indexes[i];
net.delta[index] += l.delta[i];
}
}
池化过程的反向传播较为简单。
l.indexes记录了输出特征图每个点的来源,因此遍历l.indexes,在输入特征图上对应点的的梯度敏感度(或者说系数)为1,直接将反向传回来的梯度累加即可。(累加的原因是:在池化过程中,可能池化区域重合,输出特征图上多个点可能都是由该点计算而来,因此反向传播过程中,该点的梯度应该累加)
补,关于池化过程的梯度如何回流:
例如2*2池化,输入4个点,则输出为1个点,该点对应的梯度在回流过程中,仅传给4个点中最大的那个点(因为输出的点是由该点得到的);平均池化中,梯度平均分给四个点。
https://github.com/pjreddie/darknet
本文详细解析了Darknet框架(用C语言实现)中的最大池化层操作。通过四个for循环遍历4D Tensor,确定输出特征图上每个点的最大值。在反向传播时,由于记录了输出点的来源,只需将梯度累加到输入特征图对应的最大值点。最大池化中,输出点的梯度只传给最大值输入点,而平均池化则平均分配给所有输入点。

1191

被折叠的 条评论
为什么被折叠?



