日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了

發(fā)布時(shí)間:2025/3/12 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

??作者丨科技猛獸

編輯丨極市平臺(tái)

導(dǎo)讀

?

本文對(duì)Vision Transformer的原理和代碼進(jìn)行了非常全面詳細(xì)的解讀,一切從Self-attention開(kāi)始、Transformer的實(shí)現(xiàn)和代碼以及Transformer+Detection:引入視覺(jué)領(lǐng)域的首創(chuàng)DETR。>>加入極市CV技術(shù)交流群,走在計(jì)算機(jī)視覺(jué)的最前沿

Transformer 是 Google 的團(tuán)隊(duì)在 2017 年提出的一種 NLP 經(jīng)典模型,現(xiàn)在比較火熱的 Bert 也是基于 Transformer。Transformer 模型使用了 Self-Attention 機(jī)制,不采用RNN順序結(jié)構(gòu),使得模型可以并行化訓(xùn)練,而且能夠擁有全局信息。本文將對(duì)Vision Transformer的原理和代碼進(jìn)行非常全面的解讀??紤]到每篇文章字?jǐn)?shù)的限制,每一篇文章將按照目錄的編排包含三個(gè)小節(jié),而且這個(gè)系列會(huì)隨著Vision Transformer的發(fā)展而長(zhǎng)期更新。

目錄

(每篇文章對(duì)應(yīng)一個(gè)Section,目錄持續(xù)更新。)

  • Section 1

1 一切從Self-attention開(kāi)始
1.1 處理Sequence數(shù)據(jù)的模型
1.2 Self-attention
1.3 Multi-head Self-attention
1.4 Positional Encoding

2 Transformer的實(shí)現(xiàn)和代碼解讀 (NIPS2017)
(來(lái)自Google Research, Brain Team)
2.1 Transformer原理分析
2.2 Transformer代碼解讀

3 Transformer+Detection:引入視覺(jué)領(lǐng)域的首創(chuàng)DETR (ECCV2020)
(來(lái)自Facebook AI)
3.1 DETR原理分析
3.2 DETR代碼解讀

  • Section 2

4 Transformer+Detection:Deformable DETR:可變形的Transformer (ICLR2021)
(來(lái)自商湯代季峰老師組)
4.1 Deformable DETR原理分析
4.2 Deformable DETR代碼解讀

5 Transformer+Classification:用于分類任務(wù)的Transformer (ICLR2021)
(來(lái)自Google Research, Brain Team)
5.1 ViT原理分析
5.2 ViT代碼解讀

6 Transformer+Image Processing:IPT:用于底層視覺(jué)任務(wù)的Transformer
(來(lái)自北京華為諾亞方舟實(shí)驗(yàn)室)
6.1 IPT原理分析

  • Section 3

7 Transformer+Segmentation:SETR:基于Transformer 的語(yǔ)義分割
(來(lái)自復(fù)旦大學(xué),騰訊優(yōu)圖等)
7.1 SETR原理分析

8 Transformer+GAN:VQGAN:實(shí)現(xiàn)高分辨率的圖像生成
(來(lái)自德國(guó)海德堡大學(xué))
8.1 VQGAN原理分析
8.2 VQGAN代碼解讀

9 Transformer+Distillation:DeiT:高效圖像Transformer
(來(lái)自Facebook AI)
9.1 DeiT原理分析

1 一切從Self-attention開(kāi)始

  • 1.1 處理Sequence數(shù)據(jù)的模型:

Transformer是一個(gè)Sequence to Sequence model,特別之處在于它大量用到了self-attention。

要處理一個(gè)Sequence,最常想到的就是使用RNN,它的輸入是一串vector sequence,輸出是另一串vector sequence,如下圖1左所示。

如果假設(shè)是一個(gè)single directional的RNN,那當(dāng)輸出?時(shí),默認(rèn)?都已經(jīng)看過(guò)了。如果假設(shè)是一個(gè)bi-directional的RNN,那當(dāng)輸出?任意時(shí),默認(rèn)?都已經(jīng)看過(guò)了。RNN非常擅長(zhǎng)于處理input是一個(gè)sequence的狀況。

那RNN有什么樣的問(wèn)題呢?它的問(wèn)題就在于:RNN很不容易并行化 (hard to parallel)。

為什么說(shuō)RNN很不容易并行化呢?假設(shè)在single directional的RNN的情形下,你今天要算出?,就必須要先看?再看?再看?再看?,所以這個(gè)過(guò)程很難平行處理。

所以今天就有人提出把CNN拿來(lái)取代RNN,如下圖1右所示。其中,橘色的三角形表示一個(gè)filter,每次掃過(guò)3個(gè)向量?,掃過(guò)一輪以后,就輸出了一排結(jié)果,使用橘色的小圓點(diǎn)表示。

這是第一個(gè)橘色的filter的過(guò)程,還有其他的filter,比如圖2中的黃色的filter,它經(jīng)歷著與橘色的filter相似的過(guò)程,又輸出一排結(jié)果,使用黃色的小圓點(diǎn)表示。

圖1:處理Sequence數(shù)據(jù)的模型圖2:處理Sequence數(shù)據(jù)的模型

所以,用CNN,你確實(shí)也可以做到跟RNN的輸入輸出類似的關(guān)系,也可以做到輸入是一個(gè)sequence,輸出是另外一個(gè)sequence。

但是,表面上CNN和RNN可以做到相同的輸入和輸出,但是CNN只能考慮非常有限的內(nèi)容。比如在我們右側(cè)的圖中CNN的filter只考慮了3個(gè)vector,不像RNN可以考慮之前的所有vector。但是CNN也不是沒(méi)有辦法考慮很長(zhǎng)時(shí)間的dependency的,你只需要堆疊filter,多堆疊幾層,上層的filter就可以考慮比較多的資訊,比如,第二層的filter (藍(lán)色的三角形)看了6個(gè)vector,所以,只要疊很多層,就能夠看很長(zhǎng)時(shí)間的資訊。

而CNN的一個(gè)好處是:它是可以并行化的 (can parallel),不需要等待紅色的filter算完,再算黃色的filter。但是必須要疊很多層filter,才可以看到長(zhǎng)時(shí)的資訊。所以今天有一個(gè)想法:self-attention,如下圖3所示,目的是使用self-attention layer取代RNN所做的事情。

圖3:You can try to replace any thing that has been done by RNNwith self attention

所以重點(diǎn)是:我們有一種新的layer,叫self-attention,它的輸入和輸出和RNN是一模一樣的,輸入一個(gè)sequence,輸出一個(gè)sequence,它的每一個(gè)輸出?都看過(guò)了整個(gè)的輸入sequence,這一點(diǎn)與bi-directional RNN相同。但是神奇的地方是:它的每一個(gè)輸出?可以并行化計(jì)算。

  • 1.2 Self-attention:

那么self-attention具體是怎么做的呢?

圖4:self-attention具體是怎么做的?

首先假設(shè)我們的input是圖4的?,是一個(gè)sequence,每一個(gè)input (vector)先乘上一個(gè)矩陣?得到embedding,即向量?。接著這個(gè)embedding進(jìn)入self-attention層,每一個(gè)向量?分別乘上3個(gè)不同的transformation matrix?,以向量?為例,分別得到3個(gè)不同的向量?。

圖5:self-attention具體是怎么做的?

接下來(lái)使用每個(gè)query?去對(duì)每個(gè)key?做attention,attention就是匹配這2個(gè)向量有多接近,比如我現(xiàn)在要對(duì)?和?做attention,我就可以把這2個(gè)向量做scaled inner product,得到?。接下來(lái)你再拿?和?做attention,得到?,你再拿?和?做attention,得到?,你再拿?和?做attention,得到?。那這個(gè)scaled inner product具體是怎么計(jì)算的呢?

式中,?是?跟?的維度。因?yàn)?的數(shù)值會(huì)隨著dimension的增大而增大,所以要除以?的值,相當(dāng)于歸一化的效果。

接下來(lái)要做的事如圖6所示,把計(jì)算得到的所有?值取?操作。

圖6:self-attention具體是怎么做的?

取完?操作以后,我們得到了?,我們用它和所有的?值進(jìn)行相乘。具體來(lái)講,把?乘上?,把?乘上?,把?乘上?,把?乘上?,把結(jié)果通通加起來(lái)得到?,所以,今天在產(chǎn)生?的過(guò)程中用了整個(gè)sequence的資訊 (Considering the whole sequence)。如果要考慮local的information,則只需要學(xué)習(xí)出相應(yīng)的?,?就不再帶有那個(gè)對(duì)應(yīng)分支的信息了;如果要考慮global的information,則只需要學(xué)習(xí)出相應(yīng)的?,?就帶有全部的對(duì)應(yīng)分支的信息了。

圖7:self-attention具體是怎么做的?

同樣的方法,也可以計(jì)算出?,如下圖8所示,?就是拿query?去對(duì)其他的?做attention,得到?,再與value值?相乘取weighted sum得到的。

圖8:self-attention具體是怎么做的?

經(jīng)過(guò)了以上一連串計(jì)算,self-attention layer做的事情跟RNN是一樣的,只是它可以并行的得到layer輸出的結(jié)果,如圖9所示?,F(xiàn)在我們要用矩陣表示上述的計(jì)算過(guò)程。

圖9:self-attention的效果

首先輸入的embedding是?,然后用?乘以transformation matrix?得到?,它的每一列代表著一個(gè)vector?。同理,用?乘以transformation matrix?得到?,它的每一列代表著一個(gè)vector?。用?乘以transformation matrix?得到?,它的每一列代表著一個(gè)vector?。

圖10:self-attention的矩陣計(jì)算過(guò)程

接下來(lái)是?與?的attention過(guò)程,我們可以把vector?橫過(guò)來(lái)變成行向量,與列向量?做內(nèi)積,這里省略了?。這樣,?就成為了?的矩陣,它由4個(gè)行向量拼成的矩陣和4個(gè)列向量拼成的矩陣做內(nèi)積得到,如圖11所示。

在得到?以后,如上文所述,要得到?, 就要使用?分別與?相乘再求和得到,所以?要再左乘?矩陣。

圖11:self-attention的矩陣計(jì)算過(guò)程

到這里你會(huì)發(fā)現(xiàn)這個(gè)過(guò)程可以被表示為,如圖12所示:輸入矩陣?分別乘上3個(gè)不同的矩陣?得到3個(gè)中間矩陣?。它們的維度是相同的。把?轉(zhuǎn)置之后與?相乘得到Attention矩陣?,代表每一個(gè)位置兩兩之間的attention。再將它取?操作得到?,最后將它乘以?矩陣得到輸出vector?。

圖12:self-attention就是一堆矩陣乘法,可以實(shí)現(xiàn)GPU加速
  • 1.3 Multi-head Self-attention:

還有一種multi-head的self-attention,以2個(gè)head的情況為例:由?生成的?進(jìn)一步乘以2個(gè)轉(zhuǎn)移矩陣變?yōu)?和?,同理由?生成的?進(jìn)一步乘以2個(gè)轉(zhuǎn)移矩陣變?yōu)?和?,由?生成的?進(jìn)一步乘以2個(gè)轉(zhuǎn)移矩陣變?yōu)?和?。接下來(lái)?再與?做attention,得到weighted sum的權(quán)重?,再與?做weighted sum得到最終的?。同理得到??,F(xiàn)在我們有了?和?,可以把它們concat起來(lái),再通過(guò)一個(gè)transformation matrix調(diào)整維度,使之與剛才的?維度一致(這步如圖13所示)。

圖13:multi-head self-attention圖13:調(diào)整b的維度

從下圖14可以看到 Multi-Head Attention 包含多個(gè) Self-Attention 層,首先將輸入?分別傳遞到 2個(gè)不同的 Self-Attention 中,計(jì)算得到 2 個(gè)輸出結(jié)果。得到2個(gè)輸出矩陣之后,Multi-Head Attention 將它們拼接在一起 (Concat),然后傳入一個(gè)Linear層,得到 Multi-Head Attention 最終的輸出???梢钥吹?Multi-Head Attention 輸出的矩陣?與其輸入的矩陣?的維度是一樣的。

圖14:multi-head self-attention

這里有一組Multi-head Self-attention的解果,其中綠色部分是一組query和key,紅色部分是另外一組query和key,可以發(fā)現(xiàn)綠色部分其實(shí)更關(guān)注global的信息,而紅色部分其實(shí)更關(guān)注local的信息。

圖15:Multi-head Self-attention的不同head分別關(guān)注了global和local的訊息
  • 1.4 Positional Encoding:

以上是multi-head self-attention的原理,但是還有一個(gè)問(wèn)題是:現(xiàn)在的self-attention中沒(méi)有位置的信息,一個(gè)單詞向量的“近在咫尺”位置的單詞向量和“遠(yuǎn)在天涯”位置的單詞向量效果是一樣的,沒(méi)有表示位置的信息(No position information in self attention)。所以你輸入"A打了B"或者"B打了A"的效果其實(shí)是一樣的,因?yàn)椴](méi)有考慮位置的信息。所以在self-attention原來(lái)的paper中,作者為了解決這個(gè)問(wèn)題所做的事情是如下圖16所示:

圖16:self-attention中的位置編碼

具體的做法是:給每一個(gè)位置規(guī)定一個(gè)表示位置信息的向量?,讓它與?加在一起之后作為新的?參與后面的運(yùn)算過(guò)程,但是這個(gè)向量?是由人工設(shè)定的,而不是神經(jīng)網(wǎng)絡(luò)學(xué)習(xí)出來(lái)的。每一個(gè)位置都有一個(gè)不同的?。

那到這里一個(gè)自然而然的問(wèn)題是:為什么是?與?相加?為什么不是concatenate?加起來(lái)以后,原來(lái)表示位置的資訊不就混到?里面去了嗎?不就很難被找到了嗎?

這里提供一種解答這個(gè)問(wèn)題的思路:

如圖15所示,我們先給每一個(gè)位置的?append一個(gè)one-hot編碼的向量?,得到一個(gè)新的輸入向量?,這個(gè)向量作為新的輸入,乘以一個(gè)transformation matrix?。那么:

所以,與?相加就等同于把原來(lái)的輸入?concat一個(gè)表示位置的獨(dú)熱編碼?,再做transformation。

這個(gè)與位置編碼乘起來(lái)的矩陣?是手工設(shè)計(jì)的,如圖17所示。

圖17:與位置編碼乘起來(lái)的轉(zhuǎn)移矩陣WP

Transformer 中除了單詞的 Embedding,還需要使用位置 Embedding 表示單詞出現(xiàn)在句子中的位置。因?yàn)?Transformer 不采用 RNN 的結(jié)構(gòu),而是使用全局信息,不能利用單詞的順序信息,而這部分信息對(duì)于 NLP 來(lái)說(shuō)非常重要。所以 Transformer 中使用位置 Embedding 保存單詞在序列中的相對(duì)或絕對(duì)位置。

位置 Embedding 用 PE表示,PE 的維度與單詞 Embedding 是一樣的。PE 可以通過(guò)訓(xùn)練得到,也可以使用某種公式計(jì)算得到。在 Transformer 中采用了后者,計(jì)算公式如下:

式中,?表示token在sequence中的位置,例如第一個(gè)token "我" 的?。

,或者準(zhǔn)確意義上是?和?表示了Positional Encoding的維度,的取值范圍是:?。所以當(dāng)?為1時(shí),對(duì)應(yīng)的Positional Encoding可以寫(xiě)成:

式中,?。底數(shù)是10000。為什么要使用10000呢,這個(gè)就類似于玄學(xué)了,原論文中完全沒(méi)有提啊,這里不得不說(shuō)說(shuō)論文的readability的問(wèn)題,即便是很多高引的文章,最基本的內(nèi)容都討論不清楚,所以才出現(xiàn)像上面提問(wèn)里的討論,說(shuō)實(shí)話這些論文還遠(yuǎn)遠(yuǎn)沒(méi)有做到easy to follow。這里我給出一個(gè)假想:是一個(gè)比較接近1的數(shù)(1.018),如果用100000,則是1.023。這里只是猜想一下,其實(shí)大家應(yīng)該完全可以使用另一個(gè)底數(shù)。

這個(gè)式子的好處是:

  • 每個(gè)位置有一個(gè)唯一的positional encoding。

  • 使?能夠適應(yīng)比訓(xùn)練集里面所有句子更長(zhǎng)的句子,假設(shè)訓(xùn)練集里面最長(zhǎng)的句子是有 20 個(gè)單詞,突然來(lái)了一個(gè)長(zhǎng)度為 21 的句子,則使用公式計(jì)算的方法可以計(jì)算出第 21 位的 Embedding。

  • 可以讓模型容易地計(jì)算出相對(duì)位置,對(duì)于固定長(zhǎng)度的間距?,任意位置的?都可以被?的線性函數(shù)表示,因?yàn)槿呛瘮?shù)特性:

接下來(lái)我們看看self-attention在sequence2sequence model里面是怎么使用的,我們可以把Encoder-Decoder中的RNN用self-attention取代掉。

圖18:Seq2seq with Attention

2 Transformer的實(shí)現(xiàn)和代碼解讀

  • 2.1 Transformer原理分析:

圖19:Transformer

Encoder:

這個(gè)圖19講的是一個(gè)seq2seq的model,左側(cè)為 Encoder block,右側(cè)為 Decoder block。紅色圈中的部分為Multi-Head Attention,是由多個(gè)Self-Attention組成的,可以看到 Encoder block 包含一個(gè) Multi-Head Attention,而 Decoder block 包含兩個(gè) Multi-Head Attention (其中有一個(gè)用到 Masked)。Multi-Head Attention 上方還包括一個(gè) Add & Norm 層,Add 表示殘差連接 (Residual Connection) 用于防止網(wǎng)絡(luò)退化,Norm 表示 Layer Normalization,用于對(duì)每一層的激活值進(jìn)行歸一化。比如說(shuō)在Encoder Input處的輸入是機(jī)器學(xué)習(xí),在Decoder Input處的輸入是<BOS>,輸出是machine。再下一個(gè)時(shí)刻在Decoder Input處的輸入是machine,輸出是learning。不斷重復(fù)知道輸出是句點(diǎn)(.)代表翻譯結(jié)束。

接下來(lái)我們看看這個(gè)Encoder和Decoder里面分別都做了什么事情,先看左半部分的Encoder:首先輸入?通過(guò)一個(gè)Input Embedding的轉(zhuǎn)移矩陣?變?yōu)榱艘粋€(gè)張量,即上文所述的?,再加上一個(gè)表示位置的Positional Encoding?,得到一個(gè)張量,去往后面的操作。

它進(jìn)入了這個(gè)綠色的block,這個(gè)綠色的block會(huì)重復(fù)?次。這個(gè)綠色的block里面有什么呢?它的第1層是一個(gè)上文講的multi-head的attention。你現(xiàn)在一個(gè)sequence,經(jīng)過(guò)一個(gè)multi-head的attention,你會(huì)得到另外一個(gè)sequence?。

下一個(gè)Layer是Add & Norm,這個(gè)意思是說(shuō):把multi-head的attention的layer的輸入?和輸出?進(jìn)行相加以后,再做Layer Normalization,至于Layer Normalization和我們熟悉的Batch Normalization的區(qū)別是什么,請(qǐng)參考圖20和21。

圖20:不同Normalization方法的對(duì)比

其中,Batch Normalization和Layer Normalization的對(duì)比可以概括為圖20,Batch Normalization強(qiáng)行讓一個(gè)batch的數(shù)據(jù)的某個(gè)channel的?,而Layer Normalization讓一個(gè)數(shù)據(jù)的所有channel的?。

圖21:Batch Normalization和Layer Normalization的對(duì)比

接著是一個(gè)Feed Forward的前饋網(wǎng)絡(luò)和一個(gè)Add & Norm Layer。

所以,這一個(gè)綠色的block的前2個(gè)Layer操作的表達(dá)式為:

這一個(gè)綠色的block的后2個(gè)Layer操作的表達(dá)式為:

所以Transformer的Encoder的整體操作為:

Decoder:

現(xiàn)在來(lái)看Decoder的部分,輸入包括2部分,下方是前一個(gè)time step的輸出的embedding,即上文所述的?,再加上一個(gè)表示位置的Positional Encoding?,得到一個(gè)張量,去往后面的操作。它進(jìn)入了這個(gè)綠色的block,這個(gè)綠色的block會(huì)重復(fù)?次。這個(gè)綠色的block里面有什么呢?

首先是Masked Multi-Head Self-attention,masked的意思是使attention只會(huì)attend on已經(jīng)產(chǎn)生的sequence,這個(gè)很合理,因?yàn)檫€沒(méi)有產(chǎn)生出來(lái)的東西不存在,就無(wú)法做attention。

輸出是: 對(duì)應(yīng)?位置的輸出詞的概率分布。

輸入是:?的輸出對(duì)應(yīng)?位置decoder的輸出。所以中間的attention不是self-attention,它的Key和Value來(lái)自encoder,Query來(lái)自上一位置?的輸出。

解碼:這里要特別注意一下,編碼可以并行計(jì)算,一次性全部Encoding出來(lái),但解碼不是一次把所有序列解出來(lái)的,而是像?一樣一個(gè)一個(gè)解出來(lái)的,因?yàn)橐蒙弦粋€(gè)位置的輸入當(dāng)作attention的query。

明確了解碼過(guò)程之后最上面的圖就很好懂了,這里主要的不同就是新加的另外要說(shuō)一下新加的attention多加了一個(gè)mask,因?yàn)橛?xùn)練時(shí)的output都是Ground Truth,這樣可以確保預(yù)測(cè)第?個(gè)位置時(shí)不會(huì)接觸到未來(lái)的信息。

  • 包含兩個(gè) Multi-Head Attention 層。

  • 第一個(gè) Multi-Head Attention 層采用了 Masked 操作。

  • 第二個(gè) Multi-Head Attention 層的Key,Value矩陣使用 Encoder 的編碼信息矩陣?進(jìn)行計(jì)算,而Query使用上一個(gè) Decoder block 的輸出計(jì)算。

  • 最后有一個(gè) Softmax 層計(jì)算下一個(gè)翻譯單詞的概率。

下面詳細(xì)介紹下Masked Multi-Head Self-attention的具體操作,Masked在Scale操作之后,softmax操作之前

圖22:Masked在Scale操作之后,softmax操作之前

因?yàn)樵诜g的過(guò)程中是順序翻譯的,即翻譯完第?個(gè)單詞,才可以翻譯第?個(gè)單詞。通過(guò) Masked 操作可以防止第?個(gè)單詞知道第?個(gè)單詞之后的信息。下面以 "我有一只貓" 翻譯成 "I have a cat" 為例,了解一下 Masked 操作。在 Decoder 的時(shí)候,是需要根據(jù)之前的翻譯,求解當(dāng)前最有可能的翻譯,如下圖所示。首先根據(jù)輸入 "<Begin>" 預(yù)測(cè)出第一個(gè)單詞為 "I",然后根據(jù)輸入 "<Begin> I" 預(yù)測(cè)下一個(gè)單詞 "have"。

Decoder 可以在訓(xùn)練的過(guò)程中使用 Teacher Forcing 并且并行化訓(xùn)練,即將正確的單詞序列 (<Begin> I have a cat) 和對(duì)應(yīng)輸出 (I have a cat <end>) 傳遞到 Decoder。那么在預(yù)測(cè)第?個(gè)輸出時(shí),就要將第? 之后的單詞掩蓋住,注意 Mask 操作是在 Self-Attention 的 Softmax 之前使用的,下面用 0 1 2 3 4 5 分別表示 "<Begin> I have a cat <end>"。

圖23:Decoder過(guò)程

注意這里transformer模型訓(xùn)練和測(cè)試的方法不同:

測(cè)試時(shí):

  • 輸入<Begin>,解碼器輸出 I 。

  • 輸入前面已經(jīng)解碼的<Begin>和 I,解碼器輸出have。

  • 輸入已經(jīng)解碼的<Begin>,I, have, a, cat,解碼器輸出解碼結(jié)束標(biāo)志位<end>,每次解碼都會(huì)利用前面已經(jīng)解碼輸出的所有單詞嵌入信息。

  • Transformer測(cè)試時(shí)的解碼過(guò)程:

    訓(xùn)練時(shí):

    不采用上述類似RNN的方法 一個(gè)一個(gè)目標(biāo)單詞嵌入向量順序輸入訓(xùn)練,想采用類似編碼器中的矩陣并行算法,一步就把所有目標(biāo)單詞預(yù)測(cè)出來(lái)。要實(shí)現(xiàn)這個(gè)功能就可以參考編碼器的操作,把目標(biāo)單詞嵌入向量組成矩陣一次輸入即可。即:并行化訓(xùn)練。

    但是在解碼have時(shí)候,不能利用到后面單詞a和cat的目標(biāo)單詞嵌入向量信息,否則這就是作弊(測(cè)試時(shí)候不可能能未卜先知)。為此引入mask。具體是:在解碼器中,self-attention層只被允許處理輸出序列中更靠前的那些位置,在softmax步驟前,它會(huì)把后面的位置給隱去。

    Masked Multi-Head Self-attention的具體操作 如圖24所示。

    Step1: 輸入矩陣包含 "<Begin> I have a cat" (0, 1, 2, 3, 4) 五個(gè)單詞的表示向量,Mask是一個(gè) 5×5 的矩陣。在Mask可以發(fā)現(xiàn)單詞 0 只能使用單詞 0 的信息,而單詞 1 可以使用單詞 0, 1 的信息,即只能使用之前的信息。輸入矩陣?經(jīng)過(guò)transformation matrix變?yōu)?個(gè)矩陣:Query,Key?和Value?。

    Step2:?得到 Attention矩陣?,此時(shí)先不急于做softmax的操作,而是先于一個(gè)?矩陣相乘,使得attention矩陣的有些位置 歸0,得到Masked Attention矩陣?。?矩陣是個(gè)下三角矩陣,為什么這樣設(shè)計(jì)?是因?yàn)橄朐谟?jì)算?矩陣的某一行時(shí),只考慮它前面token的作用。即:在計(jì)算?的第一行時(shí),刻意地把?矩陣第一行的后面幾個(gè)元素屏蔽掉,只考慮?。在產(chǎn)生have這個(gè)單詞時(shí),只考慮 I,不考慮之后的have a cat,即只會(huì)attend on已經(jīng)產(chǎn)生的sequence,這個(gè)很合理,因?yàn)檫€沒(méi)有產(chǎn)生出來(lái)的東西不存在,就無(wú)法做attention。

    Step3: Masked Attention矩陣進(jìn)行 Softmax,每一行的和都為 1。但是單詞 0 在單詞 1, 2, 3, 4 上的 attention score 都為 0。得到的結(jié)果再與?矩陣相乘得到最終的self-attention層的輸出結(jié)果?。

    Step4:?只是某一個(gè)head的結(jié)果,將多個(gè)head的結(jié)果concat在一起之后再最后進(jìn)行Linear Transformation得到最終的Masked Multi-Head Self-attention的輸出結(jié)果?。

    圖24:Masked Multi-Head Self-attention的具體操作

    第1個(gè)Masked Multi-Head Self-attention的?均來(lái)自O(shè)utput Embedding。

    第2個(gè)Multi-Head Self-attention的?來(lái)自第1個(gè)Self-attention layer的輸出,?來(lái)自Encoder的輸出。

    為什么這么設(shè)計(jì)? 這里提供一種個(gè)人的理解:

    來(lái)自Transformer Encoder的輸出,所以可以看做句子(Sequence)/圖片(image)內(nèi)容信息(content,比如句意是:"我有一只貓",圖片內(nèi)容是:"有幾輛車,幾個(gè)人等等")。

    表達(dá)了一種訴求:希望得到什么,可以看做引導(dǎo)信息(guide)。

    通過(guò)Multi-Head Self-attention結(jié)合在一起的過(guò)程就相當(dāng)于是把我們需要的內(nèi)容信息指導(dǎo)表達(dá)出來(lái)。

    Decoder的最后是Softmax 預(yù)測(cè)輸出單詞。因?yàn)?Mask 的存在,使得單詞 0 的輸出?只包含單詞 0 的信息。Softmax 根據(jù)輸出矩陣的每一行預(yù)測(cè)下一個(gè)單詞,如下圖25所示。

    圖25:Softmax 根據(jù)輸出矩陣的每一行預(yù)測(cè)下一個(gè)單詞

    如下圖26所示為Transformer的整體結(jié)構(gòu)。

    圖26:Transformer的整體結(jié)構(gòu)
    • 2.2 Transformer代碼解讀:

    代碼來(lái)自:

    https://github.com/jadore801120/attention-is-all-you-need-pytorch

    ScaledDotProductAttention:
    實(shí)現(xiàn)的是圖22的操作,先令?,再對(duì)結(jié)果按位乘以?矩陣,再做?操作,最后的結(jié)果與?相乘,得到self-attention的輸出。

    class ScaledDotProductAttention(nn.Module): ''' Scaled Dot-Product Attention '''def __init__(self, temperature, attn_dropout=0.1): super().__init__() self.temperature = temperature self.dropout = nn.Dropout(attn_dropout)def forward(self, q, k, v, mask=None):attn = torch.matmul(q / self.temperature, k.transpose(2, 3))if mask is not None: attn = attn.masked_fill(mask == 0, -1e9)attn = self.dropout(F.softmax(attn, dim=-1)) output = torch.matmul(attn, v)return output, attn

    位置編碼 PositionalEncoding:
    實(shí)現(xiàn)的是式(5)的位置編碼。

    class PositionalEncoding(nn.Module):def __init__(self, d_hid, n_position=200): super(PositionalEncoding, self).__init__()# Not a parameter self.register_buffer('pos_table', self._get_sinusoid_encoding_table(n_position, d_hid))def _get_sinusoid_encoding_table(self, n_position, d_hid): ''' Sinusoid position encoding table ''' # TODO: make it with torch instead of numpydef get_position_angle_vec(position): return [position / np.power(10000, 2 * (hid_j // 2) / d_hid) for hid_j in range(d_hid)]sinusoid_table = np.array([get_position_angle_vec(pos_i) for pos_i in range(n_position)]) sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2]) # dim 2i sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2]) # dim 2i+1return torch.FloatTensor(sinusoid_table).unsqueeze(0)#(1,N,d)def forward(self, x): # x(B,N,d) return x + self.pos_table[:, :x.size(1)].clone().detach()

    MultiHeadAttention:
    實(shí)現(xiàn)圖13,14的多頭self-attention。

    class MultiHeadAttention(nn.Module): ''' Multi-Head Attention module '''def __init__(self, n_head, d_model, d_k, d_v, dropout=0.1): super().__init__()self.n_head = n_head self.d_k = d_k self.d_v = d_vself.w_qs = nn.Linear(d_model, n_head * d_k, bias=False) self.w_ks = nn.Linear(d_model, n_head * d_k, bias=False) self.w_vs = nn.Linear(d_model, n_head * d_v, bias=False) self.fc = nn.Linear(n_head * d_v, d_model, bias=False)self.attention = ScaledDotProductAttention(temperature=d_k ** 0.5)self.dropout = nn.Dropout(dropout) self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)def forward(self, q, k, v, mask=None):d_k, d_v, n_head = self.d_k, self.d_v, self.n_head sz_b, len_q, len_k, len_v = q.size(0), q.size(1), k.size(1), v.size(1)residual = q# Pass through the pre-attention projection: b x lq x (n*dv) # Separate different heads: b x lq x n x dv q = self.w_qs(q).view(sz_b, len_q, n_head, d_k) k = self.w_ks(k).view(sz_b, len_k, n_head, d_k) v = self.w_vs(v).view(sz_b, len_v, n_head, d_v)# Transpose for attention dot product: b x n x lq x dv q, k, v = q.transpose(1, 2), k.transpose(1, 2), v.transpose(1, 2)if mask is not None: mask = mask.unsqueeze(1) # For head axis broadcasting.q, attn = self.attention(q, k, v, mask=mask)#q (sz_b,n_head,N=len_q,d_k) #k (sz_b,n_head,N=len_k,d_k) #v (sz_b,n_head,N=len_v,d_v)# Transpose to move the head dimension back: b x lq x n x dv # Combine the last two dimensions to concatenate all the heads together: b x lq x (n*dv) q = q.transpose(1, 2).contiguous().view(sz_b, len_q, -1)#q (sz_b,len_q,n_head,N * d_k) q = self.dropout(self.fc(q)) q += residualq = self.layer_norm(q)return q, attn

    前向傳播Feed Forward Network:

    class PositionwiseFeedForward(nn.Module): ''' A two-feed-forward-layer module '''def __init__(self, d_in, d_hid, dropout=0.1): super().__init__() self.w_1 = nn.Linear(d_in, d_hid) # position-wise self.w_2 = nn.Linear(d_hid, d_in) # position-wise self.layer_norm = nn.LayerNorm(d_in, eps=1e-6) self.dropout = nn.Dropout(dropout)def forward(self, x):residual = xx = self.w_2(F.relu(self.w_1(x))) x = self.dropout(x) x += residualx = self.layer_norm(x)return x

    EncoderLayer:
    實(shí)現(xiàn)圖26中的一個(gè)EncoderLayer,具體的結(jié)構(gòu)如圖19所示。

    class EncoderLayer(nn.Module): ''' Compose with two layers '''def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1): super(EncoderLayer, self).__init__() self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout) self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)def forward(self, enc_input, slf_attn_mask=None): enc_output, enc_slf_attn = self.slf_attn( enc_input, enc_input, enc_input, mask=slf_attn_mask) enc_output = self.pos_ffn(enc_output) return enc_output, enc_slf_attn

    DecoderLayer:
    實(shí)現(xiàn)圖26中的一個(gè)DecoderLayer,具體的結(jié)構(gòu)如圖19所示。

    class DecoderLayer(nn.Module): ''' Compose with three layers '''def __init__(self, d_model, d_inner, n_head, d_k, d_v, dropout=0.1): super(DecoderLayer, self).__init__() self.slf_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout) self.enc_attn = MultiHeadAttention(n_head, d_model, d_k, d_v, dropout=dropout) self.pos_ffn = PositionwiseFeedForward(d_model, d_inner, dropout=dropout)def forward( self, dec_input, enc_output, slf_attn_mask=None, dec_enc_attn_mask=None): dec_output, dec_slf_attn = self.slf_attn( dec_input, dec_input, dec_input, mask=slf_attn_mask) dec_output, dec_enc_attn = self.enc_attn( dec_output, enc_output, enc_output, mask=dec_enc_attn_mask) dec_output = self.pos_ffn(dec_output) return dec_output, dec_slf_attn, dec_enc_attn

    Encoder:
    實(shí)現(xiàn)圖26,19左側(cè)的Encoder:

    class Encoder(nn.Module): ''' A encoder model with self attention mechanism. '''def __init__( self, n_src_vocab, d_word_vec, n_layers, n_head, d_k, d_v, d_model, d_inner, pad_idx, dropout=0.1, n_position=200):super().__init__()self.src_word_emb = nn.Embedding(n_src_vocab, d_word_vec, padding_idx=pad_idx) self.position_enc = PositionalEncoding(d_word_vec, n_position=n_position) self.dropout = nn.Dropout(p=dropout) self.layer_stack = nn.ModuleList([ EncoderLayer(d_model, d_inner, n_head, d_k, d_v, dropout=dropout) for _ in range(n_layers)]) self.layer_norm = nn.LayerNorm(d_model, eps=1e-6)def forward(self, src_seq, src_mask, return_attns=False):enc_slf_attn_list = []# -- Forwardenc_output = self.dropout(self.position_enc(self.src_word_emb(src_seq))) enc_output = self.layer_norm(enc_output)for enc_layer in self.layer_stack: enc_output, enc_slf_attn = enc_layer(enc_output, slf_attn_mask=src_mask) enc_slf_attn_list += [enc_slf_attn] if return_attns else []if return_attns: return enc_output, enc_slf_attn_list return enc_output,

    Decoder:
    實(shí)現(xiàn)圖26,19右側(cè)的Decoder:

    class Decoder(nn.Module): ''' A decoder model with self attention mechanism. '''def forward(self, trg_seq, trg_mask, enc_output, src_mask, return_attns=False):dec_slf_attn_list, dec_enc_attn_list = [], []# -- Forward dec_output = self.dropout(self.position_enc(self.trg_word_emb(trg_seq))) dec_output = self.layer_norm(dec_output)for dec_layer in self.layer_stack: dec_output, dec_slf_attn, dec_enc_attn = dec_layer( dec_output, enc_output, slf_attn_mask=trg_mask, dec_enc_attn_mask=src_mask) dec_slf_attn_list += [dec_slf_attn] if return_attns else [] dec_enc_attn_list += [dec_enc_attn] if return_attns else []if return_attns: return dec_output, dec_slf_attn_list, dec_enc_attn_list return dec_output,

    整體結(jié)構(gòu):
    實(shí)現(xiàn)圖26,19整體的Transformer:

    class Transformer(nn.Module): ''' A sequence to sequence model with attention mechanism. '''def __init__( self, n_src_vocab, n_trg_vocab, src_pad_idx, trg_pad_idx, d_word_vec=512, d_model=512, d_inner=2048, n_layers=6, n_head=8, d_k=64, d_v=64, dropout=0.1, n_position=200, trg_emb_prj_weight_sharing=True, emb_src_trg_weight_sharing=True):super().__init__()self.src_pad_idx, self.trg_pad_idx = src_pad_idx, trg_pad_idxself.encoder = Encoder( n_src_vocab=n_src_vocab, n_position=n_position, d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner, n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v, pad_idx=src_pad_idx, dropout=dropout)self.decoder = Decoder( n_trg_vocab=n_trg_vocab, n_position=n_position, d_word_vec=d_word_vec, d_model=d_model, d_inner=d_inner, n_layers=n_layers, n_head=n_head, d_k=d_k, d_v=d_v, pad_idx=trg_pad_idx, dropout=dropout)self.trg_word_prj = nn.Linear(d_model, n_trg_vocab, bias=False)for p in self.parameters(): if p.dim() > 1: nn.init.xavier_uniform_(p) assert d_model == d_word_vec, \ 'To facilitate the residual connections, \ the dimensions of all module outputs shall be the same.'self.x_logit_scale = 1. if trg_emb_prj_weight_sharing: # Share the weight between target word embedding & last dense layer self.trg_word_prj.weight = self.decoder.trg_word_emb.weight self.x_logit_scale = (d_model ** -0.5)if emb_src_trg_weight_sharing: self.encoder.src_word_emb.weight = self.decoder.trg_word_emb.weightdef forward(self, src_seq, trg_seq):src_mask = get_pad_mask(src_seq, self.src_pad_idx) trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)enc_output, *_ = self.encoder(src_seq, src_mask) dec_output, *_ = self.decoder(trg_seq, trg_mask, enc_output, src_mask) seq_logit = self.trg_word_prj(dec_output) * self.x_logit_scalereturn seq_logit.view(-1, seq_logit.size(2))

    產(chǎn)生Mask:

    def get_pad_mask(seq, pad_idx): return (seq != pad_idx).unsqueeze(-2)def get_subsequent_mask(seq): ''' For masking out the subsequent info. ''' sz_b, len_s = seq.size() subsequent_mask = (1 - torch.triu( torch.ones((1, len_s, len_s), device=seq.device), diagonal=1)).bool() return subsequent_mask

    src_mask = get_pad_mask(src_seq, self.src_pad_idx)
    用于產(chǎn)生Encoder的Mask,它是一列Bool值,負(fù)責(zé)把標(biāo)點(diǎn)mask掉。
    trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)
    用于產(chǎn)生Decoder的Mask。它是一個(gè)矩陣,如圖24中的Mask所示,功能已在上文介紹。

    3 Transformer+Detection:引入視覺(jué)領(lǐng)域的首創(chuàng)DETR

    論文名稱:End-to-End Object Detection with Transformers

    論文地址:

    https://arxiv.org/abs/2005.12872arxiv.org

    • 3.1 DETR原理分析:

    網(wǎng)絡(luò)架構(gòu)部分解讀

    本文的任務(wù)是Object detection,用到的工具是Transformers,特點(diǎn)是End-to-end。

    目標(biāo)檢測(cè)的任務(wù)是要去預(yù)測(cè)一系列的Bounding Box的坐標(biāo)以及Label, 現(xiàn)代大多數(shù)檢測(cè)器通過(guò)定義一些proposal,anchor或者windows,把問(wèn)題構(gòu)建成為一個(gè)分類和回歸問(wèn)題來(lái)間接地完成這個(gè)任務(wù)。文章所做的工作,就是將transformers運(yùn)用到了object detection領(lǐng)域,取代了現(xiàn)在的模型需要手工設(shè)計(jì)的工作,并且取得了不錯(cuò)的結(jié)果。在object detection上DETR準(zhǔn)確率和運(yùn)行時(shí)間上和Faster RCNN相當(dāng);將模型 generalize 到 panoptic segmentation 任務(wù)上,DETR表現(xiàn)甚至還超過(guò)了其他的baseline。DETR第一個(gè)使用End to End的方式解決檢測(cè)問(wèn)題,解決的方法是把檢測(cè)問(wèn)題視作是一個(gè)set prediction problem,如下圖27所示。

    圖27:DETR結(jié)合CNN和Transformer的結(jié)構(gòu),并行實(shí)現(xiàn)預(yù)測(cè)

    網(wǎng)絡(luò)的主要組成是CNN和Transformer,Transformer借助第1節(jié)講到的self-attention機(jī)制,可以顯式地對(duì)一個(gè)序列中的所有elements兩兩之間的interactions進(jìn)行建模,使得這類transformer的結(jié)構(gòu)非常適合帶約束的set prediction的問(wèn)題。DETR的特點(diǎn)是:一次預(yù)測(cè),端到端訓(xùn)練,set loss function和二分匹配。

    文章的主要有兩個(gè)關(guān)鍵的部分。

    第一個(gè)是用transformer的encoder-decoder架構(gòu)一次性生成?個(gè)box prediction。其中?是一個(gè)事先設(shè)定的、比遠(yuǎn)遠(yuǎn)大于image中object個(gè)數(shù)的一個(gè)整數(shù)。

    第二個(gè)是設(shè)計(jì)了bipartite matching loss,基于預(yù)測(cè)的boxex和ground truth boxes的二分圖匹配計(jì)算loss的大小,從而使得預(yù)測(cè)的box的位置和類別更接近于ground truth。

    DETR整體結(jié)構(gòu)可以分為四個(gè)部分:backbone,encoder,decoder和FFN,如下圖28所示,以下分別解釋這四個(gè)部分:

    圖28:DETR整體結(jié)構(gòu)

    1 首先看backbone: CNN backbone處理?維的圖像,把它轉(zhuǎn)換為維的feature map(一般來(lái)說(shuō)?或),backbone只做這一件事。

    2 再看encoder: encoder的輸入是維的feature map,接下來(lái)依次進(jìn)行以下過(guò)程:

    • 通道數(shù)壓縮: 先用?convolution處理,將channels數(shù)量從?壓縮到?,即得到維的新feature map。

    • 轉(zhuǎn)化為序列化數(shù)據(jù): 將空間的維度(高和寬)壓縮為一個(gè)維度,即把上一步得到的維的feature map通過(guò)reshape成維的feature map。

    • 位置編碼: 在得到了維的feature map之后,正式輸入encoder之前,需要進(jìn)行 Positional Encoding 。這一步在第2節(jié)講解transformer的時(shí)候已經(jīng)提到過(guò),因?yàn)?strong>在self-attention中需要有表示位置的信息,否則你的sequence = "A打了B" 還是sequence = "B打了A"的效果是一樣的。但是transformer encoder這個(gè)結(jié)構(gòu)本身卻無(wú)法體現(xiàn)出位置信息。也就是說(shuō),我們需要對(duì)這個(gè)?維的feature map做positional encoding。

    進(jìn)行完位置編碼以后根據(jù)paper中的圖片會(huì)有個(gè)相加的過(guò)程,如下圖問(wèn)號(hào)處所示。很多讀者有疑問(wèn)的地方是:論文圖示中相加的2個(gè)張量,一個(gè)是input embedding,另一個(gè)是位置編碼維度看上去不一致,是怎么相加的?后面會(huì)解答。

    圖:怎么相加的?

    原版Transformer和Vision Transformer (第4節(jié)講述)的Positional Encoding的表達(dá)式為:

    式中,?就是這個(gè)?維的feature map的第一維,?。表示token在sequence中的位置,sequence的長(zhǎng)度是?,例如第一個(gè)token 的?。

    ,或者準(zhǔn)確意義上是?和?表示了Positional Encoding的維度,的取值范圍是:?。所以當(dāng)?為1時(shí),對(duì)應(yīng)的Positional Encoding可以寫(xiě)成:

    式中,?。

    第一點(diǎn)不同的是,原版Transformer只考慮?方向的位置編碼,但是DETR考慮了?方向的位置編碼,因?yàn)閳D像特征是2-D特征。采用的依然是?模式,但是需要考慮?兩個(gè)方向。不是類似vision transoformer做法簡(jiǎn)單的將其拉伸為?,然后從?進(jìn)行長(zhǎng)度為256的位置編碼,而是考慮了?方向同時(shí)編碼,每個(gè)方向各編碼128維向量,這種編碼方式更符合圖像特點(diǎn)。

    Positional Encoding的輸出張量是:?,其中?代表位置編碼的長(zhǎng)度,?代表張量的位置。意思是說(shuō),這個(gè)特征圖上的任意一個(gè)點(diǎn)?有個(gè)位置編碼,這個(gè)編碼的長(zhǎng)度是256,其中,前128維代表?的位置編碼,后128維代表?的位置編碼。

    假設(shè)你想計(jì)算任意一個(gè)位置?的Positional Encoding,把?代入(11)式的?式和?式可以計(jì)算得到128維的向量,它代表?的位置編碼,再把?代入(11)式的?式和?式可以計(jì)算得到128維的向量,它代表?的位置編碼,把這2個(gè)128維的向量拼接起來(lái),就得到了一個(gè)256維的向量,它代表?的位置編碼。

    計(jì)算所有位置的編碼,就得到了?的張量,代表這個(gè)batch的位置編碼。編碼矩陣的維度是?,也把它序列化成維度為?維的張量。

    準(zhǔn)備與維的feature map相加以后輸入Encoder。

    值得注意的是,網(wǎng)上許多解讀文章沒(méi)有搞清楚 "轉(zhuǎn)化為序列化數(shù)據(jù)"這一步和 "位置編碼"的順序關(guān)系,以及變量的shape到底是怎樣變化的,這里我用一個(gè)圖表達(dá),終結(jié)這個(gè)問(wèn)題。

    圖29:變量的shape的變化,變量一律使用方塊表達(dá)。

    所以,了解了DETR的位置編碼之后,你應(yīng)該明白了其實(shí)input embedding和位置編碼維度其實(shí)是一樣的,只是論文圖示為了突出二位編碼所以畫(huà)的不一樣罷了,如下圖所示:

    圖:input embedding與positional embedding的shape是一致的

    另一點(diǎn)不同的是,原版Transformer 只在Encoder之前使用了Positional Encoding,而且是在輸入上進(jìn)行Positional Encoding,再把輸入經(jīng)過(guò)transformation matrix變?yōu)镼uery,Key和Value這幾個(gè)張量。但是DETR在Encoder的每一個(gè)Multi-head Self-attention之前都使用了Positional Encoding,且只對(duì)Query和Key使用了Positional Encoding,即:只把維度為維的位置編碼與維度為維的Query和Key相加,而不與Value相加。

    如圖30所示為DETR的Transformer的詳細(xì)結(jié)構(gòu),讀者可以對(duì)比下原版Transformer的結(jié)構(gòu),如圖19所示,為了閱讀的方便我把圖19又貼在下面了。

    可以發(fā)現(xiàn),除了Positional Encoding設(shè)置的不一樣外,Encoder其他的結(jié)構(gòu)是一致的。每個(gè)Encoder Layer包含一個(gè)multi-head self-attention 的module和一個(gè)前饋網(wǎng)絡(luò)Feed Forward Network。

    Encoder最終輸出的是?維的編碼矩陣Embedding,按照原版Transformer的做法,把這個(gè)東西給Decoder。

    總結(jié)下和原始transformer編碼器不同的地方:

    • 輸入編碼器的位置編碼需要考慮2-D空間位置。

    • 位置編碼向量需要加入到每個(gè)Encoder Layer中。

    • 在編碼器內(nèi)部位置編碼Positional Encoding僅僅作用于Query和Key,即只與Query和Key相加,Value不做任何處理。

    圖30:Transformer詳細(xì)結(jié)構(gòu)。為了方便理解,我把每個(gè)變量的維度標(biāo)在了圖上。圖19:Transformer整體結(jié)構(gòu)

    3 再看decoder:

    DETR的Decoder和原版Transformer的decoder是不太一樣的,如下圖30和19所示。

    先回憶下原版Transformer,看下圖19的decoder的最后一個(gè)框:output probability,代表我們一次只產(chǎn)生一個(gè)單詞的softmax,根據(jù)這個(gè)softmax得到這個(gè)單詞的預(yù)測(cè)結(jié)果。這個(gè)過(guò)程我們表達(dá)為:predicts the output sequence one element at a time。

    不同的是,DETR的Transformer Decoder是一次性處理全部的object queries,即一次性輸出全部的predictions;而不像原始的Transformer是auto-regressive的,從左到右一個(gè)詞一個(gè)詞地輸出。這個(gè)過(guò)程我們表達(dá)為:decodes the N objects in parallel at each decoder layer。

    DETR的Decoder主要有兩個(gè)輸入:

  • Transformer Encoder輸出的Embedding與 position encoding 之和。

  • Object queries。

  • 其中,Embedding就是上文提到的?的編碼矩陣。這里著重講一下Object queries。

    Object queries是一個(gè)維度為?維的張量,數(shù)值類型是nn.Embedding,說(shuō)明這個(gè)張量是可以學(xué)習(xí)的,即:我們的Object queries是可學(xué)習(xí)的。Object queries矩陣內(nèi)部通過(guò)學(xué)習(xí)建模了100個(gè)物體之間的全局關(guān)系,例如房間里面的桌子旁邊(A類)一般是放椅子(B類),而不會(huì)是放一頭大象(C類),那么在推理時(shí)候就可以利用該全局注意力更好的進(jìn)行解碼預(yù)測(cè)輸出。

    Decoder的輸入一開(kāi)始也初始化成維度為?維的全部元素都為0的張量,和Object queries加在一起之后充當(dāng)?shù)?個(gè)multi-head self-attention的Query和Key。第一個(gè)multi-head self-attention的Value為Decoder的輸入,也就是全0的張量。

    到了每個(gè)Decoder的第2個(gè)multi-head self-attention,它的Key和Value來(lái)自Encoder的輸出張量,維度為?,其中Key值還進(jìn)行位置編碼。Query值一部分來(lái)自第1個(gè)Add and Norm的輸出,維度為?的張量,另一部分來(lái)自O(shè)bject queries,充當(dāng)可學(xué)習(xí)的位置編碼。所以,第2個(gè)multi-head self-attention的Key和Value的維度為?,而Query的維度為。

    每個(gè)Decoder的輸出維度為?,送入后面的前饋網(wǎng)絡(luò),具體的變量維度的變化見(jiàn)圖30。

    到這里你會(huì)發(fā)現(xiàn):Object queries充當(dāng)?shù)钠鋵?shí)是位置編碼的作用,只不過(guò)它是可以學(xué)習(xí)的位置編碼,所以,我們對(duì)Encoder和Decoder的每個(gè)self-attention的Query和Key的位置編碼做個(gè)歸納,如圖31所示,Value沒(méi)有位置編碼:

    圖31:Transformer的位置編碼來(lái)自哪里?

    損失函數(shù)部分解讀

    得到了Decoder的輸出以后,如前文所述,應(yīng)該是輸出維度為?的張量。接下來(lái)要送入2個(gè)前饋網(wǎng)絡(luò)FFN得到class和Bounding Box。它們會(huì)得到?個(gè)預(yù)測(cè)目標(biāo),包含類別和Bounding Box,當(dāng)然這個(gè)100肯定是大于圖中的目標(biāo)總數(shù)的。如果不夠100,則采用背景填充,計(jì)算loss時(shí)候回歸分支分支僅僅計(jì)算有物體位置,背景集合忽略。所以,DETR輸出張量的維度為輸出的張量的維度是?和?。對(duì)應(yīng)COCO數(shù)據(jù)集來(lái)說(shuō),?,?指的是每個(gè)預(yù)測(cè)目標(biāo)歸一化的?。歸一化就是除以圖片寬高進(jìn)行歸一化。

    到這里我們了解了DETR的網(wǎng)絡(luò)架構(gòu),我們發(fā)現(xiàn),它輸出的張量的維度是 分類分支:回歸分支:?,其中,前者是指100個(gè)預(yù)測(cè)框的類型,后者是指100個(gè)預(yù)測(cè)框的Bounding Box,但是讀者可能會(huì)有疑問(wèn):預(yù)測(cè)框和真值是怎么一一對(duì)應(yīng)的?換句話說(shuō):你怎么知道第47個(gè)預(yù)測(cè)框?qū)?yīng)圖片里的狗,第88個(gè)預(yù)測(cè)框?qū)?yīng)圖片里的車?等等。

    我們下面就來(lái)聊聊這個(gè)問(wèn)題。

    相比Faster R-CNN等做法,DETR最大特點(diǎn)是將目標(biāo)檢測(cè)問(wèn)題轉(zhuǎn)化為無(wú)序集合預(yù)測(cè)問(wèn)題(set prediction)。論文中特意指出Faster R-CNN這種設(shè)置一大堆a(bǔ)nchor,然后基于anchor進(jìn)行分類和回歸其實(shí)屬于代理做法即不是最直接做法,目標(biāo)檢測(cè)任務(wù)就是輸出無(wú)序集合,而Faster R-CNN等算法通過(guò)各種操作,并結(jié)合復(fù)雜后處理最終才得到無(wú)序集合屬于繞路了,而DETR就比較純粹了?,F(xiàn)在核心問(wèn)題來(lái)了:輸出的?個(gè)檢測(cè)結(jié)果是無(wú)序的,如何和?計(jì)算loss?這就需要用到經(jīng)典的雙邊匹配算法了,也就是常說(shuō)的匈牙利算法,該算法廣泛應(yīng)用于最優(yōu)分配問(wèn)題。

    一幅圖片,我們把第?個(gè)物體的真值表達(dá)為?,其中,?表示它的?,?表示它的?。我們定義?為網(wǎng)絡(luò)輸出的?個(gè)預(yù)測(cè)值。

    假設(shè)我們已經(jīng)了解了什么是匈牙利算法(先假裝了解了),對(duì)于第?個(gè)?,?為匈牙利算法得到的與?對(duì)應(yīng)的prediction的索引。我舉個(gè)栗子,比如?,意思就是:與第3個(gè)真值對(duì)應(yīng)的預(yù)測(cè)值是第18個(gè)。

    那我能根據(jù)?匈牙利算法,找到?與每個(gè)真值對(duì)應(yīng)的預(yù)測(cè)值是哪個(gè),那究竟是如何找到呢?

    我們看看這個(gè)表達(dá)式是甚么意思,對(duì)于某一個(gè)真值?,假設(shè)我們已經(jīng)找到這個(gè)真值對(duì)應(yīng)的預(yù)測(cè)值?,這里的?是所有可能的排列,代表從真值索引到預(yù)測(cè)值索引的所有的映射,然后用?最小化?和?的距離。這個(gè)?具體是:

    意思是:假設(shè)當(dāng)前從真值索引到預(yù)測(cè)值索引的所有的映射為?,對(duì)于圖片中的每個(gè)真值?,先找到對(duì)應(yīng)的預(yù)測(cè)值?,再看看分類網(wǎng)絡(luò)的結(jié)果?,取反作為?的第1部分。再計(jì)算回歸網(wǎng)絡(luò)的結(jié)果?與真值的?的差異,即?,作為?的第2部分。

    所以,可以使得?最小的排列?就是我們要找的排列,即:對(duì)于圖片中的每個(gè)真值?來(lái)講,?就是這個(gè)真值所對(duì)應(yīng)的預(yù)測(cè)值的索引。

    請(qǐng)讀者細(xì)品這個(gè) 尋找匹配的過(guò)程 ,這就是匈牙利算法的過(guò)程。是不是與Anchor或Proposal有異曲同工的地方,只是此時(shí)我們找的是一對(duì)一匹配。

    接下來(lái)就是使用上一步得到的排列?,計(jì)算匈牙利損失:

    式中的?具體為:

    最常用的?對(duì)于大小?會(huì)有不同的標(biāo)度,即使它們的相對(duì)誤差是相似的。為了緩解這個(gè)問(wèn)題,作者使用了?和廣義IoU損耗?的線性組合,它是比例不變的。

    Hungarian意思就是匈牙利,也就是前面的?,上述意思是需要計(jì)算?個(gè)?和?個(gè)輸預(yù)測(cè)出集合兩兩之間的廣義距離,距離越近表示越可能是最優(yōu)匹配關(guān)系,也就是兩者最密切。廣義距離的計(jì)算考慮了分類分支和回歸分支。

    最后,再概括一下DETR的End-to-End的原理,前面那么多段話就是為了講明白這個(gè)事情,如果你對(duì)前面的論述還存在疑問(wèn)的話,把下面一直到Experiments之前的這段話看懂就能解決你的困惑。

    DETR是怎么訓(xùn)練的?

    訓(xùn)練集里面的任何一張圖片,假設(shè)第1張圖片,我們通過(guò)模型產(chǎn)生100個(gè)預(yù)測(cè)框?,假設(shè)這張圖片有只3個(gè)?,它們分別是?。

    問(wèn)題是:我怎么知道這100個(gè)預(yù)測(cè)框哪個(gè)是對(duì)應(yīng)?,哪個(gè)是對(duì)應(yīng)?,哪個(gè)是對(duì)應(yīng)??

    我們建立一個(gè)?的矩陣,矩陣?yán)锩娴脑鼐褪?式的計(jì)算結(jié)果,舉個(gè)例子:比如左上角的?號(hào)元素的含義是:第1個(gè)預(yù)測(cè)框?qū)?yīng)?的情況下的?值。我們用scipy.optimize 這個(gè)庫(kù)中的 linear_sum_assignment 函數(shù)找到最優(yōu)的匹配,這個(gè)過(guò)程我們稱之為:"匈牙利算法 (Hungarian Algorithm)"。

    假設(shè)linear_sum_assignment 做完以后的結(jié)果是:第?個(gè)預(yù)測(cè)框?qū)?yīng)?,第?個(gè)預(yù)測(cè)框?qū)?yīng)?,第?個(gè)預(yù)測(cè)框?qū)?yīng)?。

    現(xiàn)在把第?個(gè)預(yù)測(cè)框挑出來(lái),按照?式計(jì)算Loss,得到這個(gè)圖片的Loss。

    把所有的圖片按照這個(gè)模式去訓(xùn)練模型。

    訓(xùn)練完以后怎么用?

    訓(xùn)練完以后,你的模型學(xué)習(xí)到了一種能力,即:模型產(chǎn)生的100個(gè)預(yù)測(cè)框,它知道某個(gè)預(yù)測(cè)框該對(duì)應(yīng)什么?,比如,模型學(xué)習(xí)到:第1個(gè)?對(duì)應(yīng)?,第2個(gè)?對(duì)應(yīng)?,第3個(gè)?對(duì)應(yīng)?,第4個(gè)?對(duì)應(yīng)?,第5個(gè)?對(duì)應(yīng)?,第6-100個(gè)?對(duì)應(yīng)?,等等。

    以上只是我舉的一個(gè)例子,意思是說(shuō):模型知道了自己的100個(gè)預(yù)測(cè)框每個(gè)該做什么事情,即:每個(gè)框該預(yù)測(cè)什么樣的?。

    為什么訓(xùn)練完以后,模型學(xué)習(xí)到了一種能力,即:模型產(chǎn)生的100個(gè)預(yù)測(cè)框,它知道某個(gè)預(yù)測(cè)框該對(duì)應(yīng)什么??

    還記得前面說(shuō)的Object queries嗎?它是一個(gè)維度為?維的張量,初始時(shí)元素全為?。實(shí)現(xiàn)方式是nn.Embedding(num_queries, hidden_dim),這里num_queries=100,hidden_dim=256,它是可訓(xùn)練的。這里的?指的是batch size,我們考慮單張圖片,所以假設(shè)Object queries是一個(gè)維度為?維的張量。我們訓(xùn)練完模型以后,這個(gè)張量已經(jīng)訓(xùn)練完了,那此時(shí)的Object queries究竟代表什么?

    我們把此時(shí)的Object queries看成100個(gè)格子,每個(gè)格子是個(gè)256維的向量。訓(xùn)練完以后,這100個(gè)格子里面注入了不同?的位置信息和類別信息。比如第1個(gè)格子里面的這個(gè)256維的向量代表著?這種?的位置信息,這種信息是通過(guò)訓(xùn)練,考慮了所有圖片的某個(gè)位置附近的?編碼特征,屬于和位置有關(guān)的全局?統(tǒng)計(jì)信息。

    測(cè)試時(shí),假設(shè)圖片中有?三種物體,該圖片會(huì)輸入到編碼器中進(jìn)行特征編碼,假設(shè)特征沒(méi)有丟失,Decoder的KeyValue就是編碼器輸出的編碼向量(如圖30所示),而Query就是Object queries,就是我們的100個(gè)格子。

    Query可以視作代表不同?的信息,而Key和Value可以視作代表圖像的全局信息。

    現(xiàn)在通過(guò)注意力模塊將QueryKey計(jì)算,然后加權(quán)Value得到解碼器輸出。對(duì)于第1個(gè)格子的Query會(huì)和Key中的所有向量進(jìn)行計(jì)算,目的是查找某個(gè)位置附近有沒(méi)有?,如果有那么該特征就會(huì)加權(quán)輸出,對(duì)于第3個(gè)格子的Query會(huì)和Key中的所有向量進(jìn)行計(jì)算,目的是查找某個(gè)位置附近有沒(méi)有?,很遺憾,這個(gè)沒(méi)有,所以輸出的信息里面沒(méi)有?。

    整個(gè)過(guò)程計(jì)算完成后就可以把編碼向量中的?的編碼嵌入信息提取出來(lái),然后后面接?進(jìn)行分類和回歸就比較容易,因?yàn)樘卣饕呀?jīng)對(duì)齊了。

    發(fā)現(xiàn)了嗎?Object queries在訓(xùn)練過(guò)程中對(duì)于?個(gè)格子會(huì)壓縮入對(duì)應(yīng)的和位置和類別相關(guān)的統(tǒng)計(jì)信息,在測(cè)試階段就可以利用該Query去和某個(gè)圖像的編碼特征Key,Value計(jì)算,若圖片中剛好有Query想找的特征,比如?,則這個(gè)特征就能提取出來(lái),最后通過(guò)2個(gè)?進(jìn)行分類和回歸。所以前面才會(huì)說(shuō)Object queries作用非常類似Faster R-CNN中的anchor,這個(gè)anchor是可學(xué)習(xí)的,由于維度比較高,故可以表征的東西豐富,當(dāng)然維度越高,訓(xùn)練時(shí)長(zhǎng)就會(huì)越長(zhǎng)。

    這就是DETR的End-to-End的原理,可以簡(jiǎn)單歸結(jié)為上面的幾段話,你讀懂了上面的話,也就明白了DETR以及End-to-End的Detection模型原理。

    Experiments:

    1. 性能對(duì)比:

    圖32:DETR與Fast R-CNN的性能對(duì)比

    2. 編碼器層數(shù)對(duì)比實(shí)驗(yàn):

    圖33:編碼器數(shù)目與模型性能

    可以發(fā)現(xiàn),編碼器層數(shù)越多越好,最后就選擇6。

    下圖34為最后一個(gè)Encoder Layer的attention可視化,Encoder已經(jīng)分離了instances,簡(jiǎn)化了Decoder的對(duì)象提取和定位。

    圖34:最后一個(gè)Encoder Layer的attention可視化

    3. 解碼器層數(shù)對(duì)比實(shí)驗(yàn):

    圖35:每個(gè)Decoder Layer后的AP和AP 50性能。

    可以發(fā)現(xiàn),性能隨著解碼器層數(shù)的增加而提升,DETR本不需要NMS,但是作者也進(jìn)行了,上圖中的NMS操作是指DETR的每個(gè)解碼層都可以輸入無(wú)序集合,那么將所有解碼器無(wú)序集合全部保留,然后進(jìn)行NMS得到最終輸出,可以發(fā)現(xiàn)性能稍微有提升,特別是AP50。這可以通過(guò)以下事實(shí)來(lái)解釋:Transformer的單個(gè)Decoder Layer不能計(jì)算輸出元素之間的任何互相關(guān),因此它易于對(duì)同一對(duì)象進(jìn)行多次預(yù)測(cè)。在第2個(gè)和隨后的Decoder Layer中,self-attention允許模型抑制重復(fù)預(yù)測(cè)。所以NMS帶來(lái)的改善隨著Decoder Layer的增加而減少。在最后幾層,作者觀察到AP的一個(gè)小損失,因?yàn)镹MS錯(cuò)誤地刪除了真實(shí)的positive prediction。

    圖36:Decoder Layer的attention可視化

    類似于可視化編碼器注意力,作者在圖36中可視化解碼器注意力,用不同的顏色給每個(gè)預(yù)測(cè)對(duì)象的注意力圖著色。觀察到,解碼器的attention相當(dāng)局部,這意味著它主要關(guān)注對(duì)象的四肢,如頭部或腿部。我們假設(shè),在編碼器通過(guò)全局關(guān)注分離實(shí)例之后,解碼器只需要關(guān)注極端來(lái)提取類和對(duì)象邊界。

    • 3.2 DETR代碼解讀:

    https://github.com/facebookresearch/detr

    分析都注釋在了代碼中。

    二維位置編碼:
    DETR的二維位置編碼:
    首先構(gòu)造位置矩陣x_embed和y_embed,這里用到了python函數(shù)cumsum,作用是對(duì)一個(gè)矩陣的元素進(jìn)行累加,那么累加以后最后一個(gè)元素就是所有累加元素的和,省去了求和的步驟,直接用這個(gè)和做歸一化,對(duì)應(yīng)x_embed[:, :, -1:]和y_embed[:, -1:, :]。
    這里我想著重強(qiáng)調(diào)下代碼中一些變量的shape,方便讀者掌握作者編程的思路:
    值得注意的是,tensor_list的類型是NestedTensor,內(nèi)部自動(dòng)附加了mask,用于表示動(dòng)態(tài)shape,是pytorch中tensor新特性https://github.com/pytorch/nestedtensor。全是false。
    x:(b,c,H,W)
    mask:(b,H,W),全是False。
    not_mask:(b,H,W),全是True。
    首先出現(xiàn)的y_embed:(b,H,W),具體是1,1,1,1,......,2,2,2,2,......3,3,3,3,......
    首先出現(xiàn)的x_embed:(b,H,W),具體是1,2,3,4,......,1,2,3,4,......1,2,3,4,......
    self.num_pos_feats = 128
    首先出現(xiàn)的dim_t = [0,1,2,3,.....,127]
    pos_x:(b,H,W,128)
    pos_y:(b,H,W,128)
    flatten后面的數(shù)字指的是:flatten()方法應(yīng)從哪個(gè)軸開(kāi)始展開(kāi)操作。
    torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3)
    pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4)
    這一步執(zhí)行完以后變成(b,H,W,2,64)通過(guò)flatten()方法從第3個(gè)軸開(kāi)始展平,變?yōu)?#xff1a;(b,H,W,128)
    torch.cat((pos_y, pos_x), dim=3)之后變?yōu)?b,H,W,256),再最后permute為(b,256,H,W)。
    PositionEmbeddingSine類繼承nn.Module類。

    class PositionEmbeddingSine(nn.Module):def __init__(self, num_pos_feats=64, temperature=10000, normalize=False, scale=None): super().__init__() self.num_pos_feats = num_pos_feats self.temperature = temperature self.normalize = normalize if scale is not None and normalize is False: raise ValueError("normalize should be True if scale is passed") if scale is None: scale = 2 * math.pi self.scale = scaledef forward(self, tensor_list: NestedTensor):#輸入是b,c,h,w#tensor_list的類型是NestedTensor,內(nèi)部自動(dòng)附加了mask,#用于表示動(dòng)態(tài)shape,是pytorch中tensor新特性https://github.com/pytorch/nestedtensor x = tensor_list.tensors# 附加的mask,shape是b,h,w 全是false mask = tensor_list.mask assert mask is not None not_mask = ~mask# 因?yàn)閳D像是2d的,所以位置編碼也分為x,y方向# 1 1 1 1 .. 2 2 2 2... 3 3 3... y_embed = not_mask.cumsum(1, dtype=torch.float32)# 1 2 3 4 ... 1 2 3 4... x_embed = not_mask.cumsum(2, dtype=torch.float32) if self.normalize: eps = 1e-6 y_embed = y_embed / (y_embed[:, -1:, :] + eps) * self.scale x_embed = x_embed / (x_embed[:, :, -1:] + eps) * self.scale # num_pos_feats = 128# 0~127 self.num_pos_feats=128,因?yàn)榍懊孑斎胂蛄渴?56,編碼是一半sin,一半cos dim_t = torch.arange(self.num_pos_feats, dtype=torch.float32, device=x.device) dim_t = self.temperature ** (2 * (dim_t // 2) / self.num_pos_feats) # 輸出shape=b,h,w,128 pos_x = x_embed[:, :, :, None] / dim_t pos_y = y_embed[:, :, :, None] / dim_t pos_x = torch.stack((pos_x[:, :, :, 0::2].sin(), pos_x[:, :, :, 1::2].cos()), dim=4).flatten(3) pos_y = torch.stack((pos_y[:, :, :, 0::2].sin(), pos_y[:, :, :, 1::2].cos()), dim=4).flatten(3) pos = torch.cat((pos_y, pos_x), dim=3).permute(0, 3, 1, 2)# 每個(gè)特征圖的xy位置都編碼成256的向量,其中前128是y方向編碼,而128是x方向編碼 return pos# b,n=256,h,w

    作者定義了一種數(shù)據(jù)結(jié)構(gòu):NestedTensor,里面打包存了兩個(gè)變量:x 和mask。

    NestedTensor:
    里面打包存了兩個(gè)變量:x 和mask。
    to()函數(shù):把變量移到GPU中。

    Backbone:

    class BackboneBase(nn.Module):def __init__(self, backbone: nn.Module, train_backbone: bool, num_channels: int, return_interm_layers: bool): super().__init__() for name, parameter in backbone.named_parameters(): if not train_backbone or 'layer2' not in name and 'layer3' not in name and 'layer4' not in name: parameter.requires_grad_(False) if return_interm_layers: return_layers = {"layer1": "0", "layer2": "1", "layer3": "2", "layer4": "3"} else: return_layers = {'layer4': "0"} #作用的模型:定義BackboneBase時(shí)傳入的nn.Moduleclass的backbone,返回的layer:來(lái)自bool變量return_interm_layers self.body = IntermediateLayerGetter(backbone, return_layers=return_layers) self.num_channels = num_channelsdef forward(self, tensor_list: NestedTensor):#BackboneBase的輸入是一個(gè)NestedTensor#xs中間層的輸出, xs = self.body(tensor_list.tensors) out: Dict[str, NestedTensor] = {} for name, x in xs.items(): m = tensor_list.mask assert m is not None#F.interpolate上下采樣,調(diào)整mask的size#to(torch.bool) 把mask轉(zhuǎn)化為Bool型變量 mask = F.interpolate(m[None].float(), size=x.shape[-2:]).to(torch.bool)[0] out[name] = NestedTensor(x, mask) return outclass Backbone(BackboneBase): """ResNet backbone with frozen BatchNorm.""" def __init__(self, name: str, train_backbone: bool, return_interm_layers: bool, dilation: bool):#根據(jù)name選擇backbone, num_channels, return_interm_layers等,傳入BackboneBase初始化 backbone = getattr(torchvision.models, name)( replace_stride_with_dilation=[False, False, dilation], pretrained=is_main_process(), norm_layer=FrozenBatchNorm2d) num_channels = 512 if name in ('resnet18', 'resnet34') else 2048 super().__init__(backbone, train_backbone, num_channels, return_interm_layers)

    把Backbone和之前的PositionEmbeddingSine連在一起:
    Backbone完以后輸出(b,c,h,w),再經(jīng)過(guò)PositionEmbeddingSine輸出(b,H,W,256)。

    class Joiner(nn.Sequential): def __init__(self, backbone, position_embedding): super().__init__(backbone, position_embedding)def forward(self, tensor_list: NestedTensor): xs = self[0](tensor_list) out: List[NestedTensor] = [] pos = [] for name, x in xs.items(): out.append(x) # position encoding pos.append(self[1](x).to(x.tensors.dtype))return out, posdef build_backbone(args):#position_embedding是個(gè)nn.module position_embedding = build_position_encoding(args) train_backbone = args.lr_backbone > 0 return_interm_layers = args.masks#backbone是個(gè)nn.module backbone = Backbone(args.backbone, train_backbone, return_interm_layers, args.dilation)#nn.Sequential在一起 model = Joiner(backbone, position_embedding) model.num_channels = backbone.num_channels return model

    Transformer的一個(gè)Encoder Layer:

    class TransformerEncoderLayer(nn.Module):def __init__(self, d_model, nhead, dim_feedforward=2048, dropout=0.1, activation="relu", normalize_before=False): super().__init__() self.self_attn = nn.MultiheadAttention(d_model, nhead, dropout=dropout) # Implementation of Feedforward model self.linear1 = nn.Linear(d_model, dim_feedforward) self.dropout = nn.Dropout(dropout) self.linear2 = nn.Linear(dim_feedforward, d_model)self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.dropout1 = nn.Dropout(dropout) self.dropout2 = nn.Dropout(dropout)self.activation = _get_activation_fn(activation) self.normalize_before = normalize_beforedef with_pos_embed(self, tensor, pos: Optional[Tensor]): return tensor if pos is None else tensor + posdef forward_post(self, src, src_mask: Optional[Tensor] = None, src_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None): # 和標(biāo)準(zhǔn)做法有點(diǎn)不一樣,src加上位置編碼得到q和k,但是v依然還是src, # 也就是v和qk不一樣 q = k = self.with_pos_embed(src, pos) src2 = self.self_attn(q, k, value=src, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0]#Add and Norm src = src + self.dropout1(src2) src = self.norm1(src)#FFN src2 = self.linear2(self.dropout(self.activation(self.linear1(src))))#Add and Norm src = src + self.dropout2(src2) src = self.norm2(src) return srcdef forward_pre(self, src, src_mask: Optional[Tensor] = None, src_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None): src2 = self.norm1(src) q = k = self.with_pos_embed(src2, pos) src2 = self.self_attn(q, k, value=src2, attn_mask=src_mask, key_padding_mask=src_key_padding_mask)[0] src = src + self.dropout1(src2) src2 = self.norm2(src) src2 = self.linear2(self.dropout(self.activation(self.linear1(src2)))) src = src + self.dropout2(src2) return srcdef forward(self, src, src_mask: Optional[Tensor] = None, src_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None): if self.normalize_before: return self.forward_pre(src, src_mask, src_key_padding_mask, pos) return self.forward_post(src, src_mask, src_key_padding_mask, pos)

    有了一個(gè)Encoder Layer的定義,再看Transformer的整個(gè)Encoder:

    class TransformerEncoder(nn.Module): def __init__(self, encoder_layer, num_layers, norm=None): super().__init__() # 編碼器copy6份 self.layers = _get_clones(encoder_layer, num_layers) self.num_layers = num_layers self.norm = normdef forward(self, src, mask: Optional[Tensor] = None, src_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None): # 內(nèi)部包括6個(gè)編碼器,順序運(yùn)行 # src是圖像特征輸入,shape=hxw,b,256 output = src for layer in self.layers: # 每個(gè)編碼器都需要加入pos位置編碼 # 第一個(gè)編碼器輸入來(lái)自圖像特征,后面的編碼器輸入來(lái)自前一個(gè)編碼器輸出 output = layer(output, src_mask=mask, src_key_padding_mask=src_key_padding_mask, pos=pos) return output

    Object Queries:可學(xué)習(xí)的位置編碼:
    注釋中已經(jīng)注明了變量的shape的變化過(guò)程,最終輸出的是與Positional Encoding維度相同的位置編碼,維度是(b,H,W,256),只是現(xiàn)在這個(gè)位置編碼是可學(xué)習(xí)的了。

    class PositionEmbeddingLearned(nn.Module): """ Absolute pos embedding, learned. """ def __init__(self, num_pos_feats=256): super().__init__()]#這里使用了nn.Embedding,這是一個(gè)矩陣類,里面初始化了一個(gè)隨機(jī)矩陣,矩陣的長(zhǎng)是字典的大小,寬是用來(lái)表示字典中每個(gè)元素的屬性向量,# 向量的維度根據(jù)你想要表示的元素的復(fù)雜度而定。類實(shí)例化之后可以根據(jù)字典中元素的下標(biāo)來(lái)查找元素對(duì)應(yīng)的向量。輸入下標(biāo)0,輸出就是embeds矩陣中第0行。 self.row_embed = nn.Embedding(50, num_pos_feats) self.col_embed = nn.Embedding(50, num_pos_feats) self.reset_parameters()def reset_parameters(self): nn.init.uniform_(self.row_embed.weight) nn.init.uniform_(self.col_embed.weight) #輸入依舊是NestedTensor def forward(self, tensor_list: NestedTensor): x = tensor_list.tensors h, w = x.shape[-2:] i = torch.arange(w, device=x.device) j = torch.arange(h, device=x.device) #x_emb:(w, 128)#y_emb:(h, 128) x_emb = self.col_embed(i) y_emb = self.row_embed(j) pos = torch.cat([ x_emb.unsqueeze(0).repeat(h, 1, 1),#(1,w,128) → (h,w,128) y_emb.unsqueeze(1).repeat(1, w, 1),#(h,1,128) → (h,w,128) ], dim=-1).permute(2, 0, 1).unsqueeze(0).repeat(x.shape[0], 1, 1, 1)#(h,w,256) → (256,h,w) → (1,256,h,w) → (b,256,h,w) return posdef build_position_encoding(args): N_steps = args.hidden_dim // 2 if args.position_embedding in ('v2', 'sine'): # TODO find a better way of exposing other arguments position_embedding = PositionEmbeddingSine(N_steps, normalize=True) elif args.position_embedding in ('v3', 'learned'): position_embedding = PositionEmbeddingLearned(N_steps) else: raise ValueError(f"not supported {args.position_embedding}")return position_embedding

    Transformer的一個(gè)Decoder Layer:
    注意變量的命名:
    object queries(query_pos)
    Encoder的位置編碼(pos)
    Encoder的輸出(memory)

    def forward_post(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None): #query,key的輸入是object queries(query_pos) + Decoder的輸入(tgt),shape都是(100,b,256)#value的輸入是Decoder的輸入(tgt),shape = (100,b,256) q = k = self.with_pos_embed(tgt, query_pos) #Multi-head self-attention tgt2 = self.self_attn(q, k, value=tgt, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask)[0]#Add and Norm tgt = tgt + self.dropout1(tgt2) tgt = self.norm1(tgt) #query的輸入是上一個(gè)attention的輸出(tgt) + object queries(query_pos)#key的輸入是Encoder的位置編碼(pos) + Encoder的輸出(memory)#value的輸入是Encoder的輸出(memory) tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt, query_pos), key=self.with_pos_embed(memory, pos), value=memory, attn_mask=memory_mask, key_padding_mask=memory_key_padding_mask)[0] #Add and Norm tgt = tgt + self.dropout2(tgt2) tgt = self.norm2(tgt) #FFN tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt)))) tgt = tgt + self.dropout3(tgt2) tgt = self.norm3(tgt) return tgtdef forward_pre(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None): tgt2 = self.norm1(tgt) q = k = self.with_pos_embed(tgt2, query_pos) tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask)[0] tgt = tgt + self.dropout1(tgt2) tgt2 = self.norm2(tgt) tgt2 = self.multihead_attn(query=self.with_pos_embed(tgt2, query_pos), key=self.with_pos_embed(memory, pos), value=memory, attn_mask=memory_mask, key_padding_mask=memory_key_padding_mask)[0] tgt = tgt + self.dropout2(tgt2) tgt2 = self.norm3(tgt) tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2)))) tgt = tgt + self.dropout3(tgt2) return tgtdef forward(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None): if self.normalize_before: return self.forward_pre(tgt, memory, tgt_mask, memory_mask, tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos) return self.forward_post(tgt, memory, tgt_mask, memory_mask, tgt_key_padding_mask, memory_key_padding_mask, pos, query_pos)

    有了一個(gè)Decoder Layer的定義,再看Transformer的整個(gè)Decoder:

    class TransformerDecoder(nn.Module):#值得注意的是:在使用TransformerDecoder時(shí)需要傳入的參數(shù)有:# tgt:Decoder的輸入,memory:Encoder的輸出,pos:Encoder的位置編碼的輸出,query_pos:Object Queries,一堆mask def forward(self, tgt, memory, tgt_mask: Optional[Tensor] = None, memory_mask: Optional[Tensor] = None, tgt_key_padding_mask: Optional[Tensor] = None, memory_key_padding_mask: Optional[Tensor] = None, pos: Optional[Tensor] = None, query_pos: Optional[Tensor] = None): output = tgtintermediate = []for layer in self.layers: output = layer(output, memory, tgt_mask=tgt_mask, memory_mask=memory_mask, tgt_key_padding_mask=tgt_key_padding_mask, memory_key_padding_mask=memory_key_padding_mask, pos=pos, query_pos=query_pos) if self.return_intermediate: intermediate.append(self.norm(output))if self.norm is not None: output = self.norm(output) if self.return_intermediate: intermediate.pop() intermediate.append(output)if self.return_intermediate: return torch.stack(intermediate)return output.unsqueeze(0)

    然后是把Encoder和Decoder拼在一起,即總的Transformer結(jié)構(gòu)的實(shí)現(xiàn):
    此處考慮到字?jǐn)?shù)限制,省略了代碼。

    實(shí)現(xiàn)了Transformer,還剩后面的FFN:

    class MLP(nn.Module): """ Very simple multi-layer perceptron (also called FFN)""" 代碼略,簡(jiǎn)單的Pytorch定義layer。

    匈牙利匹配HungarianMatcher類:
    這個(gè)類的目的是計(jì)算從targets到predictions的一種最優(yōu)排列。
    predictions比targets的數(shù)量多,但我們要進(jìn)行1-to-1 matching,所以多的predictions將與?匹配。
    這個(gè)函數(shù)整體在構(gòu)建(13)式,cost_class,cost_bbox,cost_giou,對(duì)應(yīng)的就是(13)式中的幾個(gè)損失函數(shù),它們的維度都是(b,100,m)。
    m包含了這個(gè)batch內(nèi)部所有的?。

    # pred_logits:[b,100,92] # pred_boxes:[b,100,4] # targets是個(gè)長(zhǎng)度為b的list,其中的每個(gè)元素是個(gè)字典,共包含:labels-長(zhǎng)度為(m,)的Tensor,元素是標(biāo)簽;boxes-長(zhǎng)度為(m,4)的Tensor,元素是Bounding Box。 # detr分類輸出,num_queries=100,shape是(b,100,92)bs, num_queries = outputs["pred_logits"].shape[:2]# We flatten to compute the cost matrices in a batchout_prob = outputs["pred_logits"].flatten(0, 1).softmax(-1) # [batch_size * num_queries, num_classes] = [100b, 92]out_bbox = outputs["pred_boxes"].flatten(0, 1) # [batch_size * num_queries, 4] = [100b, 4]# 準(zhǔn)備分類target shape=(m,)里面存儲(chǔ)的是類別索引,m包括了整個(gè)batch內(nèi)部的所有g(shù)t bbox# Also concat the target labels and boxestgt_ids = torch.cat([v["labels"] for v in targets])# (m,)[3,6,7,9,5,9,3] # 準(zhǔn)備bbox target shape=(m,4),已經(jīng)歸一化了tgt_bbox = torch.cat([v["boxes"] for v in targets])# (m,4)#(100b,92)->(100b, m),對(duì)于每個(gè)預(yù)測(cè)結(jié)果,把目前gt里面有的所有類別值提取出來(lái),其余值不需要參與匹配 #對(duì)應(yīng)上述公式,類似于nll loss,但是更加簡(jiǎn)單# Compute the classification cost. Contrary to the loss, we don't use the NLL,# but approximate it in 1 - proba[target class].# The 1 is a constant that doesn't change the matching, it can be ommitted. #行:取每一行;列:只取tgt_ids對(duì)應(yīng)的m列cost_class = -out_prob[:, tgt_ids]# (100b, m)# Compute the L1 cost between boxes, 計(jì)算out_bbox和tgt_bbox兩兩之間的l1距離 (100b, m)cost_bbox = torch.cdist(out_bbox, tgt_bbox, p=1)# (100b, m)# Compute the giou cost betwen boxes, 額外多計(jì)算一個(gè)giou loss (100b, m)cost_giou = -generalized_box_iou(box_cxcywh_to_xyxy(out_bbox), box_cxcywh_to_xyxy(tgt_bbox))#得到最終的廣義距離(100b, m),距離越小越可能是最優(yōu)匹配# Final cost matrixC = self.cost_bbox * cost_bbox + self.cost_class * cost_class + self.cost_giou * cost_giou #(100b, m)--> (b, 100, m)C = C.view(bs, num_queries, -1).cpu()#計(jì)算每個(gè)batch內(nèi)部有多少物體,后續(xù)計(jì)算時(shí)候按照單張圖片進(jìn)行匹配,沒(méi)必要batch級(jí)別匹配,徒增計(jì)算sizes = [len(v["boxes"]) for v in targets] #匈牙利最優(yōu)匹配,返回匹配索引 #enumerate(C.split(sizes, -1))]:(b,100,image1,image2,image3,...)indices = [linear_sum_assignment(c[i]) for i, c in enumerate(C.split(sizes, -1))] return [(torch.as_tensor(i, dtype=torch.int64), torch.as_tensor(j, dtype=torch.int64)) for i, j in indices]

    在得到匹配關(guān)系后算loss就水到渠成了。loss_labels計(jì)算分類損失,loss_boxes計(jì)算回歸損失,包含?。

    PS:作者將繼續(xù)更新Section2和Section3,請(qǐng)保持關(guān)注~

    參考文獻(xiàn):

    code:
    https://github.com/jadore801120/attention-is-all-you-need-pytorch
    https://github.com/lucidrains/vit-pytorch
    https://github.com/facebookresearch/detr

    video:
    https://www.bilibili.com/video/av71295187/?spm_id_from=333.788.videocard.8

    blog:
    https://baijiahao.baidu.com/s?id%3D1651219987457222196&wfr=spider%26for=pc
    https://zhuanlan.zhihu.com/p/308301901
    https://blog.csdn.net/your_answer/article/details/79160045

    ◎作者檔案

    作者:科技猛獸

    往期精彩回顧適合初學(xué)者入門人工智能的路線及資料下載機(jī)器學(xué)習(xí)及深度學(xué)習(xí)筆記等資料打印機(jī)器學(xué)習(xí)在線手冊(cè)深度學(xué)習(xí)筆記專輯《統(tǒng)計(jì)學(xué)習(xí)方法》的代碼復(fù)現(xiàn)專輯 AI基礎(chǔ)下載機(jī)器學(xué)習(xí)的數(shù)學(xué)基礎(chǔ)專輯 獲取本站知識(shí)星球優(yōu)惠券,復(fù)制鏈接直接打開(kāi): https://t.zsxq.com/qFiUFMV 本站qq群704220115。加入微信群請(qǐng)掃碼:

    總結(jié)

    以上是生活随笔為你收集整理的【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。