TensorFlow訓練分類模型及Finetune實操

轉自:https://blog.csdn.net/czp_374/article/details/81133641

寫的太好了!!!

這篇文章關注的重點是如何使用TensorFlow 在自己的圖像數據上訓練深度學習模型,主要涉及的方法是對已經預訓練好的ImageNet模型進行微調( Fine-tune)。本章將會從四個方面講解:數據準備、訓練模型、在測試集上驗證準確率、導出模型並對單張圖片分類。

1.微調的原理

在自己的數據集上訓練一個新的深度學習模型時,一般採取在預訓練
ImageNet 上進行微調的方法。什麼是微調?這裏以VGG16爲例進行講解。

這裏寫圖片描述

如上圖所示,VGG16的結構爲卷積+全連接層。卷積層分爲5 個部
分共13 層, 即圖中的conv 1~ conv 5。還有3層是全連接層,即圖中的fc6、fc7、fc8。卷積層加上全連接層合起來一共爲16層,因此他被稱爲VGG16。如果要將VGG16的結構用於一個新的數據集,首先要將fc8這一層去掉。原因是fc8層的輸入時fc7層的特徵,輸出是1000類的改,v,這1000類正好對應了ImageNet模型中的1000個類別。在自己的數據中,類別一般不是1000類,因此fc8層的結構在此時是不適用的,必須將fc8層去掉,重新採用符合數據類別的全連接層,作爲新的fc8。比如數據集爲5類。

此外,在訓練的時候,網絡的參數的初始值並不是隨機化生成的,而是瞎用VGG16在ImageNet上已經訓練好的初始值。這樣做的原因在於,在ImageNet數據集上訓練過的VGG16 中的參數已經包含了大量有用的卷積過濾器,與其從零開始初始化VGG16 的所高參數,不如使用已經訓練好的參數當作訓練的起點。這樣做不僅可以節約大量訓練時間,而且高助於分類器性能的提高。

載入VGG16 的參數後,就可以開始訓練了。此時需要指定訓練層數的
範圍。一般來說,可以選擇以下幾種範圍進行訓練:

  • 只訓練fc8。訓練範圍一定要包含fc8這一層。之前說過,fc8的結構被調整過,因此它的參數不能直接從ImageNet預訓練模型中取得。可以只訓練fc8,保持其它層的參數不懂。這就相當於將VGG16當作一個“特徵提取器”:用fc7層提取的特徵做一個Softmax模型分類。這樣做的好處是訓練速度快,但往往性能不會太好。
  • 訓練所有參數。還可以對網絡的所有參數進行訓練,這種方法的訓練速度可能比較慢,但是取得較高的性能,可以充分發揮深度模型的威力。
  • 訓練部分參數。通常固定千層參數不變,訓練深層參數。如固定conv1、conv2部分的參數不訓練,只訓練conv3、conv4、conv5、fc6、fc7、fc8的參數。

這種訓練方法就是所謂的對神經網絡模型做微調。藉助微調,可以從預
訓練模型出發,將神經網絡應用到自己的數據集上。下面介紹如何在
Tensor Flow 中進行微調。

2.數據準備

首先要做一些數據準備方面的工作:一是把數據集切分爲訓練集和驗證集, 二是轉換爲tfrecord 格式。在data_prepare/文件夾中提供了會用到的數據集和代碼。

首先要將自己的數據集切分爲訓練集和驗證集,訓練集用於訓練模型,
驗證集用來驗證模型的準確率。這篇文章已經提供了一個實驗用的衛星圖片分類數據集,這個數據集一共高6個類別, 見表3-1 。
這裏寫圖片描述

在data_prepare 目錄中3 用一個pie 文件夾保存原始的圖像文件,圖像
文件保存的結構如下:
這裏寫圖片描述

將圖片分爲train 和validation 兩個目錄,分別表示訓練使用的圖片和驗
證使用的圖片。在每個目錄中,分別以類別名爲文件夾名保存所高圖像。在每個類別文件夾下,存放的就是原始的圖像(如jpg 格式的圖像文件)。下面,在data_prepare 文件夾下,使用預先編制好的腳本data_convert .py,將圖片轉換爲爲tfrecord 格式:
這裏寫圖片描述
解釋這裏的參數含義:

  • -t pic/: 表示轉換pic文件夾中的數據。pic文件夾中必須有一個train目錄和一個validation目錄,分別代表訓練和驗證數據集。每個目錄下按類別存放了圖像數據。
  • –train-shards 2:將訓練數據集分成兩塊,即最後的訓練數據就是兩個tfrecord格式的文件。如果自己的數據集較大,可以考慮將其分爲更多的數據塊。
  • –validation-shards 2: 將驗證數據集分爲兩塊。
  • –num-threads 2:採用兩個線程產生數據。注意線程數必須要能整除train-shaeds和validation-shards,來保證每個線程處理的數據塊是相同的。
  • –dataset-name satellite: 給生成的數據集起一個名字。這裏將數據集起名叫“satellite”,最後生成的頭文件就是staellite_trian和satellite_validation。

運行上述命令後,就可以在pic文件夾中找到5 個新生成的文件,分別
是訓練數據satellite_train_00000-of-00002. tfrecord 、satellite_train 00001-of-00002. tfrecord ,以及驗證數據satellite_validation_00000-of-00002. tfrecord 、satellite_validation_00001-of-00002 .tfrecord 。另外,還高一個文本文件label.txt ,官表示圖片的內部標籤(數字)到真實類別(字符串)之間的映射順序。如圖片在tfrecord 中的標籤爲0 ,那麼就對應label.txt 第一行的類別,在tfrecord的標籤爲1 ,就對應label.txt 中第二行的類別,依此類推。

3.使用TensorFlow Slim微調模型

TensorFlow Slim 是Google 公司公佈的一個圖像分類工具包,它不僅定義了一些方便的接口,還提供了很多ImageNet數據集上常用的網絡結構和預訓練模型。截至2017 年7 月, Slim 提供包括VGG16 、VGG19 、InceptionVl ~ V4, ResNet 50 、ResNet 101, MobileNet 在內大多數常用模型的結構以及預訓練模型,更多的模型還會被持續添加進來。

在本節中,先介紹如何下載Slim 的源代碼,再介紹如何在Slim 中定義
新的數據庫,最後介紹如何使用新的數據庫訓練以及如何進行參數調整。

3.1 下載TensorFlow Slim的源代碼

如果需要使用Slim 微調模型,首先要下載Slim的源代碼。Slim的源代
碼保存在tensorflow/models 項目中,可以使用下面的git命令下載
tensorflow/models:

git clone https://github.com/tensorflow/models.git
  • 1

找到models/research/ 目錄中的slim文件夾,這就是要用到的TensorFlowSlim 的源代碼。這裏簡單介紹TensorFlowSlim的代碼結構, 見表3-2。
這裏寫圖片描述

3.2 定義新的datasets文件

在slim/datasets 中, 定義了所有可以使用的數據庫,爲了使用在第3.2節中創建的tfrecord數據進行訓練,必須要在datasets中定義新的數據庫。

首先,在datasets/目錄下新建一個文件satellite.py,並將flowers.py 文件中的內容複製到satellite.py 中。接下來,需要修改以下幾處內容:第一處是FILE_PATTERN 、SPLITS_TO SIZES 、NUM_CLASSES , 將其進行以下修改:

_FILE_PATTERN = 'satellite_%s_*.tfrecord'
SPLITS_TO_SIZES = {'train':4800, 'validation':1200}
_NUM_CLASSES = 6
  • 1
  • 2
  • 3

FILE_PATTERN變量定義了數據的文件名的格式和訓練集、驗證集的數量。這裏定義_FILE_PATTERN = ‘satellite%s_*.tfrecord’和SPLITS_TO_SIZES = {‘train’:4800, ‘validation’:1200},就表明數據集中,訓練集的文件格式爲satellite_train_*.tfrecord,共包含4800張圖片,驗證集文件名格式爲satellite_validation_*.tfrecord,共包含1200張圖片。_NUM_CLASSES變量定義了數據集中圖片的類別數目。

第二處修改image/format部分,將之修改爲:

'image/format' tf.FixedLenFeature( (), tf. string, default_value ='jpg'),
  • 1

此處定義了圖片的默認格式。收集的衛星圖片的格式爲jpg圖片,因此修改爲jpg 。最後,也可以對文件中的註釋內容進行合適的修改。

修改完satellite.py後,還需要在同目錄的dataset_factory.py文件中註冊satellite數據庫。未修改的dataset_factory. py 中註冊數據庫的對應代碼爲:

from datasets import cifar10
from datasets import flowers
from datasets import imagenet
from datasets import mnist
from datasets import satellite # 自己添加的

datasets_map = {
    'cifar10': cifar10,
    'flowers': flowers,
    'imagenet': imagenet,
    'mnist': mnist,
    'satellite': satellite,
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

3.3 準備訓練文件夾

定義完數據集後,在slim文件夾下再新建一個satellite目錄,在這個目錄中,完成最後的幾項準備工作:

  • 新建一個data目錄,並將3.2 節中準備好的5 個轉換好格式的訓練數據複製進去。
  • 新建一個空的train_dir 目錄,用來保存訓練過程中的日誌和模型。
  • 新建一個pretrained目錄,在slim的GitHub頁面找到Inception V3 模型的下載地址,下載並解壓後,會得到一個inception_v3 .ckpt 文件,將該文件複製到pretrained 目錄下。

最後形成的目錄結構爲:
這裏寫圖片描述

3.4 開始訓練

在slim 文件夾下,運行以下命令就可以開始訓練了:
這裏寫圖片描述

這裏面的額參數比較多,下面一 一進行介紹:

  • –trainable_scopes=InceptionV3/Logits,InceptionV3/AuxLogits:首先來解釋trainable_scope的作用,因爲它非常重要。trainable_scopes規定了在模型中微調變量的範圍。這裏的設定表示只對InceptionV3/Logits,InceptionV3/AuxLogits 兩個變量進行微調,其它的變量都不動。InceptionV3/Logits,InceptionV3/AuxLogits就相當於在第一節中所講的fc8,他們是Inception V3的“末端層”。如果不設定trainable_scopes,就會對模型中所有的參數進行訓練。
  • –train_dir=satellite/train_dir:表明會在satellite/train_dir目錄下保存日誌和checkpoint。
  • –dataset_name=satellite、–dataset_split_name=train:指定訓練的數據集。在3.2節中定義的新的dataset就是在這裏發揮用處的。
  • –dataset_dir=satellite/data: 指定訓練數據集保存的位置。
  • –model_ name=inception_v3 :使用的模型名稱。
  • –checkpoint_path=satellite/pretrained/inception_v3.ckpt:預訓練模型的保存位置。
  • –checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits : 在恢復預訓練模型時,不恢復這兩層。正如之前所說,這兩層是InceptionV3模型的末端層,對應着ImageNet 數據集的1000 類,和當前的數據集不符, 因此不要去恢復它。
  • –max_number_of_steps 100000 :最大的執行步數。
  • –batch size =32 :每步使用的batch 數量。
  • –learning rate=0.001 : 學習率。
  • –learning_rate_decay_type=fixed:學習率是否自動下降,此處使用固定的學習率。
  • –save interval secs=300 :每隔300s ,程序會把當前模型保存到train dir中。此處就是目錄satellite/train dir 。
  • –save_summaries_secs=2 :每隔2s,就會將日誌寫入到train_dir 中。可以用TensorBoard 查看該日誌。此處爲了方便觀察,設定的時間間隔較多,實際訓練時,爲了性能考慮,可以設定較長的時間間隔。
  • –log_every_n_steps=10: 每隔10 步,就會在屏喜上打出訓練信息。
  • –optimizer=rmsprop: 表示選定的優化器。
  • –weight_decay=0.00004 :選定的weight_decay值。即模型中所高參數的二次正則化超參數。

以上命令是隻訓練末端層InceptionV3/Logits, InceptionV3 /AuxLogits, 還可以使用以下命令對所有層進行訓練:

這裏寫圖片描述

對比只訓練、末端層的命令,只再一處發生了變化,即去掉了
–trainable_ scopes 參數。原先的–trainable_ scopes= Inception V3 /Logits ,InceptionV3 / AuxLogits 表示只對末端層Inception V3 /Logits 和Inception V3 / AuxLogits 進行訓練,去掉後就可以訓練模型中的所有參數了。下面會比較這兩種訓練方式的效果。

3.5 訓練程序行爲

當train_ image_ classifier. py 程序啓動後,如果訓練文件夾(即satellite/train_ dir )裏沒再已經保存的模型,就會加載checkpoint_path中的預訓練模型,緊接着,程序會把初始模型保存到train_dir中,命名爲model.cpkt-0,0表示第0步。這之後,每隔5min(參數一save interval secs=300 指定了每隔300s 保存一次,即5min)。程序還會把當前模型保存到同樣的文件夾中,命名格式和第一次保存的格式一樣。因爲模型比較大,程序只會保留最新的5 個模型。

此外,**如果中斷了程序井再次運行,程序會首先檢查train dir 中有無已經保存的模型,如果有,就不會去加載checkpoint_path中的預訓練模型, 而是直接加載train dir 中已經訓練好的模型,並以此爲起點進行訓練。**Slim之所以這樣設計,是爲了在微調網絡的時候,可以方便地按階段手動調整學習率等參數。

3.6 驗證模型準確率

如何查看保存的模型在驗證數據集上的準確率呢?可以用eval_image classifier.py 程序進行驗證,即執行下列命令:
這裏寫圖片描述
這裏參數含義爲:

  • –checkpoint_path=satellite/train _ dir: 這個參數既可以接收一個目錄的路徑,也可以接收一個文件的路徑。如果接收的是一個目錄的路徑,如這裏的satellite/train_dir,就會在這個目錄中尋找最新保存的模型文件,執行驗證。也可以指定一個模型驗證,以第300步爲例,在satellite/train_ dir 文件夾下它被保存爲model.clcpt-300.meta 、
    model.ckpt-300.index 、model. ckpt-3 00.data-00000-of-00001 三個文件。此時,如果要對它執行驗證,給checkpoint_path 傳遞的參數應該爲satellite/train_ dir/model.ckpt-300 。
  • –eval_dir=satellite/eval_dir :執行結果的曰志就保存在eval_dir 中,同樣可以通過TensorBoard 查看。
  • –dataset_name=satellite 、–dataset_split_name=validation 指定需要執行的數據集。注意此處是使用驗證集( validation )執行驗證。
  • –dataset_dir=satellite/data :數據集保存的位置。
  • –model_ name「nception_ v3 :使用的模型。

執行後,應該會出現類似下面的結果:

eval/Accuracy[0.51]
eval/Recall_5[0.97333336]
  • 1
  • 2

Accuracy表示模型的分類準確率,而Recall_5 表示Top 5 的準確率,即在輸出的各類別概率中,正確的類別只要落在前5 個就算對。由於此處的類別數比較少,因此可以不執行Top 5 的準確率,民而執行Top 2 或者Top 3的準確率,只要在eval_image_classifier.py 中修改下面的部分就可以了:
這裏寫圖片描述

3.7 TensorBoard 可視化與起參數選擇

在訓練時,可以使用TensorBoard 對訓練過程進行可視化,這也有助於
設定訓練模型的萬式及超參數。使用下列命令可以打開TensorBoard (其實就是指定訓練文件夾):

tensorboard --logdir satellite/train_dir
  • 1

在TensorBoard中,可以看到損失的變化由線3 如圖3-1 所示。觀察損
失曲線高助於調整參數。當損失曲線比較平緩,收斂較慢時, 可以考慮增大學習率,以加快收斂速度;如果揭失曲線波動較大,無法收斂,就可能是因爲學習率過大,此時就可以嘗試適當減小學習率。
這裏寫圖片描述

此外,使用TensorBoard ,還可以對比不同模型的損失變化曲線。如在3.6節中給出了兩條命令,一條命令是隻微調Inception V3 末端層的,
另外一條命令是微調整個網絡的。可以在train_dir 中建立兩個文件夾,訓練這兩個模型時,通過調整train_dir參數,將它們的日誌分別寫到新建的文件夾中,此時再使用命令tensorboard –logdir satellite/train_dir 打開TensorBoard,就可以比較這兩個模型的變化曲線了。如圖3-2 所示, 上方的曲線爲只訓練末端層的損失,下方的曲線爲訓練所高層的損失。僅看損失,訓練所高層的效果應該比只訓練末端層要好。事實也是如此,只訓練末端層最後達到的分類準確率在76%左右,而訓練所高層的分類準確率在82%左右。讀者還可以進一步調整訓練、變量、學習率等參數,以達到更好的效果。
這裏寫圖片描述

3.8 導出模型並對單張圖片進行識別

訓練完模型後,常見的應用場景是:部署訓練好的模型並對單張圖片做
識別。這裏提供了兩個代碼文件: freeze_graph. py 和classify_image_inception_v3. py 。前者可以導出一個用於識別的模型,後者則是使用inception_v3 模型對單張圖片做識別的腳本。

TensorFlow Slim提供了導出網絡結構的腳本export_inference_ graph.py 。首先在slim 文件夾下運行:

python export_inference_ graph.py \
--alsologtostderr \
--model_name=inception_v3 \
--output_file=satallite/inception_v3_inf_graph.pb \
--dataset_name satellite
  • 1
  • 2
  • 3
  • 4
  • 5

這個命令會在satellite 文件夾中生成一個inception_v3 _inf _graph. pb 文件。注意: inception_v3 _inf _graph.pb 文件中只保存了Inception V3 的網絡結構,並不包含訓練得到的模型參數,需要將checkpoint 中的模型參數保存進來。方法是使用freeze_graph. py 腳本(在chapter_3 文件夾下運行):

python freeze-graph.py \ 
--input_graph slim/satellite/inception_v3_inf_graph.pb \
--input_checkpoint slim/satallite/train_dir/model.ckpt-5271 \
--input_binary true \
--output_node_names InceptionV3/Predictions/Reshape_1 \
--output_graph slim/satellite/frozen_graph.pb
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

這裏參數含義爲:

  • –input_graph slim/satellite/inception_v3_inf_graph.pb。這個參數很好理解,它表示使用的網絡結構文件,即之前已經導出的inception_v3 _inf_gr aph.pb 。
  • –input_checkpoint slim/satallite/train_dir/model.ckpt-5271。具體將哪一個checkpoint 的參數載入到網絡結構中。這裏使用的是訓練文件夾train _d讓中的第5271 步模型文件。我們需要根據訓練文件夾下checkpoint的實際步數,將5271修改成對應的數值。
  • input_binary true。導入的inception_v3_inf_graph.pb實際是一個protobuf文件。而protobuf 文件有兩種保存格式,一種是文本形式,一種是二進制形式。inception_v3 _ inf graph. pb 是二進制形式,所以對應的參數是–input binary true 。初學的話對此可以不用深究,若高興趣的話可以參考資料
  • –output_graph slim/satellite/frozen_graph.pb。最後導出的模型保存爲slim/satellite/frozen_graph.pb 文件。

如何使用導出的frozen_graph.pb 來對單張圖片進行預測?編寫了一個classify image_inception_ v3.py 腳本來完成這件事。先來看這個腳本的使用方法:

python classify_image_inception_v3.py \
--model_path slim/satellite/frozen_graph.pb \
--label_path data_prepare/pic/label.txt \
--image_file test_image.jpg
  • 1
  • 2
  • 3
  • 4

一model_path 很好理解,就是之前導出的模型frozen_graph. pb 。模型的輸出實際是“第0 類’、“第1 類”……所以用–label_path 指定了一個label文件, label文件中按順序存儲了各個類別的名稱,這樣腳本就可以把類別的id號轉換爲實際的類別名。–image _file 是需要測試的單張圖片。腳本的運行結果應該類似於:

water (score = 5.46853)
wetland (score = 5.18641)
urban (score = 1.57151)
wood (score = -1.80627)
glacier (score = -3.88450)
  • 1
  • 2
  • 3
  • 4
  • 5

這就表示模型預測圖片對應的最可能的類別是water,接着是wetland 、urban 、wood 等。score 是各個類別對應的Logit 。

最後來看classify_image_inception_ v3 . py 的實現方式。代碼中包含一個preprocess for_ eval函數, 它實際上是從slim/preprocessing/inception_preprocess ing.py裏複製而來的,用途是對輸入的圖片做預處理。
classify_ image_inception_v3.py 的主要邏輯在run_inference_on_ image函數中,第一步就是讀取圖片,並用preprocess_for_eval做預處理:

with tf.Graph().as_default():
  image_data = tf.gfile.FastGFile(image, 'rb').read()
  image_data = tf.image.decode_jpeg(image_data)
  image_data = preprocess_for_eval(image_data, 299, 299)
  image_data = tf.expand_dims(image_data, 0)
  with tf.Session() as sess:
    image_data = sess.run(image_data)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

Inception V3 的默認輸入爲299 * 299 ,所以調用preprocess_for_eval 時指定了寬和高都是299 。接着調用create_graph()將模型載入到默認的計算圖中。

def create_graph():
  """Creates a graph from saved GraphDef file and returns a saver."""
  # Creates graph from saved graph_def.pb.
  with tf.gfile.FastGFile(FLAGS.model_path, 'rb') as f:
    graph_def = tf.GraphDef()
    graph_def.ParseFromString(f.read())
    _ = tf.import_graph_def(graph_def, name='')
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

FLAGS.model_path 就是保存的slim/satellite/frozen_graph.pb 。將之導入後先轉換爲graph_def,然後用tf.import_graph_def()函數導入。導入後,就可以創建Session 並測試圖片了,對應的代碼爲:

 with tf.Session() as sess:
    softmax_tensor = sess.graph.get_tensor_by_name('InceptionV3/Logits/SpatialSqueeze:0')
    predictions = sess.run(softmax_tensor,
                           {'input:0': image_data})
    predictions = np.squeeze(predictions)

    # Creates node ID --> English string lookup.
    node_lookup = NodeLookup(FLAGS.label_path)

    top_k = predictions.argsort()[-FLAGS.num_top_predictions:][::-1]
    for node_id in top_k:
      human_string = node_lookup.id_to_string(node_id)
      score = predictions[node_id]
      print('%s (score = %.5f)' % (human_string, score))
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

InceptionV3/Logits/SpatialSqueeze:0是各個類別Logit值對應的節點。輸入預處理後的圖片image_data,使用sess.run()函數去除各個類別預測Logit。默認只取最有可能的FLAGS.num_top_predictions個類別輸出,這個值默認是5。可以運行腳本時用–num_top_predictions參數來改變此默認值。node_ lookup 定義了一個NodeLookup 類,它會讀取label文件,並將模型輸出的類別id轉換成實際類別名,實現代碼比較簡單,就不再詳細介紹了。

4. 總結

這篇文章首先簡要介紹了微調神經網絡的基本原理,接着詳細介紹瞭如何使用TensorFlow Slim 微調預訓練模型,包括數據準備、定義新的datasets文件、訓練、驗證、導出模型井測試單張圖片等。如果需要訓練自己的數據,可以參考從第2節開始的步驟,修改對應的代碼,來打造自己的圖像識別模型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章