目標檢測系列——SSD(上)

一鬼贱、SSD原理

??本文記錄一階段目標檢測模型SSD的學習筆記及代碼復(fù)現(xiàn)過程移怯。首先個人感覺SSD和YOLO相比稍微簡單一些,理論比較直白吩愧,不過實現(xiàn)起來依然比較繁瑣。本文大體以李沐老師的Gluon教學視頻7-9為基礎(chǔ)增显,在Anchors sampling和Training Targets兩個點上加上了一些自己的挖掘雁佳。

??根據(jù)李沐老師的說法,SSD的算法可以看做Faster-RCNN的一個簡化同云,具體說來就是直接把RPN中那個負責Region Proposal的二分類Softmax擴展了一下糖权,區(qū)分出背景的同時將前景細分成物體的類別,即擴展成了n+1分類炸站,同時bbox regressor一步回歸到位星澳。這樣理論上就足夠得到box和類別信息了。不過為了提高準確性旱易,SSD在此基礎(chǔ)上又加上多尺度預(yù)測禁偎,具體來說就是將主干網(wǎng)絡(luò)輸出的特征通過downsampling Conv降低一半再預(yù)測cls和box,以此類推阀坏。

??看到這里如暖,一個必須要想清楚的問題是:尺寸較小的feature map負責提高大目標還是小目標的精確度?

??這個問題其實本質(zhì)是對anchor概念的理解忌堂。

1.1 Anchors

??關(guān)于Anchors的概念盒至,等到寫Faster-RCNN時再詳細記錄。這里先簡單理解士修,就是在特征圖的每個像素點上采樣一組預(yù)定義尺寸和形狀的先驗框枷遂,然后希望這些所有尺度的先驗框能夠覆蓋圖像中所有可能的boundingbox位置。

??當然棋嘲,anchors的巧妙之處遠不止這些酒唉。比如anchor的引入使我們得以避開了編碼boudingbox規(guī)則這一難題,見[1]沸移。

Fig. 1. Anchors的尺寸隨grid的尺寸成比例變化

??上圖回答了1.1的問題黔州。每一組anchors都是以feature map中的像素為中心定義的耍鬓,故anchors的尺寸和像素之間的距離——grid的尺寸成正比,即和feature map的尺寸成反比流妻。所以大的feature map負責檢測小物體牲蜀,小的feature map負責檢測大物體。下面的Anchors Sampling代碼實現(xiàn)部分也有類似的可視化結(jié)果绅这。

??這里有個小問題:上面說的小的feature map負責檢測大物體是因為anchor占feature的比例很大涣达,這個說法應(yīng)該是在我們默認深度神經(jīng)網(wǎng)絡(luò)feature map越小,receptive field越大這一前提下证薇?我們假設(shè)小的feature map的感受野已經(jīng)足夠覆蓋原圖大部分區(qū)域度苔?

二、各部件實現(xiàn):

??由于SSD原來相對簡單浑度,這一部分以理論和代碼塊實現(xiàn)相結(jié)合的形式展開記錄寇窑。基本的組件有如下幾塊:

  1. Anchors Sampling.
  2. Class Predictor.
  3. Bbox Predictor.
  4. Downsample Block.
  5. 整合得到完整的SSD模型.
  6. 數(shù)據(jù)集加載(有時間會記錄rec文件生成方法).
  7. Training Targets.
  8. 優(yōu)化:Focal Loss與Negative example mining.

2.1 Anchors Sampling

??原始圖像送入主干網(wǎng)絡(luò)之后箩张,得到一個feature map甩骏,如 1×32×20×20。現(xiàn)在我們需要在該feature的每一個像素點(x, y)上sample出多個預(yù)先定義的anchor。這一點和RPN是一致的。不過有個細節(jié)屋休,SSD的anchor不像Faster-RCNN那樣盯荤,指定了一組size和一組寬高比,最后的anchor數(shù)就是size的個數(shù)n乘以ratio的個數(shù)m;SSD簡化了下,將anchor總數(shù)改成 n+m-1

??規(guī)則見下圖:簡單的說就是寬高比不變時无午,只使用第一個ratio(通常為1),得到幾個不同大小的正方形祝谚;而size不變時指厌,只使用第一個size,得到幾個相同size不同寬高比的anchor踊跟,這兩種情況會有一個anchor取了兩次(第一個size和第一個ratio對應(yīng)anchor)踩验,去掉一個即可。

Fig. 2. SSD中Anchor的采樣規(guī)則和Faster-RCNN中稍有不同

??雖然我們可以像課程中那樣商玫,用contrib.ndarray中的MultiBoxPrior來sample anchors, 并且實際模型訓練中肯定也會使用這個函數(shù)以獲取最佳優(yōu)化效果和backward能力箕憾,但是在此之前有必要先用Python簡單實現(xiàn)下,以加深理解拳昌。簡單起見袭异,這里就直接用Numpy和list,不用NDArray了炬藤。因為NDArray對于list的支持不是很友好御铃。

??中間的探索過程比較長碴里,就不列了,剛興趣的同學可以參考此notebook上真。下面直接上代碼:

import numpy as np
import time
import matplotlib.pyplot as plt
from mxnet import nd, autograd
from mxnet.contrib.ndarray import MultiBoxPrior

def sample_anchor(area, ratio, center_coor, input_shape):
    """
    實現(xiàn):給定anchor的面積和寬高比咬腋,以及中心坐標,求出anchor的box
    area:anchor的絕對面積(size_ratio * size_input)
    ratio:單個寬高比
    center_coor:tuple: (x, y)
    input_shape: tuple: (h, w, c)
    輸出:單個長度為4的list
    """
    x, y = center_coor
    H, W = input_shape[:2]
    
    dh = np.sqrt(area/ratio)
    dw = dh * ratio
    # 返回對輸入feature尺寸歸一化之后的anchor坐標
    anchor = [(x-dw/2)/W, (y-dh/2)/H, (x+dw/2)/W, (y+dh/2)/H]  
    return anchor

def sample_one_pixel(sizes, ratios, center_coor, input_shape):
    """
    實現(xiàn):給定sizes和ratios兩個list以及輸入圖像寬高睡互,在單個坐標點上sample anchors.
    sizes: list of size_ratios, 為了便于計算根竿,首先轉(zhuǎn)換為list_of_absolute_areas
    輸出:lists of lists, n+m-1個對輸入尺寸歸一化過的輸出anchor,每個anchor為四維list 
    """
    H, W = input_shape[:2]
    areas = [size**2 * H*W for size in sizes] #獲取絕對areas
    anchors = []
    for a in areas:
        anchors.append(sample_anchor(a, ratios[0], center_coor, input_shape))
    for r in ratios[1:]:
        anchors.append(sample_anchor(areas[0], r, center_coor, input_shape))
    return np.array(anchors)  # .reshape(-1, 4)  # 這里發(fā)現(xiàn)lists of lists 直接變array不需要reshape就珠。但是NDArray不支持類似的模糊轉(zhuǎn)換

def sample_one_feature(feature, sizes, ratios):
    input_shape = feature.shape
    H, W = input_shape[:2]
    n, m = len(sizes), len(ratios)
    anchors = np.zeros((H, W, n+m-1, 4))
    for h in range(H):
        for w in range(W):
            # 這里實現(xiàn)需要的組織方式
            anchors[h,w,:,:]=sample_one_pixel(sizes, ratios, (w,h), input_shape)
    return anchors

def draw_one_pixel(feature, center_coor, anchors):
    colors = ['blue', 'green', 'red', 'black', 'magenta']
    x, y = center_coor
    feature_display = np.array([feature[:,:,0]]*3).transpose((1,2,0)) 
    plt.imshow(np.ones_like(feature_display)*255)
    anchors_this_pixel = anchors[y,x,:,:]
    for i, anchor in enumerate(anchors_this_pixel):
        plt.gca().add_patch(plt.Rectangle((anchor[0]*n, anchor[1]*n), 
                                          (anchor[2]-anchor[0])*n, (anchor[3]-anchor[1])*n, fill=False, color=colors[i]))
        plt.grid()
    plt.show()

if __name__ == "__main__":
    sizes = [.5, .25, .1]
    ratios = [1, 2., .5]
    plt.subplot(121)
    n = 10
    feature = np.arange(n*n*6).reshape(n, n, -1)
    print(feature.shape)
    anchors = sample_one_feature(feature, sizes, ratios)
    print(anchors.shape)
    draw_one_pixel(feature, (4, 4), anchors)
    
    plt.subplot(122)
    n = 40
    feature = np.arange(n*n*6).reshape(n, n, -1)
    print(feature.shape)
    anchors = sample_one_feature(feature, sizes, ratios)
    print(anchors.shape)
    draw_one_pixel(feature, (16, 16), anchors)
Fig. 3. 測試代碼輸出

??注意:上面的可視化再次顯示出寇壳,anchor的尺寸是隨著grid的尺寸成比例變化的。即妻怎,feature map越小壳炎,gird越少,anchor覆蓋面積越大逼侦。進一步匿辩,小feature map是為了檢測大目標,而大的feature map是為了檢測小目標偿洁。

??現(xiàn)在再來熟悉下Mxnet中C++實現(xiàn)的MultiBoxPrior接口:


Fig. 4. MultiBoxPrior函數(shù)實現(xiàn)Anchors Sampling

可見MultiBoxPrior的輸出已經(jīng)被reshape過了撒汉,維度為(batchSize, total_num_anchors, 4)

??另外這里有一個重要的問題待后續(xù)解決:sizes如何預(yù)定義沟优。YOLO中的Anchor直接通過cluster獲得涕滋,SSD呢?

2.2 預(yù)測物體類別——class_predictor

??SSD的一個重要特點就是挠阁,其在Faster-RCNN RPN的基礎(chǔ)上宾肺,將二分類的Softmax直接擴展為n+1分類,其中n代表類別侵俗,1代表是否包含物體锨用。這里cls_predictor就是將每個scale的feature送入一個Conv layer,該Conv要保證輸出的特征通道數(shù)為num_anchors*(num_classes+1)隘谣,即每個通道對應(yīng)一個anchor對 某個類別的置信度增拥。具體見下面描述:

Fig. 5. 使用卷積代替FC作為分類頭

  • 注意,這里存放class_probabilities的形式和YOLO稍有不同寻歧,YOLO是直接將每個grid cell用FC回歸到指定長度向量掌栅,向量每個位置代表啥都是很清楚的。SSD由于不使用FC回歸码泛,而是用"same" conv猾封,因此得到的不是一個長向量,而是一個維度為[batchSize, (numClasses+1) * numAnchors, h_feature, w_feature]的特征圖噪珊。其中的含義如下:每一個h_feature x w_feature大小的通道存放了所有像素點的某一個anchor對某個類的預(yù)測概率晌缘。所以所有的class probabilities總共有h_feature x w_feature x (numClasses+1) * numAnchors個數(shù)值齐莲。
from mxnet.gluon import nn
import mxnet as mx

def cls_predictor(num_anchors, num_classes):
    return nn.Conv2D(num_anchors*(num_classes+1), 3, 1, 1)

2.3 預(yù)測邊界框——box_predictor

??同理,將每個scale的feature送入一個新的Conv layer磷箕,該Conv只需要保證輸出channel數(shù)為num_anchors * 4.

Fig. 6. 使用卷積代替FC實現(xiàn)回歸
def box_predictor(num_anchors):
    return nn.Conv2D(num_anchors*4, 3, 1, 1)

2.4 減半(下采樣)模塊

??接下來定義減半模塊选酗。這里直接通過兩個'same'卷積后接一個MaxPooling實現(xiàn). 這種連續(xù)多個conv組成的小block中有一個小細節(jié): 往往最后一個Conv負責輸出指定的通道數(shù), 也就是說前面幾個Conv其實通道數(shù)是可以改變的。這時候一個選項就是把前面第一個卷即塊先通過1x1 Conv減少通道數(shù),然后接下來的Conv都使用這個小通道數(shù), 操作到最后一個Conv時再把通道數(shù)升上來, 這樣相比一開始的Conv就帶著目標通道數(shù)進行一系列連續(xù)運算, 參數(shù)量可以減少一些. 這個就是Residual Block Bottleneck的想法. 不過這里先按教程中的做法來搭建.

Fig. 7. 兩個ConvBlock實現(xiàn)空間維度減半
def downsample_block(channels):
    block = nn.HybridSequential()
    for _ in range(2):
        block.add(
            nn.Conv2D(channels, 3, 1, 1),
            nn.BatchNorm(),
            nn.Activation('relu')
        )
    block.add(nn.MaxPool2D(strides=2))
    return block


  • 定義好了三種基本網(wǎng)絡(luò)塊, 現(xiàn)在有兩個很自然的問題:
    這三個模塊之間是如何連接的?
    ②不同scale上的兩個Conv head的輸出應(yīng)該如何組合到一起, 得到最后的, 要送入loss函數(shù)的形式?

  • 問題答案如下:
    ??主干網(wǎng)絡(luò)和若干downsample block是首尾相連的搀捷,然后每個scale上的Conv heads(box_predictor和cls_predictor)均與主干網(wǎng)絡(luò)(或downsample)在當前尺度下的輸出特征圖相連接星掰,這些Conv heads之間沒有連接。在模型前向傳播過程中嫩舟,每個scale經(jīng)過Conv heads之后氢烘,會得到兩個代表anchors的class probability和box的tensor,我們需要將每個scale的這兩個tensor分別(按某種維度)Concat到一起家厌,最終得到兩個較大的tensor播玖,分別代表多尺度anchors的class probability和box信息,最后我們將這兩個合并之后的tensor送入loss函數(shù)饭于。

??SSD中會在多個尺度上進行預(yù)測蜀踏。由于每個尺度上的輸入高寬和錨框的選取不一樣,導(dǎo)致其形狀各不相同掰吕。下面例子中我們構(gòu)造兩個尺度的輸入果覆,其中第二個為第一個的高寬減半。然后構(gòu)造兩個類別預(yù)測層殖熟,其分別對每個輸入像素構(gòu)造5個和3個錨框局待。

Fig. 8. 不同尺度cls_preds的維度信息. 進行Concat時, 往往需要找到相同shape的維度

??可見, 預(yù)測的輸出格式為(批量大小,通道數(shù)菱属,高钳榨,寬)∨γ牛可以看到除了批量大小外薛耻,其他維度大小均不一樣。因此Concat時可以利用的維度就只有第一個維度. 故我們需要考慮如何將它們變形成統(tǒng)一的格式, 進而將多尺度的輸出合并起來赏陵,讓后續(xù)的處理變得簡單饼齿。

??我們首先將通道,即預(yù)測結(jié)果蝙搔,放到最后(這里是因為通常分類任務(wù)習慣將class probability放到最后一維缕溉,這里不放也沒關(guān)系)其實這里我不知道為啥要調(diào)整下把通道放最后一維(可能是為了適應(yīng)其他接口杂瘸?)倒淫,根據(jù)2.2部分下面的分析,除了第一維代表batchSize之外,其他所有維度乘起來代表的就是總共的class probabilities敌土。因為不同尺度下批量大小保持不變镜硕,所以將結(jié)果轉(zhuǎn)成二維的(批量大小,高 × 寬 × 通道數(shù))格式返干,方便之后的拼接兴枯。

??然后將多個尺度的preds按最后一個維度拼接就可以了.

def flatten_pred(pred):
    return pred.transpose(axes=(0, 2, 3, 1)).flatten()

def concat_preds(preds_multi_scales, F=mx.ndarray):
    return F.concat(*[flatten_pred(pred) for pred in preds_multi_scales], dim=1)

## check
concat_preds([y1, y2]).shape
[Out]: (2, 25300)

2.5 完整模型

2.5.1 定義主干網(wǎng)絡(luò)

??這里先用一個簡單的主干網(wǎng)絡(luò)與上述部件結(jié)合起來, 搭建一個完整的ToySSD模型. 后面的實驗中可以再嘗試不同的主干網(wǎng)絡(luò), 如ResNet50.

??直接用連續(xù)三個下采樣模塊構(gòu)成主干網(wǎng)絡(luò), 實現(xiàn)對輸入圖像進行8倍下采樣:

def body_block(channel_list=[16, 32, 64]):
    net = nn.HybridSequential()
    for channels in channel_list:
        net.add(downsample_block(channels))
    return net
2.5.2 一個"容器"模型

??在2.4中我們已經(jīng)討論過模型各個部件的關(guān)系了。為了代碼簡潔矩欠,我們定義一個額外的函數(shù)將所有組件放到一個Block(或者直接放到一個list中)财剖。注意:雖然這個Block是一個單獨的HybridBlock對象,但是和之前定義的完整網(wǎng)絡(luò)不一樣癌淮,該對象只是一個容器將所有組件存放起來躺坟,看做list即可(之所以可以當list使用,是利用HybridSequential可以直接unpack的特點


??對比SSD和前面練習的分類網(wǎng)絡(luò), 一個有趣的地方是, 分類任務(wù)通橙樾睿可以一氣呵成地定義一個完整模型, 然后前向傳播直接pred = model(X); 而SSD并不是一個傳統(tǒng)的前后相連的網(wǎng)絡(luò)模型, 而是由幾個相對獨立的部件構(gòu)成, 理論上我們只需要先把不同的組件放到同一個容器中保存, 然后前向傳播時在各個尺度分別輸出預(yù)測結(jié)果咪橙。 換句話說, 其實我們并沒有"定義"模型, 只是定義了部件, 前向傳播過程需要我們具體定義這些部件功能

??理論上這個用于存儲各部件的容器是很寬泛的, 比如我們可以用list或者tuple, 只要到時候能夠unpack取出各個組件進行前向傳播就可以了. 不過一個實際問題在于, 如果用這種方式, 返回的代表模型的"容器"只能在unpack之后對每個部件分別初始化, 顯然會使代碼顯得比較繁冗. 故我當時放棄了下面這種寫法, 盡管這種寫法更能體現(xiàn)出SSD這種松散的結(jié)構(gòu)特點:


Fig. 9. SSD的模型本質(zhì)上是一個保存組件的容器

??經(jīng)過上面分析, 我們可以把各個部件放到同一個HybridSequential對象中, 盡管這些部件之間的連接看起來是序貫連接(依次放入了model.add函數(shù)作為參數(shù)), 實際上這個model只是一個容器而已. 其優(yōu)勢在于: ①可以像一般容器一樣unpack; ②初始化時只需要調(diào)用model.initialize()即可初始化所有部件.

def ssd_model(num_anchors, num_classes):
    downsamplers, cls_predictors, box_predictors = nn.HybridSequential(), nn.HybridSequential(), nn.HybridSequential()
    for _ in range(3):
        downsamplers.add(downsample_block(128))
    for _ in range(5):
        cls_predictors.add(cls_predictor(num_anchors, num_classes))
        box_predictors.add(box_predictor(num_anchors))
    
    model = nn.HybridSequential()
    model.add(body_block([16, 32, 64]), cls_predictors, box_predictors, downsamplers)
    return model

??這種網(wǎng)絡(luò)組織的方式非常有趣, 但是仔細一想又很自然, 因為HybridSequential對象本身就支持索引, 有一些list的特性, 故可以unpack出各個組件也是容易理解的虚倒。我們可以通過下面的驗證進一步確定這一點:

t_model = ssd_model(5, 2)
print(len(t_model))
[Out]: 4
2.5.3 定義前向傳播

??由于反向傳播不需要我們考慮, 模型的前向傳播就成為整個網(wǎng)絡(luò)最重要的部分美侦。我們需要將前面定義的部件都整合起來。

??該前向傳播函數(shù)需要接收的輸入: ①上面的"容器"模型; ②輸入圖像矩陣; ③sizes和ratios, 用于逐個scale中sample anchors, 而sample出的anchor用于和當前batch的labels比較IOU, 進而生成當前batch的training targets(sample anchor應(yīng)該在前向傳播中完成, training_targets的獲取可以放在后面就行)魂奥。

?? 該前向傳播函數(shù)的輸出: ①所有scale的anchors②所有scale的cls_preds③所有scale的box_preds菠剩。

??根據(jù)接口的輸入輸出容易定義出如下ssd_forward函數(shù):

from mxnet.contrib.ndarray import MultiBoxPrior
def ssd_forward(model, x, sizes, ratios, F=mx.ndarray, verbose=True):
    # !!注意別寫成 backbone, cls_predictors, box_predictors, downsamplers = ssd_model(5, 2)了...
    backbone, cls_predictors, box_predictors, downsamplers = model
    x = backbone(x)
    anchors, cls_preds, box_preds = [], [], []
    #anchors, cls_preds, box_preds = [None]*5, [None]*5, [None]*5
    for i in range(5):
        # MultiBoxPrior: (batch_size, num_total_anchors_this_scale, 4)
        anchors.append(MultiBoxPrior(x, sizes[i], ratios[i]))
        cls_preds.append(cls_predictors[i](x))
        box_preds.append(box_predictors[i](x))
        if verbose:
            print('Predict scale {}, {} with {} anchors.'.format(i, x.shape, anchors[-1].shape[1]))
        if i < 3:
            x = downsamplers[i](x)
        elif i == 3:
            x = nn.GlobalMaxPool2D()(x)
        ## i=4時用的是GAP之后的feature,到這里就不用管了耻煤,所以沒有else
        
    # 注意concat anchors時具壮,將各個尺度的anchors按num_anchors那個維度拼接。得到的結(jié)果是(batchSize, num_total_anchors, 4)
    return (F.concat(*anchors, dim=1), concat_preds(cls_preds, F), concat_preds(box_preds, F))
  • 注意: 這個函數(shù)中我設(shè)置了一個參數(shù)F, 是希望將nd的操作擴展為Symble和NDArray公有的操作, 從而在模型定義完, 調(diào)試無誤之后使用model.hybridize()接口违霞。
Fig. 10. 測試前向傳播函數(shù), 注意觀察輸出的維度
  • 進一步, 將ssd_model和ssd_forward封裝到一個類中:
class ToySSD(nn.HybridBlock):
    def __init__(self, num_classes, **kwags):
        super(ToySSD, self).__init__(**kwags)
        # 思考:sizes是怎么得到的嘴办?瞬场?
        self.sizes = [[0.2, 0.272], [0.37, 0.447], [0.54, 0.619], 
                      [0.71, 0.79], [0.88, 0.961]]
        self.ratios = [[1, 2, 0.5]] * 5
        self.num_classes = num_classes
        num_anchors = len(self.sizes[0]) + len(self.ratios[0]) - 1
        with self.name_scope():
            self.model = ssd_model(num_anchors, num_classes)
            
    def hybrid_forward(self, F, x):
        anchors, cls_preds, box_preds = ssd_forward(self.model, x, self.sizes, self.ratios, F, verbose=False)
        # 這里還需要額外的一步:將cls_preds整理成最后一個維度代表probability的形式买鸽。這樣維度變成:
        # (batchSize, num_total_anchors, class_prob_each_anchor)
        cls_preds = cls_preds.reshape(0, -1, self.num_classes+1)
        return anchors, cls_preds, box_preds

  • 這段代碼中值得注意的是, 最后需要額外的一步, 將cls_preds整理成(batchSize, num_total_anchors, class_proba_each_anchor)的形式。這是分類任務(wù)中一個必要的步驟: 將代表class_probability的tensor單獨放到一個維度(通常是最后一維), 以配合cls_loss = SoftmaxCrossEntropyLoss(axis=-1)一起使用贯被。語義分割中這個問題會更明顯, 因為目標是對每個像素進行分類, 而FCN最后一個轉(zhuǎn)置卷積層的輸出維度是(batchSize, numClasses, height, width), 即原本代表通道的那一維(第1維)存放類別信息, 因此loss用的是SoftmaxCrossEntropyLoss(axis=1)眼五。

  • 總而言之, 計算機視覺任務(wù)中, 網(wǎng)絡(luò)輸出的predict_tensor可能是高度濃縮的, 因此搞清楚哪個維度代表了什么含義, 哪一維應(yīng)該拿出來求交叉熵, 是很重要的。

三彤灶、總結(jié)與遺留

??這篇博客記錄了SSD的學習筆記, 除了李沐老師講的知識之外, 加上了一些自己對contrib庫函數(shù)的探索, 以及學習過程中的思考看幼。總結(jié)下, 這篇博客主要涉及一下幾個有趣的點, 以及幾個遺留問題:

3.1 幾個有趣的點:

  • 在Anchor Sampling階段, 每個尺度的特征圖都直接映射到原圖大小(Fig. 1), 特征圖上每個像素點Sample出的anchor尺寸和grid尺寸成正比, 和特征圖大小成反比, 即大特征圖用于檢測小目標, 小特征圖用于檢測大目標幌陕。

  • SSD和FasterRCNN采集Anchor的數(shù)目不一樣多, SSD是n+m-1個诵姜。

  • SSD直接將各個尺度的特征圖通過兩個Conv head回歸到分別代表box_preds和num_classes+1分類的cls_preds上。

  • "容器"模型的思想: 模型并不一定要是嚴格的連接關(guān)系, 甚至有時候并沒有嚴格定義的模型, 只是為了初始化方便將幾個部件放到同一個HybridSequential中搏熄。只要前向傳播能unpack出各個部件, 實現(xiàn)預(yù)期輸出即可棚唆。

  • Concat時往往先找公共維度, 如果很多維度尺寸不一樣, 可以考慮乘到一起, 再利用公共維度進行拼接暇赤。

  • 定義模型時, 如果用到NDArray的接口, 想辦法用F代替, 這樣或許可以用model.hybridize()。

  • 搞清楚輸出tensor的哪一維要拿出來求交叉熵, 為什么是這一維!!

3.2 遺留問題:

  • SSD的sizes如何選擇?

  • 每個scale的cls_pred以及box_pred為啥要先用same卷積再flatten宵凌?可以用fc head嗎鞋囊?
    答:如果用fc的話可能要求每個scale feature map維度固定。

  • 關(guān)于每個scale的cls_predictior輸出維度的含義瞎惫。為什么flatten前要將代表通道的維度放到最后面溜腐?

[1]: DeepLearning for CV with Python. ImageNet Bundle.
[2]: 動手學深度學習

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓜喇,隨后出現(xiàn)的幾起案子挺益,更是在濱河造成了極大的恐慌,老刑警劉巖乘寒,帶你破解...
    沈念sama閱讀 221,576評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件矩肩,死亡現(xiàn)場離奇詭異,居然都是意外死亡肃续,警方通過查閱死者的電腦和手機黍檩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,515評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來始锚,“玉大人刽酱,你說我怎么就攤上這事∏瓢疲” “怎么了棵里?”我有些...
    開封第一講書人閱讀 168,017評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長姐呐。 經(jīng)常有香客問我殿怜,道長,這世上最難降的妖魔是什么曙砂? 我笑而不...
    開封第一講書人閱讀 59,626評論 1 296
  • 正文 為了忘掉前任头谜,我火速辦了婚禮,結(jié)果婚禮上鸠澈,老公的妹妹穿的比我還像新娘柱告。我一直安慰自己,他們只是感情好笑陈,可當我...
    茶點故事閱讀 68,625評論 6 397
  • 文/花漫 我一把揭開白布际度。 她就那樣靜靜地躺著,像睡著了一般涵妥。 火紅的嫁衣襯著肌膚如雪乖菱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,255評論 1 308
  • 那天,我揣著相機與錄音窒所,去河邊找鬼娜氏。 笑死,一個胖子當著我的面吹牛墩新,可吹牛的內(nèi)容都是我干的贸弥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,825評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼海渊,長吁一口氣:“原來是場噩夢啊……” “哼绵疲!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起臣疑,我...
    開封第一講書人閱讀 39,729評論 0 276
  • 序言:老撾萬榮一對情侶失蹤盔憨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后讯沈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體郁岩,經(jīng)...
    沈念sama閱讀 46,271評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,363評論 3 340
  • 正文 我和宋清朗相戀三年缺狠,在試婚紗的時候發(fā)現(xiàn)自己被綠了问慎。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,498評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡挤茄,死狀恐怖如叼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情穷劈,我是刑警寧澤笼恰,帶...
    沈念sama閱讀 36,183評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站歇终,受9級特大地震影響社证,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜评凝,卻給世界環(huán)境...
    茶點故事閱讀 41,867評論 3 333
  • 文/蒙蒙 一追葡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧肥哎,春花似錦辽俗、人聲如沸疾渣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,338評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榴捡。三九已至杈女,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背达椰。 一陣腳步聲響...
    開封第一講書人閱讀 33,458評論 1 272
  • 我被黑心中介騙來泰國打工翰蠢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啰劲。 一個月前我還...
    沈念sama閱讀 48,906評論 3 376
  • 正文 我出身青樓梁沧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蝇裤。 傳聞我的和親對象是個殘疾皇子廷支,可洞房花燭夜當晚...
    茶點故事閱讀 45,507評論 2 359

推薦閱讀更多精彩內(nèi)容