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
实现细节
- 在进入第一个dense block之前,对输入数据做一个输出通道为16(对于DenseNet-BC结构,输出通道数要翻倍,也就是32)的卷积操作。
- 对于每个卷积核大小为3x3的卷积层,以0填充的方式在每边增加一个像素,以保持特征图尺寸不变。
- 相邻两个dense block之间我们使用一个1x1的卷积层再接一个2x2的平均池化层作为转换层
- 在最后一个dense block之后,使用一个全局平均池化层,紧接着就是softmax分类器。
参考文献
- 基于tf-slim框架的DenseNet实现
- DenseNet 简介
- DenseNet论文解读理解
更多推荐
DenseNet介绍与基于tf
发布评论