设输入x为(6,32,10,10)的张量
测试代码:
class MyNet(nn.Module):
def __init__(self):
super(MyNet,self).__init__()
def forward(self,x):
x = self.lvc(x)
return x
model = MyNet()
y = torch.randn(6,32,10,10)
y = model(y)
print(y.size)
LVC:
class Encoding(nn.Module):
#目的:注意这段代码需要得到由num_codes视觉中心数下的,以视觉中心与图片中各个像素点的以通道数为组成的特征向量的相关性的基础下,得到的通道注意力机制的权重因子
def __init__(self, in_channels, num_codes):
super(Encoding, self).__init__()
# init codewords and smoothing factor
self.in_channels, self.num_codes = in_channels, num_codes
num_codes = 64
std = 1. / ((num_codes * in_channels)**0.5)
# [num_codes, channels]
self.codewords = nn.Parameter(
torch.empty(num_codes, in_channels, dtype=torch.float).uniform_(-std, std), requires_grad=True)
#相当于生成了一个(视觉中心数,通道数)尺寸的权重向量
# [num_codes]
self.scale = nn.Parameter(torch.empty(num_codes, dtype=torch.float).uniform_(-1, 0), requires_grad=True)
#注意这里生成的是(-1,0)是因为他需要被当做权重因子用不是,但是由于后面有个softmax的函数,
# 那么这个时候公式就是:e的-x(i)/【i从1到最后 e的-x(i)求和】,由于这里有个负号,所以需要规定在-1到0之间,这样就能充当权重因子了
@staticmethod
def scaled_l2(x, codewords, scale):
'''
目的:用于得到一组像素点到视觉中心的权重因子
1.这个方法则将输入向量x改成x_expanded(6,100,64,32)100是100个像素点,64是为了与64个视觉中心的关系相对应所以插入了64这个第三维度,32是针对于每个视觉中心的特征向量
2.而codewords也由(64,32)变成了reshaped_codewords(1,1,64,32)64代表64个视觉中心,32代表每个视觉中心的特征向量
注意:这里的特征向量是我们最终想要的,需要通过他进行通道注意力机制从而实现特征的提取
3.x_expanded(6,100,64,32)与reshaped_codewords(1,1,64,32)的相减,意思就是把每个像素点相对于视觉中心的特征向量与所有视觉中心的特征向量进行相减
这样做是为了找到像素点特征向量与每个视觉中心下的特征向量的相关性,所以直接在第三个维度加了个64,这样就可以将本来是表达像素点特征向量的一个张量,-改成-》,表达像素点与各个视觉中心特征向量关系的张量
4.这样我们就得到了(6,100,64,32)的张量,但是表示的是100个像素点,中每个像素点与设定的视觉中心的特征向量的距离。
5.所以这时候32个数组成的特征向量,不再是特征本身了,而是与设定视觉中心的距离关系
'''
num_codes, in_channels = codewords.size()
b = x.size(0)
expanded_x = x.unsqueeze(2).expand((b, x.size(1), num_codes, in_channels))#(6,100,64,32)
#注意这里之所以要expanded是为了和视觉中心的尺寸相匹配,也就是说,100个像素点下有
# ---处理codebook (num_code, c1)
reshaped_codewords = codewords.view((1, 1, num_codes, in_channels))#(1,1,64,32)相当于bk bk是码本中的设定的可以学习到的视觉中心
'''
1.这里可以这么理解,32代表像素点在32个通道的数值,实际上也表示的是在32个通道的数值,32个数值去表示了视觉中心的特征,64表示的则是我设定的64个视觉中心
2.所以这里就相当于设定了64个视觉中心,这64个视觉中心的位置,每一个位置分别用像素点在所有通道中的具体数值来表示
值得注意的是,这个视觉中心的位置需要我们去学习,奖罚机制的媒介是与原图像特征的距离信息,来源是最终的loss
'''
# 把scale从1, num_code变成 batch, c2, N, num_codes
reshaped_scale = scale.view((1, 1, num_codes)) #(1,1,64)相当于sk # N, num_codes sk相当于一个权重因子用于衡量这个视觉中心的重要性
# ---计算rik = z1 - d # b, N, num_codes
scaled_l2_norm = reshaped_scale * (expanded_x - reshaped_codewords).pow(2).sum(dim=3)
'''
用x本身的特征减去视觉中心的位置,然后平方再求和,就算得了视觉中心和欧几里得距离
(6,100,64)把最终的32个数构成的距离向量求和,这个时候我们就得到了这个像素点到所有视觉中心的距离抽象值,这个抽象值经过后续的变换可以当做权重因子来使用
所以后续用softmax对此进行计算的到权重因子assignment_weights
'''
return scaled_l2_norm
@staticmethod
def aggregate(assignment_weights, x, codewords):
#由scaled_l2方法已经得到了,每个像素点相对于每个视觉中心距离该乘以的权重因子的,这里就是用这个权重因子再乘以这个距离本身
num_codes, in_channels = codewords.size()
# ---处理codebook
reshaped_codewords = codewords.view((1, 1, num_codes, in_channels))#(1,1,64,32)
b = x.size(0)
# ---处理特征向量x b, c1, N
expanded_x = x.unsqueeze(2).expand((b, x.size(1), num_codes, in_channels))#(6,100,64,32)
#变换rei b, N, num_codes,-
assignment_weights = assignment_weights.unsqueeze(3)#(6,100,64,1) # b, N, num_codes,
# ---开始计算eik,必须在Rei计算完之后
encoded_feat = (assignment_weights * (expanded_x - reshaped_codewords)).sum(1)#(6,64,32)
'''
1.此处就是用expanded_x(6,100,64,32)中每个像素点对于每个视觉中心的特征向量再减去码本本身视觉中心的特征向量-》得到每个像素点对于视觉中心特征向量的距离
2.然后这个距离特征向量需要再乘以由距离特征向量本身平方求和再softmax后的权重值,这就可以使得距离远的他对应的权重值也大,乘以权重以后它更大了,所以距离远的特征就更容易被关注,抢的则更强。
3.最后再把每个像素点下的对于所有视觉中心的特征向量求和,我们就得到了一组以像素点与视觉中心相关性生成的张量为主要影响的64个视觉中心对应的32个通道数的张量
注意:
a.这个(6,64,32)的张量被用于生成可以进行通道注意力机制的张量。
b.通过这个张量进行的通道注意力机制操作可以实现定位角落区域的特征,
c.因为在最终的(6,100,64.32)距离远的特征中表达他本身的32个数组成的特征向量当中的数字会有高有低,这些有高有低的数字组成的向量就会在最后的sum(1)操作后,
d.即100个像素点的特征向量做合之后,占比重更大
e.(之所以占比重更大是他通过以上的两个方法实现的,以上的两个方法使得表达的特征向量与视觉中心具有较远距离的像素点的特征向量更为突出),
f.也就选出了可以使得角落区域更显著的通道的权重大小
'''
return encoded_feat
def forward(self, x):
assert x.dim() == 4 and x.size(1) == self.in_channels
b, in_channels, w, h = x.size()
# [batch_size, height x width, channels]
x = x.view(b, self.in_channels, -1).transpose(1, 2).contiguous()
# assignment_weights: [batch_size, channels, num_codes]
assignment_weights = F.softmax(self.scaled_l2(x, self.codewords, self.scale), dim=2)
'''
1.这里使用softmax函数,并且是在第三维度使用的
2.输入的self.scaled_l2(x, self.codewords, self.scale)是一个三维张量(6,100,64),这个张量的含义是,100个像素点与64个码本的相关性的一个数值
3.这里对这个相关性的数值进行softmax就相当于得到一个0-1之间的概率
4.所以此操作就得到了每个像素点到码本中各个点的的相关性概率
'''
# aggregate
encoded_feat = self.aggregate(assignment_weights, x, self.codewords)
return encoded_feat#(6,64,32)最终输出此张量
LVCBlock:
class LVCBlock(nn.Module):
def __init__(self, c1, c2, num_codes, channel_ratio=0.25, base_channel=64):
super(LVCBlock, self).__init__()
self.c2 = c2
self.num_codes = num_codes
num_codes = 64
self.conv_1 = ConvBlock(in_channels=c1, out_channels=c1, res_conv=True, stride=1)
self.LVC = nn.Sequential(
nn.Conv2d(c1, c1, 1, bias=False),
nn.BatchNorm2d(c1),
nn.ReLU(inplace=True),
Encoding(in_channels=c1, num_codes=num_codes),#这块能得到(6,32,64)的张量,32是通道数,64是码本视觉中心数
nn.BatchNorm1d(num_codes),#进行归一化
nn.ReLU(inplace=True),#取正值,去掉负值
Mean(dim=1))#进行平均化的操作,这里之所以在码本维度即64处进行均值,而不是其他的,是为了不让视觉中心本身去影响通道权重因子的结果
#经过lvc后会得到一组用于通道注意力机制的权重因子
self.fc = nn.Sequential(nn.Linear(c1, c1), nn.Sigmoid())
#注意输入(6,32)是一个二维张量,而linear按理说只能输入一维张量,这里之所以能进行linear是因为可以利用它们的广播机制和自动重塑功能来简化代码的实现
#这个广播机制说白了就是,如果你按理说需要输入的是n维张量,那你你就可以输入n+1维张量或者n维张量,输入之后他统一给你化简为需要的形状
#所以这里就是对6*32个数字进行了linear然后再返回6*32个数字然后再自动组合成(6,32)的张量
def forward(self, x):
x = self.conv_1(x, return_x_2=False)#(6,32,10,10)
en = self.LVC(x)#(6,32)
gam = self.fc(en)#(6,32)
b, in_channels, _, _ = x.size()
y = gam.view(b, in_channels, 1, 1)#(6,32,1,1)
x = torch.relu_(x + x * y)
#这里由于y是(6,32,1,1)所以需要广播机制变成(6,32,10,10)将最后两个维度的数复制100次,与x相对应后再相乘
#这里相当于对每个通道都乘以一个权重因子么,也算是通道注意力机制吧,不过他这个权重计算的很麻烦用了LVC后又用了fc
#值得注意的是,这里的通道注意力的权重是通过像素点与视觉中心的距离为基本要素得到的,最终的一个效果按论文来说的话就是得到了边缘的特征
return x