1. 程式人生 > >SSD演算法理解(2)

SSD演算法理解(2)

作者:葉 虎

編輯:祝鑫泉

前言

目標檢測近年來已經取得了很重要的進展,主流的演算法主要分為兩個型別:(1)two-stage方法,如R-CNN系演算法,其主要思路是先通過啟發式方法(selective search)或者CNN網路(RPN)產生一系列稀疏的候選框,然後對這些候選框進行分類與迴歸,two-stage方法的優勢是準確度高;(2)one-stage方法,如Yolo和SSD,其主要思路是均勻地在圖片的不同位置進行密集抽樣,抽樣時可以採用不同尺度和長寬比,然後利用CNN提取特徵後直接進行分類與迴歸,整個過程只需要一步,所以其優勢是速度快,但是均勻的密集取樣的一個重要缺點是訓練比較困難,這主要是因為正樣本與負樣本(背景)極其不均衡(參見Focal Loss,https://arxiv.org/abs/1708.02002),導致模型準確度稍低。不同演算法的效能如圖1所示,可以看到兩類方法在準確度和速度上的差異。

圖1 不同檢測演算法的效能對比

本文講解的是SSD演算法,其英文全名是Single Shot MultiBox Detector,名字取得不錯,Single shot指明瞭SSD演算法屬於one-stage方法,MultiBox指明瞭SSD是多框預測。在上一篇文章中我們已經講了Yolo演算法,從圖1也可以看到,SSD演算法在準確度和速度(除了SSD512)上都比Yolo要好很多。圖2給出了不同演算法的基本框架圖,對於Faster R-CNN,其先通過CNN得到候選框,然後再進行分類與迴歸,而Yolo與SSD可以一步到位完成檢測。相比Yolo,SSD採用CNN來直接進行檢測,而不是

像Yolo那樣在全連線層之後做檢測。其實採用卷積直接做檢測只是SSD相比Yolo的其中一個不同點,另外還有兩個重要的改變,一是SSD提取了不同尺度的特徵圖來做檢測,大尺度特徵圖(較靠前的特徵圖)可以用來檢測小物體,而小尺度特徵圖(較靠後的特徵圖)用來檢測大物體;二是SSD採用了不同尺度和長寬比的先驗框(Prior boxes, Default boxes,在Faster R-CNN中叫做錨,Anchors)。Yolo演算法缺點是難以檢測小目標,而且定位不準,但是這幾點重要改進使得SSD在一定程度上克服這些缺點。下面我們詳細講解SDD演算法的原理,並最後給出如何用TensorFlow實現SSD演算法。

圖2 不同演算法的基本框架圖

1

設計理念

SSD和Yolo一樣都是採用一個CNN網路來進行檢測,但是卻採用了多尺度的特徵圖,其基本架構如圖3所示。下面將SSD核心設計理念總結為以下三點:

圖3 SSD基本框架

(1)採用多尺度特徵圖用於檢測

所謂多尺度採用大小不同的特徵圖,CNN網路一般前面的特徵圖比較大,後面會逐漸採用stride=2的卷積或者pool來降低特徵圖大小,這正如圖3所示,一個比較大的特徵圖和一個比較小的特徵圖,它們都用來做檢測。這樣做的好處是比較大的特徵圖來用來檢測相對較小的目標,而小的特徵圖負責檢測大目標,如圖4所示,8x8的特徵圖可以劃分更多的單元,但是其每個單元的先驗框尺度比較小。

圖4 不同尺度的特徵圖

(2)採用卷積進行檢測

與Yolo最後採用全連線層不同,SSD直接採用卷積對不同的特徵圖來進行提取檢測結果。對於形狀為m*n*p,的特徵圖,只需要採用3*3*3的特徵圖,只需要採用。

(3)設定先驗框

在Yolo中,每個單元預測多個邊界框,但是其都是相對這個單元本身(正方塊),但是真實目標的形狀是多變的,Yolo需要在訓練過程中自適應目標的形狀。而SSD借鑑了Faster R-CNN中anchor的理念,每個單元設定尺度或者長寬比不同的先驗框,預測的邊界框(bounding boxes)是以這些先驗框為基準的,在一定程度上減少訓練難度。一般情況下,每個單元會設定多個先驗框,其尺度和長寬比存在差異,如圖5所示,可以看到每個單元使用了4個不同的先驗框,圖片中貓和狗分別採用最適合它們形狀的先驗框來進行訓練,後面會詳細講解訓練過程中的先驗框匹配原則。

圖5 SSD的先驗框

SSD的檢測值也與Yolo不太一樣。對於每個單元的每個先驗框,其都輸出一套獨立的檢測值,對應一個邊界框,主要分為兩個部分。第一部分是各個類別的置信度或者評分,值得注意的是SSD將背景也當做了一個特殊的類別,如果檢測目標共有C個類別,SSD其實需要預測C+1個置信度值,其中第一個置信度指的是不含目標或者屬於背景的評分。後面當我們說C個個類別置信度時,請記住裡面包含背景那個特殊的類別,即真實的檢測類別只有C-1個。在預測過程中,置信度最高的那個類別就是邊界框所屬的類別,特別地,當第一個置信度值最高時,表示邊界框中並不包含目標。第二部分就是邊界框的location,包含4個值(cx,cy,w,h),分別表示邊界框的中心座標以及寬高。但是真實預測值其實只是邊界框相對於先驗框的轉換值(paper裡面說是offset,但是覺得transformation更合適,參見R-CNN,https://arxiv.org/abs/1311.2524)。先驗框位置用

,表示,其對應邊界框用

表示,那麼邊界框的預測值l,其實是b相對於d的轉換值:

習慣上,我們稱上面這個過程為邊界框的編碼(encode),預測時,你需要反向這個過程,即進行解碼(decode),從預測值l中得到邊界框的真實位置b:

然而,在SSD的Caffe原始碼(https://github.com/weiliu89/caffe/tree/ssd)實現中還有trick,那就是設定variance超引數來調整檢測值,通過bool引數variance_encoded_in_target來控制兩種模式,當其為True時,表示variance被包含在預測值中,就是上面那種情況。但是如果是Fasle(大部分採用這種方式,訓練更容易?),就需要手動設定超引數variance,用來對l的4個值進行放縮,此時邊界框需要這樣解碼:

綜上所述,對於一個大小m*n的特徵圖,共有mn個單元,每個單元設定的先驗框數目記為k,那麼每個單元共需要(c+4)k個預測值,所有的單元共需要(c+4)kmn個預測值,由於SSD採用卷積做檢測,所以就需要(c+4)k個卷積核完成這個特徵圖的檢測過程。

2

網路結構

SSD採用VGG16作為基礎模型,然後在VGG16的基礎上新增了卷積層來獲得更多的特徵圖以用於檢測。SSD的網路結構如圖6所示。上面是SSD模型,下面是Yolo模型,可以明顯看到SSD利用了多尺度的特徵圖做檢測。模型的輸入圖片大小是300*300(還可以是512*512,其與前者網路結構沒有差別,只是最後新增一個卷積層,本文不再討論)。

圖5 SSD網路結構

採用VGG16做基礎模型,首先VGG16是在ILSVRC CLS-LOC資料集預訓練。然後借鑑了DeepLab-LargeFOV,連結https://export.arxiv.org/pdf/1606.00915,分別將VGG16的全連線層fc6和fc7轉換成3*3卷積層conv6和1*1卷積層conv7,同時將池化層pool5由原來的2*2-s2變成3*3-s1(猜想是不想reduce特徵圖大小),為了配合這種變化,採用了一種Atrous Algorithm,其實就是conv6採用擴展卷積或帶孔卷積(Dilation Conv,https://arxiv.org/abs/1511.07122),其在不增加引數與模型複雜度的條件下指數級擴大卷積的視野,其使用擴張率(dilation rate)引數,來表示擴張的大小,如下圖6所示,(a)是普通的3*3卷積,其視野就是3*3,,(b)是擴張率為2,此時視野變成7*7,(c)擴張率為4時,視野擴大為15*15,但是視野的特徵更稀疏了。Conv6採用3*3大小但dilation rate=6的擴展卷積。

圖6 擴展卷積

然後移除dropout層和fc8層,並新增一系列卷積層,在檢測資料集上做finetuing。

其中VGG16中的Conv4_3層將作為用於檢測的第一個特徵圖。conv4_3層特徵圖大小是38*38,但是該層比較靠前,其norm較大,所以在其後面增加了一個L2 Normalization層(參見ParseNet,https://arxiv.org/abs/1506.04579),以保證和後面的檢測層差異不是很大,這個和Batch Normalization層不太一樣,其僅僅是對每個畫素點在channle維度做歸一化,而Batch Normalization層是在[batch_size, width, height]三個維度上做歸一化。歸一化後一般設定一個可訓練的放縮變數gamma,使用TF可以這樣簡單實現:

# l2norm (not bacth norm, spatial normalization)
def l2norm(x, scale, trainable=True, scope="L2Normalization"):
    n_channels = x.get_shape().as_list()[-1]
    l2_norm = tf.nn.l2_normalize(x, [3], epsilon=1e-12)
    with tf.variable_scope(scope):
        gamma = tf.get_variable("gamma", shape=[n_channels, ], dtype=tf.float32,
                                initializer=tf.constant_initializer(scale),
                                trainable=trainable)
        return l2_norm * gamma
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

從後面新增的卷積層中提取Conv7,Conv8_2,Conv9_2,Conv10_2,Conv11_2作為檢測所用的特徵圖,加上Conv4_3層,共提取了6個特徵圖,其大小分別是(38,38),(19,19),(10,10),(5,5),(3,3,),(1,1),,但是不同特徵圖設定的先驗框數目不同(同一個特徵圖上每個單元設定的先驗框是相同的,這裡的數目指的是一個單元的先驗框數目)。先驗框的設定,包括尺度(或者說大小)和長寬比兩個方面。對於先驗框的尺度,其遵守一個線性遞增規則:隨著特徵圖大小降低,先驗框尺度線性增加:

其中m指的特徵圖個數,但卻是5,因為第一層(Conv4_3層)是單獨設定的,Sk示先驗框大小相對於圖片的比例,而Smin和Smax表示比例的最小值與最大值,paper裡面取0.2和0.9。對於第一個特徵圖,其先驗框的尺度比例一般設定為Smin/2=0.1,那麼尺度為300*0.1=30。對於後面的特徵圖,先驗框尺度按照上面公式線性增加,但是先將尺度比例先擴大100倍,此時增長步長為

,這樣各個特徵圖的Sk為20,37,54,71,88,將這些比例除以100,然後再乘以圖片大小,可以得到各個特徵圖的尺度為60,111,162,213,264,這種計算方式是參考SSD的Caffe原始碼。綜上,可以得到各個特徵圖的先驗框尺度30,60,111,162,213,264。對於長寬比,一般選取

,對於特定的長寬比,按如下公式計算先驗框的寬度與高度(後面的Sk均指的是先驗框實際尺度,而不是尺度比例):

預設情況下,每個特徵圖會有一個

且尺度為Sk的先驗框,除此之外,還會設定一個尺度為

的先驗框,這樣每個特徵圖都設定了兩個長寬比為1但大小不同的正方形先驗框。注意最後一個特徵圖需要參考一個虛擬

來計算

。因此,每個特徵圖一共有6個先驗框

,但是在實現時,Conv4_3,Conv10_2和Conv11_2層僅使用4個先驗框,它們不使用長寬比為3,1/3的先驗框。每個單元的先驗框的中心點分佈在各個單元的中心,即

,其中

為特徵圖的大小。

得到了特徵圖之後,需要對特徵圖進行卷積得到檢測結果,圖7給出了一個5*5大小的特徵圖的檢測過程。其中Priorbox是得到先驗框,前面已經介紹了生成規則。檢測值包含兩個部分:類別置信度和邊界框位置,各採用一次3*3卷積來進行完成。令

為該特徵圖所採用的先驗框數目,那麼類別置信度需要的卷積核數量為

,而邊界框位置需要的卷積核數量為

。由於每個先驗框都會預測一個邊界框,所以SSD300一共可以預測

個邊界框,這是一個相當龐大的數字,所以說SSD本質上是密集取樣。

圖7 基於卷積得到檢測結果

3

訓練過程

(1)先驗框匹配

在訓練過程中,首先要確定訓練圖片中的ground truth(真實目標)與哪個先驗框來進行匹配,與之匹配的先驗框所對應的邊界框將負責預測它。在Yolo中,ground truth的中心落在哪個單元格,該單元格中與其IOU最大的邊界框負責預測它。但是在SSD中卻完全不一樣,SSD的先驗框與ground truth的匹配原則主要有兩點。首先,對於圖片中每個ground truth,找到與其IOU最大的先驗框,該先驗框與其匹配,這樣,可以保證每個ground truth一定與某個先驗框匹配。通常稱與ground truth匹配的先驗框為正樣本,反之,若一個先驗框沒有與任何ground truth進行匹配,那麼該先驗框只能與背景匹配,就是負樣本。一個圖片中ground truth是非常少的, 而先驗框卻很多,如果僅按第一個原則匹配,很多先驗框會是負樣本,正負樣本極其不平衡,所以需要第二個原則。第二個原則是:對於剩餘的未匹配先驗框,若某個ground truth的IOU大於某個閾值(一般是0.5),那麼該先驗框也與這個ground truth進行匹配。這意味著某個ground truth可能與多個先驗框匹配,這是可以的。但是反過來卻不可以,因為一個先驗框只能匹配一個ground truth,如果多個ground truth與某個先驗框IOU大於閾值,那麼先驗框只與IOU最大的那個先驗框進行匹配。第二個原則一定在第一個原則之後進行,仔細考慮一下這種情況,如果某個ground truth所對應最大IOU小於閾值,並且所匹配的先驗框卻與另外一個ground truth的IOU大於閾值,那麼該先驗框應該匹配誰,答案應該是前者,首先要確保某個ground truth一定有一個先驗框與之匹配。但是,這種情況我覺得基本上是不存在的。由於先驗框很多,某個ground truth的最大IOU肯定大於閾值,所以可能只實施第二個原則既可以了,這裡的TensorFlow(https://github.com/xiaohu2015/SSD-Tensorflow/blob/master/nets/ssd_common.py)版本就是隻實施了第二個原則,但是這裡的Pytorch(https://github.com/amdegroot/ssd.pytorch/blob/master/layers/box_utils.py)兩個原則都實施了。圖8為一個匹配示意圖,其中綠色的GT是ground truth,紅色為先驗框,FP表示負樣本,TP表示正樣本。

圖8 先驗框匹配示意圖

儘管一個ground truth可以與多個先驗框匹配,但是ground truth相對先驗框還是太少了,所以負樣本相對正樣本會很多。為了保證正負樣本儘量平衡,SSD採用了hard negative mining,就是對負樣本進行抽樣,抽樣時按照置信度誤差(預測背景的置信度越小,誤差越大)進行降序排列,選取誤差的較大的top-k作為訓練的負樣本,以保證正負樣本比例接近1:3。

(2)損失函式

訓練樣本確定了,然後就是損失函數了。損失函式定義為位置誤差(locatization loss, loc)與置信度誤差(confidence loss, conf)的加權和:

其中N是先驗框的正樣本數量。這裡

為一個指示引數,當

時表示第i個先驗框與第j個ground truth匹配,並且ground truth的類別為p。c為類別置信度預測值。l為先驗框的所對應邊界框的位置預測值,而g

是ground truth的位置引數。對於位置誤差,其採用Smooth L1 loss,定義如下:

由於的

存在,所以位置誤差僅針對正樣本進行計算。值得注意的是,要先對ground truth的g進行編碼得到

,因為預測值l也是編碼值,若設定variance_encoded_in_target=True,編碼時要加上variance:

對於置信度誤差,其採用softmax loss:

權重係數a通過交叉驗證設定為1。

(3)資料擴增

採用資料擴增(Data Augmentation)可以提升SSD的效能,主要採用的技術有水平翻轉(horizontal flip),隨機裁剪加顏色扭曲(random crop & color distortion),隨機採集塊域(Randomly sample a patch)(獲取小目標訓練樣本),如下圖所示:

圖9 資料擴增方案

其它的訓練細節如學習速率的選擇詳見論文,這裡不再贅述。

4

預測過程

預測過程比較簡單,對於每個預測框,首先根據類別置信度確定其類別(置信度最大者)與置信度值,並過濾掉屬於背景的預測框。然後根據置信度閾值(如0.5)過濾掉閾值較低的預測框。對於留下的預測框進行解碼,根據先驗框得到其真實的位置引數(解碼後一般還需要做clip,防止預測框位置超出圖片)。解碼之後,一般需要根據置信度進行降序排列,然後僅保留top-k(如400)個預測框。最後就是進行NMS演算法,過濾掉那些重疊度較大的預測框。最後剩餘的預測框就是檢測結果了。

5

效能評估

首先整體看一下SSD在VOC2007,VOC2012及COCO資料集上的效能,如表1所示。相比之下,SSD512的效能會更好一些。加*的表示使用了image expansion data augmentation(通過zoom out來創造小的訓練樣本)技巧來提升SSD在小目標上的檢測效果,所以效能會有所提升。

表1 SSD在不同資料集上的效能

SSD與其它檢測演算法的對比結果(在VOC2007資料集)如表2所示,基本可以看到,SSD與Faster R-CNN有同樣的準確度,並且與Yolo具有同樣較快地檢測速度。

表2 SSD與其它檢測演算法的對比結果(在VOC2007資料集)

文章還對SSD的各個trick做了更為細緻的分析,表3為不同的trick組合對SSD的效能影響,從表中可以得出如下結論:

  • 資料擴增技術很重要,對於mAP的提升很大;
  • 使用不同長寬比的先驗框可以得到更好的結果;

表3 不同的trick組合對SSD的效能影響

同樣的,採用多尺度的特徵圖用於檢測也是至關重要的,這可以從表4中看出:

表4 多尺度特徵圖對SSD的影響

6

TensorFlow上的實現

SSD在很多框架上都有了開源的實現,這裡基於balancap的TensorFlow版本(https://github.com/balancap/SSD-Tensorflow)來實現SSD的Inference過程。這裡實現的是SSD300,與paper裡面不同的是,這裡採用

。首先定義SSD的引數:

self.ssd_params = SSDParams(img_shape=(300, 300),   # 輸入圖片大小
                                num_classes=21,     # 類別數+背景
                                no_annotation_label=21,
                                feat_layers=["block4", "block7", "block8", "block9", "block10", "block11"],   # 要進行檢測的特徵圖name
                                feat_shapes=[(38, 38), (19, 19), (10, 10), (5, 5), (3, 3), (1, 1)],  # 特徵圖大小
                                anchor_size_bounds=[0.15, 0.90],  # 特徵圖尺度範圍
                                anchor_sizes=[(21., 45.),
                                              (45., 99.),
                                              (99., 153.),
                                              (153., 207.),
                                              (207., 261.),
                                              (261., 315.)],  # 不同特徵圖的先驗框尺度(第一個值是s_k,第2個值是s_k+1)
                                anchor_ratios=[[2, .5],
                                               [2, .5, 3, 1. / 3],
                                               [2, .5, 3, 1. / 3],
                                               [2, .5, 3, 1. / 3],
                                               [2, .5],
                                               [2, .5]], # 特徵圖先驗框所採用的長寬比(每個特徵圖都有2個正方形先驗框)
                                anchor_steps=[8, 16, 32, 64, 100, 300],  # 特徵圖的單元大小
                                anchor_offset=0.5,                       # 偏移值,確定先驗框中心
                                normalizations=[20, -1, -1, -1, -1, -1],  # l2 norm
                                prior_scaling=[0.1, 0.1, 0.2, 0.2]       # variance
                                )
     
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

然後構建整個網路,注意對於stride=2的conv不要使用TF自帶的padding="same",而是手動pad,這是為了與Caffe一致:

def _built_net(self):
    """Construct the SSD net"""
    self.end_points = {}  # record the detection layers output
    self._images = tf.placeholder(tf.float32, shape=[None, self.ssd_params.img_shape[0],
                                                    self.ssd_params.img_shape[1], 3])
    with tf.variable_scope("ssd_300_vgg"):
        # original vgg layers
        # block 1
        net = conv2d(self._images, 64, 3, scope="conv1_1")
        net = conv2d(net, 64, 3, scope="conv1_2")
        self.end_points["block1"] = net
        net = max_pool2d(net, 2, scope="pool1")
        # block 2
        net = conv2d(net, 128, 3, scope="conv2_1")
        net = conv2d(net, 128, 3, scope="conv2_2")
        self.end_points["block2"] = net
        net = max_pool2d(net, 2, scope="pool2")
        # block 3
        net = conv2d(net, 256, 3, scope="conv3_1")
        net = conv2d(net, 256, 3, scope="conv3_2")
        net = conv2d(net, 256, 3, scope="conv3_3")
        self.end_points["block3"] = net
        net = max_pool2d(net, 2, scope="pool3")
        # block 4
        net = conv2d(net, 512, 3, scope="conv4_1")
        net = conv2d(net, 512, 3, scope="conv4_2")
        net = conv2d(net, 512, 3, scope="conv4_3")
        self.end_points["block4"] = net
        net = max_pool2d(net, 2, scope="pool4")
        # block 5
        net = conv2d(net, 512, 3, scope="conv5_1")
        net = conv2d(net, 512, 3, scope="conv5_2")
        net = conv2d(net, 512, 3, scope="conv5_3")
        self.end_points["block5"] = net
        print(net)
        net = max_pool2d(net, 3, stride=1, scope="pool5")
        print(net)
    # additional SSD layers
    # block <span class="token number">6</span><span class="token punctuation">:</span> use dilate conv
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">1024</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> dilation_rate<span class="token operator">=</span><span class="token number">6</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv6"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>end_points<span class="token punctuation">[</span><span class="token string">"block6"</span><span class="token punctuation">]</span> <span class="token operator">=</span> net
    #net <span class="token operator">=</span> <span class="token function">dropout</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> is_training<span class="token operator">=</span>self<span class="token punctuation">.</span>is_training<span class="token punctuation">)</span>
    # block <span class="token number">7</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">1024</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv7"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>end_points<span class="token punctuation">[</span><span class="token string">"block7"</span><span class="token punctuation">]</span> <span class="token operator">=</span> net
    # block <span class="token number">8</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">256</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv8_1x1"</span><span class="token punctuation">)</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span><span class="token function">pad2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">512</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> stride<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv8_3x3"</span><span class="token punctuation">,</span>
                 padding<span class="token operator">=</span><span class="token string">"valid"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>end_points<span class="token punctuation">[</span><span class="token string">"block8"</span><span class="token punctuation">]</span> <span class="token operator">=</span> net
    # block <span class="token number">9</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">128</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv9_1x1"</span><span class="token punctuation">)</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span><span class="token function">pad2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token number">256</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> stride<span class="token operator">=</span><span class="token number">2</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv9_3x3"</span><span class="token punctuation">,</span>
                 padding<span class="token operator">=</span><span class="token string">"valid"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>end_points<span class="token punctuation">[</span><span class="token string">"block9"</span><span class="token punctuation">]</span> <span class="token operator">=</span> net
    # block <span class="token number">10</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">128</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv10_1x1"</span><span class="token punctuation">)</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">256</span><span class="token punctuation">,</span> <span class="token number">3</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv10_3x3"</span><span class="token punctuation">,</span> padding<span class="token operator">=</span><span class="token string">"valid"</span><span class="token punctuation">)</span>
    self<span class="token punctuation">.</span>end_points<span class="token punctuation">[</span><span class="token string">"block10"</span><span class="token punctuation">]</span> <span class="token operator">=</span> net
    # block <span class="token number">11</span>
    net <span class="token operator">=</span> <span class="token function">conv2d</span><span class="token punctuation">(</span>net<span class="token punctuation">,</span> <span class="token number">128</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> scope<span class="token operator">=</span><span class="token string">"conv11_1x1"</span><span class="token punctuation">)</span>
    net <span class="token operator">=<