1. 程式人生 > >TensorFlow實戰:Chapter-9下(DeepLabv3+在自己的資料集訓練)

TensorFlow實戰:Chapter-9下(DeepLabv3+在自己的資料集訓練)

基本配置

資料集處理

我的資料集是3分類問題,但因為資料集的保密協議,在後面的demo中我沒有放出原圖片,我會盡量將訓練細節寫出來。為了方便記錄,我又使用了CamVid資料集(從這裡下載)測試了一下。

資料集處理分成三大步:

  • 標註資料
  • 製作指示檔案
  • 將資料打包成TFRecord

相關資源:

對於CamVid資料集,我將製作好的TFRecord上傳到了CSDN上,如果只是單單測試程式的話,可以直接下載使用。
同時,對應的用於生成CamVid不同資料集的index檔案我也上傳到github了。

標註資料

這裡需要提前說明一個概念,我自己在這裡耽誤了很長時間:

ignore_label

如果你的資料集存在ignore_label,注意不要把ignore_labelbackground混淆。 ignore_label是沒有做標註的,不在預測範圍內的,ignore_label是不參與計算loss的。我們在mask中將ignore_label的灰度值標記為255

mask的灰度值設定

CamVid有11個分類,我自己的資料集有3個分類。生成的mask必須為單通道(灰度圖),建議為png格式。

注意,在製作mask時,對所有object的灰度畫素值有要求

對於所有object包括background在內,在mask中要將灰度值標註為0~n,雖然產生的圖片很難看出區別,但是這對訓練是有效的。注意,不要把object的灰度值標註成10,20,100…(這樣程式碼會直接將灰度值和class匹配,這在調整權重等操作會有麻煩~)

再強調一下:我在製作個人的資料集上將background標註為0,注意這是background而不是ignore_label.

最終,我在個人的資料集上關於影象的mask,設定如下:

  • background的灰度值設定為0
  • object1-2的灰度值設定為1,2
  • 因為我的資料集中沒有ignore_label,所以沒有點設定為255(我的資料集和CamVid都是沒有的,如果存在ingore_label,在視覺上是白色的)

對於CamVid,原標註的資料集格式滿足要求。示例如下,可以看到這裡面是不存在ignore_label(沒有白色),取一個圖片示例如下:

這裡寫圖片描述

這裡寫圖片描述

製作指引檔案

再製作TFRecord之前,需要有檔案指引將資料集分類成訓練/測試/驗證集,故我們需要建立指引檔案。

將上述生成的所有mask和原輸入圖片分在兩個資料夾下,我的設定如下:

  • /root/dataset/CamVid/image:存放所有的輸入圖片,共有701張,這其中包括訓練/測試/驗證集的圖片
  • /root/dataset/CamVid/mask:存放所有的labeled圖片,共有701張,和輸入圖片是一一對應的

對於CamVid資料集,建立了一個目錄/root/dataset/CamVid/index,該目錄下包含三個.txt檔案:

  • train.txt:所有訓練集的檔名稱
  • trainval.txt:所有驗證集的檔名稱
  • val.txt:所有測試集的檔名稱

對應的CamVid資料集目錄如下:

#from /root/dataset/CamVid/
    + image
    + mask
    + index # 自己建立
        - train.txt
        - trainval.txt
        - val.txt
    + tfrecord #自己建立

上述的三個.txt檔案,也是從CamVid上下載的,參照製作指令碼的要求。

詳細的來講,只存放圖片的名稱,對應train/val.txt檔案部分擷取(可以使用Sublime或其他文字編輯工具統一find然後刪除):

# train.txt
0001TP_006690
0001TP_006720
...
0016E5_08640
#共367行

# val.txt
0016E5_07959
...
0016E5_08157
0016E5_08159
# 共101行

對應的用於生成CamVid不同資料集的index檔案我也上傳到github了。

將資料打包成TFRecord

對於CamVid資料集,我將製作好的TFRecord上傳到了CSDN上,如果只是單單測試程式的話,可以直接下載使用。

標準的指令格式如下:

python ./build_voc2012_data.py \
  --image_folder="${IMAGE_FOLDER}" \
  --semantic_segmentation_folder="${SEMANTIC_SEG_FOLDER}" \
  --list_folder="${LIST_FOLDER}" \
  --image_format="jpg" \
  --output_dir="${OUTPUT_DIR}"
  • ${IMAGE_FOLDER} :資料集中原輸入資料的檔案目錄地址
  • ${SEMANTIC_SEG_FOLDER}:資料集中標籤的檔案目錄地址
  • ${LIST_FOLDER}: 將資料集分類成訓練集、驗證集等的指示目錄檔案目錄
  • image_format:輸入圖片資料的格式,CamVid的是png格式
  • output_dir:製作的TFRecord存放的目錄地址(自己建立)

依據上面對於資料集的處理,對於CamVid資料集,製作TFRecord,使用的指令如下:

# from /research/deeplab/dataset
python ./build_voc2012_data.py \
  --image_folder="/root/dataset/CamVid/image" \
  --semantic_segmentation_folder="/root/dataset/CamVid/mask" \
  --list_folder="/root/dataset/CamVid/index" \
  --image_format="png" \
  --output_dir="/root/dataset/CamVid/tfrecord"

指定執行結果部分輸出如下:

#trainval.txt
>> Converting image 59/233 shard 0
>> Converting image 118/233 shard 1
>> Converting image 177/233 shard 2
>> Converting image 233/233 shard 3
#train.txt
>> Converting image 92/367 shard 0
>> Converting image 184/367 shard 1
>> Converting image 276/367 shard 2
>> Converting image 367/367 shard 3
#val.txt
>> Converting image 26/101 shard 0
>> Converting image 52/101 shard 1
>> Converting image 78/101 shard 2
>> Converting image 101/101 shard 3

可以在對應的/root/dataset/CamVid/tfrecord目錄下檢視已經制作好的TFRecord檔案:

# from  /root/dataset/CamVid/tfrecord
$: ls
train-00000-of-00004.tfrecord     trainval-00002-of-00004.tfrecord
train-00001-of-00004.tfrecord     trainval-00003-of-00004.tfrecord
train-00002-of-00004.tfrecord     val-00000-of-00004.tfrecord
train-00003-of-00004.tfrecord     val-00001-of-00004.tfrecord
trainval-00000-of-00004.tfrecord  val-00002-of-00004.tfrecord
trainval-00001-of-00004.tfrecord  val-00003-of-00004.tfrecord

這樣關於資料集的處理都完事了。

修改訓練指令碼

在DeepLabv3+模型的基礎上,主要需要修改以下幾個檔案

  • segmentation_dataset.py檔案
  • train_utils.py

segmentation_dataset.py

CamVid描述配置

# segmentation_dataset.py line 110
_CAMVID_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
        'train': 367,  # num of samples in images/training
        'val': 101,  # num of samples in images/validation
    },
    num_classes=12,
    ignore_label=255,
)

因為CamVid共有11個classes,所以加上ignore_label共12個。

個人資料集描述配置

對於我個人的資料集,一共有object1,object2,background三個類別,加上ignore_label,所以num_classes=4.

_MYDATA_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
        'train': 444,  # num of samples in images/training
        'val': 46,  # num of samples in images/validation
    },
    num_classes=4,
    ignore_label=255,
)

註冊資料集

_DATASETS_INFORMATION = {
    'cityscapes': _CITYSCAPES_INFORMATION,
    'pascal_voc_seg': _PASCAL_VOC_SEG_INFORMATION,
    'ade20k': _ADE20K_INFORMATION,
    'mydata':_MYDATA_INFORMATION, #我自己的資料集
    'camvid':_CAMVID_INFORMATION, #camvid示例
}

train_utils.py

對應的train_utils.py中,先將109行關於exclude_list的設定修改,作用是在使用預訓練權重時候,不載入該logit層:

# Variables that will not be restored.
exclude_list = ['global_step','logits']
if not initialize_last_layer:
exclude_list.extend(last_layers)

資料不平衡

參考DeepLabv3+一作aquariusjay的解疑,如果你的問題是二分類或者和我的資料集類似,存在object之間imablance,這需要在train_utils.py的70行修改權重.

因為CamVid資料集這個問題可以忽略,這裡重點說我個人資料集,因為是三分類問題,其中background佔了非常大的比例,並且object2比object1要稍微少一點,最終的設定的權重比例為1:10:15

irgore_weight = 0
label0_weight =1  # 對應灰度值0,即background
label1_weight = 10  # 對應object1, mask 中灰度值1
label2_weight = 15  # 對應object2,.mask 中灰度值2

not_ignore_mask = 
tf.to_float(tf.equal(scaled_labels, 0)) * label0_weight +
tf.to_float(tf.equal(scaled_labels, 1)) * label1_weight +
tf.to_float(tf.equal(scaled_labels, 2)) * label2_weight +
tf.to_float(tf.equal(scaled_labels, ignore_label)) * irgore_weight 

tf.losses.softmax_cross_entropy(
    one_hot_labels,
    tf.reshape(logits, shape=[-1, num_classes]),
    weights=not_ignore_mask,
    scope=loss_scope)

在這個步驟,我曾把ignore_labelbackground混淆了,最後我在mask 中將background的標註值為0,對應的label0_weight=1,object1的標註值為1,對應的為label1_weight = 10,… 最後才訓練成功。

訓練和視覺化

如果想在DeepLab的基礎上fine-tune其他資料集, 可在train.py中修改輸入引數。有一些選項:

  • 使用預訓練的所有權重,設定initialize_last_layer=True
  • 只使用網路的backbone,設定initialize_last_layer=Falselast_layers_contain_logits_only=False
  • 使用所有的預訓練權重,除了logits,因為如果是自己的資料集,對應的classes不同(這個我們前面已經設定不載入logits),可設定initialize_last_layer=Falselast_layers_contain_logits_only=True

最終,我的設定是:

  • initialize_last_layer=False
  • last_layers_contain_logits_only=True

初試訓練

注意:我在訓練CamVid時,是沒有考慮類別之間的的平衡問題,所以沒有做imblance的修正,如果你是二分類或者存在嚴重的imblance情況,可以參考部分troubleshot中我遇到的問題。

依據repo上的訓練示例,注意如下幾個引數:

  • tf_initial_checkpoint:預訓練的權重,因為CamVid和我的資料集都和CityScapes類似,所以我使用的是CityScapes的預訓練權重

  • train_logdir:訓練產生的檔案存放位置

  • dataset_dir:資料集的TFRecord檔案

我在CamVid上的訓練指令如下:

python deeplab/train.py \
    --logtostderr \
    --training_number_of_steps=300 \
    --train_split="train" \
    --model_variant="xception_65" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --train_crop_size=513 \
    --train_crop_size=513 \
    --train_batch_size=2 \
    --dataset="camvid" \
    --tf_initial_checkpoint='/root/newP/official_tf/models-master/research/deeplab/backbone/deeplabv3_cityscapes_train/model.ckpt' \
    --train_logdir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train' \
    --dataset_dir='/root/dataset/CamVid/tfrecord'

這裡只設置了300個step,對於crop_size設定為513,只是為了測試訓練是否能夠正確執行。 考慮到我的計算資源顯示,我設定batchsize=2。

部分的訓練輸出如下:

INFO:tensorflow:Starting Session.
INFO:tensorflow:Saving checkpoint to path /root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train/model.ckpt
INFO:tensorflow:Starting Queues.
INFO:tensorflow:global_step/sec: 0
INFO:tensorflow:Recording summary at step 0.
INFO:tensorflow:global step 10: loss = 2.7773 (0.550 sec/step)
INFO:tensorflow:global step 20: loss = 2.6438 (0.531 sec/step)
INFO:tensorflow:global step 30: loss = 2.4824 (0.555 sec/step)
INFO:tensorflow:global step 40: loss = 2.4652 (0.564 sec/step)
#...
INFO:tensorflow:global step 300: loss = 1.9276 (0.534 sec/step)
INFO:tensorflow:Stopping Training.
INFO:tensorflow:Finished training! Saving model to disk.

視覺化和評估

deepLab提供了視覺化和評估工具,這裡對於剛才在CamVid上訓練的權重測試。

視覺化的結果

因為CamVid的圖片大小和CityScapes是不同的,這裡相關的引數設定為:

  • vis_split:設定為測試集
  • vis_crop_size:設定360,480為圖片的大小
  • dataset_dir:設定為建立的TFRecord
  • colormap_type:視覺化標註的顏色

最終,視覺化的指令如下:

# From tensorflow/models/research/
python deeplab/vis.py \
    --logtostderr \
    --vis_split="val" \
    --model_variant="xception_65" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --vis_crop_size=360 \
    --vis_crop_size=480 \
    --dataset="camvid" \
    --colormap_type="pascal" \
    --checkpoint_dir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train' \
    --vis_logdir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/vis' \
    --dataset_dir='/root/dataset/CamVid/tfrecord'

視覺化指令的輸出部分如下:

INFO:tensorflow:Restoring parameters from /root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train/model.ckpt-300
INFO:tensorflow:Visualizing batch 1 / 101
INFO:tensorflow:Visualizing batch 2 / 101
INFO:tensorflow:Visualizing batch 3 / 101
...
INFO:tensorflow:Visualizing batch 100 / 101
INFO:tensorflow:Visualizing batch 101 / 101

我截取了視覺化輸出圖:

這裡寫圖片描述

可以看到模型是可以正確分類的,雖然結果不怎麼好。

評估結果

同樣的,eval的引數也需要做一些改變:

  • eval_split:設定為測試集
  • crop_size:同樣設定為360和480
  • dataset:設定為camvid
  • dataset_dir:設定為我們建立的資料集

我們評估的指令如下:

python deeplab/eval.py \
    --logtostderr \
    --eval_split="val" \
    --model_variant="xception_65" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --eval_crop_size=360 \
    --eval_crop_size=480 \
    --dataset="camvid" \
    --checkpoint_dir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train' \
    --eval_logdir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/eval' \
    --dataset_dir='/root/dataset/CamVid/tfrecord'

評估指令的輸出部分如下:

INFO:tensorflow:Restoring parameters from /root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train/model.ckpt-300
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Starting evaluation at 2018-07-06-06:34:28
INFO:tensorflow:Evaluation [10/101]
INFO:tensorflow:Evaluation [20/101]
#...
INFO:tensorflow:Evaluation [101/101]
INFO:tensorflow:Finished evaluation at 2018-07-06-06:34:34
miou_1.0[0.149601415]

結果一般般,但是證明了我們訓練過程是沒有大問題~

進階訓練

在前面初始訓練後,針對CamVid資料集,我做了進一步參考修改,簡單測試了一下新的結果。

我將前面儲存在train_logdir裡面的權重刪除了,一些引數的修改:

  • training_number_of_steps設定為3000
  • crop_size縮小為321
  • batch_size可以提高到4

進階的訓練指令如下:

python deeplab/train.py \
    --logtostderr \
    --training_number_of_steps=3000 \
    --train_split="train" \
    --model_variant="xception_65" \
    --atrous_rates=6 \
    --atrous_rates=12 \
    --atrous_rates=18 \
    --output_stride=16 \
    --decoder_output_stride=4 \
    --train_crop_size=321 \
    --train_crop_size=321 \
    --train_batch_size=4 \
    --dataset="camvid" \
    --tf_initial_checkpoint='/root/newP/official_tf/models-master/research/deeplab/backbone/deeplabv3_cityscapes_train/model.ckpt' \
    --train_logdir='/root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train' \
    --dataset_dir='/root/dataset/CamVid/tfrecord'

進階訓練的輸出部分如下:

INFO:tensorflow:global step 2960: loss = 0.6281 (0.407 sec/step)
INFO:tensorflow:Saving checkpoint to path /root/newP/official_tf/models-master/research/deeplab/exp/camvid_train/train/model.ckpt
INFO:tensorflow:global_step/sec: 2.45499
INFO:tensorflow:Recording summary at step 2962.
INFO:tensorflow:global step 2970: loss = 0.8240 (0.439 sec/step)
INFO:tensorflow:global step 2980: loss = 0.9588 (0.385 sec/step)
INFO:tensorflow:global step 2990: loss = 0.8880 (0.412 sec/step)
INFO:tensorflow:global step 3000: loss = 0.8292 (0.392 sec/step)

進階訓練結果評估

重新使用eval.py做了驗證:

INFO:tensorflow:Evaluation [101/101]
INFO:tensorflow:Finished evaluation at 2018-07-06-07:03:30
miou_1.0[0.4015598]

可以看到,新的訓練結果有了顯著的提示。

進階訓練結果視覺化

重新使用vis.py,部分視覺化結果:

這裡寫圖片描述

可以看到明顯比前面好多了。

部分troubleshot

一開始我是一直在DeepLabv3+上測試自己的資料集,故問題都是集中於自己的資料集上。

我主要犯了三個錯誤:

  • 1:資料集的mask的生成
  • 2:混淆了ignore_label和background
  • 3:錯誤的設定各個object和background之間的權重

對於mask的生成,我在前面詳細的講了如何生成,這是非常重要的一步。,我犯的錯誤是將不同的object和background的畫素值設定為0,100,150.這導致後續的預測會有問題,無法解決imblance。

混淆了ignore_label和background

對於ignore_label我曾經設定為0,對應的segmentation_dataset.py檔案:

# segmentation_dataset.py

_CAMVID_INFORMATION = DatasetDescriptor(
    splits_to_sizes={
        'train': 367,  # num of samples in images/training
        'val': 101,  # num of samples in images/validation
    },
    num_classes=3,
    ignore_label=0,
)

這裡我錯誤的把ignore_label設定為0,並且錯誤的將num_classes設定為3.

train_utils.py檔案中計算如下:

ignore_weight = 0
label0_weight =20
label1_weight = 20

not_ignore_mask = 
tf.to_float(tf.equal(scaled_labels, 1)) * label0_weight 
+ tf.to_float(tf.equal(scaled_labels, 2)) * label1_weight 
+ tf.to_float(tf.equal(scaled_labels, ignore_label)) * ignore_weight

因為ignore_label設定為0了,這會導致結果背景不做任何loss,因為我每次都是訓練了200個step,可以看到如下兩個圖,是有學習到資訊,但是在預測上是有存在問題的。

這裡寫圖片描述

設定權重問題

預測是一種顏色

對應各個類別之間權重設定有問題,預測權重設定出現了問題:

## 輸出全藍色
irgore_weight = 0
label0_weight =10
label1_weight = 20


## 輸出全黑色
irgore_weight = 10
label0_weight =10
label1_weight = 10

## 輸出全綠色
irgore_weight = 0
label0_weight =20
label1_weight = 10

輸出結果為全藍色/黑色/綠色,對應的object2/background/object1設定過大,且沒有考慮background,這導致模型不計算object2/background/object1的loss,一股腦的預測藍色/黑色/綠色就能得到不錯的loss。

這裡寫圖片描述

成功的訓練

我的資料集中大部分都是background,object1一般會比object2大一些,故最終測試的權重比例為1:10:15,在實際過程中可通過畫素統計給出一個更為精確的值:

irgore_weight = 0
label0_weight =1  # background
label1_weight = 10 # object1
label2_weight = 15 #object2

not_ignore_mask = 
tf.to_float(tf.equal(scaled_labels, 0)) * label0_weight +
tf.to_float(tf.equal(scaled_labels, 1)) * label1_weight +
tf.to_float(tf.equal(scaled_labels, 2)) * label2_weight +
tf.to_float(tf.equal(scaled_labels, ignore_label)) * irgore_weight 

這是我在訓練200個step和4000個step之間模型的測試結果如下:

這裡寫圖片描述

參考資料

參考aquariusjay

這裡寫圖片描述

這裡寫圖片描述

參考zhaolewen

這裡寫圖片描述