(一)Se-net的原理和思路
Se-net严格来说是一个小结构,它可以直接插入已有的网络结构中,帮助原有结构获得更好的效果,如插入Resnet网络中。
Se-net的整个流程如下:
(1)假设一个特征图,它的维度是(1,3,255,255),首先将它进行一般卷积。这个过程也可以是其他网络的操作过程。之后我们得到一个新的维度的特征图,假设卷积块为64个,那么输出的特征图的维度为(1,64,255,255)。
(2)在以往的网络中,我们仅仅是对网络通过深度学习的方法来优化参数的值,这Se-net中我们引入了通道注意力机制这是因为64个通道代表了图像不同的特征分布,引入注意力机制我们可以给不同通道不同的权重,以便于该通道在后续的计算中占据更重要的成分,这样有助于我们更好的捕捉不同区域的特征。
所以这里我们首先需要抽象提取出每一个通道的整体特征参数,这里根据论文的测试,选用的全局平均池化,它将维度从(1,64,255,255)可以变化为(1,64,1,1)然后通过全连接->激活->全连接->sigmoid的连接获得每一个通道的权重参数。这里用sigmoid也是根据测试得出来的。然后在用简单的乘法对原数据进行权重增减。这样就获得了一个新的特征图,它在理论上能够更好的表达的图片的特征。
(二)Se-net网络代码实现
以下只给出一个网络本身的定义,代码还是很好理解的,在 super里定义清楚每一层的输入输出量就可以了,这里指的注意的是他有一个reduction量,他是用来调节线性层负责度的参数,在综合性能和复杂度的基础上选择16作为默认值。
class SELayer(nn.Module):
def __init__(self, channel, reduction=16):
super(SELayer, self).__init__()
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Sequential(
nn.Linear(channel, channel // reduction, bias=False),
nn.ReLU(inplace=True),
nn.Linear(channel // reduction, channel, bias=False),
nn.Sigmoid()
)
def forward(self, x):
b, c, _, _ = x.size()
y = self.avg_pool(x).view(b, c)
y = self.fc(y).view(b, c, 1, 1)
return x * y.expand_as(x) 。
(三)Se-net+Resnet
左图是ResNet结构,右图是ResNet和Se-net联合的网络,可以看到添加的部分就是Se-net的核心。
这里也就是在基础的Resnet中添加了Se-net模块,这里这个模块应该添加在哪里在论文中也是做了比较的,给的代码只是一个参考位置。
self.conv1 = nn.Conv2d(mid_channels, mid_channels, 3, 1, 1, bias=True)
self.conv2 = nn.Conv2d(mid_channels, mid_channels, 3, 1, 1, bias=True)
self.avd_layer = nn.AvgPool2d(3, 1, padding=1)
self.relu = nn.ReLU(inplace=True)
self.se = SELayer(64, 16)
residual = x
out = self.conv1(x)
out = self.relu(out)
out = self.conv2(out)
out = self.se(out)
out += residual
out = self.relu(out)
return out
(四)SK-net的原理和思路
省略掉过程其实SK-net和Se-net的思路是有贴近的地方的,它们都是输入一个图片特征,通过一定处理使得卷积后的图片特征的每个通道的权重不一样。Se-net是根据注意力机制,学习到了不同的权重,而SK-net是根据自适应卷积的方式,来挑选和融合出不同的卷积,也就是每一个通道都可以选择最适合自己的卷积块(以及使用不同的感受野来获取图像特征)。
SK-net的整个流程如下:
(1)Split划分
首先使用不同的卷积核获得两个不同的特征图。
(2)Fuse融合
融合两个不同的特征图,然后将它们通过全局平均池化的方式提取出每一个通道的融合值。再通过线性层进行通过压缩,这里和Se-net的通道注意力是一样的。
(3)Select选择
在通过一个Softmax层进行选择,后续的操作和Se-net是一样的,得到通过挑选的特征图。这里的特征图可以看到每一个通道所使用的卷积核是不一样的,也是通过学习得到的。
(五)SK-net网络代码实现
在网络里主要有①获得不同的卷积块②全局平均池化层③线性层④softmax层。所以在super函数里主要就是对这几个部分进行初始化。同时因为卷积块可以有多个不只有两个,线性层的压缩量等参数都可以自己设定,所以也需要初始化。这里的d就是线性层压缩后的通道数量,M是采用的卷积块个数,首先要初始化卷积不同的卷积块,再定义全局平均池化,再定义几个线性层和softmax函数,注意每一层的输入输出值。
d = max(in_channels//r, L)
self.M = M
self.out_channels = out_channels
self.conv = nn.ModuleList()
for i in range(M):
self.conv.append(nn.Sequential(nn.Conv2d(in_channels, out_channels, 3, stride, padding=1+i, dilation=1+i, groups=32, bias=False),
nn.BatchNorm2d(out_channels),
nn.ReLU(inplace=True)))
self.global_pool = nn.AdaptiveAvgPool2d(output_size=1)
self.fc1 = nn.Sequential(nn.Conv2d(out_channels, d, 1, bias=False),
nn.BatchNorm2d(d),
nn.ReLU(inplace=True))
self.fc2 = nn.Conv2d(d, out_channels*M, 1, 1, bias=False)
self.softmax = nn.Softmax(dim=1)
在前馈函数里,首先需要执行split的过程,使用reduce将对初始特征图采用不同卷积块卷积后的特征图进行相加。然后全局池化,之后进行两个线性层,再通过softmax,后续操作和Se-net相同,注意一下每一层的参数,和怎么进行拼贴等问题就行了。
def forward(self, input):
batch_size = input.size(0)
output = []
for i, conv in enumerate(self.conv):
output.append(conv(input))
U = reduce(lambda x, y:x+y, output)
s = self.global_pool(U)
z = self.fc1(s)
a_b = self.fc2(z)
a_b = a_b.reshape(batch_size,self.M,self.out_channels,-1)
a_b = self.softmax(a_b)
a_b = list(a_b.chunk(self.M,dim=1))
a_b = list(map(lambda x:x.reshape(batch_size,self.out_channels,1,1),a_b))
V = list(map(lambda x,y:x*y,output,a_b))
V = reduce(lambda x,y:x+y,V)
return V
SK-net网络和Se-net网络其实是一样的不能称作一个完整的网络,它可以是一个增加模块嵌入到一些网络中,SK-net也可以嵌入和Resnet中进行补强,嵌入和方法和Se-net类似。