Date: 2020/07/14
Coder: CW
Foreword:
這一篇開始對 DETR 的模型構(gòu)建部分進(jìn)行解析呈野,model主要由兩部分組成烛愧,其中一部分是backbone酪惭,另一部分是Transformer希痴。另外,在DETR的源碼實(shí)現(xiàn)中春感,將位置編碼模塊與backbone集成到一起作為一個(gè)module砌创,在backbone輸出特征圖的同時(shí)對其進(jìn)行位置編碼虏缸,以便后續(xù)Transformer使用。
Outline
I. Build Backbone
II. Build Position Encoding
III. Joiner
Build Backbone
backbone的構(gòu)建通過bulid_backbone這個(gè)方法封裝嫩实,主要做的就是分別構(gòu)建位置編碼部分與backbone刽辙,然后將兩者封裝到一個(gè)nn.Module里,在前向過程中實(shí)現(xiàn)兩者的功能甲献。
先來看backbone的構(gòu)建宰缤,以下這個(gè)類繼承BackboneBase這個(gè)類,實(shí)際的backbone是使用torchvision里實(shí)現(xiàn)的resnet晃洒。其中 pretrained=is_main_process() 代表僅在主進(jìn)程中使用預(yù)訓(xùn)練權(quán)重慨灭。
而對于 norm_layer=FrozenBatchNorm2d,代表這里使用的歸一化層是FrozenBatchNorm2d球及,這個(gè)nn.Module與batch normalization的工作原理類似氧骤,只不過將統(tǒng)計(jì)量(均值與方差)和可學(xué)習(xí)的仿射參數(shù)固定住,doc string里的描述是:
BatchNorm2d where the batch statistics and the affine parameters are fixed.
在實(shí)現(xiàn)的時(shí)候吃引,需要將以上4個(gè)量注冊到buffer筹陵,以便阻止梯度反向傳播而更新它們,同時(shí)又能夠記錄在模型的state_dict中镊尺。
在BackboneBase中可以看到朦佩,若return_interm_layers設(shè)置為True,則需要記錄每一層(ResNet的layer)的輸出庐氮。
注意语稠,IntermediateLayerGetter 這個(gè)類是在torchvision中實(shí)現(xiàn)的,它繼承nn.ModuleDict旭愧,接收一個(gè)nn.Module和一個(gè)dict作為初始化參數(shù)颅筋,dict的key對應(yīng)nn.Module的模塊,value則是用戶自定義的對應(yīng)各個(gè)模塊輸出的命名输枯,官方給出的例子如下:
Examples::
? ? ? ? >>> m = torchvision.models.resnet18(pretrained=True)
? ? ? ? >>> # extract layer1 and layer3, giving as names `feat1` and feat2`
? ? ? ? >>> new_m = torchvision.models._utils.IntermediateLayerGetter(m,
? ? ? ? >>>? ? {'layer1': 'feat1', 'layer3': 'feat2'})
? ? ? ? >>> out = new_m(torch.rand(1, 3, 224, 224))
? ? ? ? >>> print([(k, v.shape) for k, v in out.items()])
? ? ? ? >>>? ? [('feat1', torch.Size([1, 64, 56, 56])),
? ? ? ? >>>? ? ? ('feat2', torch.Size([1, 256, 14, 14]))]
現(xiàn)在回過頭來看看BackboneBase的前向過程议泵。self.body就是上述提到的IntermediateLayerGetter,它的輸出是一個(gè)dict桃熄,對應(yīng)了每層的輸出先口,key是用戶自定義的賦予輸出特征圖的名字。
注意BackboneBase的前向方法中的輸入是NestedTensor這個(gè)類的實(shí)例瞳收,其實(shí)質(zhì)就是將圖像張量和對應(yīng)的mask封裝到一起碉京。
至此,Backbone的構(gòu)建就完事了螟深。綜上可知谐宙,若設(shè)置return_interm_layers為True,即指定需要返回每層的輸出界弧,那么backbone的輸出將是 out={'0': f1, '1': f2, '2': f3, '3': f4}凡蜻,否則輸出將是 out={'0': f4},搭综,其中代表第i層的輸出(比如ResNet的話,就是
)划栓。
Build Position Encoding
這部分的實(shí)現(xiàn)和Transformer那篇paper中的基本類似兑巾,有兩種方式來實(shí)現(xiàn)位置編碼:一種是可學(xué)習(xí)的;另一種則是使用正忠荞、余弦函數(shù)來對各位置的奇蒋歌、偶維度進(jìn)行編碼,不需要額外的參數(shù)進(jìn)行學(xué)習(xí)委煤,Transformer和DETR默認(rèn)使用的也是這種堂油。
不同的是,這里處理的對象是2D圖像特征碧绞,而非1D的序列称诗,因此位置編碼需要分別對行、列進(jìn)行(當(dāng)然头遭,你可能想到把特征圖flatten成的1D序列,但應(yīng)該是考慮到保持圖像結(jié)構(gòu)因此DETR并沒有那么做癣诱,感興趣的童鞋可以試試计维,并且進(jìn)行實(shí)驗(yàn)對比下效果)。
先來對可學(xué)習(xí)的編碼方式進(jìn)行解析撕予。
這里默認(rèn)需要編碼的特征圖的行鲫惶、列不超為50(相當(dāng)于特征圖尺寸不超過50x50),即位置索引在0~50范圍內(nèi)实抡,對每個(gè)位置都嵌入到num_pos_feats(默認(rèn)256)維欠母。
下面是前向過程,分別對一行和一列中的每個(gè)位置進(jìn)行編碼吆寨。
最后將行赏淌、列編碼結(jié)果拼接起來并擴(kuò)充第一維,與batch size對應(yīng)啄清,得到以下變量pos六水。
在這種方式的編碼下,所有行同一列的橫坐標(biāo)(x_emb)編碼結(jié)果是一樣的辣卒,在dim1中處于pos的前num_pos_feats維掷贾;同理,所有列所有列同一行的縱坐標(biāo)(y_emb)編碼結(jié)果也是一樣的荣茫,在dim1中處于pos的后num_pos_feats維想帅。
再來看看正、余弦編碼的方式啡莉。
這種方式是將每個(gè)位置的各個(gè)維度映射到角度上港准,因此有個(gè)scale參數(shù)旨剥,若初始化時(shí)沒有指定,則默認(rèn)為0~2π叉趣。
在該系列上一篇:?源碼解析目標(biāo)檢測的跨界之星DETR(二)泞边、模型訓(xùn)練過程與數(shù)據(jù)處理?中的數(shù)據(jù)處理部分,CW提到過mask指示了圖像哪些位置是padding而來的疗杉,其值為True的部分就是padding的部分阵谚,這里取反后得到not_mask,那么值為True的部分就是圖像真實(shí)有效(而非padding)的部分烟具。
上圖中使用了張量的cumsum()方法在列和行的方向分別進(jìn)行累加梢什,并且數(shù)據(jù)類型由布爾型轉(zhuǎn)換為浮點(diǎn)型。不得不說朝聋,這個(gè)操作十分妙嗡午!
在行方向累加,就會得到以下形式(y_embed):
[ [1,1,1,..,1],
? [2,2,2,..,2],
? ...
? [h,h,h,..,h] ]
而在列方向累加冀痕,則得到以下形式(x_embed):
[ [1,2,3,..,w],
? [1,2,3,..,w],
? ...
? [1,2,3,..,w]?]
這樣荔睹,各行(列)都映射到不同的值(當(dāng)然,pad部分的位置由于mask取反后值是0言蛇,因此累加后得到的值會與前面行僻他、列的值重復(fù),但是不要緊腊尚,通過下一篇文章關(guān)于注意力權(quán)重計(jì)算的講解會知道這些位置會被忽略掉而不受影響)吨拗,并且,最后一行(列)是所有行(列)的總和h(w)婿斥,還方便進(jìn)行歸一化操作:
下圖部分的代碼與正劝篷、余弦編碼的公式對應(yīng):
;
使用這種方式編碼民宿,就是對各行各列的奇偶維度分別進(jìn)行正娇妓、余弦編碼。
對于每個(gè)位置(x,y)活鹰,其所在列對應(yīng)的編碼值排在通道這個(gè)維度上的前num_pos_feats維峡蟋,而其所在行對應(yīng)的編碼值則排在通道這個(gè)維度上的后num_pos_feats維。這樣华望,特征圖上的各位置(總共個(gè))都對應(yīng)到不同的維度為
的編碼值(向量)蕊蝗。
至于這種方式為何能夠保證不同位置映射到不同的位置編碼可以看下CW這篇文章中的分析(在最后一小節(jié)):Transformer 修煉之道(一)、Input Embedding
另外赖舟,這種方式相比于可學(xué)習(xí)的方式還有個(gè)“可拓展”的好處:即使在測試時(shí)來到一個(gè)比以往訓(xùn)練時(shí)遇到的圖像尺寸都大的圖像蓬戚,也照樣能夠獲得編碼值。
Joiner
Joiner就是將backbone和position encoding集成的一個(gè)nn.Module里宾抓,使得前向過程中更方便地使用兩者的功能子漩。
Joiner是nn.Sequential的子類豫喧,通過初始化,使得self[0]是backbone幢泼,self[1]是position encoding紧显。前向過程就是對backbone的每層輸出都進(jìn)行位置編碼,最終返回backbone的輸出及對應(yīng)的位置編碼結(jié)果缕棵。
@最后
就這篇文章的整體內(nèi)容來說孵班,重點(diǎn)應(yīng)該是在位置編碼的部分,backbone部分畢竟是調(diào)用torchvision的內(nèi)置模型招驴,因此幾乎沒什么可講的了篙程,位置編碼的技巧可以私下多碼幾遍,碼熟它别厘,從而掌握一個(gè)基本技能虱饿,便于日后應(yīng)用。