天天看點

Ubuntu 16.04下Caffe-SSD的應用(四)——ssd_pascal.py源碼解讀

前言

caffe-ssd所有的訓練時的參數,全部由ssd_pascal.py來定義,之後再去調用相關的腳本和函數,是以想要訓練自己的資料,首先要明白ssd_pascal.py各個定義參數的大體意思。

ssd_pascal.py源碼解讀

from __future__ import print_function
import caffe
from caffe.model_libs import *
from google.protobuf import text_format

import math
import os
import shutil
import stat
import subprocess
import sys
 
# 給基準網絡後面增加額外的卷積層(為了避免此處的卷積層的名稱和基準網絡卷積層的名稱重複,
#這裡可以用基準網絡最後一個層的名稱進行開始命名),這一部分的具體實作方法可以對照檔案
#~/caffe/python/caffe/model_libs.py檢視,SSD的實作基本上就是ssd_pascal.py和model_libs.py
#兩個檔案在控制,剩下的則是caffe底層代碼中編寫各個功能子產品。
def AddExtraLayers(net, use_batchnorm=True):
    use_relu = True
    
#生成附加網絡的第一個卷積層,卷積核的數量為256,卷積核的大小為1*1,pad的尺寸為0,stride為1.
# 獲得基準網絡的最後一層,作為conv6-1層的輸入
    from_layer = net.keys()[-1]
    # TODO(weiliu89): Construct the name using the last layer to avoid duplication.
    out_layer = "conv6_1"
    #conv6_1生成完畢
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 1, 0, 1)

#生成附加網絡的第一個卷積層,卷積核的數量為512,卷積核的大小為3*3,pad的尺寸為1,stride為2.
    from_layer = out_layer
    out_layer = "conv6_2"
    #conv6_2生成完畢
    ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 512, 3, 1, 2)

#conv7_1到conv9_2的生成
    for i in xrange(7, 9):
      from_layer = out_layer
      out_layer = "conv{}_1".format(i)
      ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 128, 1, 0, 1)

      from_layer = out_layer
      out_layer = "conv{}_2".format(i)
      ConvBNLayer(net, from_layer, out_layer, use_batchnorm, use_relu, 256, 3, 1, 2)

    #添加全局池層
    name = net.keys()[-1]
    net.pool6 = L.Pooling(net[name], pool=P.Pooling.AVE, global_pooling=True)

    return net


### 相應地修改一下參數 ###  
# 包含caffe代碼的路徑  
# 假設目前路徑是在caffe跟目錄下運作代碼
caffe_root = os.getcwd()  #擷取caffe的根目錄  

#在生成所有訓練檔案之後就開始訓練,這裡run_soon給予參數Ture.  
run_soon = True
#如果接着上次的訓練,繼續進行訓練,這裡的參數為Ture,(就是訓練一半停止了,重新啟動的時候,這裡的Ture保證繼續接着上次的訓練進行訓練)
#否則為False,表示将從下面定義的預訓練模型處進行加載。(這個表示就是不管上次訓練一半的模型,直接從預訓練好的基準模型哪裡開始訓練)  
resume_training = True
# 如果是Ture的話,表示要移除舊的模型訓練檔案,否則是不移除的。 
remove_old_models = False

#如果想用CPU進行訓練,設定為TRUE
use_cpu = False

#訓練資料的資料庫檔案,就是create_data.sh生成的trainval_lmdb檔案
train_data = "data/VOC0712/trainval_lmdb"
#測試資料的資料庫檔案,就是create_data.sh生成的test_lmdb檔案
test_data = "data/VOC0712/test_lmdb"
# 指定批量采樣器,可以改成500X500
resize_width = 300
resize_height = 300
resize = "{}x{}".format(resize_width, resize_height)
batch_sampler = [
        {
                'sampler': {
                        },
                'max_trials': 1,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.1,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.3,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.5,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.7,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'min_jaccard_overlap': 0.9,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        {
                'sampler': {
                        'min_scale': 0.3,
                        'max_scale': 1.0,
                        'min_aspect_ratio': 0.5,
                        'max_aspect_ratio': 2.0,
                        },
                'sample_constraint': {
                        'max_jaccard_overlap': 1.0,
                        },
                'max_trials': 50,
                'max_sample': 1,
        },
        ]
train_transform_param = {
        'mirror': True,
        'mean_value': [104, 117, 123],#均值  
        'resize_param': {#存儲資料轉換器用于調整大小政策的參數的消息。
                'prob': 1,#使用這個調整政策的可能性 
                'resize_mode': P.Resize.WARP,#重定義大小的模式,caffe.proto中定義的是枚舉類型  
                'height': resize_height,
                'width': resize_width,
                'interp_mode': [#插值模式用于調整大小,定義為枚舉類型 
                        P.Resize.LINEAR,
                        P.Resize.AREA,
                        P.Resize.NEAREST,
                        P.Resize.CUBIC,
                        P.Resize.LANCZOS4,
                        ],
                },
        'emit_constraint': {
            'emit_type': caffe_pb2.EmitConstraint.CENTER,
            }
        }
test_transform_param = {#測試轉換參數,類似于訓練轉換參數。
        'mean_value': [104, 117, 123],
        'resize_param': {
                'prob': 1,
                'resize_mode': P.Resize.WARP,
                'height': resize_height,
                'width': resize_width,
                'interp_mode': [P.Resize.LINEAR],
                },
        }

#如果為ture,則對所有新添加的層使用批處理規範。
#目前隻測試了非批處理規範版本。
use_batchnorm = False
#使用不同的初始學習率
if use_batchnorm:
    base_lr = 0.0004
else:
    # 當batch_size = 1, num_gpus = 1時的學習率.
    base_lr = 0.00004   #由于上面use_batchnorm = false,是以我們一般調整初始學習率時隻需更改這一部分,目前為0.001。  

#可以在這裡更改工作路徑與名稱.
job_name = "SSD_{}".format(resize)
#更改生成的模型名稱.
model_name = "VGG_VOC0712_{}".format(job_name)

# 存儲模型.prototxt檔案的目錄.
save_dir = "models/VGGNet/VOC0712/{}".format(job_name)
# 存儲模型快照的目錄.
snapshot_dir = "models/VGGNet/VOC0712/{}".format(job_name)
# 存儲工作腳本和日志檔案的目錄.
job_dir = "jobs/VGGNet/VOC0712/{}".format(job_name)
# 存儲檢測結果的目錄.
output_result_dir = "data/VOC0712/results/{}/Main".format(job_name)

# 模型定義檔案.
train_net_file = "{}/train.prototxt".format(save_dir)
test_net_file = "{}/test.prototxt".format(save_dir)
deploy_net_file = "{}/deploy.prototxt".format(save_dir)
solver_file = "{}/solver.prototxt".format(save_dir)
# 快照字首.
snapshot_prefix = "{}/{}".format(snapshot_dir, model_name)
# 工作腳本路徑.
job_file = "{}/{}_train.bat".format(job_dir, model_name)

#存儲測試圖像的名稱和大小,是create_list.sh生成的test_name_size.txt檔案路徑
name_size_file = "data/VOC0712/test_name_size.txt"
#預訓練模型。 使用完卷積截斷的VGGNet,使用官方或者别從成熟模型參數
pretrain_model = "models/VGGNet/VGG_ILSVRC_16_layers_fc_reduced.caffemodel"
#存儲類型的檔案
label_map_file = "data/VOC0712/labelmap_voc.prototxt"

#要預測的類的數量。VOC所标注的分類數加背景圖.
num_classes = 21
#位置共享,如果為true,邊框在不同的類中共享
share_location = True
#背景圖的标簽名稱
background_label_id=0
#是否考慮困難的ground truth,預設為true
train_on_diff_gt = True
#如何規範跨越批次,空間次元或其他次元聚集的損失層的損失。 
#目前隻在SoftmaxWithLoss和SigmoidCrossEntropyLoss圖層中實作。
#按照批次中的示例數量乘以空間次元。 在計算歸一化因子時,
#不會忽略接收忽略标簽的輸出。定義為枚舉,四種類型分别是:FULL,
#除以不帶ignore_label的輸出位置總數。 如果未設定ignore_label,則表現為FULL;VALID;
normalization_mode = P.Loss.VALID
#bbox的編碼方式。此參數定義在PriorBoxParameter參數定義解釋中,
#為枚舉類型,三種類型為:CORNER,CENTER_SIZE和CORNER_SIZE。  
code_type = P.PriorBox.CENTER_SIZE
#負/正比率,即文中所說的1:3
neg_pos_ratio = 3.
#位置損失的權重
loc_weight = (neg_pos_ratio + 1.) / 4.
multibox_loss_param = {	#存儲MultiBoxLossLayer使用的參數的消息
    'loc_loss_type': P.MultiBoxLoss.SMOOTH_L1,#位置損失類型,定義為枚舉,有L2和SMOOTH_L1兩種類型.
    'conf_loss_type': P.MultiBoxLoss.SOFTMAX,	#置信損失類型,定義為枚舉,有SOFTMAX和LOGISTIC兩種。  
    'loc_weight': loc_weight,
    'num_classes': num_classes,
    'share_location': share_location,
    'match_type': P.MultiBoxLoss.PER_PREDICTION,#訓練中的比對方法。定義為枚舉,有BIPARTITE和PER_PREDICTION兩種。如果match_type為PER_PREDICTION(即每張圖預測),則使用overlap_threshold來确定額外的比對bbox。  
    'overlap_threshold': 0.5,	#閥值大小。即我們所說的IoU的大小
    'use_prior_for_matching': True,#是否使用先驗比對,一般為true。
    'background_label_id': background_label_id,	#背景标簽的類别編号,一般為0
    'use_difficult_gt': train_on_diff_gt,#是否考慮困難的ground truth,預設為true。
    'do_neg_mining': True,
    'neg_pos_ratio': neg_pos_ratio,	#負/正比率,即文中所說的1:3
    'neg_overlap': 0.5,#對于不比對的預測,上限為負的重疊。即如果重疊小于0.5則定義為負樣本,Faster R-CNN設定為0.3。
    'code_type': code_type,	#bbox的編碼方式。此參數定義在PriorBoxParameter參數定義解釋中,為枚舉類型,三種類型為:CORNER,CENTER_SIZE和CORNER_SIZE。
    }
loss_param = {#存儲由損失層共享的參數的消息  
    'normalization': normalization_mode,#如何規範跨越批次,空間次元或其他次元聚集的損失層的損失。目前隻在SoftmaxWithLoss和SigmoidCrossEntropyLoss圖層中實作。按照批次中的示例數量乘以空間次元。 在計算歸一化因子時,不會忽略接收忽略标簽的輸出。定義為枚舉,四種類型分别是:FULL,除以不帶ignore_label的輸出位置總數。 如果未設定ignore_label,則表現為FULL;VALID;BATCH_SIZE,除以批量大小;NONE,不要規範化損失。  
    }

#參數生成先驗。  
#輸入圖像的最小尺寸  
min_dim = 300#次元
# conv4_3 ==> 38 x 38
# fc7 ==> 19 x 19
# conv6_2 ==> 10 x 10
# conv7_2 ==> 5 x 5
# conv8_2 ==> 3 x 3
# pool6 ==> 1 x 1
#prior_box來源層,可以更改。很多改進都是基于此處的調整。
mbox_source_layers = ['conv4_3', 'fc7', 'conv6_2', 'conv7_2', 'conv8_2', 'pool6']
#這裡即是論文中所說的Smin=0.2,Smax=0.9的初始值,經過下面的運算即可得到min_sizes,max_sizes。
min_ratio = 20
max_ratio = 95
#取一個間距步長,即在下面for循環給ratio取值時起一個間距作用。可以用一個具體的數值代替,這裡等于17
step = int(math.floor((max_ratio - min_ratio) / (len(mbox_source_layers) - 2)))
#經過以下運算得到min_sizes和max_sizes。
min_sizes = []
max_sizes = []
#從min_ratio至max_ratio+1每隔step=17取一個值指派給ratio。注意xrange函數的作用。  
for ratio in xrange(min_ratio, max_ratio + 1, step):
#min_sizes.append()函數即把括号内部每次得到的值依次給了min_sizes。
  min_sizes.append(min_dim * ratio / 100.)
  max_sizes.append(min_dim * (ratio + step) / 100.)
min_sizes = [min_dim * 10 / 100.] + min_sizes
max_sizes = [[]] + max_sizes
#這裡指的是橫縱比,六種尺度對應六個産生prior_box的卷積層。
#具體可檢視生成的train.prototxt檔案一一對應每層的aspect_ratio參數,
#此參數在caffe.proto中有定義,關于aspect_ratios如何把其内容傳遞
#給了aspect_ratio,在model_libs.py檔案中有詳細定義。  
aspect_ratios = [[2], [2, 3], [2, 3], [2, 3], [2, 3], [2, 3]]
#對卷積層conv4_3做歸一化。model_libs.py裡産生了normallize層,具體的層定義,
#參看底層代碼~/caffe/src/layers/Normalize_layer.cpp,
#為什麼這裡設定conv4_3為20我也沒看懂,原諒C++太渣,這裡每個數對應每個先驗層,
#隻要哪個層對應的數不為-1則産生normal。  
normalizations = [20, -1, -1, -1, -1, -1]
#兩種選擇,根據參數code_type的選擇決定,由于上面已經将code_type標明。有人了解
#為變量variance用來對bbox的回歸目标進行放大,進而加快對應濾波器參數的收斂。
#除以variance是對預測box和真實box的誤差進行放大,進而增加loss,增大梯度,加快收斂。
#另外,top_data += top[0]->offset(0, 1);已經使指針指向新的位址,是以variance不會覆
#蓋前面的結果。prior_variance在model_libs.py中傳遞給了variance變量,
#然後利用prior_box_layer.cpp将其運算定義至priorbox_layer層中,
#具體可檢視train.prototxt中的每一個先驗卷積層層中産生先驗框的層中,即**_mbox_priorbox。  
  prior_variance = [0.1, 0.1, 0.2, 0.2]
else:
  prior_variance = [0.1]
#如果為true,則會翻轉每個寬高比。例如,
#如果有縱橫比“r”,我們也會産生縱橫比“1.0 / r”。故産生{1,2,3,1/2,1/3}。
flip = True
#做clip操作是為了讓prior的候選坐标位置保持在[0,1]範圍内。
#在caffe.proto檔案中有關于參數clip的解釋,為”如果為true,則将先驗框裁剪為[0,1]
clip = True

# 求解參數。  
# 定義要使用的GPU。
gpus = "0" #"0,1,2,3"  #多塊GPU的編号,如果隻有一塊,這裡隻需保留0,否則會出錯。
gpulist = gpus.split(",")#擷取GPU的清單。
num_gpus = len(gpulist)	#擷取GPU編号。

#使用CPU進行訓練
use_cpu = False

# 将小批次劃分為不同的GPU
if use_cpu:
  num_gpus = 0

#設定訓練樣本輸入的數量,不要超出記憶體就好。
batch_size = 2 # 32
#這裡與batch_size相搭配産生下面的iter_size。在看了下一行你就知道它的作用了。  
accum_batch_size = 32
#如果iter_size=1,則前向傳播一次後進行一次反向傳遞,如果=2,
#則兩次前傳後進行一次反傳,這樣做是減少每次傳播所占用的記憶體空間,
#有的硬體不行的話就無法訓練,但是增加iter會使訓練時間增加,但是總的疊代次數不變。
iter_size = accum_batch_size / batch_size
solver_mode = P.Solver.CPU
device_id = 0
#批次傳遞
batch_size_per_device = batch_size
if num_gpus > 0:
	#這裡指如果你有多塊GPU則可以将這些訓練任務均分給多塊GPU訓練,進而加快訓練速度。
  batch_size_per_device = int(math.ceil(float(batch_size) / num_gpus))
  #多塊GPU的iter_size大小計算,上面的是一塊的時候。
  iter_size = int(math.ceil(float(accum_batch_size) / (batch_size_per_device * num_gpus)))
  solver_mode = P.Solver.GPU
  device_id = int(gpulist[0])

#如果損失層的參數NormalizationMode選擇NONE,即沒有歸一化模式,則基礎學習率為本檔案之
#上的base_lr=0.0004除以batch_size_per_device=32得到新的base_lr=1.25*10^(-5)。  
if normalization_mode == P.Loss.NONE:
  base_lr /= batch_size_per_device
#同理,根據不同的歸一化模式選擇不同的base_lr。在本檔案上面我們看到
#normalization_mode = P.Loss.VALID,而loc_weight = (neg_pos_ratio + 1.) / 4==1,
#是以新的base_lr=25*0.0004=0.001,這就是為什麼我們最後生成的solver.prototxt檔案
#中的base_lr=0.001的原因,是以如果訓練發散想通過減小base_lr來實驗,
#則要更改最上面的base_lr=0.0004才可以。  
elif normalization_mode == P.Loss.VALID:
  base_lr *= 25. / loc_weight
elif normalization_mode == P.Loss.FULL:
  # 每幅圖像大概有2000個先驗bbox。  
  # TODO(weiliu89): 估計确切的先驗數量。
  base_lr *= 2000.  #base_lr=2000*0.0004=0.8。

# Which layers to freeze (no backward) during training.
freeze_layers = ['conv1_1', 'conv1_2', 'conv2_1', 'conv2_2']

# 評估整個測試集。
num_test_image = 4952#整個測試集圖像的數量。		
test_batch_size = 1	#測試時的batch_size。理想情況下,test_batch_size應該被num_test_image整除,否則mAP會略微偏離真實值。這裡計算每測試疊代多少次可以覆寫整個測試集,和分類網絡中的是一緻的。這裡4952/8=619,如果你的測試圖檔除以你的test_batch_size不等于整數,那麼這裡會取一個近似整數。  
test_iter = num_test_image / test_batch_size

#solver.prototxt檔案中的各參數的取值,這裡相信做過caffe訓練的人應該大緻有了解。
solver_param = {  
     # 訓練參數
    'base_lr': base_lr,#網絡的基礎學習速率,一般設一個很小的值,然後根據疊代到不同次數,對學習速率做相應的變化.lr過大不會收斂,過小收斂過慢
    'weight_decay': 0.0005,#權衰量,用于防止過拟合
    'lr_policy': "step",	#學習速率的衰減政策,詳細見後面
    'stepsize': 40000,#每40000次疊代減少學習率(這一項和lr_policy有關)
    'gamma': 0.1,#學習率變化的比率(這一項和lr_policy有關)
    'momentum': 0.9,	#網絡的沖量;學習的參數,不用變;上一次梯度更新的權重
    'iter_size': iter_size,	#實際使用的batch size。 相當于讀取batchsize*itersize個圖像才做一下gradient decent。這個參數可以規避由于gpu不足而導緻的batchsize的限制 因為你可以用多個iteration做到很大的batch 即使單次batch有限
    'max_iter': 60000,#最大疊代次數,告訴網絡何時停止訓練.太小達不到收斂,太大會導緻震蕩
    'snapshot': 40000,#每40000次疊代列印一次快照(就是把目前資料儲存下來,友善下次重用,如果電源不穩定容易意外關機建
    'display': 10,#每經過10次疊代,在螢幕上列印一次運作log(告訴你目前的loss之類的...)
    'average_loss': 10,#取多次foward的loss作平均,進行顯示輸出
    'type': "SGD",#選擇一種優化算法
    'solver_mode': solver_mode,#選擇CPU or GPU
    'device_id': device_id#選擇幾塊GPU
    'debug_info': False,
    'snapshot_after_train': True,#表示在訓練完後把最後一次的訓練結果儲存下來
    # 測試參數 
    'test_iter': [test_iter],
    'test_interval': 10000,#測試10000次輸出一次測試結果 
    'eval_type': "detection",
    'ap_version': "11point",
    'test_initialization': False,#表示可以用上次儲存的snapshot來繼續訓練
    }

# 生成檢測輸出的參數。
det_out_param = {
    'num_classes': num_classes,#類别數目
    'share_location': share_location,	#位置共享。
    'background_label_id': background_label_id,#背景類别編号,這裡為0。
    'nms_param': {'nms_threshold': 0.45, 'top_k': 400},#非最大抑制參數,閥值為0.45,top_k表示最大數量的結果要保留,文中介紹,非最大抑制的作用就是消除多餘的框,就是使評分低的框剔除。參數解釋在caffe.proto中有介紹。  
    #用于儲存檢測結果的參數,這一部分參數在caffe.proto中的SaveOutputParameter有定義。
    'save_output_param': {
    	#輸出目錄。 如果不是空的,我們将儲存結果。前面我們有定義結果儲存的路徑.
        'output_directory': output_result_dir,	
        #輸出名稱字首。	
        'output_name_prefix': "comp4_det_test_",
        #輸出格式。VOC  -  PASCAL VOC輸出格式。COCO  -  MS COCO輸出格式.
        'output_format': "VOC",
        #如果要輸出結果,還必須提供以下兩個檔案。否則,我們将忽略儲存結果。
        #标簽映射檔案。這在前面中有給label_map_file附檔案,也就是我們在訓練
        #的時候所做的labelmap.prototxt檔案的位置
        'label_map_file': label_map_file,
        #即我們在訓練時定義的test_name_size.txt檔案的路徑。該檔案表示測試圖檔的大小。  
        'name_size_file': name_size_file,
        #測試圖檔的數量。
        'num_test_image': num_test_image,
        },
    #nms步之後每個圖像要保留的bbox總數。-1表示在nms步之後保留所有的bbox.
    'keep_top_k': 200,
    #隻考慮可信度大于門檻值的檢測。 如果沒有提供,請考慮所有的框。
    'confidence_threshold': 0.01,
    #bbox的編碼方式。
    'code_type': code_type,
    }

# parameters for evaluating detection results.
det_eval_param = {
    'num_classes': num_classes,
    'background_label_id': background_label_id,
    'overlap_threshold': 0.5,
    'evaluate_difficult_gt': False,
    'name_size_file': name_size_file,
    }

###不需要改變以下參數 ###
#檢查檔案。這一部分是檢查你的所有訓練驗證過程必須有的檔案與資料提供。
check_if_exist(train_data)
check_if_exist(test_data)
check_if_exist(label_map_file)
check_if_exist(pretrain_model)
make_if_not_exist(save_dir)
make_if_not_exist(job_dir)
make_if_not_exist(snapshot_dir)

#建立訓練網絡。這一部分主要是在model_libs.py中完成的。
net = caffe.NetSpec()
#調用model_libs.py中的CreateAnnotatedDataLayer()函數,建立标注資料傳遞層,将括号中的參數傳遞進去。
#model_libs.py檔案中提供了四種基礎網絡,即VGG、ZF、ResNet101和ResNet152。 
net.data, net.label = CreateAnnotatedDataLayer(train_data, batch_size=batch_size_per_device,
        train=True, output_label=True, label_map_file=label_map_file,
        transform_param=train_transform_param, batch_sampler=batch_sampler)
#調用model_libs.py中的VGGNetBody()函數建立截斷的VGG基礎網絡。參數傳遞進去。model_libs.py檔案中提供了四種基礎網絡,
#即VGG、ZF、ResNet101和ResNet152。可以分别檢視不同基礎網絡的調用方式。 
VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
   #這些參數分别表示:from_layer表示本基礎網絡的資料源來自data層的輸出,fully_conv=Ture表示使用全卷積,
   #reduced=Ture在該檔案中可以發現是負責選用全卷積層的某幾個參數的取值和最後選擇不同參數的全連結層,
   #dilated=True表示是否需要fc6和fc7間的pool5層以及選擇其參數還有配合reduced共同選擇全卷積層的參數選擇,
   #dropout表示是否需要dropout層flase表示不需要。  
   dropout=False, freeze_layers=freeze_layers)

#以下為添加特征提取的層,即調用我們本檔案最上面定義的需要額外添加的幾個層,即conv6_1,conv6_2等等。  
AddExtraLayers(net, use_batchnorm)

#調用CreateMultiBoxHead()函數建立先驗框的提取及比對等層數,下面這些參數其實我們在上面全部都有解釋,
#具體仍然可以參照caffe.proto和model_libs.py以及該層對應的cpp實作檔案去閱讀了解。
mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1)

#建立MultiBoxLossLayer。即建立損失層。這裡包括置信損失和位置損失的疊加。
#具體計算的實作在multibox_loss_layer.cpp中實作,其中的哥哥參數想multi_loss_param
#和loss_param等參數在前面均有定義。  
name = "mbox_loss"
mbox_layers.append(net.label)
#這裡重點講一下參數propagate_down,指定是否反向傳播到每個底部。如果未指定,
#Caffe會自動推斷每個輸入是否需要反向傳播來計算參數梯度。如果對某些輸入設定為true,
#則強制向這些輸入反向傳播; 如果對某些輸入設定為false,則會跳過對這些輸入的反向傳播。
#大小必須是0或等于底部的數量。具體解讀cpp檔案中的參數propagate_down[0]~[3]
net[name] = L.MultiBoxLoss(*mbox_layers, multibox_loss_param=multibox_loss_param,
        loss_param=loss_param, include=dict(phase=caffe_pb2.Phase.Value('TRAIN')),
        propagate_down=[True, True, False, False])

#打開檔案将上面編輯的這些層寫入到prototxt檔案中。
with open(train_net_file, 'w') as f:
    print('name: "{}_train"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
#将寫入的訓練檔案train.prototxt複制一份給目錄job_dir。
shutil.copy(train_net_file, job_dir)

#建立測試網絡。前一部分基本上與訓練網絡一緻。
net = caffe.NetSpec()
net.data, net.label = CreateAnnotatedDataLayer(test_data, batch_size=test_batch_size,
        train=False, output_label=True, label_map_file=label_map_file,
        transform_param=test_transform_param)

VGGNetBody(net, from_layer='data', fully_conv=True, reduced=True, dilated=True,
    dropout=False, freeze_layers=freeze_layers)

AddExtraLayers(net, use_batchnorm)

mbox_layers = CreateMultiBoxHead(net, data_layer='data', from_layers=mbox_source_layers,
        use_batchnorm=use_batchnorm, min_sizes=min_sizes, max_sizes=max_sizes,
        aspect_ratios=aspect_ratios, normalizations=normalizations,
        num_classes=num_classes, share_location=share_location, flip=flip, clip=clip,
        prior_variance=prior_variance, kernel_size=3, pad=1)

#置信的交叉驗證
conf_name = "mbox_conf"
if multibox_loss_param["conf_loss_type"] == P.MultiBoxLoss.SOFTMAX:
  reshape_name = "{}_reshape".format(conf_name)
  net[reshape_name] = L.Reshape(net[conf_name], shape=dict(dim=[0, -1, num_classes]))
  softmax_name = "{}_softmax".format(conf_name)
  net[softmax_name] = L.Softmax(net[reshape_name], axis=2)
  flatten_name = "{}_flatten".format(conf_name)
  net[flatten_name] = L.Flatten(net[softmax_name], axis=1)
  mbox_layers[1] = net[flatten_name]
elif multibox_loss_param["conf_loss_type"] == P.MultiBoxLoss.LOGISTIC:
  sigmoid_name = "{}_sigmoid".format(conf_name)
  net[sigmoid_name] = L.Sigmoid(net[conf_name])
  mbox_layers[1] = net[sigmoid_name]

#下面這一部分是test網絡獨有的,為檢測輸出和評估網絡。
net.detection_out = L.DetectionOutput(*mbox_layers,
    detection_output_param=det_out_param,
    include=dict(phase=caffe_pb2.Phase.Value('TEST')))
net.detection_eval = L.DetectionEvaluate(net.detection_out, net.label,
    detection_evaluate_param=det_eval_param,
    include=dict(phase=caffe_pb2.Phase.Value('TEST')))

with open(test_net_file, 'w') as f:
    print('name: "{}_test"'.format(model_name), file=f)
    print(net.to_proto(), file=f)
shutil.copy(test_net_file, job_dir)

# 建立deploy網絡。  
# 從測試網中删除第一層和最後一層。
deploy_net = net
with open(deploy_net_file, 'w') as f:
    net_param = deploy_net.to_proto()
    # 從測試網中删除第一個(AnnotatedData)和最後一個(DetectionEvaluate)層。 
    del net_param.layer[0]		#删除首層
    del net_param.layer[-1]		#删除尾層。
    net_param.name = '{}_deploy'.format(model_name)		#建立網絡名
    net_param.input.extend(['data'])									#輸入擴充為data。
    #deploy.prototxt檔案中特有的輸入資料次元資訊,這裡應該為[1,3,300,300]。 
    net_param.input_shape.extend([
        caffe_pb2.BlobShape(dim=[1, 3, resize_height, resize_width])])
    print(net_param, file=f)		#輸出到檔案 
shutil.copy(deploy_net_file, job_dir)	 #複制一份到job_dir中。

# 建立Slover.prototxt。
solver = caffe_pb2.SolverParameter(		#将上面定義的solver參數統統拿下來。 
        train_net=train_net_file,
        test_net=[test_net_file],
        snapshot_prefix=snapshot_prefix,
        **solver_param)

#将拿下來的參數統統寫入solver.prototxt中。
with open(solver_file, 'w') as f:
    print(solver, file=f)
#複制一份到job_dir中。
shutil.copy(solver_file, job_dir)		

#最大疊代次數首先初始化為0。
max_iter = 0
#找到最近的快照。即如果中途中斷訓練,再次訓練首先尋找上次中斷時儲存的模型繼續訓練。
for file in os.listdir(snapshot_dir):	 #依次在快照模型所儲存的檔案中查找相對應的模型。
  if file.endswith(".solverstate"):		#如果存在此模型,則繼續往下訓練。
    basename = os.path.splitext(file)[0]
    iter = int(basename.split("{}_iter_".format(model_name))[1])
    if iter > max_iter:			#如果已疊代的次數大于max_iter,則指派給max_iter。
      max_iter = iter

#以下部分為訓練指令。
train_src_param = ''	  
if os.path.isfile(pretrain_model): 
	#權重的初始參數即從我們定義的imagenet訓練VGG16模型中擷取。  
  train_src_param = '\t--weights={} ^\n'.format(os.path.normpath(pretrain_model))
if resume_training:
  if max_iter > 0:
    train_src_param = '\t--snapshot={}_iter_{}.solverstate ^\n'.format(os.path.normpath(snapshot_prefix), max_iter)

#删除任何小于max_iter的快照。上一段和本段程式主要的目的是随着訓練的推進,
##max_iter随之逐漸增大,知道訓練至120000次後把前面生成的快照模型都删除了,
就#是儲存下一次的模型後删除上一次的模型。
if remove_old_models:
  for file in os.listdir(snapshot_dir):		#周遊查找模型檔案。
    if file.endswith(".solverstate"):			#找到字尾為solverstate的模型檔案。
      basename = os.path.splitext(file)[0]
      iter = int(basename.split("{}_iter_".format(model_name))[1])	#擷取已疊代的次數。
      if max_iter > iter:				#如果疊代滿足條件,則下一條語句去删除。
        os.remove("{}/{}".format(snapshot_dir, file))
    if file.endswith(".caffemodel"):	#找到字尾為caffemodel的模型檔案。
      basename = os.path.splitext(file)[0]
      iter = int(basename.split("{}_iter_".format(model_name))[1])	#擷取疊代次數iter。
      if max_iter > iter:		#判斷如果滿足條件則删除已存在的模型。
        os.remove("{}/{}".format(snapshot_dir, file))

# 建立工作檔案。
with open(job_file, 'w') as f:	#将訓練檔案寫入執行檔案中生成.sh可執行檔案後執行指令訓練。
  f.write('SET GLOG_logtostderr=1\n')
  f.write('set Datum=%DATE:~6,4%_%DATE:~3,2%_%DATE:~0,2%\n')
  f.write('set Uhrzeit=%TIME:~0,2%_%TIME:~3,2%_%TIME:~6,2%\n')
  f.write('set TIMESTAMP=%Datum%_%Uhrzeit%\n')
  f.write('\n'.format(caffe_root))
  f.write('cd {}\n'.format(caffe_root))
  f.write('"Build\{}\Release\caffe" train ^\n'.format('x64'))
  f.write('\t--solver={} ^\n'.format(os.path.normpath(solver_file)))
  f.write(train_src_param) 
  if solver_param['solver_mode'] == P.Solver.GPU:
	f.write('\t--gpu {} 2>&1 | "tools\mtee" "{}\{}-train-%TIMESTAMP%.log"\n'.format(gpus, os.path.normpath(job_dir), model_name))   
  else:
    f.write('\t2>&1 | "tools\mtee" "{}\{}-train-%TIMESTAMP%.log"\n'.format(os.path.normpath(job_dir), model_name))
   
#複制本腳本隻job_dir中。
py_file = os.path.abspath(__file__)
shutil.copy(py_file, job_dir)

# 運作。
os.chmod(job_file, stat.S_IRWXU)
if run_soon:
  subprocess.call(os.path.normpath(job_file), shell=True)
 
           

結語

1.以上是關于ssd_pascal.py源碼的注示。

2.關于ssd_pascal.py源碼了解,都可以加這個群(487350510)互相讨論學。

繼續閱讀