一.Selective Kernel Networks
1.介绍:
SKNet是SENet的加强版,让每一个神经元能够动态的调整感受野的大小。
2.大体流程:
1.Split
首先对原本C*H*W的输入经过多个(自己定义,图中为3个)大小不同的卷积核得到U1,U2,U3三个特征图。
注意,这里涉及了空洞卷积的内容。
2.Fuse
为了融合多个感受野的信息,这里得出U=U1+U2+U3的特征图。
之后在H,W维度求平局值,得到一个关于channel信息的C*1*1的一维向量,该向量代表通道的重要程度。
最后用一个线性变换将原本的C维映射成为d维的信息,进行信息的抽取。(???)
其中L默认设置为32。
3.Select
首先用三个线性变换,将Z维恢复为C维,让后使用softmax对c维进行归一化,这时每个channel对应一个分数,代表channal的重要程度。
之后将得到的三个值分别乘以对应的U1,U2,U3,得到A1,A2,A3,然后将其相加,进行特征融合,的到最后的融合多个感受野信息的模块A。
import torch import torch.nn as nn import torch.nn.functional as F class SKConv(nn.Module): def __init__(self,in_channels,r=16,L=32): nn.Conv2d super(SKConv,self).__init__() self.in_channels = in_channels d = max(round(in_channels/r),L) self.conv_A = nn.Conv2d(in_channels,in_channels,3,stride=1,padding=1,groups=32,bias=False) self.bn_A = nn.BatchNorm2d(in_channels) self.conv_B = nn.Conv2d(in_channels,in_channels,3,stride=1,padding=2,dilation=1,groups=32,bias=False) #dilation=2表示使用空洞卷积 self.bn_B = nn.BatchNorm2d(in_channels) self.globalAvgPool = nn.AdaptiveAvgPool2d((1,1)) self.conv_fc1 = nn.Conv2d(in_channels,d,1,bias=False) self.bn_fc1 = nn.BatchNorm2d(d) self.conv_fc2 = nn.Conv2d(d,2*in_channels,1,bias=False) #前一半结果是第一个分支的,后一半结果是第二个分支的 def forward(self,x): dA = F.relu(self.bn_A(self.conv_A(x))) dB = F.relu(self.bn_B(self.conv_B(x))) print(dA.shape) print(dB.shape) out = self.globalAvgPool(dA+dB) out = F.relu(self.bn_fc1(self.conv_fc1(out))) out = self.conv_fc2(out) out = out.reshape(-1,2,self.in_channels,1,1) out = F.softmax(out,1) dA = dA * out[:,0] dB = dB * out[:,1] out = dA + dB return out
二:Strip Pooling:Rethinking Spatial Pooling for Scene Parsing
1.目的:传统的N*N的池化层对于长而窄的目标,往往会丢失部分信息;在处理不规则形状的目标时,它会包含许多不相关的区域。
2.Strip Pooling
对于一个H*W的输入,分别使用一个H*1的strip pool和1*W的strip pool进行平均值池化,得到两个输出。这两个输出专注于捕获局部的细节。
之后用1*1卷积对两个输出进行扩张(到H*W),然后经过1*1卷积和Sigmoid函数,最后将该值与原输入进行element-wise multiplication,得到输出(注意力机制)。
3.Mixed Pooling Module
目的是通过池化操作来收集不同类型的上下文信息,使特征更具有鉴别性。
(1)首先了解金字塔池化结构(PPM):参考原文链接:https://blog.csdn.net/rocking_struggling/article/details/108550637
(2)作者改进了PPM,设计了MPM:
如上图(a),为原始PPM,可以捕获较短距离的特征间的依赖关系
如上图(b),MPM可以捕获更长距离特征之间的依赖关系。
作后将这两个分支拼接起来,进行卷积升维。
这里参考大佬代码:
import torch import torch.nn as nn import torch.nn.functional as F class MPMBlock(nn.Module): def __init__(self,in_channels,pool_size): ''' Parameters ---------- in_channels : TYPE 输入通道. pool_size : TYPE 第一个分支中前两个通路进行池化后的尺寸. Returns ------- None. ''' super(MPMBlock,self).__init__() hide_channels = round(in_channels/4) #数据进入两个分支前先用1x1卷积降维 self.redu_conv1 = nn.Sequential(nn.Conv2d(in_channels,hide_channels,1,bias=False), nn.BatchNorm2d(hide_channels), nn.ReLU(inplace=True)) self.redu_conv2 = nn.Sequential(nn.Conv2d(in_channels,hide_channels,1,bias=False), nn.BatchNorm2d(hide_channels), nn.ReLU(inplace=True)) #定义第一个分支的操作 #可以注意到有三个分路,其中最下面的并没有进行池化,所以我们定义两个池化层 self.pool1_1 = nn.AdaptiveAvgPool2d(pool_size[0]) self.pool1_2 = nn.AdaptiveAvgPool2d(pool_size[1]) #接着定义这三个分路的卷积层 self.conv1_1 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,3,1,1,bias=False), nn.BatchNorm2d(hide_channels)) self.conv1_2 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,3,1,1,bias=False), nn.BatchNorm2d(hide_channels)) self.conv1_3 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,3,1,1,bias=False), nn.BatchNorm2d(hide_channels)) #接着定义将上面三个卷积层输出相加后,再进行的卷积操作 self.conv1_4 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,3,1,1, bias=False), nn.BatchNorm2d(hide_channels), nn.ReLU(True)) #定义第二个分支的操作 #首先是两个条形池化,这里用的是1x3和3x1 self.pool2_1 = nn.AdaptiveAvgPool2d([1,None])#例如32x3x64x64的输入经过这一步操作,得到输出32x3x1x64 self.pool2_2 = nn.AdaptiveAvgPool2d([None,1]) #接着是两个卷积层扩充条形池化的结果 #注意此处使用的卷积核也是条形的 self.conv2_1 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,(1,3),1,(0,1),bias=False), nn.BatchNorm2d(hide_channels)) self.conv2_2 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,(3,1),1,(1,0),bias=False), nn.BatchNorm2d(hide_channels)) #接着定义将上面两个卷积层输出相加后,再进行的卷积操作 self.conv2_3 = nn.Sequential(nn.Conv2d(hide_channels,hide_channels,(3,1),1,(1,0),bias=False), nn.BatchNorm2d(hide_channels), nn.ReLU(inplace=True)) #最后将两个分支的结果拼接起来,进行卷积升维,使维度同原始输入一致 self.conv3 = nn.Sequential(nn.Conv2d(hide_channels*2,in_channels,1,bias=False), nn.BatchNorm2d(in_channels)) def forward(self,x): #F.interpolate是将输入数据上采样或者下采样到你输入的维度,可以指定采样方法等,有些复杂,并未深入研究 #通过F.interpolate就可以使多个分支的输出维度保持一致,方便后面操作 _,_,h,w = x.shape x1 = self.redu_conv1(x) x2 = self.redu_conv2(x) x1_1 = F.interpolate(self.conv1_1(self.pool1_1(x1)),(h,w)) x1_2 = F.interpolate(self.conv1_2(self.pool1_2(x1)),(h,w)) x1_3 = self.conv1_3(x1) x1 = self.conv1_4(F.relu(x1_1 + x1_2 + x1_3)) print(x1.shape) x2_1 = F.interpolate(self.conv2_1(self.pool2_1(x2)),(h,w)) x2_2 = F.interpolate(self.conv2_2(self.pool2_2(x2)),(h,w)) x2 = self.conv2_3(F.relu(x2_1+x2_2)) out = F.relu(self.conv3(torch.cat([x1,x2],dim=1))) #在channel维度拼接 return out + x
三.HRNet:Deep High-Resolution Representation Learning for Human Pose Estimation
与常规的多尺度特征提取网络(高分辨率-->低分辨率-->高分辨率)不同,HRNet在整个特征提取的过程中,始终保持着高分辨率特征图,在实现多尺度特征提取的过程中,在高分辨率特征图主网络逐渐并行加入低分辨率特征图子网络,不同网络实现多尺度融合和特征提取。
1.人体姿态估计:人体姿态估计,又称关键点检测,是指从大小为W×H×3的图像I中检测出K个关键点或身体部位(如肘部、腕部等)的位置。
2.并行多分辨率卷积
本文的核心思想,从一个高分辨率卷积作为第一阶段,逐步添加高分辨率到低分辨率的流,形成新的阶段,并行连接多分辨率流。即后一阶段的并行流的分辨率由前一阶段的并行流和更低的分辨率组成。
如图所示,其中Nsr表示第s阶段的r分辨率指数(前一阶段的r为后一阶段的)
3.重复多尺度融合
下面以第三阶段为例:
下采样:使用步长为2的3*3卷积进行下采样,如上图(3)所示,如果要进行4倍降采样,则采用两个串行的卷积。
上采样:最近邻插值
4.网络具体化:
网络由四个阶段组成,由四个并行的子网络组成,其分辨率逐渐下降到一半,网络宽度(通道数量)增加到原来的两倍。