Date: 2020/07/25 Coder: CW Foreword: 本文是該系列的重點之一,通過對DETR中Transformer部分的代碼解析,你就會知道Transformer是如何在目標(biāo)檢測領(lǐng)域work的了,并且你還可以自己動手實踐一番,是不是很誘人?哈哈哈!Transformer在這里與NLP的流程類似,主要包括 Embedding、Encoder 和 Decoder,如果對Transformer基本原理不了解的朋友們,可以閱讀下Transformer系列的這幾篇文章: Transformer 修煉之道(一)、Input Embedding Transformer 修煉之道(二)、Encoder Transformer 修煉之道(三)、Decoder 之前寫以上這幾篇文章正是為該系列作鋪墊,以便不熟悉Transformer原理的朋友們快速了解下相關(guān)知識點。
OutlineI. EncoderII. Query EmbeddingIII. DecoderIV. Transformer (Combine Encoder with Decoder)
EncoderEncoder通常有6層,每層結(jié)構(gòu)相同,這里使用_get_clones()方法將結(jié)構(gòu)相同的層復(fù)制(注意是deepcopy)多次,返回一個nn.ModuleList實例。 _get_clones()方法一行代碼就能KO掉。 Encoder的前向過程即循環(huán)調(diào)用每層的前向過程,前一層的輸出作為后一層的輸入。最后,若指定了需要歸一化,那么就對最后一層的輸出作歸一化。 這里注意下幾個輸入?yún)?shù)的意義,CW 已為大家在下圖中作了注釋。 Encoder的每層是下圖這個TransformerEncoderLayer的一個實例,其主要由 多頭自注意力層(Multi-Head Self-Attention) 和 前向反饋層(FFN)構(gòu)成,另外還包含了層歸一化、激活層、Dropout層以及殘差連接。 _get_activation_fn()方法根據(jù)輸入?yún)?shù)指定的激活方式返回對應(yīng)的激活層,默認(rèn)是ReLU。 這里歸一化使用了nn.LayerNorm,即層歸一化。與BatchNorm(批歸一化)不同,后者是在通道這個維度上進(jìn)行歸一化,而前者可以指定對最后的哪幾個維度進(jìn)行歸一化。另外,其可學(xué)習(xí)參數(shù)是對應(yīng)做歸一化的那些維度上的每個元素的,而非批歸一化那種一個通道共享一對標(biāo)量。還有一點與批歸一化不同,就是其在測試過程中也會計算統(tǒng)計量。 官方給出的說明如下:
Unlike Batch Normalization and Instance Normalization, which applies scalar scale and bias for each entire channel/plane with the :attr:`affine` option, Layer Normalization applies per-element scale and bias with :attr:`elementwise_affine`.
This layer uses statistics computed from input data in both training and evaluation modes.
EncoderLayer的前向過程分為兩種情況,一種是在輸入多頭自注意力層和前向反饋層前先進(jìn)行歸一化,另一種則是在這兩個層輸出后再進(jìn)行歸一化操作。 先進(jìn)行歸一化的前向過程如下圖。 self.self_attn是nn.MultiheadAttention的實例,其前向過程返回兩部分,第一個是自注意力層的輸出,第二個是自注意力權(quán)重,因此這里取了輸出索引為0的部分即代表自注意力層的輸出。 另外,這里的key_padding_mask對應(yīng)上述Encoder的src_key_padding_mask,是backbone最后一層輸出特征圖對應(yīng)的mask,值為True的那些位置代表原始圖像padding的部分,在生成注意力的過程中會被填充為-inf,這樣最終生成注意力經(jīng)過softmax時輸出就趨向于0,相當(dāng)于忽略不計,官方對該參數(shù)的解釋如下: key_padding_mask – if provided, specified padding elements in the key will be ignored by the attention. This is an binary mask. When the value is True, the corresponding value on the attention layer will be filled with -inf.
而src_mask是在Transformer中用來“防作弊”的,即遮住當(dāng)前預(yù)測位置之后的位置,忽略這些位置,不計算與其相關(guān)的注意力權(quán)重。而這里的序列是圖像特征(反而就是要計算圖像各位置的全局關(guān)系),不同于NLP,因此沒有這個需求,在這里也就沒有使用。 還有一點,在輸入多頭自注意力層時需要先進(jìn)行位置嵌入,即結(jié)合位置編碼。注意僅對query和key實施,而value不需要。query和key是在圖像特征中各個位置之間計算相關(guān)性,而value作為原圖像特征,使用計算出來的相關(guān)性加權(quán)上去,得到各位置結(jié)合了全局相關(guān)性(增強(qiáng)/削弱)后的特征表示。 (不過好奇的童鞋也可以試著對value也進(jìn)行position embedding,實驗對比下效果,然后再想想怎么講故事..) 后進(jìn)行歸一化的前向過程與上類似。
Query Embedding
在解析Decoder前,有必要先簡要地談?wù)剄uery embedding,因為它是Decoder的主要輸入之一。 query embedding 有點anchor的味道,而且是自學(xué)習(xí)的anchor,作者使用了nn.Embedding實現(xiàn): 其中num_queries代表圖像中有多少個目標(biāo)(位置),默認(rèn)是100個,對這些目標(biāo)(位置)全部進(jìn)行嵌入,維度映射到hidden_dim,將query_embedding的權(quán)重作為參數(shù)輸入到Transformer的前向過程,使用時與position encoding的方式相同:直接相加。 而這個query embedding應(yīng)該加在哪呢?當(dāng)然是我們需要預(yù)測的目標(biāo)(query object)咯!可是網(wǎng)絡(luò)一開始還沒有輸出,我們都不知道預(yù)測目標(biāo)在哪里呀,如何將它實體化?作者也不知道,于是就簡單粗暴地直接將它初始化為全0,shape和query embedding 的權(quán)重一致(從而可以element-wise add)。
DecoderDecoder的結(jié)構(gòu)和Encoder十分類似。 其前向過程如下。具體操作和Encoder的也類似,只不過需要先將以下紅框部分的參數(shù)梳理清楚,明白各自代表的意義,之后整個代碼看起來就十分好理解了。 提一句,這里tgt_mask和memory_mask都是用于“防作弊”的,這里均未使用。 在下圖中,需要注意的是intermediate中記錄的是每層輸出后的歸一化結(jié)果,而每一層的輸入是前一層輸出(沒有歸一化)的結(jié)果。 感覺下圖紅框部分是“多此一舉”,因為上圖中本身intermediate記錄的就是每層輸出的歸一化結(jié)果了。 另外,不知道你們發(fā)現(xiàn)了不,這里的實現(xiàn)有點“令人不舒服”。self.norm是通過初始化時傳進(jìn)來的參數(shù)norm(默認(rèn)為None)設(shè)置的,那么self.norm就有可能是None,因此下圖第一句代碼也對此作了判斷。但是在上圖中,卻在沒有作判斷的情況下直接碼出了self.norm(output)這句,所以有可能會引發(fā)異常。 在整體項目代碼中,作者在構(gòu)建Decoder使始終傳了norm參數(shù),使得其不為None,因此不會引發(fā)異常。但就單獨看Decoder的這部分實現(xiàn)來說,確實是有問題的,如果朋友們直接拿這部分去用,需要注意下這點(沒想到FAIR寫的代碼居然也有這種level的水平,被我抓到了..)。 DecoderLayer與Encoder的實現(xiàn)類似,只不過多了一層 Encoder-Decoder Layer,其實質(zhì)也是多頭自注意力層,但是key和value來自于Encoder的輸出。 DecoderLayer的前向過程也如同EncoderLayer一樣分為兩種情況,這里就對“后進(jìn)行歸一化”的情況作解析。 在第一個多頭自注意層中,輸入均和Encoder無關(guān)。 注意,和Encoder中一樣,會對query和key進(jìn)行position embedding(而value則不需要)。 到了第二個多頭自注意力層,即 Encoder-Decoder Layer,key和value均來自Encoder的輸出。同樣地,query和key要進(jìn)行位置嵌入(而value不用)。 這里自注意力層計算的相關(guān)性是目標(biāo)物體與圖像特征各位置的相關(guān)性,然后再把這個相關(guān)性系數(shù)加權(quán)到Encoder編碼后的圖像特征(value)上,相當(dāng)于獲得了object features的意思,更好地表征了圖像中的各個物體。 通過Decoder和Encoder的實現(xiàn)可以發(fā)現(xiàn),作者極力強(qiáng)調(diào)位置嵌入的作用,每次在self-attention操作時都伴隨著position embedding,這是因為繼承了Transformer的permute invariant特性,即對排列和位置是不care的,而我們很清楚,在detection任務(wù)中,位置信息有著舉足輕重的地位! 另外,看完DecoderLayer的實現(xiàn),會發(fā)現(xiàn)可能存在“重復(fù)歸一化”的問題。當(dāng)使用后歸一化的前向方式時(如以上兩幅圖所示),每個DecoderLayer的輸出是歸一化后的結(jié)果,但是在Decoder的前向過程中會使用self.norm對其再進(jìn)行一次歸一化!
Transformer (Combine Encoder with Decoder)將Encoder和Decoder封裝在一起構(gòu)成Transformer。 Transformer的前向過程如下,首先是將輸入(圖像特征)flatten成序列,這里的src是已經(jīng)將CNN提取的特征維度映射到hidden_dim的結(jié)果。 然后就是將以上輸入?yún)?shù)依次送進(jìn)Encoder和Decoder,最后再reshape到需要的結(jié)果。 注意,tgt是與query embedding形狀一直且設(shè)置為全0的結(jié)果,意為初始化需要預(yù)測的目標(biāo)。因為一開始并不清楚這些目標(biāo),所以初始化為全0。其會在Decoder的各層不斷被refine,相當(dāng)于一個coarse-to-fine的過程,但是真正要學(xué)習(xí)的是query embedding,學(xué)習(xí)到的是整個數(shù)據(jù)集中目標(biāo)物體的統(tǒng)計特征,而tgt在每次迭代訓(xùn)練(一個batch數(shù)據(jù)剛到來)時會被重新初始化為0。 (那么這里就可以思考下了,這種學(xué)習(xí)方式會不會使得模型的泛化能力受限?在遇到另一個分布差異巨大的數(shù)據(jù)集時模型的表現(xiàn)想必會很尷尬?不過當(dāng)前深度學(xué)習(xí)中基于特定數(shù)據(jù)集訓(xùn)練而來的模型貌似都有這個通病,畢竟你在A分布上訓(xùn)練,卻要在B分布(與A分布差異巨大)上測試,是不是太不善良了..) 以上有一處需要注意下,就是作者在項目代碼中構(gòu)建Transformer時設(shè)置了return_intermediate為True,因此Decoder會返回6層的輸出結(jié)果,于是hs的第一個維度是6。
@最后本文解析了Transformer在DETR中的實現(xiàn),這部分是DETR的核心部分之一。但是還沒完,經(jīng)過Transformer后只能說DETR的主要工作基本做完了,其輸出結(jié)果并不是最終的預(yù)測結(jié)果,還需要進(jìn)行轉(zhuǎn)換,后續(xù)部分的內(nèi)容會在該系列后續(xù)的文章中給出,還請諸位客官稍候不知道多少刻..
|