CT-ICP

传统 CNN 中,训练完成后所有 kernal 参数则固定。对于任意的输入,所有的 kernal 都对他们同等对待。所以为了提高模型 capacity,大多数方法堆叠卷积层(深度)或者增加卷积层的 channel 数(宽度),这种做法一定程度上可以提升模型能力,但会降低计算效率。

为了压缩模型,在增加模型 capacity 的同时不会增加太多参数和计算量,动态卷积在训练结束后,kernal 不再是一个定值,而是一个由 input 决定的变量。因此 kernal 相当于一个以 input 为自变量的函数。这种做法变相增加了模型 capacity,而模型参数和计算量是非常小的。

动态卷积

1 简介

随着模型参数的不断增加,计算成本也越来越高,对于一些对 latency 有较高要求的任务,显然是一种挑战。在传统 CNN 中,训练完成后所有 kernal 参数则固定。对于任意的输入,所有的 kernal 都对他们同等对待。所以为了提高模型 capacity,大多数方法堆叠卷积层(深度)或者增加卷积层的 channel 数(宽度),这种做法一定程度上可以提升模型能力,但会降低计算效率。

为了压缩模型,在增加模型 capacity 的同时不会增加太多参数和计算量,动态卷积在训练结束后,kernal 不再是一个定值,而是一个由 input 决定的变量。因此 kernal 相当于一个以 input 为自变量的函数。这种做法变相增加了模型 capacity,而模型参数和计算量是非常小的。

2 动态感知机

通过感知机模型引出动态卷积,设感知机模型如下,W,b,gW,b,g 分别表示权重、偏置和激活函数:

y=g(WTx+b)y = g(W^T x + b)

动态感知机的定义如下,动态感知机模型如 yy 式,聚合权重(W~(x)\tilde{W}(x))和聚合偏置(b~(x)\tilde{b}(x))通过 WW 式和 bb 式中多个权重和偏置加权求和得到。需要满足的条件约束是权重系数和为 1,这也说明了权重系数不固定,随着输入数据的变化而变化。

y=g(W~T(x)x+b~(x))W~(x)=k=1Kπk(x)W~kb~(x)=k=1Kπk(x)b~ks.t. 0πk(x)1, k=1Kπk(x)=1y = g(\tilde{W}^T(x) x + \tilde{b}(x)) \\ \tilde{W}(x) = \sum_{k=1}^K \pi_k (x) \tilde{W}_k \\ \tilde{b}(x) = \sum_{k=1}^K \pi_k (x) \tilde{b}_k \\ \mathrm{s.t.} \ 0 \le \pi_k (x) \le 1, \ \sum_{k=1}^K \pi_k (x) = 1

式中 πk\pi_k 表示第 kk 个线性函数 W~kTx+b~k\tilde{W}_k^T x + \tilde{b}_k 的 attention 权重,这个权重在输入 xx 不同的情况下是不同的。因此在给定 input 的情况下,动态感知器代表了该 input 的最佳线性函数组合。

3 动态卷积

图-1 动态卷积

动态卷积有 KK 个卷积核,这些卷积核有相同的大小,相同的输入和输出通道。

3.1 注意力

动态卷积采用压缩激励操作来计算卷积核注意力 πk(x)\pi_k(x)。首先,全局信息通过平均池化来进行压缩。然后,使用两个全连接层(之间采用 ReLU 激活函数)和 softmax 函数,来生成 KK 个卷积核的归一化注意力权重。其中,第一个全连接层用于降维。

3.2 聚合卷积核

由于核的尺寸较小,聚合卷积核的计算效率较高。同时,在 softmax 中采用了以恶很大的温度来平衡注意力:

πk=exp(zk/γ)jexp(zj/γ)\pi_k = \frac{\exp (z_k / \gamma)}{\sum_j \exp (z_j / \gamma)}

zkz_k 是注意力分支中的第二个全连接层的输出,γ\gamma 是温度,原始 softmax 中 γ=1\gamma = 1。采用退火技巧,在前 10 次迭代中将 γ\gamma 从 30 变成 1,可以进一步提高准确度。

4 代码实现样例(PyTorch)

4.1 attention 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
class Attention2d(nn.Module):
def __init__(self, in_planes, K):
super(Attention2d, self).__init__()
self.avgpool = nn.AdaptiveAvgPool2d(1)
self.fc1 = nn.Conv2d(in_planes, K, 1)
self.fc2 = nn.Conv2d(K, K, 1)

def forward(self, x):
x = self.avgpool(x)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x).view(x.size(0), -1)
return F.softmax(x, 1)

4.2 Dynamic Convolution 实现

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class DynamicConvolution2d(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0,
dilation=1, groups=1, bias=True, K=4):
super(DynamicConvolution2d, self).__init__()
assert in_planes % groups == 0
self.in_planes = in_planes
self.out_planes = out_planes
self.kernel_size = kernel_size
self.stride = stride
self.padding = padding
self.dilation = dilation
self.groups = groups
self.bias = bias
self.K = K
self.attention = Attention2d(in_planes, K)

self.weight = nn.Parameter(torch.Tensor(K, out_planes,
in_planes//groups, kernel_size,
kernel_size),
requires_grad=True)
if bias:
self.bias = nn.Parameter(torch.Tensor(K, out_planes))
else:
self.bias = None

def forward(self, x):
# 将 batch 视作维度变量进行组卷积,因为组卷积的权重不同,动态卷积的权重也不同
softmax_attention = self.attention(x)
batch_size, in_planes, height, width = x.size()
x = x.view(1, -1, height, width)
weight = self.weight.view(self.K, -1)

# 动态卷积权重的生成,生成 batch_size 个卷积参数
aggregate_weight = torch.mm(sofrmax_attention,
weight).view(self.out_planes, -1,
self.kernel_size,
self.kernel_size)
if self.bias is not None:
aggregate_bias = torch.mm(softmax_attention, slef.bias).view(-1)
output = F.conv2d(x, weight=aggregate_weight, bias=aggreagate_bias,
stride=self.stride, padding=self.padding,
dilation=self.dilation,
groups=self.groups*batch_size)
else:
output = F.conv2d(x, weight=aggregate_weight, bias=None,
stride=self.stride, padding=self.padding,
dilation=self.dilation,
groups=self.groups*batch_size)

output = output.view(batch_size, self.out_planes,
output.size(-2), output.size(-1))
return output

参考

源码