# 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
```