CARAFE上采样算子

CARAFE

​ 在图像任务中,不同的部分图像对操作的要求不一样(比如图像边界需要提出边界信息,插值的方法就不如反最大值池化的操作;图像内部我们又需要邻域像素点的信息进行上采样的操作,这种情况下反最大值池化又不如插值方法),所以我们不能在图像中简单地用一个算子进行上采样,CARAFA是基于图像内容特征进行的采样方式。

上采样方式:

CARAFE net

kernel Prediction Module

​ 产生上采样算子的模块,Channel Compresser进行的一部是1×1×C1 \times 1 \times C 的卷积操作,这部的目的是减少参数量,减少Computational cost。Content Encoder就是一个全卷积层,通过卷积感知邻域信息进行编码,注意卷积核的数量要符合σ2×kup2\sigma^2 \times k_{up}^2 以满足后续的上采样操作,再将得到的feature map展平为σH×σW×kup2\sigma H \times \sigma W \times k_{up}^2 ,再经过一次正则化就产生了上采样算子

Content-aware Reassembly Module

​ 论文中上采样操作为:对于原图像中任一个像素点 x\mathscr{x} ,设N(x,kup)N(\mathscr{x}, k_{up}) 为以 x\mathscr{x} 为中心,边长为kupk_{up}的方邻域,将原 feature map 先后进行通道压缩,内容编码,变换形状后再进行正则化操作得到上采样算子,在将原 feature map 的N(x,kup)N(\mathscr{x}, k_{up}) 和上采样算子的对应点沿通道进行相乘,运算如上图所示,这样得到的feature map的大小变为了原来的σ2\sigma^2 倍,而通道数不变,这样就完成了上采样的操作。

下采样方式:

CARAFE_net

​ 下采样的结构与上采样结构相似,只是在 Content_Encoder 部分使用了步幅为 σ\sigma 的卷积,以此生成对应数量的采样核,因为大小已经满足需要,后续就不需要进行 pixeL-shuffle 操作了,接下来的过程与上采样相同

上下采样算子的协同使用:

​ 论文中提到了:Experimental results show that adopting CARAFE++ in both upsampling and downsampling can consistently and substantially outperforms CARAFE on object detection, instance segmentation, semantic segmentation and image inpainting

​ 这启发我们再后续的工作中探讨:上下采样算子的对应关系是否会影响采样的性能,我们能不能找出一般化的上下采样算子的对应准则?即去讨论上下采样算子的协同性,以此去指导上下采样过程

Question:

文章中:Note that these spatially adaptive weights are not learned as network parameters. Instead, they are predicted on-the-fly, using a lightweight fully-convolutional module with softmax activation.

not learned bur predicted 的意思是什么?

​ 动态卷积核并不是在学习卷积核,如果学习的是卷积核,那么学习完成之后的卷积核依然是静态的。backward 学习的是:根据输入 feature map 而生成的生成卷积核的函数,这样卷积核就是动态生成的了。

代码实现:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import torch
import torch.nn as nn
import torch.nn.functional as F
import einops


class CARAFE_downsample(nn.Module):
def __init__(self, c_in, c_mid, encoder_size, ratio=2, k_up=5):
super().__init__()
padding = 2 # 只允许 encoder_size=5, ratio=2
self.ratio = ratio
self.k_up = k_up
self.unfold_padding = 2 # k_up=5, ratio=2
self.comp = nn.Conv2d(c_in, c_mid, kernel_size=1, padding=0)
self.content_encoder = nn.Conv2d(c_mid, k_up**2, kernel_size=encoder_size,
stride=ratio, padding=padding)


def forward(self, x):
_, _, _, W = x.size()
y = self.comp(x)
y = self.content_encoder(y)
y = F.softmax(y, dim=1)

z = F.unfold(x, kernel_size=self.k_up, stride=self.ratio, padding=self.unfold_padding)
z = einops.rearrange(z, 'b (c k_up2) (h w) -> b k_up2 c h w',
k_up2=self.k_up**2, w=W//self.ratio)
x = torch.einsum('bkchw,bkhw->bchw',[z, y])
return x


class CARAFE_upsample(nn.Module):
def __init__(self, c_in, c_mid, encoder_size, ratio=2, k_up=5):
super().__init__()
padding = (encoder_size-1)//2 if encoder_size%2==0 else encoder_size//2
self.ratio = ratio
self.k_up = k_up
self.unfold_padding = (k_up-1)//2 if k_up%2==0 else k_up//2
self.comp = nn.Conv2d(c_in, c_mid, kernel_size=1, padding=0)
self.content_encoder = nn.Conv2d(c_mid, (ratio*k_up)**2, kernel_size=encoder_size,
padding=padding)


def forward(self, x):
_, _, _, W = x.size()
y = self.comp(x)
y = self.content_encoder(y)
y = F.pixel_shuffle(y, upscale_factor=self.ratio)
y = F.softmax(y, dim=1)

z = F.unfold(x, kernel_size=self.k_up, padding=self.unfold_padding)
z = einops.rearrange(z, 'b (c k_up2) (h w) -> b k_up2 c h w',
k_up2=self.k_up**2, w=W)
z = einops.repeat(z, 'b k c h w -> ratio_2 b k c h w', ratio_2=self.ratio**2)
z = einops.rearrange(z, '(r1 r2) b k_up2 c h w -> b k_up2 c (h r1) (w r2)',
r1=self.ratio)
x = torch.einsum('bkchw,bkhw->bchw',[z, y])
return x

if __name__ == '__main__':
# torch.manual_seed(0)
# x = torch.Tensor(3, 16, 24, 24)
# carafe1 = CARAFE_upsample(16, 64, 5, ratio=2)
# oup = carafe1(x)
# print(oup.size())

# x = torch.Tensor(3, 16, 24, 24)
# carafe2 = CARAFE_downsample(16, 64, 5, ratio=2)
# oup = carafe2(x)
# print(oup.size())
  • CARAFE在上采样时,由于生成的卷积算子要求原图像中一个点对应 σ2\sigma ^2 个点,这样会导致卷积核移位的速度不一样,不容易求和。所以使用 F.interpolate 将原图像膨胀 σ2\sigma ^2
  • 要求原 feature map 中每一个点的邻域时,使用 F.Unfold 函数可以求出每个窗口
  • 最后一步进行 einsum 操作时,将生成的四维卷积算子升为五维,多出来的一维专门用来求和,这样求和操作就变得很方便直观

参考文献

CARAFE: Content-Aware ReAssembly of FEatures