1)這里的steps_per_epoch是針對fit_generation特有的一個參數。輸入數據仍然是每次64張,由於是采用了flow_from_directory方法,會不斷的一次次從文件夾里取64張圖像輸入網絡,直到滿足800次之后才進入下一個epoch。由於加了圖像增強,所以不論多少次,網絡輸入都是不一樣的。事實上steps_per_epoch可以簡單的設置為data_size/batch_size。基本上保證了每一輪一張圖像過一遍。
深度學習可以說是一門數據驅動的學科,各種有名的CNN模型,無一不是在大型的數據庫上進行的訓練。像ImageNet這種規模的數據庫,動輒上百萬張圖片。對於普通的機器學習工作者、學習者來說,面對的任務各不相同,很難拿到如此大規模的數據集。同時也沒有谷歌,Facebook那種大公司驚人的算力支持,想從0訓練一個深度CNN網絡,基本是不可能的。但是好在已經訓練好的模型的參數,往往經過簡單的調整和訓練,就可以很好的遷移到其他不同的數據集上,同時也無需大量的算力支撐,便能在短時間內訓練得出滿意的效果。這便是遷移學習。究其根本,就是雖然圖像的數據集不同,但是底層的特征卻是有大部分通用的。
遷移學習主要分為兩種
- 第一種即所謂的transfer learning,遷移訓練時,移掉最頂層,比如ImageNet訓練任務的頂層就是一個1000輸出的全連接層,換上新的頂層,比如輸出為10的全連接層,然后訓練的時候,只訓練最后兩層,即原網絡的倒數第二層和新換的全連接輸出層。可以說transfer learning將底層的網絡當做了一個特征提取器來使用。
- 第二種叫做fine tune,和transfer learning一樣,換一個新的頂層,但是這一次在訓練的過程中,所有的(或大部分)其它層都會經過訓練。也就是底層的權重也會隨着訓練進行調整。
一個典型的遷移學習過程是這樣的。首先通過transfer learning對新的數據集進行訓練,訓練過一定epoch之后,改用fine tune方法繼續訓練,同時降低學習率。這樣做是因為如果一開始就采用fine tune方法的話,網絡還沒有適應新的數據,那么在進行參數更新的時候,比較大的梯度可能會導致原本訓練的比較好的參數被污染,反而導致效果下降。
本課,我們將嘗試使用谷歌提出的Inception V3模型來對一個花朵數據集進行遷移學習的訓練。
數據集為17種不同的花朵,每種有80張樣本,一共1360張圖像,屬於典型的小樣本集。數據下載地址:http://www.robots.ox.ac.uk/~vgg/data/flowers/17/
官方沒有給出圖像對應的label,我寫了一段代碼,把每張圖像加上標簽,https://gist.github.com/tsycnh/177bbf7d93adc6207242fd334ce3bb60
同時,Keras對於數據的格式要求如下:我也寫了一個腳本來做轉換https://gist.github.com/tsycnh/1b35103adec1ad2be5090c486354859f
這個腳本我將訓練集划分為800張,驗證集和測試集分別為260張,圖片順序做了隨機打亂
如果你懶得自己轉換,我已經把處理好的數據進行上傳,直接下載即可:https://download.csdn.net/download/tsyccnh/10581591
請注意,這里的花朵識別仍屬於最簡單的單分類任務,樣張如下
這個腳本我將訓練集划分為800張,驗證集和測試集分別為260張,圖片順序做了隨機打亂
如果你懶得自己轉換,我已經把處理好的數據進行上傳,直接下載即可:https://download.csdn.net/download/tsyccnh/10581591
請注意,這里的花朵識別仍屬於最簡單的單分類任務,樣張如下
這個腳本我將訓練集划分為800張,驗證集和測試集分別為260張,圖片順序做了隨機打亂
如果你懶得自己轉換,我已經把處理好的數據進行上傳,直接下載即可:https://download.csdn.net/download/tsyccnh/10581591
請注意,這里的花朵識別仍屬於最簡單的單分類任務,樣張如下
這個腳本我將訓練集划分為800張,驗證集和測試集分別為260張,圖片順序做了隨機打亂
如果你懶得自己轉換,我已經把處理好的數據進行上傳,直接下載即可:https://download.csdn.net/download/tsyccnh/10581591
請注意,這里的花朵識別仍屬於最簡單的單分類任務,樣張如下
from keras.preprocessing.image import ImageDataGenerator from keras.applications.inception_v3 import InceptionV3,preprocess_input from keras.layers import GlobalAveragePooling2D,Dense from keras.models import Model from keras.utils.vis_utils import plot_model from keras.optimizers import Adagrad # 數據准備 train_datagen = ImageDataGenerator( preprocessing_function=preprocess_input,# ((x/255)-0.5)*2 歸一化到±1之間 rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, ) val_datagen = ImageDataGenerator( preprocessing_function=preprocess_input, rotation_range=30, width_shift_range=0.2, height_shift_range=0.2, shear_range=0.2, zoom_range=0.2, horizontal_flip=True, )
這里用到的數據集和之前都不同,之前用的是一些公共的、Keras內置的數據集,這次用到的是自己准備的數據集。由於數據的圖像大小比較大,不適合一次全部載入到內存中,所以使用了flow_from_directory方法來按批次從硬盤讀取圖像數據,並實時進行圖像增強
train_generator = train_datagen.flow_from_directory(directory='./flowers17/train', target_size=(299,299),#Inception V3規定大小 batch_size=64) val_generator = val_datagen.flow_from_directory(directory='./flowers17/validation', target_size=(299,299), batch_size=64)
首先我們需要加載骨架模型,這里用的InceptionV3模型,其兩個參數比較重要,一個是weights,如果是’imagenet’,Keras就會自動下載已經在ImageNet上訓練好的參數,如果是None,系統會通過隨機的方式初始化參數,目前該參數只有這兩個選擇。另一個參數是include_top,如果是True,輸出是1000個節點的全連接層。如果是False,會去掉頂層,輸出一個8 * 8 * 2048的張量。
ps:在keras.applications里還有很多其他的預置模型,比如VGG,ResNet,以及適用於移動端的MobileNet等。大家都可以拿來玩玩。
一般我們做遷移訓練,都是要去掉頂層,后面接上各種自定義的其它新層。這已經成為了訓練新任務慣用的套路。
輸出層先用GlobalAveragePooling2D函數將8 * 8 * 2048的輸出轉換成1 * 2048的張量。后面接了一個1024個節點的全連接層,最后是一個17個節點的輸出層,用softmax激活函數。
# 構建基礎模型 base_model = InceptionV3(weights='imagenet',include_top=False) # 增加新的輸出層 x = base_model.output x = GlobalAveragePooling2D()(x) # GlobalAveragePooling2D 將 MxNxC 的張量轉換成 1xC 張量,C是通道數 x = Dense(1024,activation='relu')(x) predictions = Dense(17,activation='softmax')(x) model = Model(inputs=base_model.input,outputs=predictions) # plot_model(model,'tlmodel.png')
構建完新模型后需要進行模型的配置。下面的兩個函數分別對transfer learning和fine tune兩種方法分別進行了配置。每個函數有兩個參數,分別是model和base_model。這里可能會有同學有疑問,上面定義了model,這里又將base_model一起做配置,對base_model的更改會對model產生影響么?
答案是會的。如果你debug追進去看的話,可以看到model的第一層和base_model的第一層是指向同一個內存地址的。這里將base_model作為參數,只是為了方便對骨架模型進行設置。
setup_to_transfer_learning: 這個函數將骨架模型的所有層都設置為不可訓練
setup_to_fine_tune:這個函數將骨架模型中的前幾層設置為不可訓練,后面的所有Inception模塊都設置為可訓練。
這里面的GAP_LAYER需要配合打印圖和調試的方法確認正確的值,感興趣具體怎么操作的同學,可以私信我,以后看有沒有必要把這個點寫成教程。
''' 這里的base_model和model里面的iv3都指向同一個地址 ''' def setup_to_transfer_learning(model,base_model):#base_model for layer in base_model.layers: layer.trainable = False model.compile(optimizer='adam',loss='categorical_crossentropy',metrics=['accuracy']) def setup_to_fine_tune(model,base_model): GAP_LAYER = 17 # max_pooling_2d_2 for layer in base_model.layers[:GAP_LAYER+1]: layer.trainable = False for layer in base_model.layers[GAP_LAYER+1:]: layer.trainable = True model.compile(optimizer=Adagrad(lr=0.0001),loss='categorical_crossentropy',metrics=['accuracy'])
下面開始訓練,這段代碼也演示了如何在全部訓練過程中改變模型。
setup_to_transfer_learning(model,base_model) history_tl = model.fit_generator(generator=train_generator, steps_per_epoch=800,#800 epochs=2,#2 validation_data=val_generator, validation_steps=12,#12 class_weight='auto' ) model.save('./flowers17_iv3_tl.h5') setup_to_fine_tune(model,base_model) history_ft = model.fit_generator(generator=train_generator, steps_per_epoch=800, epochs=2, validation_data=val_generator, validation_steps=1, class_weight='auto') model.save('./flowers17_iv3_ft.h5')
可以看到經過兩個epoch的transfer learning后,驗證集准確率達到89.1%。再經過兩個epoch的fine tune后驗證集准確率達96.88%。可以看到遷移學習的效果還是很好的。
總結
- 學習了兩種常用遷移學習方法(tranfer learning,fine tune)及訓練技巧
- 學習了使用自己的數據樣本進行訓練
- 學習了加載Keras預置的經典模型
- 學習了如何在預置模型頂部添加新的層
- 學習了如何設置層的參數為不可訓練
本節課也是用Keras做CNN的一個完結,基本上涵蓋了從定義最簡單CNN到使用現成模型進行自己數據的遷移學習。下一階段看情況可能會出一個RNN系列的入門課。