1. 程式人生 > >SSD-tensorflow使用文件(二)——從資料處理到iOS移植

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檢視相應的路徑。
pyCharm配置
pyCharm環境配置

配置好就可以點選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/

3. 固化、簡化、量化模型

四、iOS怒編譯tensorflow

五、iOS怒配置Xcode路徑