一、ShortCut結構
ResNet神經網絡中有一種ShortCut Connection網絡結構,主要用的是跳遠連接的方式來解決深層神經網絡退化的問題,在跳遠連接的后需要對輸入與激活前的值進行相加,激活前的值y可能與輸入值的shape相同(稱為identity block),也可能不相同(稱為cov block),所以有ResNet有兩種方式,當shape不相同時,用1*1的卷積操作來處理,一般來說1*1的卷積對神經網絡特征的學習作用不大,通常用來做shape的調整。
如下圖,輸入是265通道的圖,虛線表示shape不同的ResNet操作,實線表示shape相同的ResNet操作。
二、ShortCut實現
用cifar10數據集對的shape進行實現,跳遠結構也比較簡單,和上一節的圖示一樣只是跳了兩層卷積,如何實現維度不同的的跳遠連接操作呢,可以通過對比ResNet的輸入與卷積之后的輸出的shape即可,如下代碼:
# 判斷輸入和輸出是否有相同的channel,不相同則用1*1卷積進行操作 if block_input.shape[-1] == cov_output.shape[-1]: block_out = block_input + cov_output return block_out else: block_out = block_input + Covunit(cov_output, cov_output.shape[-1], [1 * 1]) # 當維度不相同時用1*1的卷積處理 return block_out
代碼示例:
import tensorflow as tf # 創建卷積結構單元的函數 def Covunit(unit_input, filters, kernel_size, activation=None): ''' :param unit_input: 卷積核輸入,tensor :param filters: 卷積核個數 :param kernel_size: 卷積核大小 :param activation: 激活函數,若為0則不激活 :return:卷積后輸出的tensor ''' unit_cov = tf.keras.layers.Conv2D(filters, kernel_size, padding='same')(unit_input) # 卷積,不改變長於寬 unit_bn = tf.keras.layers.BatchNormalization()(unit_cov) # 批標准化 if activation: unit_act = tf.keras.layers.Activation(activation)(unit_bn) else: unit_act = unit_bn return unit_act # ResNet函數,網絡結構中可能有多個這樣的結構塊,所以需要如此操作 def ShortCut(block_input, filters=None, activations=None): ''' :param block_input: ResNet結構的輸入 :param filters: 該ResNet結構每個卷積層的filters個數 :param filters: 該ResNet結構每個卷積層的激活函數,最后一層不需要激活函數 :return: Inceptionblock結構輸出的tensor ''' # 如果有多個卷積操作,則依次構建卷積操作 if filters: layer_input = block_input # 定義卷積層的輸入,初始為block_input for filters, activation in zip(filters, activations): layer_input = Covunit(unit_input=layer_input, filters=filters, kernel_size=[3, 3], activation=activations) cov_output = layer_input # 卷積層的輸出 else: cov_output = block_input # 無卷積層的輸出 # 判斷輸入和輸出是否有相同的channel,不相同則用1*1卷積進行操作 if block_input.shape[-1] == cov_output.shape[-1]: block_out = block_input + cov_output return block_out else: block_out = block_input + Covunit(cov_output, cov_output.shape[-1], [1 * 1]) # 當維度不相同時用1*1的卷積處理 return block_out
三、BottleNeck結構
普通的shortcut(如下左圖)參數的計算量比較大,這個時候可以使用bottleneck瓶頸結構(如下右圖)來減少參數量,這種替換有點類似於用兩個3*3卷積替換一個5*5的卷積的情況。
四、ResNet50網絡
ResNet50網絡是一個非常典型的利用瓶頸結構進行深度網絡構建的,其結構如下圖:
這里有如下注意事項:
- 在進行layername=COV1前需要對輸入進行3*3padding stride 2的零填充操作。
- 每一個stage的第一個瓶頸層應為cov block,用來做參數結構調整,其余瓶頸層應為identity block。如cov2_x有3個瓶頸層,第一個瓶頸層應該是cov block,剩余的兩個位你identity block。
五、ResNet50網絡的實現
考慮到存在兩種不同的跳遠連接,所以這里定義cov_block與identity_block兩個函數。
代碼示例
from tensorflow.keras import layers def cov_block(input_tensor, filters, kernel_size=(3, 3), strides=(1, 1), name=None): ''' resnet網絡中,跳遠連接后,input與output的通道數不同的瓶頸卷積塊 input_tensor:輸入tensor filters:瓶頸結構各個卷積的通道數,如[64,64,265] ''' filter1, filter2, filter3 = filters # 瓶頸結構個各個卷積層的卷積核數 # 瓶頸結構1*1卷積層 x = layers.Conv2D(filter1, (1, 1), strides=strides, name=name + 'bot_cov1')(input_tensor) x = layers.BatchNormalization(name=name + 'bottle_cov1_bn')(x) x = layers.Activation('relu')(x) # 瓶頸結構3*3卷積層 x = layers.Conv2D(filter2, kernel_size, strides=strides, name=name + 'bot_cov2')(x) x = layers.BatchNormalization(name=name + 'bottle_cov2_bn')(x) x = layers.Activation('relu')(x) # 瓶頸結構1*1卷積層 x = layers.Conv2D(filter3, (1, 1), strides=strides, name=name + 'bot_cov3')(x) x = layers.BatchNormalization(name=name + 'bottle_cov3_bn')(x) # 跳遠結構,調整input與output相同的shape shortcut = layers.Conv2D(filter3, (1, 1), strides, 1, name=name + 'bot_shortcut')(input_tensor) shortcut = layers.BatchNormalization(name=name + 'bottle_cov3_bn')(shortcut) # 輸出 x = layers.add([x, shortcut]) x = layers.Activation('relu')(x) return x def identity_block(input_tensor, filters, kernel_size=(3, 3), strides=(1, 1), name=None): ''' resnet網絡中,跳遠連接后,input與output的通道數相同的瓶頸卷積塊 input_tensor:輸入tensor filters:瓶頸結構各個卷積的通道數,如[64,64,265] ''' filter1, filter2, filter3 = filters # 瓶頸結構個各個卷積層的卷積核數 # 瓶頸結構1*1卷積層 x = layers.Conv2D(filter1, (1, 1), strides=strides, name=name + 'bot_cov1')(input_tensor) x = layers.BatchNormalization(name=name + 'bottle_cov1_bn')(x) x = layers.Activation('relu')(x) # 瓶頸結構3*3卷積層 x = layers.Conv2D(filter2, kernel_size, strides=strides, name=name + 'bot_cov2')(x) x = layers.BatchNormalization(name=name + 'bottle_cov2_bn')(x) x = layers.Activation('relu')(x) # 瓶頸結構1*1卷積層 x = layers.Conv2D(filter3, (1, 1), strides=strides, name=name + 'bot_cov3')(x) x = layers.BatchNormalization(name=name + 'bottle_cov3_bn')(x) # 輸出,輸入與輸出shape相同,輸入在上一次卷積激活錢已經標准化,所以這里不需要再次標准化,直接相加 x = layers.add([x, input_tensor]) x = layers.Activation('relu')(x) return x def resnet50(input): # resnet50-cov1: 3*3 padding, 7*7,64, stride 2 x = layers.ZeroPadding2D((3, 3))(input) x = layers.Conv2D(64, (7, 7), strides=(2, 2), name='resnet_cov1')(x) x = layers.BatchNormalization(name='resnet_cov1_bn')(x) x = layers.Activation('relu')(x) # 3*3 maxpooling, stride 2 x = layers.MaxPooling2D((3, 3), (2, 2))(x) # resnet50-cov2 x = cov_block(input_tensor=x, filters=[64, 64, 256], kernel_size=(3, 3), strides=(1, 1), name='resnet_cov2_bot1') x = identity_block(input_tensor=x, filters=[64, 64, 256], kernel_size=(3, 3), strides=(1, 1), name='resnet_cov2_bot2') x = identity_block(input_tensor=x, filters=[64, 64, 256], kernel_size=(3, 3), strides=(1, 1), name='resnet_cov2_bot3') # resnet50-cov3 x = cov_block(x, [128, 128, 512], (3, 3), (1, 1), 'resnet_cov3_bot1') x = identity_block(x, [128, 128, 512], (3, 3), (1, 1), 'resnet_cov3_bot2') x = identity_block(x, [128, 128, 512], (3, 3), (1, 1), 'resnet_cov3_bot3') x = identity_block(x, [128, 128, 512], (3, 3), (1, 1), 'resnet_cov3_bot4') # resnet50-cov4 x = cov_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot1') x = identity_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot2') x = identity_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot3') x = identity_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot4') x = identity_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot5') x = identity_block(x, [256, 256, 1024], (3, 3), (1, 1), 'resnet_cov4_bot6') # resnet50-cov5 x = cov_block(x, [512, 512, 2048], (3, 3), (1, 1), 'resnet_cov5_bot1') x = identity_block(x, [512, 512, 2048], (3, 3), (1, 1), 'resnet_cov5_bot2') x = identity_block(x, [512, 512, 2048], (3, 3), (1, 1), 'resnet_cov5_bot3')