在《21個項目玩轉深度學習》這本書中讀到了這章,突然發現和我之前構思過想法:利用人臉識别的深度網絡實作自己的表情識别模型這一概念有些奇妙的共同之處,看到了實作我想法的曙光,決定記錄下來友善以後用到。
我曾經畫過這樣一張圖,想要利用兩級網絡實作表情識别:

想法是這樣:
- 首先我們要實作表情識别這一目标,采用的是連續次元下的A、V值作為表情的衡量架構。
- 第一級深度神經網絡(比如原本用來實作人臉識别、人臉檢測等的網絡模型),去掉其最終的全連接配接層,将整個網絡所學習到的關于人臉的共通的表示特征進行耦合整理,作為第二級深度神經網絡的輸入(這裡就可以用到這節内容講到的微調)。
- 而第二級深度神經網絡原本是可以作為一個單獨的網絡,輸入人臉圖像,輸出連續次元下的表情。 但這裡我們将其改進為,用第一級網絡輸出的關于人臉的本質的特征作為第二級深度神經網絡的輸入,繼續進行學習。 經過第一級網絡,我們學習到排除掉人臉之外因素的本質特征,再将其作為輸入樣本,用第二級網絡繼續學習關于表情識别的特征,最終經過全連接配接層輸出表情識别結果。
下面記錄書中的細節:
這一節内容關注的重點是如何使用TensorFlow 在自己的圖像資料上訓練深度學習模型,主要涉及的方法是對已經預訓練好的ImageNet模型進行微調( Fine-tune)。下面将會從四個方面講解:資料準備、訓練模型、在測試集上驗證準确率、導出模型并對單張圖檔分類。
同時,關于這節内容的代碼以及我的一些小筆記打包如下:
連結:https://pan.baidu.com/s/1VbpsShJG3Oky-erDqxQJkw 提取碼:l6qm
1. 微調的原理
示例采用VGG16進行微調,如下圖所示,VGG16的結構為 卷積+全連接配接層卷積層分為5 個部分共13 層, 即圖中的conv 1~ conv 5。還高3 層是全連接配接層,即圈中的fc6 、
fc7 、 fc8 0 卷積層加上全連接配接層合起來一共為16 層,是以它被稱為VGG16 。
這是ImageNet的較為經典的網絡模型,當我們要将其運用到一個新的資料集時,第一步就是先去掉 fc8 全連接配接層,因為基于ImageNet,fc8的輸入是fc7層的特征,輸出為對應1000個類别的機率。 若需換成自己的資料集,也應該将重新設定 fc8層 輸出對應的類别數(比如5、6類等等,如果你的問題是 分類問題)。若你的問題是回歸問題,同樣也需要改,但可能不像這個這麼簡單(我是要改成這樣,後面試試)。
同時,在我們引入網絡結構進行訓練的時候,網絡參數的初始值并不是随機生成的,而是采用VGG16在ImageNet上已經訓練好的初始值。
原因在于,在ImageNet資料集上訓練過的VGG16 中的參數已經包含了大量有用的卷積過濾器,與其從零開始初始化VGG16 的所需參數,不如使用已經訓練好的參數當作訓練的起點。這樣做不僅可以節約大量訓練時間,而且有助于分類器性能的提高。
載入參數後,就可以進行訓練了。此時需要指定訓練層數的範圍。一般來說,可以選擇以下幾種:
- 隻訓練 fc8層。 訓練範圍一定要包含fc8 這一層。之前說過,fc8 的結構須經過調整,是以它的參數不能直接從ImageNet 預訓練模型中取得。可以隻訓練fc8 ,保持其他層的參數不動。這就相當于将VGG16 當作一個“特征提取器”:用fc7 層提取的特征做一個softmax模型分類。這樣做的好處是訓練速度快,但往往性能不會太好
- 訓練所有參數。 這相當于隻采用VGG16魔性的架構,對之前學習的參數都進行微調。對網絡中的所有參數進行訓練,這種方法的訓練速度可能比較慢,但是能取得較高的性能,可以充分發揮深度模型的優勢。
- 訓練部分參數。通常是固定淺層參數不變,訓練深層參數。如固定conv1 、conv2 部分的參數不訓練,隻訓練conv 3 、conv 4, conv 5, fc6 、fc7 、fc8層 的參數。
這種訓練方法就稱為對神經網絡模型做微調。借助微調,我們可以将深度網絡模型運用到自己的資料集上。
2. 資料準備
首先要做一些資料準備方面的工作:一是把資料集切分為訓練集和驗證集, 二是轉換為 tfrecord 格式。在data_prepare/檔案夾中提供了會用到的資料集和代碼。
首先要将自己的資料集切分為訓練集和驗證集,訓練集用于訓練模型,
驗證集用來驗證模型的準确率。這篇文章已經提供了一個實驗用的衛星圖檔分類資料集,這個資料集一共高6個類别, 見表3-1
在data_prepare 目錄中用一個pic 檔案夾儲存原始的圖像檔案,圖像 檔案儲存的結構如下:
将圖檔分為 train 和 validation 兩個目錄,分别表示訓練使用的圖檔和驗證使用的圖檔。在每個目錄中,分别以類别名為檔案夾名儲存所有圖像。在每個類别檔案夾下,存放的就是原始的圖像(如jpg 格式的圖像檔案)。
切分好了之後,我們就預先編輯好的腳本 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 微調模型
3.3.1 下載下傳TensorFlow Slim的源代碼
使用 git 指令即可:
git clone https://github.com/tensorflow/models.git
簡單介紹Slim代碼及用途如下表:
這這個實驗的slim檔案夾中,也提供了已下載下傳的代碼,可以不用下載下傳了。
3.3.2 定義新的 datasets 檔案
在slim/datasets中,定義了所有可用的資料庫。若需要用自己的資料庫,那麼就需要在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
ILE_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'},
因為本實驗資料庫中為 .jpg檔案,自己用的時候,就改成自己資料庫中檔案的格式。
最後一處修改為 在 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, #完成注冊
}
3.3.3 準備訓練檔案夾
定義完資料集後,在slim檔案夾下建立一個 satellite 目錄,在這個目錄中,完成最後幾項準備工作:
- 建立一個data目錄,并将3.2 節中準備好的5 個轉換好格式的訓練資料複制進去。
- 建立一個空的train_dir 目錄,用來儲存訓練過程中的日志和模型。
- 建立一個pretrained 目錄,在slim 的GitHub 頁面找到Inception V3 模型的下載下傳位址http:/!download. tensorflow .org/models/ inception _ v3_2016 08 28.tar.gz ,下載下傳并解壓後,會得到一個inception_v3 .ckpt 檔案l ,将該檔案複制到pretrained 目錄下。(實作代碼中,有已經下載下傳好的)
3.3.4 開始訓練
指令視窗,切換到 slim檔案夾下,然後運作以下指令:
同時,windows 指令視窗下 換行符為 先打 ’ ^ ’ 然後按 ‘ Enter’ 鍵。Linux下直接反斜杠就可以了。
(base) C:\Users\Ooorchid\slim> python train_image_classifier.py ^
More? --train_dir=satellite/train_dir ^
More? --dataset_name=satellite ^
More? --dataset_split_name=train ^
More? --dataset_dir=satellite/data ^
More? --model_name=inception_v3 ^
More? --checkpoint_path=satellite/pretrained/inception_v3.ckpt ^
More? --checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits ^
More? --trainable_scopes=InceptionV3/logits,,InceptionV3/AuxLogits ^
More? --max_number_of_steps=100000 ^
More? --batch_size=32 ^
More? --learning_rate=0.001 ^
More? --learning_rate_decay_type=fixed ^
More? --save_interval_secs=300 ^
More? --save_summaries_secs=2 ^
More? --log_every_n_steps=10 ^
More? --optimizer=rmsprop ^
More? --weight_decay=0.00004
- 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, 還可以使用以下指令對所有層進行訓練,即去掉了
隻對末端層訓練的定義語句。
(base) C:\Users\Ooorchid\slim> python train_image_classifier.py ^
More? --train_dir=satellite/train_dir ^
More? --dataset_name=satellite ^
More? --dataset_split_name=train ^
More? --dataset_dir=satellite/data ^
More? --model_name=inception_v3 ^
More? --checkpoint_path=satellite/pretrained/inception_v3.ckpt ^
More? --checkpoint_exclude_scopes=InceptionV3/Logits,InceptionV3/AuxLogits ^
More? --max_number_of_steps=100000 ^
More? --batch_size=32 ^
More? --learning_rate=0.001 ^
More? --learning_rate_decay_type=fixed ^
More? --save_interval_secs=300 ^
More? --save_summaries_secs=10 ^
More? --log_every_n_steps=1 ^
More? --optimizer=rmsprop ^
More? --weight_decay=0.00004
3.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.3.6 驗證模型準确率
如何檢視儲存的模型在驗證資料集上的準确率呢?可以用 eval_image classifier.py 程式進行驗證,即執行下列指令:
(base) C:\Users\Ooorchid\slim>python eval_image_classifier.py ^
More? --checkpoint_path=satellite/train_dir ^
More? --eval_dir=satellite/eval_dir ^
More? --dataset_name=satellite ^
More? --dataset_split_name=validation ^
More? --dataset_dir=satellite/data ^
More? --model_name=inception_v3
- –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]
Accuracy表示模型的分類準确率,而Recall_5 表示Top 5 的準确率,即在輸出的各類别機率中,正确的類别隻要落在前5 個就算對。由于此處的類别數比較少,是以可以不執行Top 5 的準确率,民而執行Top 2 或者Top 3的準确率,隻要在eval_image_classifier.py 中修改下面的部分就可以了:
# Define the metrics:
names_to_values, names_to_updates = slim.metrics.aggregate_metric_map({
'Accuracy': slim.metrics.streaming_accuracy(predictions, labels),
'Recall_5': slim.metrics.streaming_recall_at_k(
logits, labels, 5),
})
3.7 TensorBoard 可視化與起參數選擇
訓練時,可以使用TensorBoard 對訓練過程進行可視化,這也有助于 設定訓練模型的萬式及超參數。使用下列指令可以打開TensorBoard (其實就是指定訓練檔案夾)
tensorboard --logdir satellite/train_dir
在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 ^
More? --alsologtostderr ^
More? --model_name=inception_v3 ^
More? --output_file=satallite/inception_v3_inf_graph.pb ^
More? --dataset_name satellite
這個指令會在satellite 檔案夾中生成一個inception_v3 _inf _graph. pb 檔案。**注意: inception_v3 _inf _graph.pb 檔案中隻儲存了Inception V3 的網絡結構,并不包含訓練得到的模型參數,需要将checkpoint 中的模型參數儲存進來。**方法是使用freeze_graph. py 腳本(在chapter_3 檔案夾下運作):
python freeze-graph.py ^
More? --input_graph slim/satellite/inception_v3_inf_graph.pb ^
More? --input_checkpoint slim/satallite/train_dir/model.ckpt-5271 ^
More? --input_binary true ^
More? --output_node_names InceptionV3/Predictions/Reshape_1 ^
More? --output_graph slim/satellite/frozen_graph.pb
- –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_node_names InceptionV3/Predictions/Reshape_1。 在導出的模型中,制定一個輸出節點,InceptionV3/Predictions/Reshape_1 是 Inception V3最後的輸出層。
- –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 ^
More? --model_path slim/satellite/frozen_graph.pb ^
More? --label_path data_prepare/pic/label.txt ^
More? --image_file test_image.jpg
- 一model_path 很好了解,就是之前導出的模型frozen_graph. pb 。模型的輸出實際是“第0 類’、“第1 類”……是以用–label_path 指定了一個label檔案, label檔案中按順序存儲了各個類别的名稱,這樣腳本就可以把類别的id号轉換為實際的類别名。–image _file 是需要測試的單張圖檔。腳本的運作結果應該類似于: 這就表示模型預測圖檔對應的最可能的類别是water,接着是wetland 、urban 、wood 等。score 是各個類别對應的Logit 。
TensorFlow之: 用已有的深度網絡模型打造自己的圖像識别模型
最後來看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)
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='')
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))
InceptionV3/Logits/SpatialSqueeze:0是各個類别Logit值對應的節點。輸入預處理後的圖檔image_data,使用sess.run()函數去除各個類别預測Logit。預設隻取最有可能的FLAGS.num_top_predictions個類别輸出,這個值預設是5。可以運作腳本時用–num_top_predictions參數來改變此預設值。node_ lookup 定義了一個NodeLookup 類,它會讀取label檔案,并将模型輸出的類别id轉換成實際類别名,實作代碼比較簡單,就不再詳細介紹了。