(四)卷積神經網路 -- 12 稠密連線網路(DenseNet)

Fiona-Dong發表於2020-10-25

12. 稠密連線網路(DenseNet)

ResNet中的跨層連線設計引申出了數個後續工作,稠密連線網路(DenseNet)就是其中之一。

DenseNet與ResNet的主要區別,如下圖所示:

(四)卷積神經網路 -- 12 稠密連線網路(DenseNet)

圖中將部分前後相鄰的運算抽象為模組A和模組B。

與ResNet的主要區別在於:
DenseNet裡模組B的輸出,並非是ResNet那樣和模組A的輸出相加,而是在通道維上連結。
由此,模組A的輸出可以直接傳入模組B後面的層。

在這個設計裡,模組A直接跟模組B後面的所有層連線在了一起。這也是它被稱為“稠密連線”的原因。


DenseNet的主要構建模組是稠密塊(dense block)和過渡層(transition layer)。
前者定義了輸入和輸出是如何連結的,後者則用來控制通道數,使之不過大。


12.1 稠密塊

DenseNet使用了ResNet改良版的“批量歸一化、啟用和卷積”結構,該結構的實現在BottleNeck函式中:

class BottleNeck(Layer):
    def __init__(self, growth_rate, drop_rate):
        super(BottleNeck, self).__init__()
        self.bn1 = BatchNormalization()
        self.conv1 = Conv2D(filters=4 * growth_rate, kernel_size=1, padding="same", strides=1)
        self.bn2 = BatchNormalization()
        self.conv2 = Conv2D(filters=growth_rate, kernel_size=3, padding="same", strides=1)
        self.dropout = Dropout(rate=drop_rate)

        self.listLayers = [self.bn1,
                           Activation("relu"),
                           self.conv1,
                           self.bn2,
                           Activation("relu"),
                           self.conv2,
                           self.dropout]

    def call(self, x):
        y = x
        for layer in self.listLayers.layers:
            y = layer(y)
        y = concatenate([x,y], axis=-1)
        return y

其中,在前向計算時,將每塊的輸入和輸出在通道維上連結。


稠密塊由多個BottleNeck組成,每塊使用相同的輸出通道數:

class DenseBlock(Layer):
    def __init__(self, num_layers, growth_rate, drop_rate=0.5):
        super(DenseBlock, self).__init__()
        self.num_layers = num_layers
        self.growth_rate = growth_rate
        self.drop_rate = drop_rate
        self.listLayers = []
        for _ in range(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

示例:定義一個有2個輸出通道數為10的卷積塊。

使用通道數為3的輸入時,將得到通道數為3+2×10=23的輸出。

卷積塊的通道數控制了輸出通道數相對於輸入通道數的增長,因此也被稱為增長率(growth rate)。

blk = DenseBlock(2, 10)
X = tf.random.uniform((4, 8, 8, 3))
Y = blk(X)
print(Y.shape)
(4, 8, 8, 23)


12.2 過渡層

由於每個稠密塊都會帶來通道數的增加,使用過多則會帶來過於複雜的模型。

過渡層用來控制模型複雜度。

它通過1×1卷積層來減小通道數,並使用步幅為2的平均池化層減半高和寬,從而進一步降低模型複雜度。

class TransitionLayer(Layer):
    def __init__(self, out_channels):
        super(TransitionLayer, self).__init__()
        self.bn = BatchNormalization()
        self.conv = Conv2D(filters=out_channels, kernel_size=1, padding="same", strides=1)
        self.pool = MaxPool2D(pool_size=(2, 2), padding="same", strides=2)

    def call(self, inputs):
        x = self.bn(inputs)
        x = relu(x)
        x = self.conv(x)
        x = self.pool(x)
        return x

對上一個例子中稠密塊的輸出使用通道數為10的過渡層。
此時輸出的通道數減為10,高和寬均減半。

blk = TransitionLayer(10)
blk(Y).shape
TensorShape([4, 4, 4, 10])


12.3 DenseNet模型

DenseNet首先使用同ResNet的單卷積層和最大池化層。

類似於ResNet接下來使用的4個殘差塊,DenseNet使用的是4個稠密塊。

同ResNet,可以設定每個稠密塊使用多少個卷積層。這裡設為4,從而與上一節的ResNet-18保持一致。

稠密塊裡的卷積層通道數(即增長率)設為32,所以每個稠密塊將增加4*32=128個通道。

ResNet裡通過步幅為2的殘差塊在每個模組之間減小高和寬。使用過渡層來減半高和寬,並減半通道數。

class DenseNet(Model):
    def __init__(self, num_init_features, growth_rate, block_layers, compression_rate, drop_rate):
        super(DenseNet, self).__init__()
        self.conv = Conv2D(filters=num_init_features, kernel_size=7, padding="same", strides=2)
        self.bn = BatchNormalization()
        self.pool = MaxPool2D(pool_size=(3, 3), padding="same", strides=2)
        
        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 = GlobalAvgPool2D()
        self.fc = Dense(units=10, activation=softmax)

        
    def call(self, inputs):
        x = self.conv(inputs)
        x = self.bn(x)
        x = 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
def densenet():
    return DenseNet(num_init_features=64, growth_rate=32, block_layers=[4,4,4,4], compression_rate=0.5, drop_rate=0.5)

mynet=densenet()

每個子模組的輸出維度:

X = tf.random.uniform(shape=(1,  96, 96 , 1))
for layer in mynet.layers:
    X = layer(X)
    print(layer.name, 'output shape: ', X.shape)
conv2d_81 output shape:  (1, 48, 48, 64)
batch_normalization_81 output shape:  (1, 48, 48, 64)
max_pooling2d_9 output shape:  (1, 24, 24, 64)
dense_block_10 output shape:  (1, 24, 24, 192)
transition_layer_7 output shape:  (1, 12, 12, 96)
dense_block_11 output shape:  (1, 12, 12, 224)
transition_layer_8 output shape:  (1, 6, 6, 112)
dense_block_12 output shape:  (1, 6, 6, 240)
transition_layer_9 output shape:  (1, 3, 3, 120)
dense_block_13 output shape:  (1, 3, 3, 248)
global_average_pooling2d output shape:  (1, 248)
dense output shape:  (1, 10)




參考

《動手學深度學習》(TF2.0版)

A. Krizhevsky, I. Sutskever, and G. Hinton. Imagenet classification with deep convolutional neural networks. In NIPS, 2012.

相關文章