1. 程式人生 > >detectron程式碼理解(一):Resnet模型構建理解

detectron程式碼理解(一):Resnet模型構建理解

這裡具體以resnet50為例進行說明,一句一句地分析程式碼,程式碼位置位於Resnet.py,具體的分析函式為add_ResNet_convX_body.

在分析之前首先貼上resnet50的程式碼結構圖:

# add the stem (by default, conv1 and pool1 with bn; can support gn)
p, dim_in = globals()[cfg.RESNETS.STEM_FUNC](model, 'data')

這裡p = gpu_0/pool1,dim_in = 64

從上面的圖可以看出,p就是經過開始的3×3的pooling得到的,因為前面的卷積個數為64,poll不改變特徵圖個數,所以輸出的dim_in = 64。這裡之所以稱為dim_in是相對與下面bottleneck是輸入。

dim_bottleneck = cfg.RESNETS.NUM_GROUPS * cfg.RESNETS.WIDTH_PER_GROUP
(n1, n2, n3) = block_counts[:3]  #

dim_bottleneck的數值與GPU的數量有關,GPU的數量乘以這個一個基本的數值cfg.RESNETS.WIDTH_PER_GROUP就得到了dim_bottleneck,這裡dim_bottleneck = 64。

(n1, n2, n3) = (3,4,6)。這裡的n1是表格中conv2_x中的3,代表3個residual block,n2,n3同理

s, dim_in = add_stage(model, 'res2', p, n1, dim_in, 256, dim_bottleneck, 1)

新增第一個stage,其中(下面所述的表格中的都是,conv2_x這一行的)

(1)p為輸入,即gpu_0/pool1

(2)n1為block的數目,

(3)dim_in是輸入特徵圖的大小,即上面所述的64

(4)256表示經過這個stage後輸出的特徵圖數量,對應表格中的256

(5)dim_bottleneck=64,對應表格中的64

我們再來看stage函式,是一個for迴圈,同過迴圈來依次新增resiual block,n=3,也就是新增3個

def add_stage(
    model,
    prefix,
    blob_in,
    n,
    dim_in,
    dim_out,
    dim_inner,
    dilation,  # 相當於padding引數
    stride_init=2
):
    """Add a ResNet stage to the model by stacking n residual blocks."""
    # e.g., prefix = res2
    for i in range(n):
        blob_in = add_residual_block(
            model,
            '{}_{}'.format(prefix, i),
            blob_in,
            dim_in,
            dim_out,
            dim_inner,
            dilation,
            stride_init,
            # Not using inplace for the last block;
            # it may be fetched externally or used by FPN
            inplace_sum=i < n - 1
        )
        print(blob_in)
        dim_in = dim_out
    return blob_in, dim_in

add_stage這個函式會呼叫到add_residual_block函式,應該很容易的理解到,對於conv2_x這個stage每一個residual block的輸出都是256個featuremap。

同時還要理清相關的關係,於第一個residual block,下面的dim_in是64,是上面pool的輸出。對於第二個residual block,這時候的dim_in就是第一個residual block的輸出了,也就是256,不過中間的dim_inner仍是64,其目的是減少引數,最後的輸出dim_out也還是256。第三個residual block同理,對應表格就能很清楚的明白。

def add_residual_block(
    model,
    prefix,
    blob_in,
    dim_in,
    dim_out,
    dim_inner,
    dilation,
    stride_init=2,
    inplace_sum=False
):
    """Add a residual block to the model."""
    # prefix = res<stage>_<sub_stage>, e.g., res2_3

    # Max pooling is performed prior to the first stage (which is uniquely
    # distinguished by dim_in = 64), thus we keep stride = 1 for the first stage
    stride = stride_init if (
        dim_in != dim_out and dim_in != 64 and dilation == 1
    ) else 1

    # transformation blob  dim_in表示輸入,dim_out表示輸出,dim_inner表示中間
    tr = globals()[cfg.RESNETS.TRANS_FUNC](
        model,
        blob_in,
        dim_in,
        dim_out,
        stride,
        prefix,
        dim_inner,
        group=cfg.RESNETS.NUM_GROUPS,
        dilation=dilation
    )

    # sum -> ReLU
    # shortcut function: by default using bn; support gn  恆等對映
    add_shortcut = globals()[cfg.RESNETS.SHORTCUT_FUNC]
    sc = add_shortcut(model, prefix, blob_in, dim_in, dim_out, stride)  #恆等對映
    if inplace_sum:
        s = model.net.Sum([tr, sc], tr) #第二個引數相當於命名
    else:
        s = model.net.Sum([tr, sc], prefix + '_sum')

    return model.Relu(s, s)

最後生成的模型結構(加粗部分是該stage最終的輸出,res2_0表示第二層的第一個residual block,res2_1表示第二層的第二個residual block)

conv2_x gpu_0/res2_0_branch2c_bn
gpu_0/res2_1_branch2c_bn
gpu_0/res2_2_sum
conv3_x gpu_0/res3_0_branch2c_bn
gpu_0/res3_1_branch2c_bn
gpu_0/res3_2_branch2c_bn
gpu_0/res3_3_sum
conv4_x gpu_0/res4_0_branch2c_bn
gpu_0/res4_1_branch2c_bn
gpu_0/res4_2_branch2c_bn
gpu_0/res4_3_branch2c_bn
gpu_0/res4_4_branch2c_bn
gpu_0/res4_5_sum
conv5_x

gpu_0/res5_0_branch2c_bn
gpu_0/res5_1_branch2c_bn

gpu_0/res5_2_sum