论文阅读:ENet
2020/5/9 21:56:31
本文主要是介绍论文阅读:ENet,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
引言
如语义分割之类的像素级别的分类任务已经有了很多的发展,现有的模型都在朝着越来越高的精度发展。但是这样的任务对于嵌入式设备也有着重要的应用,所以做到实时语义分割就是必不可少的环节,但是大多数模型都是朝着高精度去的,在实时性上非常的差。基于实时语义分割的问题,作者提出了一种轻量级的网络结构,有着非常少的参数量可以快速进行语义分割,并且在性能上并不会损失的太多。本文作者是Abhishek Chaurasia, Sangpil Kim, Eugenio Culurciello
原论文
网络结构介绍
首先先给出ENet的整个网络的结构,论文中是以$512\times512$的图片作为的输入
可以从图中看出网络也采用了类似U型的结构,只不过是非对称的,下采样有5个阶段,下采样了8倍,上采样只有2个阶段。
整个网络核心的就是bottleneck与第一步的initial结构。
上图(a)就是Initial结构,把原图分别用步长卷积和最大池化下采样,再拼接起来进行卷积可以更好的学习得到2倍下采样的结果。同时一般网络结构并不会上来直接进行下采样而是要先进行卷积,但是这样会使得网络更大且有冗余信息,经过实验,在第一层进行下采样不会对最后的分类结果产生影响。
上图(b)就是网络中最为核心的结构,借鉴了ResNet的思想,称之为bottleneck就是因为主分支上先通过$1\times1$卷积减少了通道数,最后再恢复到输出通道数,是一种中间小两边大的结构,就像瓶颈一样,下面介绍下采样的bottleneck的细节。
主分支:
- 先通过$1\times1$卷积进行投影并减少通道数,如果是下采样类型的bottleneck,可在此使用带步长的$1\times1$卷积。
- 利用一层conv卷积层让模型有更强的学习力,这层conv有不同类型,通过之前网络架构图可以看出。type那一列标注有dilated的就是进行空洞卷积以获取更大的感受野,标注有asymmetric的先进行$1\times5$卷积再进行$5\times1$卷积,这有助于减少参数量,没有标注的就进行普通的$3\times3$卷积。
- 利用$1\times1$卷积进行投影输出特定通道数的特征图。
- 为了防止模型出现过拟合现象,再添加一层空间dropout层,在bottleneck2.0之前dropout参数为0.01,之后为0.1。
分支:
- 如果bottleneck不是下采样的,则此分支不作任何操作直接与主分支跳跃连接,即与主分支相加。
- 如果是下采样的则先通过最大池化下采样,再利用padding填充至于主分支特征图大小一致,最后进行相加操作。
以上的每个卷积操作后面都紧接BatchNormalization层和PReLU激活函数(作者使用此激活函数而不是ReLU的理由再后面会阐述)。对于$1\times1$卷积,作者都去掉了bias。
上采样类型的bottleneck结构完全相同的只不过把下采样操作换成上采样操作(可以用插值或者转置卷积)。
网络结构设计中的选择
Feature map resolution
下采样有两个缺点:
- 下采样降低了特征图的分辨率,只保留了主要特征,这意味着丢失了一些空间的信息,例如边缘信息这样的细节信息。
- 全像素的语义分割要求输出图的分辨率与输入图一样,这就要求上采样模块必须十分强大才能很好的还原图像的信息,这意味更大的模型和计算量。
于是ENet采用了类似SegNet的方法进行上采样,可以减少内存需求量。同时强力的下采样会损失精度,于是我们尽可能的限制下采样。
但与此同时下采样也带来一定的好处,那就是可以获取更大的感受野,有利于分辨不同类别的物体。于是为了增大感受野,我们在一些卷积中使用了空洞卷积。
Early downsampling
处理高分辨率的图像会消耗大量的计算资源,为了减少模型的复杂程度,我们在第一步就对图像进行了下采样操作。这样做是考虑到视觉信息在空间上是高度冗余的。我们认为最初的网络层更主要的是对特征进行提取,它并不直接有助于分类,因此过早下采样不会产生影响。最终,这样的思想也在实验中得到了很好的验证。
Decoder size
相比于SegNet中编码器和解码器的完全对称,ENet则用了一个较大的编码器和较小的解码器。作者认为Encoder主要进行信息处理和过滤,和流行的分类模型相似。而decoder主要是对encoder的输出做上采样,对细节做细微调整(我认为可能作者也是为了减少模型参数量,毕竟更多的解码器还是有助于信息的恢复的)。
Nonlinear operations
通常在卷积层之前做ReLU和Batch Norm效果会更好,但是在ENet上使用ReLU却降低了精度。相反,我们发现删除网络初始层中的大多数ReLU可以改善结果。所以最终使用了PReLU来代替ReLU,它带来了一个新参数,目的在于学习负数区域的斜率。
上图展示了PReLU中的参数的变化情况,蓝色的线代表均值,灰色的区域上界代表最大值,下界代表最小值。如果参数为0,则说明用ReLU函数更可取。开始的initial部分方差比较大,波动很剧烈。同时也可以看出在bottleneck部分,参数值更趋向于负值(即中间向下波动的部分),这也解释了为什么ReLU函数不能很好工作的原因。作者认为在bottleneck中ReLU不好用的原因是网络层数太浅,在ResNet中是有数百层的网络的。值得注意的是,解码器的权重变得偏向正数,学习到的函数功能更接近identity。这证实了我们的直觉,即解码器只用于微调上采样输出.
Information-preserving dimensionality changes
如前所述,尽早对输入进行降采样十分有必要,但过于激进的降维也会阻碍信息的流动。在Inception V3中提出了解决这一问题的一种非常好的办法。他们认为VGG使用的池化再卷积的扩展维数方式,尽管并不是十分明显,但却引入了代表性瓶颈(导致需要使用更多的filters,降低了计算效率)。
另一方面,卷积后的拼接增加了特征映射的深度(increases feature map depth),消耗了大量计算量。因此,正如在上文中所建议的,我们选择在使用步长2的卷积的同时并行执行池化操作,并将得到的特征图拼接(concatenate)起来。这种技术使我们可以将初始块的推理时间提高10倍。
此外,我们在原始ResNet架构中发现了一个问题。下采样时,卷积分支中的第一个1×1卷积在两个维度上以2的步长滑动,直接丢弃了75%的输入。
ENet将卷积核的大小增加到了2×2,这样可以让整个输入都参与下采样,从而提高信息流和精度。虽然这使得这些层的计算成本增加了4倍,但是在ENET中这些层的数量很少,开销并不明显。
Factorizing filters
卷积权重具有相当大的冗余度,并且每个n×n卷积可以被分解为彼此相继的两个较小的卷积,一个n×1和一个1×n,称为非对称卷积。我们在网络中使用了n= 5的非对称卷积,这两个操作的计算成本类似于单个3×3的卷积。增加了模块的学习功能并增加了感受野。
更重要的是,bottleneck模块中使用的一系列操作(投影,卷积,投影)可以看作是将一个大的卷积层分解为一系列更小更简单的操作,即它的低秩近似。这种因子分解大大的加速了计算速度,并减少了参数的数量,使它们更少冗余。此外,由于在层之间插入的非线性操作,功能也变的更丰富了。
Dilated convolutions
空洞卷积,可以增大感受野并更好的进行分类任务。
Regularization
大多数语义分割数据集图像数量较少,为了防止出现过拟合的现象,加入了正则化层。最开始使用了L2正则化发现效果并不好,最终选取了空间dropout层。
实验
作者实验部分可以参看原论文,可以看出相比其它一些大型网络,fps有非常显著的提升,分类精度的下降也可以接受,只在一些边缘地方能看出来比较大的瑕疵,下面就是论文中的一张实验结果对比图。
能看在边缘以及小物体的分类上有比较多的瑕疵。
我的实验
实验使用的tensorflow2.0,在google colab上跑了此数据集。
下采样bottleneck
def bottleneck(x,output,s,methods,dropout_rate=0.1,scale=4,asymmetric=5,d_rate=5): temp = output//scale x_residual = x x_residual = Conv2D(temp,(1,1),(s,s),padding='same',use_bias=False)(x_residual) x_residual = BatchNormalization()(x_residual) x_residual = PReLU(shared_axes=[1,2])(x_residual) if methods == 'norm': x_residual = Conv2D(temp,(3,3),padding='same')(x_residual) elif methods == 'dilated': x_residual = Conv2D(temp,(3,3),padding='same',dilation_rate=d_rate)(x_residual) elif methods == 'asymmetric': x_residual = Conv2D(temp,(1,asymmetric),padding='same',dilation_rate=d_rate)(x_residual) x_residual = Conv2D(temp,(asymmetric,1),padding='same',dilation_rate=d_rate)(x_residual) x_residual = BatchNormalization()(x_residual) x_residual = PReLU(shared_axes=[1,2])(x_residual) x_residual = Conv2D(output,(1,1),use_bias=False)(x_residual) x_residual = SpatialDropout2D(rate=dropout_rate)(x_residual) if s == 2: x = MaxPool2D(padding='same')(x) x = Conv2D(output,(1,1),use_bias=False)(x) x = Add()([x,x_residual]) x = BatchNormalization()(x) x = PReLU(shared_axes=[1,2])(x) return x
上采样bottle
def de_bottleneck(x,output,s,dropout_rate=0.1,scale=4): temp = output//scale x_residual = x x_residual = Conv2D(temp,(1,1),padding='same',use_bias=False)(x_residual) x_residual = BatchNormalization()(x_residual) x_residual = PReLU(shared_axes=[1,2])(x_residual) if s == 2: x_residual = Conv2DTranspose(temp,(3,3),(s,s),padding='same')(x_residual) else: x_residual = Conv2D(temp,(3,3),padding='same')(x_residual) x_residual = BatchNormalization()(x_residual) x_residual = PReLU(shared_axes=[1,2])(x_residual) x_residual = Conv2D(output,(1,1),use_bias=False)(x_residual) x_residual = SpatialDropout2D(rate=dropout_rate)(x_residual) if s == 2: x = UpSampling2D((s,s),interpolation='bilinear')(x) x = Conv2D(output,(1,1),use_bias=False)(x) x = Add()([x,x_residual]) x = BatchNormalization()(x) x = PReLU(shared_axes=[1,2])(x) return x
initial
def initial(x): x_1 = x x = MaxPool2D(padding='same')(x) x_1 = Conv2D(13,(3,3),(2,2),padding='same')(x_1) x = Concatenate()([x,x_1]) return x
主体网络结构
def ENet(input_shape,n_class): x_input = Input(input_shape) #encoder x_1 = initial(x_input) x = bottleneck(x_1,64,2,'norm',dropout_rate=0.01) x = bottleneck(x,64,1,'norm',dropout_rate=0.01) x = bottleneck(x,64,1,'norm',dropout_rate=0.01) x = bottleneck(x,64,1,'norm',dropout_rate=0.01) x_2 = bottleneck(x,64,1,'norm',dropout_rate=0.01) x = bottleneck(x_2,128,2,'norm') x = bottleneck(x,128,1,'norm') x = bottleneck(x,128,1,'dilated',d_rate=2) x = bottleneck(x,128,1,'asymmetric') x = bottleneck(x,128,1,'dilated',d_rate=4) x = bottleneck(x,128,1,'norm') x = bottleneck(x,128,1,'dilated',d_rate=8) x = bottleneck(x,128,1,'asymmetric') x = bottleneck(x,128,1,'dilated',d_rate=16) x = bottleneck(x,128,1,'norm') x = bottleneck(x,128,1,'dilated',d_rate=2) x = bottleneck(x,128,1,'asymmetric') x = bottleneck(x,128,1,'dilated',d_rate=4) x = bottleneck(x,128,1,'norm') x = bottleneck(x,128,1,'dilated',d_rate=8) x = bottleneck(x,128,1,'asymmetric') x = bottleneck(x,128,1,'dilated',d_rate=16) #decoder x = de_bottleneck(x,64,2) x = Concatenate()([x,x_2]) x = Conv2D(64,(3,3),padding='same')(x) x = BatchNormalization()(x) x = PReLU(shared_axes=[1,2])(x) x = de_bottleneck(x,64,1) x = de_bottleneck(x,64,1) x = de_bottleneck(x,16,2) x = Concatenate()([x,x_1]) x = Conv2D(16,(3,3),padding='same')(x) x = BatchNormalization()(x) x = PReLU(shared_axes=[1,2])(x) x = de_bottleneck(x,16,1) x = Conv2DTranspose(n_class,(4,4),(2,2),padding='same')(x) x = Activation('softmax')(x) model = keras.Model(x_input,x) return model
结果图
最终的训练结果在训练集上到达91%的准确率,相比大型的网络模型要低不少,在小物体和边缘方面存在不精确的地方,肉眼就能看得出。但是它的推理时间则快了很多。训练一个epoch的时间仅有大型网络的$\frac{1}{3}-\frac{1}{7}$.
这篇关于论文阅读:ENet的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-30tensorflow是什么-icode9专业技术文章分享
- 2024-10-15成功地使用本地的 NVIDIA GPU 运行 PyTorch 或 TensorFlow
- 2024-01-23供应链投毒预警 | 恶意Py包仿冒tensorflow AI框架实施后门投毒攻击
- 2024-01-19attributeerror: module 'tensorflow' has no attribute 'placeholder'
- 2024-01-19module 'tensorflow.compat.v2' has no attribute 'internal'
- 2023-07-17【2023年】第33天 Neural Networks and Deep Learning with TensorFlow
- 2023-07-10【2023年】第32天 Boosted Trees with TensorFlow 2.0(随机森林)
- 2023-07-09【2023年】第31天 Logistic Regression with TensorFlow 2.0(用TensorFlow进行逻辑回归)
- 2023-07-01【2023年】第30天 Supervised Learning with TensorFlow 2(用TensorFlow进行监督学习 2)
- 2023-06-18【2023年】第29天 Supervised Learning with TensorFlow 1(用TensorFlow进行监督学习 1)