赞
踩
目录:
SENet和SKNet很像,均为attention相关网络结构
SENet: Squeeze-and-Excitation Network
涉及领域:attention、resnet
解决:通过学习不同通道的重要程度来提升有用的特征,抑制无用的特征。
论文 官方代码 pytorch代码 讲解文章
个人理解:将c个HxW大小特征图池化为1*1大小,通道数不变,再对池化后c个特征图进行fc处理,获得每个channel的权重W1,W2……Wc,每个权重与原特征图相乘得到最终图,w1 x feature1 + w2 x feature2 + w3 x feature3+……,主要是获取不同channel的权重,每个channel代表了图片的不同特征,比如纹理、颜色等,具体channel意义可以参考:https://www.zhihu.com/question/346774831/answer/996370726?utm_source=com.yinxiang
## Pytorch 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)
SKNet: Selective Kernel Networks
网络对不同尺度对目标有不同效果,能够捕获不同尺度的目标物体
论文
官方代码
pytorch代码
参考文章1
参考文章2
个人理解:通过不同的感受野,并对不同感受野结果赋予不同权重,可以同时照顾到小目标和大目标
文章关注点主要是不同大小的感受野对于不同尺度的目标有不同的效果,而我们的目的是使得网络可以自动地利用对分类有效的感受野捕捉到的信息。
为了解决这个问题,作者提出了一种在CNN中对卷积核的动态选择机制,该机制允许每个神经元根据输入信息的多尺度自适应地调整其感受野(卷积核)的大小。其灵感来源是,我们在看不同尺寸不同远近的物体时,视觉皮层神经元接受域大小是会根据刺激来进行调节的。具体是设计了一个称为选择性内核单元(SK)的构建块,其中,多个具有不同内核大小的分支在这些分支中的信息引导下,使用SoftMax进行融合。由多个SK单元组成SKNet,SKNet中的神经元能够捕获不同尺度的目标物体。
import torch.nn as nn import torch from functools import reduce class SKConv(nn.Module): def __init__(self,in_channels,out_channels,stride=1,M=2,r=16,L=32): ''' :param in_channels: 输入通道维度 :param out_channels: 输出通道维度 原论文中 输入输出通道维度相同 :param stride: 步长,默认为1 :param M: 分支数 :param r: 特征Z的长度,计算其维度d 时所需的比率(论文中 特征S->Z 是降维,故需要规定 降维的下界) :param L: 论文中规定特征Z的下界,默认为32 ''' super(SKConv,self).__init__() d=max(in_channels//r,L) # 计算向量Z 的长度d self.M=M self.out_channels=out_channels self.conv=nn.ModuleList() # 根据分支数量 添加 不同核的卷积操作 for i in range(M): # 为提高效率,原论文中 扩张卷积5x5为 (3X3,dilation=2)来代替。 且论文中建议组卷积G=32 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(1) # 自适应pool到指定维度 这里指定为1,实现 GAP 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) # 指定dim=1 使得两个全连接层对应位置进行softmax,保证 对应位置a+b+..=1 def forward(self, input): batch_size=input.size(0) output=[] #the part of split for i,conv in enumerate(self.conv): #print(i,conv(input).size()) output.append(conv(input)) #the part of fusion U=reduce(lambda x,y:x+y,output) # 逐元素相加生成 混合特征U s=self.global_pool(U) z=self.fc1(s) # S->Z降维 a_b=self.fc2(z) # Z->a,b 升维 论文使用conv 1x1表示全连接。结果中前一半通道值为a,后一半为b a_b=a_b.reshape(batch_size,self.M,self.out_channels,-1) #调整形状,变为 两个全连接层的值 a_b=self.softmax(a_b) # 使得两个全连接层对应位置进行softmax #the part of selection a_b=list(a_b.chunk(self.M,dim=1))#split to a and b chunk为pytorch方法,将tensor按照指定维度切分成 几个tensor块 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)) # 权重与对应 不同卷积核输出的U 逐元素相乘 V=reduce(lambda x,y:x+y,V) # 两个加权后的特征 逐元素相加 return V class SKBlock(nn.Module): ''' 基于Res Block构造的SK Block ResNeXt有 1x1Conv(通道数:x) + SKConv(通道数:x) + 1x1Conv(通道数:2x) 构成 ''' expansion=2 #指 每个block中 通道数增大指定倍数 def __init__(self,inplanes,planes,stride=1,downsample=None): super(SKBlock,self).__init__() self.conv1=nn.Sequential(nn.Conv2d(inplanes,planes,1,1,0,bias=False), nn.BatchNorm2d(planes), nn.ReLU(inplace=True)) self.conv2=SKConv(planes,planes,stride) self.conv3=nn.Sequential(nn.Conv2d(planes,planes*self.expansion,1,1,0,bias=False), nn.BatchNorm2d(planes*self.expansion)) self.relu=nn.ReLU(inplace=True) self.downsample=downsample def forward(self, input): shortcut=input output=self.conv1(input) output=self.conv2(output) output=self.conv3(output) if self.downsample is not None: shortcut=self.downsample(input) output+=shortcut return self.relu(output) class SKNet(nn.Module): ''' 参考 论文Table.1 进行构造 ''' def __init__(self,nums_class=1000,block=SKBlock,nums_block_list=[3, 4, 6, 3]): super(SKNet,self).__init__() self.inplanes=64 # in_channel=3 out_channel=64 kernel=7x7 stride=2 padding=3 self.conv=nn.Sequential(nn.Conv2d(3,64,7,2,3,bias=False), nn.BatchNorm2d(64), nn.ReLU(inplace=True)) self.maxpool=nn.MaxPool2d(3,2,1) # kernel=3x3 stride=2 padding=1 self.layer1=self._make_layer(block,128,nums_block_list[0],stride=1) # 构建表中 每个[] 的部分 self.layer2=self._make_layer(block,256,nums_block_list[1],stride=2) self.layer3=self._make_layer(block,512,nums_block_list[2],stride=2) self.layer4=self._make_layer(block,1024,nums_block_list[3],stride=2) self.avgpool=nn.AdaptiveAvgPool2d(1) # GAP全局平均池化 self.fc=nn.Linear(1024*block.expansion,nums_class) # 通道 2048 -> 1000 self.softmax=nn.Softmax(-1) # 对最后一维进行softmax def forward(self, input): output=self.conv(input) output=self.maxpool(output) output=self.layer1(output) output=self.layer2(output) output=self.layer3(output) output=self.layer4(output) output=self.avgpool(output) output=output.squeeze(-1).squeeze(-1) output=self.fc(output) output=self.softmax(output) return output def _make_layer(self,block,planes,nums_block,stride=1): downsample=None if stride!=1 or self.inplanes!=planes*block.expansion: downsample=nn.Sequential(nn.Conv2d(self.inplanes,planes*block.expansion,1,stride,bias=False), nn.BatchNorm2d(planes*block.expansion)) layers=[] layers.append(block(self.inplanes,planes,stride,downsample)) self.inplanes=planes*block.expansion for _ in range(1,nums_block): layers.append(block(self.inplanes,planes)) return nn.Sequential(*layers) def SKNet50(nums_class=1000): return SKNet(nums_class,SKBlock,[3, 4, 6, 3]) # 论文通过[3, 4, 6, 3]搭配出SKNet50 def SKNet101(nums_class=1000): return SKNet(nums_class,SKBlock,[3, 4, 23, 3]) if __name__=='__main__': x = torch.rand(2, 3, 224, 224) model=SKNet50() y=model(x) print(y) # shape [2,1000]
todo:keras版本两个代码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。