SSD-tensorflow使用文件(二)——從資料處理到iOS移植
一、資料轉換
1. VOC資料轉換成tfrecord格式
1.1. 資料說明
- tensorflow專用的資料格式為tfrecord.
/home/doctorimage/kindlehe/common/dataset
是所有資料的存放目錄,目錄結構如下
coco :
flower : 其中flower_photos包含5個資料夾,分別對應五種花的型別
Oxford-IIT_Pet:寵物的資料集
VOC2007: voc2007資料集
VOCdevkit: voc2012資料集
VOC0712: VOC2007與voc2012合併之後,轉成tfrecord資料集後,所在的存放路徑
1.2. voc2007與voc2012單獨轉為tfrecord格式
在目錄/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell
下執行bash tf_convert_data.sh
會執行以下指令生成檔名為voc_2007_trainval_000.tfrecord
的tfrecord格式的資料:
DATASET_DIR=../../../../common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007/
OUTPUT_DIR=../../../../common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007_tfrecord/
python ../tf_convert_data.py \ #所執行的指令碼檔案
--dataset_name=pascalvoc \ #資料名字,這裡不能修改,因為在tf_convert_data.py裡被寫死了,修改之後會報錯
--dataset_dir=${DATASET_DIR} \ #輸入資料的存放目錄
--output_name=voc_2007_trainval \ #輸出資料名的字首
--output_dir=${OUTPUT_DIR} #輸出資料的存放目錄
對於voc2012只需要將輸入資料和輸出資料的目錄更改為voc2012的目錄即可,如下所示:
DATASET_DIR=#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012/
OUTPUT_DIR=#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012_tf
python ../tf_convert_data.py \ #所執行的指令碼檔案
--dataset_name=pascalvoc \ #資料名字,這裡不能修改,因為在tf_convert_data.py裡被寫死了,修改之後會報錯
--dataset_dir=${DATASET_DIR} \ #輸入資料的存放目錄
--output_name=voc_2012_trainval \ #這裡改成2012
--output_dir=${OUTPUT_DIR} #輸出資料的存放目錄
轉換成功會出現如下提示:
>> Converting image 17125/17125
Finished converting the Pascal VOC dataset!
如何根據不同的GPU記憶體,==設定每個資料檔案包含的圖片個數==呢?
在pascalvoc_to_tfrecords.py
中有一個引數
SAMPLES_PER_FILES = 5011 #控制轉出的TFrecord檔案的個數:比如一共17125張圖,要求每個檔案儲存5011個圖,那麼最終輸出17125/5011=4個檔案/
1.3. voc2007與voc2012合併之後轉為tfrecord格式
ssd作者說自己用了voc2007中trainval和voc2012的資料一起訓練的,那麼如何將這兩個資料一次性製作成tfrecord格式的資料呢?
當你生成完voc2007和voc2012後會生成以下檔案:
#/home/doctorimage/kindlehe/common/dataset/VOC2007/VOCtrainval_06-Nov-2007/VOCdevkit/VOC2007_tfrecord/目錄下生成1個檔案:
voc_2007_trainval_000.tfrecord
#/home/doctorimage/kindlehe/common/dataset/VOCdevkit/VOC2012/目錄下生成4個檔案
voc_2012_trainval_000.tfrecord
voc_2012_trainval_001.tfrecord
voc_2012_trainval_002.tfrecord
voc_2012_trainval_003.tfrecord
不要懷疑你接下來看到的.沒錯
合併資料最難的方法是寫自動化程式,一鍵搞定,但是沒這個必要,因為有一個最簡單的辦法,就是把兩個資料夾中的資料拷貝到同一個資料夾下,路徑是:
/home/doctorimage/kindlehe/common/dataset/VOC0712/
但是,拷貝進去的5個檔案,需要把voc_2007_trainval_000.tfrecord
改成voc_2012_trainval_004.tfrecord
,這時你就明白黃色部分為什麼要著重突出了,因為如果單個檔案包含的個數太少,生成的檔案數量就會太多,改資料夾名字的時候就比較麻煩,當然也可以寫自動化指令碼批量修改也不是什麼難事。==最重要在於增加單個檔案包含的圖片個數,可以減少記憶體的IO操作。==
2. 用自己的資料製作成tfrecord格式
2.1 分類任務資料集製作
資料存放目錄為/home/doctorimage/kindlehe/project/CSDN/
下面的flower_photos
資料夾,可以按照這個資料夾的目錄結構放入自己的資料
進入目錄/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/slim
,執行
bash convert_tfrecord.sh
會執行以下命令:
python download_and_convert_data.py --dataset_name=flowers --dataset_dir=/home/doctorimage/kindlehe/project/CSDN/
download_and_convert_data.py
最終會呼叫download_and_convert_flowers
去完成檔案轉換功能,這個檔案裡面有幾個地方要注意一下
L43、L49
_NUM_VALIDATION = 350 #表示驗證集的個數,驗證集一般佔所有圖片的10%
_NUM_SHARDS = 5 #表示訓練集或者測試集生成的tfrecord檔案的個數,官方建議單個Tfrecords檔案放1023張,生成檔案的個數就可以根據總圖片的數量計算得到
L83:
flower_root= os.path.join(dataset_dir,‘flower_photos’) # 如果是製作自己的資料,這一行的flower_photos改成自己的檔名即可
L190:
dataset_utils.download_and_uncompress_tarball(_DATA_URL, dataset_dir) #如果下載好了,一定要註釋掉這一行,否則會坑到自己
L210:
_clean_up_temporary_files(dataset_dir) #下載壓縮包,解壓成圖片,再轉成tfrecords,中間這些圖片被認為是臨時圖片,系統會自動清除
2.1 檢測任務資料集製作
二、訓練及測試
1. 訓練
主要的訓練過程在《SSD-tensorflow使用文件(一)》中已經詳細講解了,這裡補充一些使用細節:
1.1 pyCharm環境配置及除錯
要使用pyCharm進行除錯,首先要將cuda的環境配置好,每個機器的環境都不一樣,可以在linux終端輸入vim ./bashrc
檢視相應的路徑。
配置好就可以點選run -> debug ‘train_ssd_networ’
進行單步除錯了
也可以按照《SSD-tensorflow使用文件(一)》直接在終端中,進入ssd根目錄下的shell目錄,執行bash train_ssd_network.sh
1.2 訓練細節
對於train_ssd_network.sh
中的命令,通常存在以下幾種訓練方法:
方案1: 從vgg開始訓練其中某些層的引數
# 通過載入預訓練好的vgg16模型,對“voc07trainval+voc2012”進行訓練
# 通過checkpoint_exclude_scopes指定哪些層的引數不需要從vgg16模型裡面載入進來
# 通過trainable_scopes指定哪些層的引數是需要訓練的,未指定的引數保持不變
DATASET_DIR=/home/doctorimage/kindlehe/common/dataset/VOC0712/
TRAIN_DIR=.././log_files/log_finetune/train_voc0712_20170816_1654_VGG16/
CHECKPOINT_PATH=../checkpoints/vgg_16.ckpt
python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #訓練生成模型的存放路徑
--dataset_dir=${DATASET_DIR} \ #資料存放路徑
--dataset_name=pascalvoc_2007 \ #資料名的字首
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #載入的模型的名字
--checkpoint_path=${CHECKPOINT_PATH} \ #所載入模型的路徑
--checkpoint_model_scope=vgg_16 \ #所載入模型裡面的作用域名
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--save_summaries_secs=60 \ #每60s儲存一下日誌
--save_interval_secs=600 \ #每600s儲存一下模型
--weight_decay=0.0005 \ #正則化的權值衰減的係數
--optimizer=adam \ #選取的最優化函式
--learning_rate=0.001 \ #學習率
--learning_rate_decay_factor=0.94 \ #學習率的衰減因子
--batch_size=24 \
--gpu_memory_fraction=0.9 #指定佔用gpu記憶體的百分比
方案2: 從自己預訓練好的模型開始訓練(依然可以指定要訓練哪些層)(當你的模型通過vgg訓練的模型收斂到大概o.5mAP的時候,可以進行這一步的fine-tune)
# 通過載入預訓練好的vgg16模型,對“voc07trainval+voc2012”進行訓練
# 通過checkpoint_exclude_scopes指定哪些層的引數不需要從vgg16模型裡面載入進來
# 通過trainable_scopes指定哪些層的引數是需要訓練的,未指定的引數保持不變
DATASET_DIR=/home/doctorimage/kindlehe/common/dataset/VOC0712/
TRAIN_DIR=.././log_files/log_finetune/train_voc0712_20170816_1654_VGG16/
CHECKPOINT_PATH=./log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-253287
python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #訓練生成模型的存放路徑
--dataset_dir=${DATASET_DIR} \ #資料存放路徑
--dataset_name=pascalvoc_2007 \ #資料名的字首
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #載入的模型的名字
--checkpoint_path=${CHECKPOINT_PATH} \ #所載入模型的路徑
--checkpoint_model_scope=vgg_16 \ #所載入模型裡面的作用域名
--checkpoint_exclude_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--trainable_scopes=ssd_300_vgg/conv6,ssd_300_vgg/conv7,ssd_300_vgg/block8,ssd_300_vgg/block9,ssd_300_vgg/block10,ssd_300_vgg/block11,ssd_300_vgg/block4_box,ssd_300_vgg/block7_box,ssd_300_vgg/block8_box,ssd_300_vgg/block9_box,ssd_300_vgg/block10_box,ssd_300_vgg/block11_box \
--save_summaries_secs=60 \ #每60s儲存一下日誌
--save_interval_secs=600 \ #每600s儲存一下模型
--weight_decay=0.0005 \ #正則化的權值衰減的係數
--optimizer=adam \ #選取的最優化函式
--learning_rate=0.001 \ #學習率
--learning_rate_decay_factor=0.94 \ #學習率的衰減因子
--batch_size=24 \
--gpu_memory_fraction=0.9 #指定佔用gpu記憶體的百分比
# 方案3:從頭開始訓練自己的模型
# 註釋掉CHECKPOINT_PATH,不提供初始化模型,讓模型自己隨機初始化權重,從頭訓練
# 刪除checkpoint_exclude_scopes和trainable_scopes,因為是從頭開始訓練
# CHECKPOINT_PATH=./log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-253287
python3 ../train_ssd_network.py \
--train_dir=${TRAIN_DIR} \ #訓練生成模型的存放路徑
--dataset_dir=${DATASET_DIR} \ #資料存放路徑
--dataset_name=pascalvoc_2007 \ #資料名的字首
--dataset_split_name=train \
--model_name=ssd_300_vgg \ #載入的模型的名字
#--checkpoint_path=${CHECKPOINT_PATH} \ #所載入模型的路徑,這裡註釋掉
--checkpoint_model_scope=vgg_16 \ #所載入模型裡面的作用域名
--save_summaries_secs=60 \ #每60s儲存一下日誌
--save_interval_secs=600 \ #每600s儲存一下模型
--weight_decay=0.0005 \ #正則化的權值衰減的係數
--optimizer=adam \ #選取的最優化函式
--learning_rate=0.00001 \ #學習率
--learning_rate_decay_factor=0.94 \ #學習率的衰減因子
--batch_size=32
出現如下錯誤,一般是儲存模型的資料夾下之前已經有了模型,只需要把之前的模型刪除,或者將模型儲存在一個新的資料夾下就可以了
It was originally created here:
['File "../train_ssd_network.py", line 420, in <module>\n
tf.app.run()', 'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python
/platform/app.py", line 48, in run\n _sys.exit(main(_sys.argv[:1] +
flags_passthrough))', 'File "../train_ssd_network.py", line 416, in main\n
sync_optimizer=None)', 'File "/usr/local/lib/python3.5/dist-packages/tensorflow
/contrib/slim/python/slim/learning.py", line 655, in train\n ready_op =
tf_variables.report_uninitialized_variables()', 'File "/usr/local/lib/python3.5
/dist-packages/tensorflow/python/util/tf_should_use.py", line 170, in
wrapped\n return _add_should_use_warning(fn(*args, **kwargs))',
'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python/util
/tf_should_use.py", line 139, in _add_should_use_warning\n
wrapped = TFShouldUseWarningWrapper(x)',
'File "/usr/local/lib/python3.5/dist-packages/tensorflow/python/
/tf_should_use.py", line 96, in __init__\n
stack = [s.strip() for s in traceback.format_stack()]']
在TRAIN_DIR路徑下會產生四中檔案:
1. checkpoint :文字檔案,包含所有model.ckpt-xxxx,相當於是不同時間節點生成的所有ckpt檔案的一個索引。
2. model.ckpt-2124.data-000000-of-000001:模型檔案,儲存模型的權重
3. model.ckpt-2124.meta: 圖檔案,儲存模型的網路圖
4. model.ckpt-2124.index : 這個沒搞太清楚
5. graph.pbtxt: 用protobuf格式儲存的模型的圖
2. 測試
EVAL_DIR=../log_files/log_eval/train_voc0712_20170712_1741_VGG16/
CHECKPOINT_PATH=.././log_files/log_finetune/train_voc0712_20170712_1741_VGG16/model.ckpt-197442
python3 ../eval_ssd_network.py \
--eval_dir=${EVAL_DIR} \
--dataset_dir=${DATASET_DIR} \
--dataset_name=pascalvoc_2007 \
--dataset_split_name=test \
--model_name=ssd_300_vgg \
--checkpoint_path=${CHECKPOINT_PATH} \
--batch_size=64 \
--gpu_memory_fraction=0.8
3. 檢視絡結構張量
開啟/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell
,執行bash inspect_checkpoint.sh
,會執行:
#!/usr/bin/env bash
python inspect_checkpoint.py \
--file_name=log_files/log_finetune/train_voc0712_20170703_1104_pb/model.ckpt-3069
輸出結果如下:
eta1_power (DT_FLOAT) []
beta2_power (DT_FLOAT) []
global_step (DT_INT64) []
ssd_300_vgg/block10/conv1x1/biases (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/biases/Adam (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/biases/Adam_1 (DT_FLOAT) [128]
ssd_300_vgg/block10/conv1x1/weights (DT_FLOAT) [1,1,256,128]
ssd_300_vgg/block10/conv1x1/weights/Adam (DT_FLOAT) [1,1,256,128]
ssd_300_vgg/block10/conv1x1/weights/Adam_1 (DT_FLOAT) [1,1,256,128]
......(此處省略1萬字)
ssd_300_vgg/conv7/biases (DT_FLOAT) [1024]
ssd_300_vgg/conv7/biases/Adam (DT_FLOAT) [1024]
ssd_300_vgg/conv7/biases/Adam_1 (DT_FLOAT) [1024]
ssd_300_vgg/conv7/weights (DT_FLOAT) [1,1,1024,1024]
ssd_300_vgg/conv7/weights/Adam (DT_FLOAT) [1,1,1024,1024]
ssd_300_vgg/conv7/weights/Adam_1 (DT_FLOAT) [1,1,1024,1024]
三、模型匯出
1. 生成ssd模型專用的pb檔案
這裡的pb檔案不同於之前的model.ckpt-2124.meta檔案,這個pb檔案是一個graph, 它儲存了ssd模型圖的結構,不包含全權重資訊,因此相比 .ckpt 模型檔案,它的檔案體積小。
開啟/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell
,執行bash export.sh
,會執行:
# 程式碼參考slim庫做出修改:/home/doctorimage/kindlehe/project/models/slim/export_inference_graph.py
python -u ../export_inference_graph.py \
--model_name=ssd_300_vgg \
--output_file=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--dataset_name=pascalvoc_2007 \
--dataset_dir=/home/doctorimage/kindlehe/common/dataset/VOC0712/ #指定用那些資料訓練的模型,因為分類數和labels要從這裡面取
如何修改自己的模型並生成對應的pb檔案呢?
a. --model_name=ssd_300_vgg
意思是export_inference_graph.py
最終會進入/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/nets
目錄,根據ssd_vgg_300.py
裡面定義好的模型,去生成對應的====pb檔案====。所以想修改網路結構,只需要在ssd_vgg_300.py
這裡修改,然後進行在訓練的時候把model_name
改成你自己設定的名字即可。執行export.sh
指令碼即可生成修改的模型所對應的pb檔案。
b. --dataset_dir
這個引數用來指定章節“VOC資料轉換成tfrecord格式”中生成的tfrecord檔案及標籤所在的路徑,因為在生成pb檔案的時候,會根據資料確定”分類類別的數目”並提取”label”.
c. 注意,這裡生成的pb檔案如果未設定tf.train.write_graph(,,as_text=False)當中as_text=true,那麼會以二進位制格式儲存,在接下來的feeeze_graph.py固化操作中,應該加上一個引數--input_binary=true
2. 模型固化
2.1 模型固化小工具freeze_graph.py
實際上使用訓練得到模型時,需要把權重固定,不然每次測試一張圖片就相當於繼續訓練模型,權重也要重新變化一次,這會導致測試一張圖片需要很久的時間。在固定權重的基礎上,還需要整合圖的定義。這個過程就是 freeze graph 。
這裡freeze_graph.py的作用就是將上一個步驟生成的pb圖檔案和訓練得到的ckpt引數檔案固化到一個檔案中,將變數引數型別替換成常量引數型別,模型大小從 降到
開啟/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell
,執行bash export.sh
,會執行:
#!/usr/bin/env bash
python ../freeze_graph.py \
--input_binary=true\
--input_graph=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--input_checkpoint=../log_files/log_finetune/train_voc0712_20170703_1104_pb/model.ckpt-3069 \
--output_graph= ../log_files/log_finetune/train_voc0712_20170703_1104_pb/frozen_train_voc0712_20170703_1104.pb \
--output_node_names=output/output
引數說明
--input_binary=true :參見freeze_graph.py的bug清單
--input_graph: 模型的圖的定義檔案 train_voc0712_20170703_1104.pb(不包含權重);
--input_checkpoint: 模型的引數檔案 model.ckpt-3069;
--output_graph: 繫結後包含引數的圖模型檔案 frozen_train_voc0712_20170703_1104.pb;
-- output_node_names: 輸出待計算的tensor名字【重要】;
《freeze_graph.py的bug清單》
發現tensorflow不同版本下執行freeze_graph.py 指令碼時可能遇到的Bug挺多的,列舉一下:
# Bug1: google.protobuf.text_format.ParseError: 2:1 : Message type "tensorflow.GraphDef" has no field named "J".
# 原因: tf.train.write_graph(,,as_text=False) 之前寫出的模型檔案是Binary時,
# 讀入檔案格式應該對應之前設定引數 python freeze_graph.py [***] --input_binary=true,
# 如果as_text=True則可以忽略,因為預設值 --input_binary=false。
# 參考: https://github.com/tensorflow/tensorflow/issues/5780
# Bug2: Input checkpoint '...' doesn't exist!
# 原因: 可能是命令列用了 --input_checkpoint=data.ckpt ,
# 執行 freeze_graph.py 指令碼,要在路徑引數前加上 "./" 貌似才能正確識別路徑。
# 如檔案的路徑 --input_checkpoint=data.ckpt 變為 --input_checkpoint=./data.ckpt
# 參考: http://www.it1me.seriousdigitalmedia.com/it-answers?id=42439233&ttl=How+to+use+freeze_graph.py+tool+in+TensorFlow+v1
# Bug3: google.protobuf.text_format.ParseError: 2:1 : Expected identifier or number.
# 原因: --input_checkpoint 需要找到 .ckpt.data-000*** 和 .ckpt.meta等多個檔案,
# 因為在 --input_checkpoint 引數只需要新增 ckpt的字首, 如: nn_model.ckpt,而不是完整的路徑nn_model.ckpt.data-000***
# .meta .index .data checkpoint 4個檔案
# Bug4: # you need to use a different restore operator?
# tensorflow.python.framework.errors_impl.DataLossError: Unable to open table file ./pos.ckpt.data-00000-of-00001: Data loss: not an sstable (bad magic number): perhaps your file is in a different file format and you need to use a different restore operator?
# Saver 儲存的檔案用格式V2,解決方法更新tensorflow....
# 歡迎補充
2.2 檢視output_node_names小工具
==如何確定一個模型的output_node_names
呢?==
如果不指定,會自動選擇節點名
訓練模型的時候,會生成一個graph.pbtxt
檔案,裡面包含了所有的節點名,閱讀ssd_300_vgg.py
檔案裡的網路結構,找到希望輸出的節點即可。對於ssd,我們希望輸出預測框的位置,節點為:
開啟/home/doctorimage/kindlehe/project/SSD/SSD-Tensorflow-master/shell
,執行bash print_node_name.sh
,根據上一個步驟生成的train_voc0712_20170703_1104.pb
檔案,輸出ssd網路結構實際用到的所有節點,儲存到save_dir
目錄下
#!/usr/bin/env bash
python ../print_node_name.py\
--graph_file=../log_files/log_finetune/train_voc0712_20170703_1104_pb/train_voc0712_20170703_1104.pb \
--save_dir=../log_files/log_finetune/train_voc0712_20170703_1104_pb/