LinkNet学习及实现

  1. 1. LinkNet学习及实现
    1. 1.1. 引言和相关工作
    2. 1.2. 网络结构
    3. 1.3. 代码实现
    4. 1.4. 参考资料

LinkNet学习及实现

LinkNet的论文篇幅不多,网络也很简单。首先说一下应用背景,LinkNet想要做的任务是实时的图像分割。就是需要响应时间短,准确率高的一个网络。虽然现在的算法都可以达到很高的准确率,但是都没有把重点放在有效的利用神经网络的参数上。在参数和操作数量方面很多,导致了缓慢。实时的场景理解和图像分割可以应用在自动驾驶,增强现实等场景。

引言和相关工作

​ 由于增强现实和自动驾驶汽车等任务的激增,许多研究人员已经将他们的研究重点转向场景理解,其中涉及的一个主要步骤是像素级分类/语义分割。受到Auto-Encoder的启发,大多数的现有的语义分割技术使用的是encoder-decoder的结构。Encoder将信息编码到特征空间,解码器将这些信息映射到类别空间用来分割表示。尽管语义分割针对的是需要实时操作的应用程序,讽刺的是大多深度网络需要大量的处理时间。像YOLO,Fast RNN,SSD专注于实时对象检测,但在语义分割上几乎没有任何工作。

​ 论文中分析,由于池化和卷积导致的控件信息丢失,可以由反池化或全卷积(就是反卷积)恢复。绕过空间信息,直接将encoder结果给到相应的decoder中去,提高了精度,减少了处理时间。其实说的就是特征融合的方式。然后生成器一般要么是用判别器中存的池化索引要么就是反卷积进行上采样。

​ 在SegNet中使用了预训练的VGG作为判别器。每次的最大池化后都会保存池化索引,然后在解码器中上采样的时候使用。再后来的研究人员提出了深度反卷积网络,FCN结合跳跃结构的思想,认为反池化其实没有什么需要的了。标准的预训练编码器都已经被用于分割了,为了得到更精确的分割边界,添加了后处理步骤级联,比如使用条件随机场CRF(Conditional Random Field)

网络结构

LinkNet Architecture

LinkNet的结构看图就已经很清楚了,将每一层编码器的结果保存一份,再在解码器解出的结果和对应的编码结果加和,传入下一层解码器中去。

encoder-block

这个是Encoder的结构,一看就觉得很熟悉,这不就是残差块嘛,编码器的4个Encoder-block之后,不就是ResNet18么。没错,论文里直接说了LinkNet使用的是ResNet18作为Encoder。:joy::joy::joy:

decoder-block

featuremap

代码实现

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
71
72
73
74
75
76
77
78
79
80
81
import torch
import torch.nn as nn
import torchvision.models as model

class Decoder(nn.Module):
def __init__(self, in_planes, out_planes, kernel_size, stride=1, padding=0, output_padding=0, bias=False):
super(Decoder, self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(in_planes, in_planes // 4, 1, 1, 0, bias=bias),
nn.BatchNorm2d(in_planes // 4),
nn.ReLU(inplace=True)
)
self.tp_conv = nn.Sequential(
nn.ConvTranspose2d(in_planes // 4, in_planes // 4, kernel_size, stride=stride, padding= padding, output_padding= output_padding, bias= bias),
nn.BatchNorm2d(in_planes // 4),
nn.ReLU(True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(in_planes // 4, out_planes, 1, 1, 0, bias=bias),
nn.BatchNorm2d(out_planes),
nn.ReLU(True)
)
def forward(self, x):
x = self.conv1(x)
x = self.tp_conv(x)
x = self.conv2(x)
return x

class LinkNet(nn.Module):
def __init__(self, n_classes=21):
super(LinkNet, self).__init__()
base = model.resnet18(pretrained=True)
self.in_block = nn.Sequential(
base.conv1,
base.bn1,
base.relu,
base.maxpool
)
self.encoder1 = base.layer1
self.encoder2 = base.layer2
self.encoder3 = base.layer3
self.encoder4 = base.layer4

self.decoder1 = Decoder(64, 64, 3, 1, 1, 0)
self.decoder2 = Decoder(128, 64, 3, 2, 1, 1)
self.decoder3 = Decoder(256, 128, 3, 2, 1, 1)
self.decoder4 = Decoder(512, 256, 3, 2, 1, 1)

self.tp_conv1 = nn.Sequential(
nn.ConvTranspose2d(64, 32, 3, 2, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(True)
)
self.conv2 = nn.Sequential(
nn.Conv2d(32, 32, 3, 1, 1),
nn.BatchNorm2d(32),
nn.ReLU(True)
)
self.tp_conv2 = nn.ConvTranspose2d(32, n_classes, 2, 2, 0)
def forward(self, x):
x = self.in_block(x)

e1 = self.encoder1(x)
e2 = self.encoder2(e1)
e3 = self.encoder3(e2)
e4 = self.encoder4(e3)

d4 = e3 + self.decoder4(e4)
d3 = e2 + self.decoder3(d4)
d2 = e1 + self.decoder2(d3)
d1 = x + self.decoder1(d2)
out = self.tp_conv1(d1)
out = self.conv2(out)
out = self.tp_conv2(out)
return out

if __name__ == "__main__":
inputs = torch.randn((1, 3, 352, 480))
net = LinkNet(n_classes=12)
out = net(inputs)
print(out.size())

参数设置

学习率:5e-4

优化器:RMSProp

参考资料

LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation