遷移學習是這兩年比較火的一個話題,主要緣由是在當前的機器學習中,樣本數據的獲取是成本最高的一塊。而遷移學習能夠有效的把原有的學習經驗(對於模型就是模型自己及其訓練好的權重值)帶入到新的領域,從而不須要過多的樣本數據,也能達到大批量數據所達成的效果,進一步節省了學習的計算量和時間。python
MobileNet V2是由谷歌在2018年初發布的一個視覺模型,在Keras中已經內置的並使用ImageNet完成了訓練,能夠直接拿來就用,這個咱們在本系列第五篇中已經提過了。MobileNet V2有輕量和高效的特色。經過Inverted residual block結構,能夠用較少的運算量獲得較高的精度,很適合移動端的機器學習需求。論文在這裏。
在ImageNet數據集上,MobileNet V2能達到92.5%的識別正確率。本篇中,咱們以此模型爲基礎,介紹一個典型的遷移學習實現方法。並經過調整模型完成優化。bash
MobileNet V2本來是識別圖片中主題的名稱。在ImageNet中,有大量的通過標註的照片,標註的信息也很是詳細。好比咱們熟悉的貓貓、狗狗,ImageNet並不簡單的標註爲cat或者dog,而是更詳細的標註爲加菲、德牧這樣精確到具體品種的信息。
咱們這裏使用調整以後的MobileNet V2模型,用於對圖片內容的貓貓和狗狗分類。不去管本來是哪一種具體的品種,只分紅cat/dog兩個大類。
這個問題的描述實際上隱藏了兩個重點:網絡
如同本系列第五篇同樣,先使用最簡短的代碼熟悉一下MobileNet V2。
咱們這個例子所使用的數據,是使用tensorflow_datasets模塊來自動下載、解壓、管理的。因此請先安裝這個擴展包:app
$ pip3 install tfds-nightly
程序在第一次運行的時候,會自動下載微軟的實驗數據集。請儘可能使用程序自動下載,由於下載以後會自動解壓。數據集的保存路徑爲:「~/tensorflow_datasets/」,這個是tensorflow_datasets默認的。
數據集中是隨機尺寸的圖片,程序第一步會將圖片統一到224x224的尺寸,這個是預置的MobileNet V2模型所決定的。
咱們從樣本中取頭兩個圖片顯示在屏幕上,而且使用模型預測圖片內容。請參考源代碼中的註釋閱讀程序,也能夠對照一下第五篇的vgg-19/ResNet50模型預測圖片內容的程序,這些模型的使用方法幾乎是相同的。機器學習
#!/usr/bin/env python3 # 引入所使用到的擴展庫 from __future__ import absolute_import, division, print_function import os import numpy as np import matplotlib.pyplot as plt import tensorflow as tf import tensorflow_datasets as tfds keras = tf.keras # 載入訓練數據,載入時按照80%:10%:10%的比例拆分爲訓練、驗證、測試三個數據集 # 本程序只是演示識別圖片,區分爲三類並無直接意義,但下面的程序訓練模型會使用到 SPLIT_WEIGHTS = (8, 1, 1) splits = tfds.Split.TRAIN.subsplit(weighted=SPLIT_WEIGHTS) (raw_train, raw_validation, raw_test), metadata = tfds.load( 'cats_vs_dogs', split=list(splits), with_info=True, as_supervised=True) # 圖片標註 get_label_name = metadata.features['label'].int2str # 顯示頭兩幅圖片 i = 1 plt.figure('Dog & Cat', figsize=(8, 4)) for image, label in raw_train.take(2): plt.subplot(1, 2, i) plt.imshow(image) plt.title(get_label_name(label)) i += 1 plt.show() # 全部圖片轉爲224x224的尺寸,適應模型的要求 IMG_SIZE = 224 def format_example(image, label): image = tf.cast(image, tf.float32) image = (image/127.5) - 1 image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE)) return image, label train = raw_train.map(format_example) # 載入模型,第一次執行會下載h5文件 # 預測和遷移學習所用的模型並不徹底相同,因此會下載兩次 test_model = tf.keras.applications.MobileNetV2(weights='imagenet') # 預測頭兩張照片並顯示結果 for image, _ in train.take(2): img = np.expand_dims(image, axis=0) predict_class = test_model.predict(img) desc = tf.keras.applications.mobilenet_v2.decode_predictions(predict_class, top=3) print(desc[0])
咱們執行程序看看預測結果:性能
$ ./cats_dogs_1predict.py [('n02089078', 'black-and-tan_coonhound', 0.46141574), ('n02105412', 'kelpie', 0.15314996), ('n02106550', 'Rottweiler', 0.092713624)] [('n02123045', 'tabby', 0.29928064), ('n02123159', 'tiger_cat', 0.08147916), ('n02096177', 'cairn', 0.047330838)]
程序準確的預測出告終果。學習
咱們進行貓、狗分類訓練,先了解一下樣本集。樣本集的圖片沒有什麼區別,剛纔咱們見到了。標註則很是簡單,就是1(表明Dog)或者0(表明Cat)。上面的程序中,咱們使用get_label_name(label)
將數字轉換爲可讀的字符串。這個標註比ImageNet要簡單的多。
MobileNet V2模型默認是將圖片分類到1000類,每一類都有各自的標註。由於本問題分類只有兩類,因此在代碼上,咱們構建模型的時候增長include_top=False參數,表示咱們不須要原有模型中最後的神經網絡層(分類到1000類),以便咱們增長本身的輸出層。固然這樣在第一次執行程序的時候,須要從新下載另一個不包含top層的h5模型數據文件。
隨後咱們在原有模型的後面增長一個池化層,對數據降維。最後是一個1個節點的輸出層,由於咱們須要的結果只有兩類。
到了遷移學習的重點了,咱們的基礎模型的各項參數變量,咱們並不想改變,由於這樣才能保留原來大規模訓練的優點,從而保留原來的經驗。咱們在程序中使用model.trainable = False
,設置在訓練中,基礎模型的各項參數變量不會被新的訓練修改數據。
接着咱們把數據集分爲訓練、驗證、測試三個數據集,用測試集數據和驗證集數據對新的模型進行訓練和過程驗證。隨後對完成訓練的模型,使用測試集數據進行評估。
請看源代碼:測試
#!/usr/bin/env python3 # 引入所使用到的擴展庫 from __future__ import absolute_import, division, print_function import os import numpy as np import matplotlib.pyplot as plt import tensorflow as tf import tensorflow_datasets as tfds keras = tf.keras # 載入訓練數據,載入時按照80%:10%:10%的比例拆分爲訓練、驗證、測試三個數據集 SPLIT_WEIGHTS = (8, 1, 1) splits = tfds.Split.TRAIN.subsplit(weighted=SPLIT_WEIGHTS) (raw_train, raw_validation, raw_test), metadata = tfds.load( 'cats_vs_dogs', split=list(splits), with_info=True, as_supervised=True) # cat/dog兩類圖片,複雜度要低於1000類的圖片,因此分辨率能夠略低,這樣也能節省訓練時間 # 全部圖片從新調整爲160x160點陣 IMG_SIZE = 160 def format_example(image, label): image = tf.cast(image, tf.float32) # 數據規範化爲-1到+1的浮點數 image = (image/127.5) - 1 image = tf.image.resize(image, (IMG_SIZE, IMG_SIZE)) return image, label train = raw_train.map(format_example) validation = raw_validation.map(format_example) test = raw_test.map(format_example) BATCH_SIZE = 32 SHUFFLE_BUFFER_SIZE = 1000 train_batches = train.shuffle(SHUFFLE_BUFFER_SIZE).batch(BATCH_SIZE) validation_batches = validation.batch(BATCH_SIZE) test_batches = test.batch(BATCH_SIZE) # 輸入形狀就是160x160x3,最後3爲RGB3字節色 IMG_SHAPE = (IMG_SIZE, IMG_SIZE, 3) base_model = tf.keras.applications.MobileNetV2(input_shape=IMG_SHAPE, include_top=False, # 使用不包含原有1000類輸出層的模型 weights='imagenet') # 設置基礎模型:MobileNetV2的各項權重參數不會被訓練所更改 base_model.trainable = False # 輸出模型彙總信息 base_model.summary() # 增長輸出池化層 global_average_layer = tf.keras.layers.GlobalAveragePooling2D() # 輸出層 prediction_layer = keras.layers.Dense(1) # 定義最終完整的模型 model = tf.keras.Sequential([ base_model, global_average_layer, prediction_layer ]) # 學習梯度 base_learning_rate = 0.0001 # 編譯模型 model.compile(optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate), loss='binary_crossentropy', metrics=['accuracy']) # 各部分數據數量 num_train, num_val, num_test = ( metadata.splits['train'].num_examples*weight/10 for weight in SPLIT_WEIGHTS ) # 迭代次數 initial_epochs = 10 steps_per_epoch = round(num_train)//BATCH_SIZE # 驗證和評估次數 validation_steps = 20 # 顯示一下未經訓練的初始模型評估結果 loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps) print("initial loss: {:.2f}".format(loss0)) print("initial accuracy: {:.2f}".format(accuracy0)) # 訓練 history = model.fit(train_batches.repeat(), epochs=initial_epochs, steps_per_epoch=steps_per_epoch, validation_data=validation_batches.repeat(), validation_steps=validation_steps) # 評估 loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps) print("Train1ed loss: {:.2f}".format(loss0)) print("Train1ed accuracy: {:.2f}".format(accuracy0))
由於數據集比較大,模型也比較複雜,因此程序執行起來時間很長。最終的評估結果相似爲:優化
Train1ed loss: 0.38 Train1ed accuracy: 0.95
若是不對比來看,這個結果還不錯啦。不過準確率比原來對更爲複雜的ImageNet數據集的評估值還要低不少,顯然還有很大的優化空間。人工智能
在整個模型中,咱們本身增長的部分不多,優化的餘地並很少。考慮到原有ImageNet圖片庫的樣本,大多並不是貓和狗。因此徹底保留原有的模型參數可能對MobileNet V2來說也是資源上的浪費。
所以咱們採起的優化策略就是開放一部分模型的網絡層,容許在進一步的訓練中,調整其權重值,從而指望更好的結果。
在MobileNet V2模型中,一共有155層卷積或者神經網絡。這個值可使用len(model.layers)
來查看。咱們仍然鎖定前面的100層,把後面的網絡層打開,容許訓練修改其參數。
在隨後新模型的訓練中,也不須要所有重頭開始訓練,model.fit方法中,能夠指定initial_epoch參數,接着前面的訓練繼續進行。
請看源代碼:
...繼續前面的代碼最後輸入... # 打開容許修改基礎模型的權重參數 base_model.trainable = True # 仍然鎖死前100層的權重不容許修改 fine_tune_at = 100 # 凍結fine_tune_at以前的層 for layer in base_model.layers[:fine_tune_at]: layer.trainable = False # 從新編譯模型 model.compile(loss='binary_crossentropy', optimizer=tf.keras.optimizers.RMSprop(lr=base_learning_rate/10), metrics=['accuracy']) # 輸出模型的概況,注意觀察有些層被鎖定,有些層能夠更改 model.summary() # 在前次訓練的基礎上再訓練10次 fine_tune_epochs = 10 total_epochs = initial_epochs + fine_tune_epochs # 訓練模型 history_fine = model.fit(train_batches.repeat(), steps_per_epoch=steps_per_epoch, epochs=total_epochs, initial_epoch=initial_epochs, validation_data=validation_batches.repeat(), validation_steps=validation_steps) # 評估新模型 loss0, accuracy0 = model.evaluate(test_batches, steps=validation_steps) print("final loss: {:.2f}".format(loss0)) print("final accuracy: {:.2f}".format(accuracy0))
執行優化後的程序,其中model.summary()的輸出信息:
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= mobilenetv2_1.00_160 (Model) (None, 5, 5, 1280) 2257984 _________________________________________________________________ global_average_pooling2d (Gl (None, 1280) 0 _________________________________________________________________ dense (Dense) (None, 1) 1281 ================================================================= Total params: 2,259,265 Trainable params: 1,863,873 Non-trainable params: 395,392
Non-trainable params這一部分,就是指咱們鎖定不參與新數據訓練的參數數量。若是你注意看前面一部分base_model.summary()的輸出,全部的參數都被鎖定不參與訓練。
最後的評估結果爲:
final loss: 0.23 final accuracy: 0.97
看起來雖然略好,但彷佛優化效果並不明顯。
其實這主要是觀察數值不夠直觀形成的,咱們繼續爲程序增長繪圖功能:
...繼續前面的代碼最後輸入... # 優化前訓練迭代數據 acc = history.history['accuracy'] val_acc = history.history['val_accuracy'] loss = history.history['loss'] val_loss = history.history['val_loss'] # 優化後訓練迭代數據 acc += history_fine.history['accuracy'] val_acc += history_fine.history['val_accuracy'] loss += history_fine.history['loss'] val_loss += history_fine.history['val_loss'] # 使用訓練數據繪圖 plt.figure(figsize=(8, 8)) plt.subplot(2, 1, 1) plt.plot(acc, label='Training Accuracy') plt.plot(val_acc, label='Validation Accuracy') plt.ylim([0.8, 1]) plt.plot([initial_epochs-1,initial_epochs-1], plt.ylim(), label='Start Fine Tuning') plt.legend(loc='lower right') plt.title('Training and Validation Accuracy') plt.subplot(2, 1, 2) plt.plot(loss, label='Training Loss') plt.plot(val_loss, label='Validation Loss') plt.ylim([0, 1.0]) plt.plot([initial_epochs-1,initial_epochs-1], plt.ylim(), label='Start Fine Tuning') plt.legend(loc='upper right') plt.title('Training and Validation Loss') plt.xlabel('epoch') plt.show()
咱們爲了講解方便,把最終的程序分紅了幾個部分,實際上爲了節省時間,是能夠合併在一塊兒執行的,這樣不須要重複訓練不少次。
從繪圖結果看,優化的效果仍是很明顯的:
兩張圖,中間都有一條綠線分隔開優化前和優化後的訓練數據。在前半段,正確率和損失值的優化過程是明顯比較慢的,並且訓練集和驗證集兩條線的分離也說明有過擬合的現象。在後半段,有一個明顯的階梯表現出來模型性能明顯改善,訓練集和驗證集也更接近。說明各項指標都有效改善了。 (待續...)