【神经网络】(7) 迁移学习(CNN-MobileNetV2),案例:乳腺癌二分类
各位同學好,今天和大家分享一下Tensorflow2.0中如何使用遷移學習的方法構造神經網絡。需要數據集的在評論區留個言。
1. 遷移學習
官方文檔:Module: tf.keras.applications ?|? TensorFlow Core v2.7.0 (google.cn)
使用遷移學習,可以直接獲取官方已經構建好了的網絡模型架構,以及訓練好的權重參數,可能這些參數和我們處理的問題不太一樣。但相比我們建模過程中采用隨機初始化的權重參數,預訓練的權重參數會讓我們的模型訓練速度更快,準確率提高,只要稍作調整就能達到很好的效果。
預訓練模型是一個之前基于大型數據集(通常是大型圖像分類任務)訓練的已保存網絡。您可以按原樣使用預訓練模型,也可以使用遷移學習針對給定任務自定義此模型。
遷移學習的理念是,如果一個模型是基于足夠大且通用的數據集訓練的,那么該模型將有效地充當視覺世界的通用模型。隨后,您可以利用這些學習到的特征映射,而不必通過基于大型數據集訓練大型模型而從頭開始。
官方提供了許多經典的網絡結構,在上面的文檔中可仔細研究。
tf.keras.applications.resnet50.ResNet50
tf.keras.applications.resnet.ResNet101
tf.keras.applications.inception_v3.InceptionV3
tf.keras.applications.inception_resnet_v2.InceptionResNetV2
。。。。。。。
本節我使用遷移學習導入MobileNetV2網絡,進行乳腺癌圖像二分類的研究。下面介紹兩種方法,一是將所有特征提取層的權重凍住,訓練過程中不進行更新參數;二是凍住部分層,解凍的層可以更新權重參數。
2. 數據加載
使用tf.keras.preprocessing.image_dataset_from_directory()方法,在指定的文件夾中獲取圖像數據。其中label_model參數中,'int':表示標簽被編碼成整數(例如:sparse_categorical_crossentropy loss)。'categorical':多分類,指標簽被編碼為分類向量(例如:categorical_crossentropy loss)。'binary':二分類,意味著標簽(只能有2個)被編碼為值為0或1的float32標量(例如:binary_crossentropy)。None:(無標簽)。
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import Sequential, optimizers, layers, Model
import matplotlib.pyplot as pltdef get_data(height, width, batchsz):# 獲取訓練集filepath1 = '/home/featurize/data/train'train_ds = tf.keras.preprocessing.image_dataset_from_directory(filepath1, # 指定訓練集數據路徑label_mode = 'categorical', # 導入的目標數據,進行onehot編碼image_size = (height, width), # 對圖像resizebatch_size = batchsz, # 每次迭代取32個數據 )# 獲取驗證集數據filepath2 = '/home/featurize/data/val'test_ds = tf.keras.preprocessing.image_dataset_from_directory(filepath2, # 指定訓練集數據路徑label_mode = 'categorical', image_size = (height, width), # 對圖像resizebatch_size = batchsz, # 每次迭代取32個數據 ) return(train_ds, test_ds)# 數據讀取函數,返回訓練集、驗證集、測試集
train_ds, test_ds = get_data(height=50, width=50, batchsz=32)
3. 數據可視化
iter()生成一個迭代器,配合next()使用,每運行一次,就從指定數據集中取出一個batch的數據,返回圖像數據sample[0]和標簽數據sample[1]。
# 查看導入數據的信息
sample = next(iter(train_ds))
print('x_batch.shape:', sample[0].shape, 'y_batch.shape:', sample[1].shape)
# x_batch.shape: (32, 50, 50, 3) y_batch.shape: (32, 2)
print('y[:5]:', sample[1][:5])
# [[1. 0.] [1. 0.] [1. 0.] [0. 1.] [0. 1.]]# 繪圖查看信息,1代表得了乳腺癌,0代表沒得乳腺癌
import matplotlib.pyplot as plt
for i in range(15):plt.subplot(3,5,i+1)plt.imshow(sample[0][i]/255.0) #把圖片像素壓縮到0-1之間顯示圖像,不壓縮matpolt好像畫不了plt.xticks([]) # 不顯示坐標軸plt.yticks([])
plt.show()
4. 數據預處理
對訓練集和測試集的數據集改變數據類型,并且將圖像數據從[0,255]映射到[-1,1]之間,因為我們使用的預訓練的MobileNetV2模型對輸入有要求是在[-1,1]之間,我們需要和它保持一致,具體可以看官方的文檔。tf.keras.applications.mobilenet_v2.MobileNetV2 ?|? TensorFlow Core v2.7.0 (google.cn)
# 數據預處理
def processing(x,y):x = 2*tf.cast(x, dtype=tf.float32)/255.0-1 #映射到-1-1之間y = tf.cast(y, tf.int32)return(x,y)# 預處理訓練集和驗證集
train_ds = train_ds.map(processing).shuffle(10000)
test_ds = test_ds.map(processing) # 驗證集不需要打亂順序# 查看數據信息,更改是否正確
sample = next(iter(train_ds))
print('x_batch.shape:', sample[0].shape, 'y_batch.shape:', sample[1].shape)
# x_batch.shape: (32, 50, 50, 3) y_batch.shape: (32, 2)
5. 遷移學習--凍結所有特征提取層
在進行遷移學習時,我們一般有兩種方法,法一:取卷積部分作為權重參數初始化,并繼續進行訓練;練法二:把別人訓練好的層凍住,只用于特征提取,權重參數不更新。
一般全連接層按自己的方式重新定義,把前面的特征提取的部分微調
當數據量小的時候(<1W),越需要借助別人訓練好的模型,需要凍住的層也就越多。數據量大且任務不一樣時只進行權重參數初始化,而不凍住這些層。
官方文檔:遷移學習和微調 ?|? TensorFlow Core (google.cn)
input_shape:代表我們自己的輸入圖像的大小,某些模型對輸入的大小有要求。include_top:是否要導入模型的全連接層部分,一般不需要,全連接層根據自己的任務來自定義,默認有1000個輸出。weights:加載模型的權重參數,可以是:None不加載;'imagenet'官方訓練好的權重;path自己找到的權重的文件路徑。
實例化一個已預加載基于 ImageNet 訓練的權重的 MobileNet V2 模型。通過指定include_top=False參數,可以加載不包括頂部分類層的網絡,這對于特征提取十分理想。
layer遍歷預訓練模型的所有層,使所有層的layer.trainable = False,即所有層在模型訓練的正方向傳播過程中,權重參數都不會發生變化,不進行更新。
# 遷移學習
pre_model = keras.applications.mobilenet_v2.MobileNetV2(input_shape=[50, 50, 3], include_top=False, weights='imagenet')# 凍住特征提取層
for layer in pre_model.layers:layer.trainable = False# 前向傳播,看網絡輸出什么
imgs, labels = next(iter(train_ds)) #img的shape為[32,224,224,3]
outputs = pre_model(imgs)
print('outputs.shape:', outputs.shape)
# [32,224,224,3] ==> [32,7,7,1280]
5.1 模型構建
我們獲取的pre_model預訓練模型已經包含了輸入層、特征提取層,可以通過pre_model.input得到模型的輸入,pre_model.output得到模型的特征提取層的輸出結果。輸出結果是一個形狀為[b, h, w ,c]的tensor變量,將它輸入全連接層之前需要把這個tensor壓平,變成二維的[b, n]類型。
加入Flatten()層將輸出結果壓平,設計一個全連接層,進一步特征提取,輸出1024個特征,加入Dropout層防止過擬合,最后由于本問題是二分類,采用sigmoid函數來判斷預測結果。也可以使用softmax函數。
在keras.Model容器中只需指定輸入層和輸出層就可以構建網絡,中間層通過函數的方式連接在一起。
# 自定義頂層
# 將特征提取層的輸出結果壓平后再傳入全連接層
x = layers.Flatten()(pre_model.output)
# 加入全連接層
x = layers.Dense(1024, activation='relu')(x)
x = layers.Dropout(0.4)(x) #0.4的幾率殺死神經元
# 輸出層,輸出圖像屬于10分類的概率
x = layers.Dense(2, activation='sigmoid')(x)# 模型構建
model = Model(pre_model.input, x) # 給出輸入層和輸出層# 查看網絡結構
pre_model.summary()
預訓練網絡模型結構如下,我們可以看到,Trainable params: 0,我們凍住了所有的特征提取層的權重,特征提取層不會再進行更新了,只有全連接層的權重會更新。我們先使用這個模型來訓練一下,看一下結果。
Model: "mobilenetv2_1.00_224"
__________________________________________________________________________________________________Layer (type) Output Shape Param # Connected to
==================================================================================================input_1 (InputLayer) [(None, 50, 50, 3)] 0 [] Conv1 (Conv2D) (None, 25, 25, 32) 864 ['input_1[0][0]'] bn_Conv1 (BatchNormalization) (None, 25, 25, 32) 128 ['Conv1[0][0]'] Conv1_relu (ReLU) (None, 25, 25, 32) 0 ['bn_Conv1[0][0]'] expanded_conv_depthwise (Depth (None, 25, 25, 32) 288 ['Conv1_relu[0][0]'] wiseConv2D) ----------------------
省略 N 層
----------------------block_16_project (Conv2D) (None, 2, 2, 320) 307200 ['block_16_depthwise_relu[0][0]']block_16_project_BN (BatchNorm (None, 2, 2, 320) 1280 ['block_16_project[0][0]'] alization) Conv_1 (Conv2D) (None, 2, 2, 1280) 409600 ['block_16_project_BN[0][0]'] Conv_1_bn (BatchNormalization) (None, 2, 2, 1280) 5120 ['Conv_1[0][0]'] out_relu (ReLU) (None, 2, 2, 1280) 0 ['Conv_1_bn[0][0]'] ==================================================================================================
Total params: 2,257,984
Trainable params: 0
Non-trainable params: 2,257,984
__________________________________________________________________________________________________
5.2 網絡配置
# ==1== 網絡配置
# 編譯
model.compile(optimizer = optimizers.Adam(learning_rate=0.00001), #指定學習率,即梯度下降速率loss = 'binary_crossentropy', #二分類交叉熵損失metrics = ['acc'] # 網絡評價指標)# ==2== 模型訓練
# model.fit_generator 用于大型數據集,有時直接讀圖片會導致內存占用過高
history = model.fit(train_ds, #訓練集validation_data = test_ds, #測試集epochs = 10, #迭代10次
)
模型訓練結果如下,經過10次迭代后,測試集準確率上升了5%,損失變化較為平緩
Epoch 1/10
329/329 [==============================] - 11s 23ms/step - loss: 0.5772 - acc: 0.7344 - val_loss: 0.4576 - val_acc: 0.7870
Epoch 2/10
329/329 [==============================] - 7s 21ms/step - loss: 0.4784 - acc: 0.7915 - val_loss: 0.4353 - val_acc: 0.8011
------------------------------------------
省略 N 層
------------------------------------------
Epoch 10/10
329/329 [==============================] - 7s 21ms/step - loss: 0.3424 - acc: 0.8560 - val_loss: 0.3976 - val_acc: 0.8258
5.3 模型評估可視化
通過.history獲取模型訓練后保存的準確率和損失信息,繪制準確率和損失曲線,如下圖。
# 模型評估
# 準確率
train_acc = history.history['acc']
test_acc = history.history['val_acc']
# 損失
train_loss = history.history['loss']
test_loss = history.history['val_loss']# 繪圖
# 準確率曲線
plt.figure(figsize=(10, 5))
plt.subplot(1, 2, 1)
plt.plot(train_acc, label='train_acc')
plt.plot(test_acc, label='test_acc')
plt.title('accuracy')
plt.legend() #顯示圖例label
# 損失曲線
plt.subplot(1, 2, 2)
plt.plot(train_loss, label='train_loss')
plt.plot(test_loss, label='test_loss')
plt.title('loss')
plt.legend()
plt.show()
6. 遷移學習--微調網絡
上面介紹了凍住所有的特征提取層的遷移學習方法,現在來介紹只凍住部分特征提取層。解凍的層layer.trainable = True,可以在正反向傳播的過程中更新權重參數,而被凍住的層layer.trainable = False不能更新權重參數。
微調網絡可以提高我們的模型的精度,由于我們自己的任務和預訓練模型解決的任務不相同,因此用一樣的權重參數肯定達不到理想的效果。具體解封多少層需要按照自己的數據量來定,如果數據量很少,那就越需要依靠預訓練模型的權重參數。如果數據量大,或任務相差很大的話,也需要解封很多層。
在大多數卷積網絡中,層越高,它的專門程度就越高。前幾層學習非常簡單且通用的特征,這些特征可以泛化到幾乎所有類型的圖像。隨著您向上層移動,這些特征越來越特定于訓練模型所使用的數據集。微調的目標是使這些專用特征適應新的數據集,而不是覆蓋通用學習。
6.1 選擇部分層凍結
首先我們需要將之前凍結的層全部解開pre_model.trainable = True,在這個網絡中有154層,那我們就凍結到第120層(包括第120層)。具體凍結到幾層,自己調控。使用一個for循環遍歷前120層,指定layer.trainable = False就可以將它們的權重參數都凍住了,正方向傳播都不會更新了。而解凍的就可以改變權重參數優化我們的網絡了。
# 將所有層解凍
pre_model.trainable = True
# 查看一共有多少層
print('numbers of layers:', len(pre_model.layers))
# numbers of layers: 154# 指定凍結120層之前的層數
find_tune_at = 120# 凍結到第120層
for layer in pre_model.layers[:find_tune_at]:layer.trainable = False
6.2 模型構建
采用和上面相同的方法構建網絡,如下
# 自定義頂層
# 將特征提取層的輸出結果壓平后再傳入全連接層
x = layers.Flatten()(pre_model.output)
# 加入全連接層
x = layers.Dense(1024, activation='relu')(x)
x = layers.Dropout(0.4)(x) #0.4的幾率殺死神經元
# 輸出層,輸出圖像屬于10分類的概率
x = layers.Dense(2, activation='sigmoid')(x)# 模型構建
model = Model(pre_model.input, x) # 給出輸入層和輸出層# 查看預訓練網絡結構
pre_model.summary()
網絡結構如下,我們看到預訓練模型中,可訓練參數的數量明顯增加,Trainable params: 1,624,896,因此也有更多的參數可以更新權重,提高我們網絡的精度。
Model: "mobilenetv2_1.00_224"
__________________________________________________________________________________________________Layer (type) Output Shape Param # Connected to
==================================================================================================input_2 (InputLayer) [(None, 50, 50, 3)] 0 [] Conv1 (Conv2D) (None, 25, 25, 32) 864 ['input_2[0][0]'] bn_Conv1 (BatchNormalization) (None, 25, 25, 32) 128 ['Conv1[0][0]'] Conv1_relu (ReLU) (None, 25, 25, 32) 0 ['bn_Conv1[0][0]'] expanded_conv_depthwise (Depth (None, 25, 25, 32) 288 ['Conv1_relu[0][0]'] wiseConv2D) -----------------------------------------------
省略 N 層
----------------------------------------------- block_16_depthwise_relu (ReLU) (None, 2, 2, 960) 0 ['block_16_depthwise_BN[0][0]'] block_16_project (Conv2D) (None, 2, 2, 320) 307200 ['block_16_depthwise_relu[0][0]']block_16_project_BN (BatchNorm (None, 2, 2, 320) 1280 ['block_16_project[0][0]'] alization) Conv_1 (Conv2D) (None, 2, 2, 1280) 409600 ['block_16_project_BN[0][0]'] Conv_1_bn (BatchNormalization) (None, 2, 2, 1280) 5120 ['Conv_1[0][0]'] out_relu (ReLU) (None, 2, 2, 1280) 0 ['Conv_1_bn[0][0]'] ==================================================================================================
Total params: 2,257,984
Trainable params: 1,624,896
Non-trainable params: 633,088
__________________________________________________________________________________________________
6.3 網絡配置
網絡編譯和配置的方法和前面相同,經過10次迭代后,可見微調后的準確率有所上升。如6.4的損失和準確率圖所示,損失和準確率的變化更加平穩。
當您正在訓練一個大得多的模型并且想要重新調整預訓練權重時,請務必在此階段使用較低的學習率。否則,您的模型可能會很快過擬合。
# 編譯,調小學習率防止過擬合
model.compile(optimizer=optimizers.Adam(learning_rate=1e-6), # 學習率,調小一些防止過擬合loss = 'binary_crossentropy', # 二分類損失,放入sigmoid中再計算損失metrics=['acc']) # 評價指標 # 網絡訓練
history_fine = model.fit(train_ds, #訓練數據validation_data=test_ds, # 驗證數據epochs=10, # 迭代次數)
Epoch 1/10
329/329 [==============================] - 14s 31ms/step - loss: 0.8064 - acc: 0.5974 - val_loss: 0.6245 - val_acc: 0.6898
Epoch 2/10
329/329 [==============================] - 10s 28ms/step - loss: 0.6037 - acc: 0.7447 - val_loss: 0.5144 - val_acc: 0.7782
-------------------------------------------------------------------------------------
省略N層
-------------------------------------------------------------------------------------
Epoch 9/10
329/329 [==============================] - 9s 27ms/step - loss: 0.3105 - acc: 0.8850 - val_loss: 0.4067 - val_acc: 0.8399
Epoch 10/10
329/329 [==============================] - 10s 28ms/step - loss: 0.2881 - acc: 0.8927 - val_loss: 0.4056 - val_acc: 0.8407
6.4 網絡評估可視化
# 準確率
train_acc_fine = history_fine.history['acc']
test_acc_fine = history_fine.history['val_acc']
# 損失
train_loss_fine = history_fine.history['loss']
test_loss_fine = history_fine.history['val_loss']
# 繪圖
plt.figure(figsize=(20, 8))
plt.subplot(1, 2, 1)
plt.plot(train_acc_fine, label='Train_Acc')
plt.plot(test_acc_fine, label='Val_Acc')
plt.legend()
plt.title('Accuracy')plt.subplot(1, 2, 2)
plt.plot(train_loss_fine, label='Training Loss')
plt.plot(test_loss_fine, label='Validation Loss')
plt.legend()
plt.title('Loss')
plt.show()
總結
以上是生活随笔為你收集整理的【神经网络】(7) 迁移学习(CNN-MobileNetV2),案例:乳腺癌二分类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【神经网络】(6) 卷积神经网络(VGG
- 下一篇: 【神经网络】(8) 卷积神经网络(Mob