1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class CosineLinear(nn.Module):
def __init__(self, in_features, out_features, nb_proxy=1, to_reduce=False, sigma=True):
super(CosineLinear, self).__init__()
self.in_features = in_features
self.out_features = out_features * nb_proxy
self.nb_proxy = nb_proxy
self.to_reduce = to_reduce
self.weight = nn.Parameter(torch.Tensor(self.out_features, in_features))
if sigma:
self.sigma = nn.Parameter(torch.Tensor(1))
else:
self.register_parameter('sigma', None)
self.reset_parameters()

def reset_parameters(self):
stdv = 1. / math.sqrt(self.weight.size(1))
self.weight.data.uniform_(-stdv, stdv)
if self.sigma is not None:
self.sigma.data.fill_(1)

def forward(self, input):
out = F.linear(F.normalize(input, p=2, dim=1), F.normalize(self.weight, p=2, dim=1))

if self.to_reduce:
# Reduce_proxy
out = reduce_proxies(out, self.nb_proxy)

if self.sigma is not None:
out = self.sigma * out

return {'logits': out}

一、 类的构造函数 __init__

  • 输入参数:
    • in_features:输入特征的维度。
    • out_features:输出类别的数量(或者输出维度),但实际模块中的输出维度为 out_features * nb_proxy
    • nb_proxy:每个类别对应的代理(proxy)数量,默认为1。当设置大于1时,意味着每个类别用多个“代理”来表示。
    • to_reduce:布尔值标志,表示是否在前向传播中调用 reduce_proxies 函数对多个代理的输出进行整合。
    • sigma:布尔值标志,决定是否使用一个可学习的缩放参数 sigma
  • 模块参数:
    • self.weight:一个形状为 [out_features * nb_proxy, in_features] 的权重矩阵,作为线性层的参数。
    • self.sigma:如果 sigma 为 True,则定义一个可学习参数,用于后续对输出进行缩放;否则,不使用该参数。
  • 初始化调用:
    • reset_parameters() 被调用以对权重和 sigma 参数进行初始化。

二、权重初始化 reset_parameters

  • 使用均匀分布对 self.weight 进行初始化,范围为 [−1/in_features,1/in_features][-1/\sqrt{in_features}, 1/\sqrt{in_features}]。
  • 如果 sigma 存在,则将其初始化为1。

三、前向传播 forward

  • 输入归一化:
    • 对输入张量 input 和权重矩阵 self.weight 分别在第1维(特征维度)进行 L2 归一化。这一步保证了后续的点积实际上计算的是余弦相似度。
  • 线性变换:
    • 调用 F.linear 计算归一化后的输入与权重的乘积,其结果即为各类别的余弦相似度得分。
  • 代理整合(可选):
    • 如果 self.to_reduce 为 True,则调用 reduce_proxies(out, self.nb_proxy) 对输出结果进行代理整合。这里的 reduce_proxies 函数可能是将多个代理的相似度得分整合成一个代表类别的得分,但具体实现不在此代码中。
  • 缩放操作(可选):
    • 如果 self.sigma 存在,则将输出结果乘以这个可学习的缩放因子。这样可以自适应地调整余弦相似度的尺度,有助于训练时更好地收敛。
  • 返回结果:
    • 最终返回一个字典 {'logits': out},其中 logits 对应的是计算后的余弦相似度得分(可能经过代理整合和缩放)。

四、代理整合

1
2
3
4
5
6
7
8
9
10
11
12
def reduce_proxies(out, nb_proxy):
if nb_proxy == 1:
return out
bs = out.shape[0]
nb_classes = out.shape[1] / nb_proxy
assert nb_classes.is_integer(), 'Shape error'
nb_classes = int(nb_classes)

simi_per_class = out.view(bs, nb_classes, nb_proxy)
attentions = F.softmax(simi_per_class, dim=-1)

return (attentions * simi_per_class).sum(-1)

这段代码的作用是将每个类别对应的多个代理(proxy)的输出整合为一个单一的输出值,其整合方式采用了加权求和,权重由 softmax 得到。下面详细分析每一步:

1. 输入判断与初步处理

  • 判断代理数目
    如果 nb_proxy 等于1,则不需要整合,直接返回原始输出 out
  • 提取维度信息
    • bs 表示 batch size,即样本数量,取自 out.shape[0]
    • nb_classes 计算方式为 out.shape[1] / nb_proxy。这里先通过除法得到类别数,由于可能得到浮点数,因此使用 is_integer() 来断言结果确实为整数,然后将其转换为整数类型。

2. 代理重构

  • 重新排列张量
    out 重塑为形状 (bs, nb_classes, nb_proxy),使得每个类别的多个代理分开排列在最后一个维度中。

    1
    simi_per_class = out.view(bs, nb_classes, nb_proxy)

    这样,每个类别对应一个长度为 nb_proxy 的向量,表示该类别的多个相似度分值。


3. 计算权重并加权求和

  • 计算注意力权重
    对每个类别内的代理分值沿最后一个维度(即各代理之间)应用 softmax,使得每个代理获得一个归一化的权重:

    1
    attentions = F.softmax(simi_per_class, dim=-1)

    这样保证每个类别内所有代理的权重和为1,进而可以看作是对每个代理的重要性进行加权。

  • 加权求和
    对加权后的代理分值进行求和:

    1
    return (attentions * simi_per_class).sum(-1)

    这一操作会将每个类别内多个代理的相似度分值按其注意力权重整合成一个单一的分值。


4. 总结

  • 功能目的
    reduce_proxies 的主要目的是在有多个代理表示同一类别时,通过 softmax 计算每个代理的权重,再将代理的相似度分值加权求和,从而得到一个综合的相似度分值用于后续的分类决策或其他操作。
  • 优势
    • 动态加权:使用 softmax 根据各代理的相似度自动调整权重,不同样本可能选择不同的代理权重组合,从而提供更灵活的表达能力。
    • 统一输出:将多个代理的输出归约为一个统一的表示,便于后续处理。

整体来说,这种设计为类别表示引入了更多的灵活性,使得模型能够在面对类别内多样性时,通过动态调整各代理权重来获得更鲁棒的输出。

总结

  • 模块目的: 该模块实现了一个基于余弦相似度的分类器,其核心思想是将输入特征和权重都归一化后进行内积运算,得到余弦相似度。通过引入多个代理(proxy)和可学习的缩放因子 sigma,使得模型在处理类增量学习或者度量学习等任务时更具灵活性。
  • 扩展性: 通过 nb_proxyto_reduce 参数,可以方便地扩展到每个类别使用多个表示,并在必要时整合这些表示,从而提高分类的鲁棒性和表达能力。

这种设计在一些任务中(如人脸识别或细粒度分类)可以帮助模型更好地捕捉类别之间的细微差异,同时缓解由于特征分布变化带来的不稳定性。