# DenseNet ResNet极大地改变了如何参数化深层网络中函数的观点。 稠密连接网络(DenseNet)在某种程度上是 ResNet 的逻辑扩展。 ## 数学角度 任意函数的泰勒展开式(Taylor expansion) $$ f(x) = f(0)+f'(0)x+\frac{f''(0)}{2!}x^2+\frac{f'''(0)}{3!}x^3+... $$ ResNet将函数展开 $$ f(x)=x+g(x) $$ 也就是说,ResNet 将$f$分解为两部分:一个简单的线性项和一个更复杂的非线性项。 那么再向前拓展一步,如果我们想将$f$拓展成超过两部分的信息呢? ## 代码实现 DenseNet 使用了 ResNet 改良版的批量归一化、激活和卷积结构。 ```python import tensorflow as tf class ConvBlock(tf.keras.layers.Layer): def __init__(self, num_channels): super(ConvBlock, self).__init__() self.bn = tf.keras.layers.BatchNormalization() self.relu = tf.keras.layers.ReLU() self.conv = tf.keras.layers.Conv2D(filters=num_channels, kernel_size=(3, 3), padding='same') self.listLayers = [self.bn, self.relu, self.conv] def call(self, x): y = x for layer in self.listLayers.layers: y = layer(y) y = tf.keras.layers.concatenate([x, y], axis=-1) return y ``` ```python class DenseBlock(tf.keras.layers.Layer): def __init__(self, num_convs, num_channels): super(DenseBlock, self).__init__() self.listLayers = [] for _ in range(num_convs): self.listLayers.append(ConvBlock(num_channels)) def call(self, x) for layer in self.listLayers.layers: x = layer(x) return x ``` 定义一个有 2 个输出通道数为 10 的 `DenseBlock`。 使用通道数为 3 的输入时,我们会得到通道数为 3+2×10=233+2×10=23 的输出。 卷积块的通道数控制了输出通道数相对于输入通道数的增长,因此也被称为*增长率*(growth rate)。 ```python blk = DenseBlock(2, 10) X = tf.random.uniform((4, 8, 8, 3)) Y = blk(X) Y.shape >>>Tensorshape([4, 8, 8, 23]) ``` ## 过渡层 每个稠密块都会带来通道数的增加,使用过多则会过于复杂化模型。 而过渡层可以用来控制模型复杂度。 它通过 1×11×1 卷积层来减小通道数,并使用步幅为 2 的平均汇聚层减半高和宽,从而进一步降低模型复杂度。 ```python class TransitionBlock(tf.keras.layers.Layer): def __init__(self, num_channels, **kwargs): super(TransitionBlock, self).__init__(**kwargs) self.batch_norm = tf.keras.layers.BatchNormalization() self.relu = tf.keras.layers.ReLU() self.conv = tf.keras.layers.Conv2D(num_channels, kernel_size=1) self.avg_pool = tf.keras.layers.AvgPool2D(pool_size=2, strides=2) def call(self, x): x = self.batch_norm(x) x = self.relu(x) x = self.conv(x) return self.avg_pool(x) ``` ```python blk = TransitionBlock(10) blk(Y).shape ``` ## DenseNet模型 DenseNet 首先使用同 ResNet 一样的单卷积层和最大汇聚层。 ```python def block_1(): return tf.keras.Sequential([ tf.keras.layers.Conv2D(64, kernel_size=7, strides=2, padding='same'), tf.keras.layers.BatchNormalization(), tf.keras.layers.ReLU(), tf.keras.layers.Maxpool2D(pool_size=3, strides=2, padding='same') ]) ``` 接下来,类似于 ResNet 使用的 4 个残差块,DenseNet 使用的是 4 个稠密块。 与 ResNet 类似,我们可以设置每个稠密块使用多少个卷积层。 这里我们设成 4,稠密块里的卷积层通道数(即增长率)设为 32,所以每个稠密块将增加 128 个通道。在每个模块之间,ResNet 通过步幅为 2 的残差块减小高和宽,DenseNet 则使用过渡层来减半高和宽,并减半通道数。 ```python def block_2(): net = block_1() num_channels, growth_rate = 64, 32 num_convs_in_dense_blocks = [4, 4, 4, 4] for i, num_convs in enumerate(num_convs_in_dense_blocks): net.add(DenseBlock(num_convs, growth_rate)) num_channels += num_convs * growth_rate if i != len(num_convs_in_dense_blocks) - 1: num_channels //= 2 net.add(TransitionBlock(num_channels)) reutrn net ``` 最后接上全局汇聚层和全连接层来输出结果。 ```python def net(): net = block_2() net.add(tf.keras.layers.BatchNormalization()) net.add(tf.keras.layers.ReLU()) net.add(tf.keras.layers.GlobalAvgPool2D()) net.add(tf.keras.layers.Flatten()) net.add(tf.keras.layers.Dense(10)) return net ```