- 🍨 本文為🔗365天深度學習訓練營 中的學習記錄博客
- 🍖 原作者:K同學啊
一、理論知識儲備
1.CNN算法發展
- AlexNet是2012年ImageNet競賽中,由Alex Krizhevsky和Ilya Sutskever提出,在2012年ImageNet競賽中,AlexNet以top5錯誤率為15.3%取得了分類任務的第一名。
- VGGNet是2014年ImageNet競賽中,由Karen Simonyan和Andrew Zisserman提出,在2014年ImageNet競賽中,VGGNet以top5錯誤率為7.3%取得了分類任務的第二名。
- GoogLeNet是2014年ImageNet競賽中,由Christian Szegedy提出,在2014年ImageNet競賽中,GoogLeNet以top5錯誤率為6.6%取得了分類任務的第一名。
- ResNet是2015年ImageNet競賽中,由Kaiming He、Xiangyu Zhang、Saining Xie、Trevor Darrell提出,在2015年ImageNet競賽中,ResNet以top5錯誤率為3.57%取得了分類任務的第一名。
- DenseNet是2016年ImageNet競賽中,由Gao Huang、Zhuang Liu、Kaiming He、Xiangyu Zhang提出,在2016年ImageNet競賽中,DenseNet以top5錯誤率為3.03%取得了分類任務的第一名。
- SE-ResNet是2017年ImageNet競賽中,由Xiaolong Wang、Kaiming He、Jian Sun提出,在2017年ImageNet競賽中,SE-ResNet以top5錯誤率為2.97%取得了分類任務的第一名。
- ResNeXt是2017年ImageNet競賽中,由Saining Xie、Zhifeng Cai、Trevor Darrell提出,在2017年ImageNet競賽中,ResNeXt以top5錯誤率為2.80%取得了分類任務的第一名。
2.殘差網絡的由來
深度殘差網絡RestNet(deep residual network)是2015年ImageNet競賽中由何凱明等提出,因為它簡單與實用并存,隨后很多研究都是建立在ResNet-50
或者ResNet-101
的基礎上完成。
ResNet主要解決深度卷積網絡在深度加深時候的“退化”問題。在一般的卷積神經網絡中,增大網絡深度后帶來的第一個問題就是梯度消失、爆炸,這個問題Szegedy提出BN層后被順利解決。BN層能對各層的輸出做歸一化,這樣梯度在反向層層傳遞后仍能保持大小穩定,不會出現過小或過大的情況。但是作者發現加了BN后再加大深度仍然不容易收斂,其提到了第二個問題–準確率下降問題:層級大到一定程度時準確率就會飽和,然后迅速下降,這種下降即不是梯度消失引起的也不是過擬合造成的,而是由于網絡過于復雜,以至于光靠不加約束的放養式的訓練很難達到理想的錯誤率。
準確率下降問題不是網絡結構本身的問題,而是現有的訓練方式不夠理想造成的。當前廣泛使用的優化器,無論是SGD,還是RMSProp,或是Adam,都無法在網絡深度變大后達到理論上最優的收斂結果。
作者在文中證明了只要有合適的網絡結構,更深的網絡肯定會比較淺的網絡效果要好。證明過程也很簡單:假設在一種網絡A的后面添加幾層形成新的網絡B,如果增加的層級只是對A的輸出做了個恒等映射(identity mapping),即A的輸出經過新增的層級變成B的輸出后沒有發生變化,這樣網絡A和網絡B的錯誤率就是相等的,也就證明了加深后的網絡不會比加深前的網絡效果差。
何愷明提出了一種殘差結構來實現上述恒等映射(圖1):整個模塊除了正常的卷積層輸出外,還有個分支把輸入直接連到輸出上,該分支輸出和卷積的輸出做算術相加得到最終的輸出,用公式表達就是 H ( x ) = F ( x ) + x H(x)= F(x)+ x H(x)=F(x)+x $ x $是輸入, $ F(x) 是卷積分支的輸出, 是卷積分支的輸出, 是卷積分支的輸出, H(x) $是整個結構的輸出。可以證明如果 $ F(x) $分支中所有參數都是0 $ H(x) $就是個恒等映射。殘差結構人為制造了恒等映射,就能讓整個結構朝著恒等映射的方向去收斂,確保最終的錯誤率不會因為深度的變大而越來越差。如果一個網絡通過簡單的手工設置參數值就能達到想要的結果,那這種結構就很容易通過訓練來收斂到該結果,這是一條設計復雜的網絡時通用的規則。
圖2左邊的單元為 ResNet
兩層的殘差單元,兩層的殘差單元包含兩個相同輸出的通道數的 3x3
卷積,只是用于較淺的 ResNet 網絡,對較深的網絡主要使用三層的殘差單元。三層的殘差單元又稱為bottleneck 結構,先用一個1x1
卷積進行降維,然后 3x3
卷積,最后用 1x1
升維恢復原有的維度。另外,如果有輸入輸出維度不同的情況,可以對輸入做一個線性映射變換維度,再連接后面的層。層的殘差單元對于相同數量的層又減少了參數量,因此可以拓展更深的模型。通過殘差單元的組合有經典的 ResNet-50
,ResNet-101
等網絡結構。
二、前期工作
1.設置GPU
import tensorflow as tf
gpus = tf.config.list_physical_devices('GPU')if gpus:tf.config.explicitly_set_memory_growth(gpus[0], True)tf.config.set_visible_devices(gpus[0], 'GPU')print("GPUs available")
2.導入數據
import matplotlib.pyplot as plt
# 支持中文
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用來正常顯示中文標簽
plt.rcParams['axes.unicode_minus'] = False # 用來正常顯示負號import os,PIL,pathlib
import numpy as npfrom tensorflow import keras
from tensorflow.keras import layers,models
data_dir = pathlib.Path('F:/host/Data/bird_photos')
3.查看數據
image_count = len(list(data_dir.glob('*/*')))print("圖片總數為:",image_count)
三、數據預處理
文件夾 | 數量 |
---|---|
Bananaquit | 166張 |
Black Skimmer | 111張 |
Black Throated Bushtiti | 122張 |
Cockatoo | 166張 |
1.加載數據
使用image_dataset_from_directory
方法將磁盤中的數據加載到tf.data.Dataset
對象中。
batch_size = 8
img_height = 224
img_width = 224
train_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="training",seed=123,image_size=(img_height, img_width),batch_size=batch_size,
)
val_ds = tf.keras.preprocessing.image_dataset_from_directory(data_dir,validation_split=0.2,subset="validation",seed=123,image_size=(img_height, img_width),batch_size=batch_size,
)
# 我們可以通過`class_names`屬性查看類別名稱
class_names = train_ds.class_names
print(class_names)
2.可視化數據
plt.figure(figsize=(10, 5)) # 圖形的寬為10高為5
plt.suptitle('Bird Photos')for images, labels in train_ds.take(1):for i in range(8):ax = plt.subplot(2, 4, i+1)plt.imshow(images[i].numpy().astype("uint8"))plt.title(class_names[labels[i]])plt.axis("off")
plt.imshow(images[1].numpy().astype("uint8"))
3.再次檢查數據
for image_batch, labels_batch in train_ds:print(image_batch.shape)print(labels_batch.shape)break
image_batch
: 包含8張圖像的張量,形狀為(8, 224, 224, 3)。labels_batch
: 包含8個標簽的張量,形狀為(8,)。
4.配置數據集
shuffle
: 隨機打亂數據集。prefetch
: 預取數據集,以加速數據集的迭代。cache
: 緩存數據集,以加速數據集的迭代。
AUTOTUNE = tf.data.AUTOTUNEtrain_ds = train_ds.cache().shuffle(1000).prefetch(buffer_size=AUTOTUNE)
val_ds = val_ds.cache().prefetch(buffer_size=AUTOTUNE)
四、殘差網絡(ResNet)介紹
1.殘差網絡解決了什么
殘差網絡是為了解決深度神經網絡(DNN)訓練過程中梯度消失和梯度爆炸的問題而提出的。它通過引入殘差連接,將輸入直接加到輸出上,從而允許網絡學習更復雜的函數。
2.ResNet-50介紹
ResNet-50有兩個基本的塊,分別名為Conv Block
和Identity Block
。
五、構建ResNet-50網絡模型
from keras import layersfrom keras.layers import Input, Activation, BatchNormalization, Flatten
from keras.layers import Conv2D, AveragePooling2D, Dense, MaxPooling2D, ZeroPadding2D
from keras.models import Modeldef identity_block(input_tensor, kernel_size, filters, stage, block):filters1, filters2, filters3 = filtersname_base = str(stage) + block + '_identity_block_'x = Conv2D(filters1, (1, 1), name=name_base + 'conv1')(input_tensor)x = BatchNormalization(name=name_base + 'bn1')(x)x = Activation('relu',name=name_base + 'relu1')(x)x = Conv2D(filters2, kernel_size, padding='same', name=name_base + 'conv2')(x)x = BatchNormalization(name=name_base + 'bn2')(x)x = Activation('relu',name=name_base + 'relu2')(x)x = Conv2D(filters3, (1, 1), name=name_base + 'conv3')(x)x = BatchNormalization(name=name_base + 'bn3')(x)x = layers.add([x, input_tensor], name=name_base + 'add')x = Activation('relu',name=name_base + 'relu4')(x)return x# 在殘差網絡中,廣泛地使用了BN層;但是沒有使用MaxPoo1ing以便減小特征圖尺寸,
# 作為替代,在每個模塊的第一層,都使用了strides=(2,2)的方式進行特征圖尺寸縮減,
# 與使用MaxPooling相比,毫無疑問是減少了卷積的次數,輸入圖像分辨率較大時比較適合
# 在殘差網絡的最后一級,先利用layer.add()實現H(x)=x+F(x)
def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2)):filters1, filters2, filters3 = filtersres_name_base = str(stage) + block + '_conv_block_res_'name_base = str(stage) + block + '_conv_block_'x = Conv2D(filters1, (1, 1), strides=strides, name=name_base + 'conv1')(input_tensor)x = BatchNormalization(name=name_base + 'bn1')(x)x = Activation('relu',name=name_base + 'relu1')(x)x = Conv2D(filters2, kernel_size, padding='same', name=name_base + 'conv2')(x)x = BatchNormalization(name=name_base + 'bn2')(x)x = Activation('relu',name=name_base + 'relu2')(x)x = Conv2D(filters3, (1, 1), name=name_base + 'conv3')(x)x = BatchNormalization(name=name_base + 'bn3')(x)shortcut = Conv2D(filters3, (1, 1), strides=strides, name=res_name_base + 'conv')(input_tensor)shortcut = BatchNormalization(name=res_name_base + 'bn')(shortcut)x = layers.add([x, shortcut],name=name_base + 'add')x = Activation('relu',name=name_base + 'relu4')(x)return xdef ResNet50(input_shape=(224, 224, 3), num_classes=1000):img_input = Input(shape=input_shape)x = ZeroPadding2D((3, 3))(img_input)x = Conv2D(64, (7, 7), strides=(2, 2), name='conv1')(x)x = BatchNormalization(name='bn_conv1')(x)x = Activation('relu')(x)x = MaxPooling2D((3, 3), strides=(2, 2))(x)x = conv_block(x, 3, [64, 64, 256], stage=2, block='a', strides=(1, 1))x = identity_block(x, 3, [64, 64, 256], stage=2, block='b')x = identity_block(x, 3, [64, 64, 256], stage=2, block='c')x = conv_block(x, 3, [128, 128, 512], stage=3, block='a')x = identity_block(x, 3, [128, 128, 512], stage=3, block='b')x = identity_block(x, 3, [128, 128, 512], stage=3, block='c')x = identity_block(x, 3, [128, 128, 512], stage=3, block='d')x = conv_block(x, 3, [256, 256, 1024], stage=4, block='a')x = identity_block(x, 3, [256, 256, 1024], stage=4, block='b')x = identity_block(x, 3, [256, 256, 1024], stage=4, block='c')x = identity_block(x, 3, [256, 256, 1024], stage=4, block='d')x = identity_block(x, 3, [256, 256, 1024], stage=4, block='e')x = identity_block(x, 3, [256, 256, 1024], stage=4, block='f')x = conv_block(x, 3, [512, 512, 2048], stage=5, block='a')x = identity_block(x, 3, [512, 512, 2048], stage=5, block='b')x = identity_block(x, 3, [512, 512, 2048], stage=5, block='c')x = AveragePooling2D((7, 7), name='avg_pool')(x)x = Flatten()(x)x = Dense(num_classes, activation='softmax', name='fc1000')(x)model = Model(img_input, x, name='ResNet50')# 加載預訓練模型model.load_weights('./weights/resnet50_weights_tf_dim_ordering_tf_kernels.h5')return modelmodel = ResNet50()
model.summary()
六、編譯
在準備對模型進行訓練之前,還需要再對其進行一些設置。以下內容是在模型的編譯步驟中添加的:
- 損失函數(loss):用于衡量模型在訓練期間預測值和實際值之間的差距。
- 優化器(optimizer):決定模型如何根據其看到的數據和自身的損失函數進行更新。
- 指標(metrics):用于監控訓練和測試步驟。
# 設置優化器
opt = tf.keras.optimizers.Adam(learning_rate=1e-7)model.compile(optimizer="adam",loss='sparse_categorical_crossentropy',metrics=['accuracy'])
七、訓練模型
epochs = 10history = model.fit(train_ds,validation_data=val_ds,epochs=epochs)
八、評估模型
acc = history.history['accuracy']
val_acc = history.history['val_accuracy']loss = history.history['loss']
val_loss = history.history['val_loss']epochs_range = range(epochs)plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)plt.plot(epochs_range, acc, label='Training Accuracy')
plt.plot(epochs_range, val_acc, label='Validation Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')plt.subplot(1, 2, 2)
plt.plot(epochs_range, loss, label='Training Loss')
plt.plot(epochs_range, val_loss, label='Validation Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()
九、預測
# 采用加載的模型來看預測結果plt.figure(figsize=(10, 5))
plt.suptitle('Predictions')for images, labels in val_ds.take(1):for i in range(8):ax = plt.subplot(2, 4, i + 1)# 顯示圖片plt.imshow(images[i].numpy().astype("uint8"))# 需要給圖片增加一個維度img_array = tf.expand_dims(images[i], 0)# 使用模型預測圖片predictions = model.predict(img_array)plt.title(class_names[np.argmax(predictions)])plt.axis("off")
十、個人小結
在這篇文章中,我深入探討了卷積神經網絡(CNN)的發展歷程,特別是殘差網絡(ResNet)的誕生和原理。CNN在圖像識別領域取得了顯著的進展,但隨著網絡深度的增加,梯度消失和爆炸的問題逐漸顯現,影響了深層網絡的性能。ResNet通過引入殘差學習框架,有效地解決了這一問題,使得網絡能夠學習到恒等映射,從而在保持性能的同時增加深度。