DenseNet介绍与基于tf

编程入门 行业动态 更新时间:2024-10-16 22:24:42

<a href=https://www.elefans.com/category/jswz/34/1744645.html style=DenseNet介绍与基于tf"/>

DenseNet介绍与基于tf

基于tf-slim框架的DenseNet介绍与实现

  • DenseNet介绍
    • DenseNet 优点
    • DenseNet网络结构:
      • 紧密连接 (Dense connectivity)
      • 组成函数(Composite function)
      • 过滤层(Transition Layer)
      • 增长率(Growth rate)
      • 压缩(Compression)
  • TF-slim介绍
  • 模型
    • 模型参数的解释:
  • 实现
    • 实现细节
  • 参考文献

DenseNet介绍

随着CNN层次越来越深,输入信息或者说梯度当经过众多层到达网络底端后会消失。为了解决梯度消失,以及梯度下降的问题。该论文提出了一种新的连接模式:为最大化网络之间的信息流动,我们将网络中的每一层与其它层直接相连。每一层都从它之前的所有层中接收额外的输入,并将的输出(feature-maps)传递给其后的所有层中。
每一层的输入侧来自于前面所有层的输出,若有L层,那么会有L*(L+1)/2 个连接。

DenseNet 优点

  • 减轻了梯度消失
  • 加强了特征的传递
  • 更有效的利用了特征
  • 一定程度上减少了参数数量

DenseNet网络结构:

紧密连接 (Dense connectivity)


上面图中的结构是一个dense block,下图的结构是一个完整的dense net,包括3个dense block

可以发现在block之间没有dense连接,因为在pooling操作之后,改变了feature maps的大小,这时候就没法做dense 连接了。在两个block之间的是transition layer ,包括了conv ,pool,在实验中使用的是BN,(1x1 conv),(2x2 avg pool)。

这个表中的k=32,k=48中的k是growth rate,表示每个dense block中每层输出的feature map个数

另外这里每个dense block的33卷积前面都包含了一个11的卷积操作,就是所谓的bottleneck layer,目的是减少输入的feature map数量,既能降维减少计算量,又能融合各个通道的特征。

另外作者为了进一步压缩参数,在每两个dense block之间又加了transition layer,包括一个BN,一个1x1的conv和一个pooling,同样起到了降低冗余的作用,一个block中有m个feature maps,通过一个0-1之间的参数来限制输出的feature maps数量。

这种设计可以很好的降低参数量,GoogleNet也是用这种方式来降低参数的,下图是GoogleNet的一个功能模块,由1x1 64通道(通道可以理解为就是卷积核的数量),3x3 128通道,5x5 32通道的convolution和一个3x3 max pooling构成,输入的是28x28的图片192通道。那么可以分别计算出这3个卷积层的参数数量,为上一个通道数乘上当前卷积核的大小和通道数。可以算出来一个功能模块有38w个参数。

功能模块使用1x1的卷积核来实现降低参数量,方式为在3x3,5x5的卷积核之前分别添加1x1x96,1x1x16的卷积核,在pooling之后添加1x1x32的卷积核。用同样的方式来计算参数量,上一个通道数乘上当前卷积核的大小和通道数。得到当前功能模块的参数量为16w,所以可以使用1x1的卷积核,通过改变通道大小来进行参数降低。

紧密连接的方式,DenseNet有更少的网络参数,网络更窄 ,这样可以避免了ResNet 中可能的缺点(如某些层可能会被选择性丢弃)


再详细说下bottleneck和transition layer操作。 在每个Dense Block中都包含很多个子结构,以DenseNet-169的Dense Block(3)为例,包含32个11和33的卷积操作,也就是第32个子结构的输入是前面31层的输出结果,每层输出的channel是32(growth rate),那么如果不做bottleneck操作,第32层的33卷积操作的输入就是3132+(上一个Dense Block的输出channel),近1000了。而加上11的卷积,代码中的11卷积的channel是growth rate4,也就是128,然后再作为33卷积的输入。这就大大减少了计算量,这就是bottleneck。

至于transition layer, 放在两个Dense Block中间,是因为每个Dense Block结束后的输出channel个数很多,需要用1*1的卷积核来降维。 还是以DenseNet-169的Dense Block(3)为例,虽然第32层的3*3卷积输出channel只有32个(growth rate),但是紧接着还会像前面几层一样有通道的concat操作,即将第32层的输出和第32层的输入做concat,前面说过第32层的输入是1000左右的channel,所以最后每个Dense Block的输出也是1000多的channel。因此这个transition layer有个参数reduction(范围是0到1),表示将这些输出缩小到原来的多少倍,默认是0.5,这样传给下一个Dense Block的时候channel数量就会减少一半,这就是transition layer的作用。文中还用到dropout操作来随机减少分支,避免过拟合

组成函数(Composite function)

BN + ReLU + Conv(3x3)

过滤层(Transition Layer)

包含瓶颈层(bottlenneck layer)即1*1 卷积层和池化层

  • 瓶颈层
    1*1的卷积层用于压缩参数。每一层输出k个feature maps,理论上将每个Dense Block输出为4k个feature maps,然而实际情况中会大于这个数字。卷积层的作用是将一个Dense Block的参数压缩到4k个。
  • 池化层
    由于采用了Dense Connectivity结构,直接在各个层之间加入池化层是不可行的,因此采用的是Dense Block组合的方式,在各个Dense Block之间加入卷积层和池化层。

增长率(Growth rate)

这里的增长率代表的是每一层输出的feature maps的厚度。ResNet,GoogleNet等网络结构中经常能见到输出厚度为上百个,其目的主要是为了提取不同的特征。但是由于DenseNet中每一层都能直接为后面网络所用,所以k被限制在一个很小的数值。

压缩(Compression)

跟1*1卷积层作用类似,压缩参数。作者选择压缩率(theta)为0.5。

包含bottleneck layer的叫DenseNet-B,包含压缩层的叫DenseNet-C,两者都包含的叫DenseNet-BC。

TF-slim介绍

TF-slim是tensorflow用于定义、训练、评估复杂模型的一个新的轻量级的高级API(tensorflow.contrib.slim)。最近正好研究了一下DenseNet,这里基于tf-slim框架并结合论文实现一个DenseNet网络,并用来进行训练。
tf-slim框架代码可见github:

这里以DenseNet-121为示例讲解其实现过程

模型

模型代码来自:
其中nets目录下的densenet.py中已经定义了densenet网络的入口函数等
densenet论文参考 .06993

模型参数的解释:

  • dataset_name quiz # 数据集的名称
  • dataset_dir /data/ai100/quiz-w7 # tfrecord存放的目录
  • model_name densenet # 使用的网络的名称,此次固定为densenet
  • train_dir /output/ckpt # 训练目录,训练的中间文件和summary,checkpoint等都存放在这里,这个目录也是验证过程的checkpoint_path参数
  • learning_rate 0.1 # 学习率, 因为没有预训练模型,这里使用较大的学习率以加快收敛速度。
  • optimizer rmsprop # 优化器,
  • dataset_split_name validation # 数据集分块名,用于验证过程,传入train可验证train集准确度,传入validation可验证validation集准确度,这里只关注validation
  • eval_dir /output/eval # 验证目录,验证结果,包括summary等,会写入这个目录
  • max_num_batches 128 # 验证batches,这里会验证128×32共4096个图片样本的数据。

实现

DenseNet-121包含4个dense block,各dense block内部的网络层数依次为6,12,24,16。dense block内部网络层的连接方式为dense connectivity(稠密连接),这也是DenseNet的核心创新点。
代码片.

def trunc_normal(stddev): return tf.truncated_normal_initializer(stddev=stddev)"" 那么这一个函数trunc_normal就是返回 tf.truncated_normal_initializer(0.0, stddev)的值,最后产生一个平均值为0.0,标准差为stddev的截断的正太分布。
""
def bn_act_conv_drp(current, num_outputs, kernel_size, scope='block'):"""Args:num_outputs: 输出通道数"""current = slim.batch_norm(current, scope=scope + '_bn')current = tf.nn.relu(current)current = slim.conv2d(current, num_outputs, kernel_size, scope=scope + '_conv')current = slim.dropout(current, scope=scope + '_dropout')return currentdef block(net, layers, growth, scope='block'):"""Args:layers: dense block包含的网络层数growth: 增长率(每一层输出的feature maps的厚度)"""for idx in range(layers):"""BN-ReLU-Conv(1x1)-DroupOut-BN-ReLU-Conv(3x3)-DroupOut"""bottleneck = bn_act_conv_drp(net, 4 * growth, [1, 1],scope=scope + '_conv1x1' + str(idx))tmp = bn_act_conv_drp(bottleneck, growth, [3, 3],scope=scope + '_conv3x3' + str(idx))net = tf.concat(axis=3, values=[net, tmp])return net  

在dense block之间有一个转换层结构(transition layers),关于transition layers主要是作了卷积和池化操作,改变了feature map的size和深度。

代码片.

def transition(net, num_outputs, scope='transition'):net = bn_act_conv_drp(net, num_outputs, [1, 1], scope=scope + '_conv1x1')net = slim.avg_pool2d(net, [2, 2], stride=2, scope=scope + '_avgpool')return net 

ImageNet数据集上,作者使用的k也就是增长率为32,压缩率我这里用的是0.5。增长率使用24
代码片.

def densenet(images, num_classes=1001, is_training=False,dropout_keep_prob=0.8,scope='densenet'):"""Creates a variant of the densenet model.images: A batch of `Tensors` of size [batch_size, height, width, channels].num_classes: the number of classes in the dataset.is_training: specifies whether or not we're currently training the model.This variable will determine the behaviour of the dropout layer.dropout_keep_prob: the percentage of activation values that are retained.prediction_fn: a function to get predictions out of logits.scope: Optional variable_scope.Returns:logits: the pre-softmax activations, a tensor of size[batch_size, `num_classes`]end_points: a dictionary from components of the network to the correspondingactivation."""growth = 24compression_rate = 0.5def reduce_dim(input_feature):return int(int(input_feature.shape[-1]) * compression_rate)end_points = {}with tf.variable_scope(scope, 'DenseNet', [images, num_classes]):with slim.arg_scope(bn_drp_scope(is_training=is_training,keep_prob=dropout_keep_prob)) as ssc:net = imagesnet = slim.conv2d(net, 2*growth, 7, stride=2, scope='conv1')net = slim.max_pool2d(net, 3, stride=2, padding='SAME', scope='pool1')net = block(net, 6, growth, scope='block1')net = transition(net, reduce_dim(net), scope='transition1')net = slim.avg_pool2d(net, [2, 2], stride=2, scope='avgpool1')net = block(net, 12, growth, scope='block2')net = transition(net, reduce_dim(net), scope='transition2')net = block(net, 24, growth, scope='block3')net = transition(net, reduce_dim(net), scope='transition3')net = block(net, 16, growth, scope='block4')net = slim.batch_norm(net, scope='last_batch_norm_relu')net = tf.nn.relu(net)# Global average pooling = tf.reduce_mean(net, [1, 2], name='pool2', keep_dims=True)biases_initializer = tf.constant_initializer(0.1)net = slim.conv2d(net, num_classes, [1, 1], biases_initializer=biases_initializer, scope='logits')logits = tf.squeeze(net, [1, 2], name='SpatialSqueeze')end_points['Logits'] = logitsend_points['predictions'] = slim.softmax(logits, scope='predictions')return logits, end_points

实现细节

  1. 在进入第一个dense block之前,对输入数据做一个输出通道为16(对于DenseNet-BC结构,输出通道数要翻倍,也就是32)的卷积操作。
  2. 对于每个卷积核大小为3x3的卷积层,以0填充的方式在每边增加一个像素,以保持特征图尺寸不变。
  3. 相邻两个dense block之间我们使用一个1x1的卷积层再接一个2x2的平均池化层作为转换层
  4. 在最后一个dense block之后,使用一个全局平均池化层,紧接着就是softmax分类器。

参考文献

  1. 基于tf-slim框架的DenseNet实现
  2. DenseNet 简介
  3. DenseNet论文解读理解

更多推荐

DenseNet介绍与基于tf

本文发布于:2024-02-11 14:56:33,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1681576.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:DenseNet   tf

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!