DenseNet 的主要构建模块是稠密块(DenseBlock)和过渡层(TransitionLayer)。前者定义了输入和输出是如何连结的,后者则用来控制通道数,防止通道数过多。在 DenseBlock 中,包含许多 BottleNeck,完成类似于 ResNet 中 Residual 的操作。
BottleNeck 与 Residual 非常相似。与 Residual 的主要区别在于,BottleNeck 里的输出不是像 Residual 那样和输入相加,而是在通道维度上连接。即
其中,两个卷积层的步长都设为1,填充方式都设为 ‘‘same’’,这样一来,得到的输出 Y 的长和宽都和 X 相同,只有通道数不同。因此,X 和 Y 可以在通道数这一维度上连接。例如,X 的 shape 为 [4, 8, 8, 3],输出 Y 的 shape 为 [4, 8, 8, 32],那么 [X, Y] 的 shape 为 [4, 8, 8, 35]。
DenseBlock 由多个 BottleNeck 组成,每块使用相同的输出通道数。
由于每个稠密块都会带来通道数的增加,使用过多则会带来过于复杂的模型。过渡层用来控制模型复杂度。它通过 1 × 1 1\times1 1×1 卷积层来减小通道数,并使用步幅为2的平均池化层减半高和宽,从而进一步降低模型复杂度。
class BottleNeck(tf.keras.Model):
def __init__(self, growth_rate, drop_rate):
super().__init__()
self.bn1 = tf.keras.layers.BatchNormalization()
self.conv1 = tf.keras.layers.Conv2D(filters=4*growth_rate,
kernel_size=(1, 1),
strides=1,
padding='same')
self.bn2 = tf.keras.layers.BatchNormalization()
self.conv2 = tf.keras.layers.Conv2D(filters=growth_rate,
kernel_size=(3, 3),
strides=1,
padding='same')
self.dropout = tf.keras.layers.Dropout(rate=drop_rate)
self.listLayers = [self.bn1,
tf.keras.layers.Activation("relu"),
self.conv1,
self.bn2,
tf.keras.layers.Activation("relu"),
self.conv2,
self.dropout]
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
X = tf.random.uniform((4, 8, 8, 3))
net(X).shape
TensorShape([4, 8, 8, 35])
X = tf.random.uniform((4, 8, 8, 3))
for layer in net.layers:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
batch_normalization_165 output shape: (4, 8, 8, 3)
conv2d_165 output shape: (4, 8, 8, 128)
batch_normalization_166 output shape: (4, 8, 8, 128)
conv2d_166 output shape: (4, 8, 8, 32)
dropout_73 output shape: (4, 8, 8, 32)
activation_160 output shape: (4, 8, 8, 32)
activation_161 output shape: (4, 8, 8, 32)
class DenseBlock(tf.keras.Model):
def __init__(self, num_layers, growth_rate, drop_rate=0.5):
super().__init__()
self.num_layers = num_layers
self.growth_rate = growth_rate
self.drop_rate = drop_rate
self.listLayers = []
for _ in range(self.num_layers):
self.listLayers.append(BottleNeck(growth_rate=self.growth_rate, drop_rate=self.drop_rate))
def call(self, x):
for layer in self.listLayers.layers:
x = layer(x)
return x
blk = DenseBlock(2, 10)
X = tf.random.uniform((4, 8, 8, 3))
Y = blk(X)
Y.shape
TensorShape([4, 8, 8, 23])
class TransitionLayer(tf.keras.Model):
def __init__(self, out_channels):
super().__init__()
self.bn = tf.keras.layers.BatchNormalization()
self.conv = tf.keras.layers.Conv2D(filters=out_channels,
kernel_size=1,
strides=1,
padding='same')
self.pool = tf.keras.layers.MaxPool2D(pool_size=2,
strides=2,
padding='same')
self.listLayers = [self.bn,
tf.keras.layers.Activation('relu'),
self.conv,
self.pool]
def call(self, inputs):
x = inputs
for layer in self.listLayers.layers:
x = layer(x)
return x
trans = TransitionLayer(10)
trans(Y).shape
TensorShape([4, 4, 4, 10])
class DenseNet(tf.keras.Model):
def __init__(self, num_init_features, growth_rate,
block_layers, compression_rate, drop_rate):
super().__init__()
self.conv = tf.keras.layers.Conv2D(filters=num_init_features,
kernel_size=7,
strides=2,
padding='same')
self.bn = tf.keras.layers.BatchNormalization()
self.pool = tf.keras.layers.MaxPool2D(pool_size=3,
strides=2,
padding='same')
self.num_channels = num_init_features
self.dense_block_1 = DenseBlock(num_layers=block_layers[0], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[0]
self.num_channels = compression_rate * self.num_channels
self.transition_1 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_2 = DenseBlock(num_layers=block_layers[1], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[1]
self.num_channels = compression_rate * self.num_channels
self.transition_2 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_3 = DenseBlock(num_layers=block_layers[2], growth_rate=growth_rate, drop_rate=drop_rate)
self.num_channels += growth_rate * block_layers[2]
self.num_channels = compression_rate * self.num_channels
self.transition_3 = TransitionLayer(out_channels=int(self.num_channels))
self.dense_block_4 = DenseBlock(num_layers=block_layers[3], growth_rate=growth_rate, drop_rate=drop_rate)
self.avgpool = tf.keras.layers.GlobalAveragePooling2D()
self.fc = tf.keras.layers.Dense(units=10,
activation=tf.keras.activations.softmax)
def call(self, inputs):
x = self.conv(inputs)
x = self.bn(x)
x = tf.keras.activations.relu(x)
x = self.pool(x)
x = self.dense_block_1(x)
x = self.transition_1(x)
x = self.dense_block_2(x)
x = self.transition_2(x)
x = self.dense_block_3(x)
x = self.transition_3(x,)
x = self.dense_block_4(x)
x = self.avgpool(x)
x = self.fc(x)
return x
mynet = DenseNet(num_init_features=64,
growth_rate=32,
block_layers=[4,4,4,4],
compression_rate=0.5,
drop_rate=0.5)
X = tf.random.uniform(shape=(1, 224, 224 , 1))
for layer in mynet.layers:
X = layer(X)
print(layer.name, 'output shape:\t', X.shape)
conv2d_129 output shape: (1, 112, 112, 64)
batch_normalization_129 output shape: (1, 112, 112, 64)
max_pooling2d_15 output shape: (1, 56, 56, 64)
dense_block_18 output shape: (1, 56, 56, 192)
transition_layer_13 output shape: (1, 28, 28, 96)
dense_block_19 output shape: (1, 28, 28, 224)
transition_layer_14 output shape: (1, 14, 14, 112)
dense_block_20 output shape: (1, 14, 14, 240)
transition_layer_15 output shape: (1, 7, 7, 120)
dense_block_21 output shape: (1, 7, 7, 248)
global_average_pooling2d_2 output shape: (1, 248)
dense_2 output shape: (1, 10)