【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了
??作者丨科技猛獸
編輯丨極市平臺(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)移矩陣WPTransformer 中除了單詞的 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 Attention2 Transformer的實(shí)現(xiàn)和代碼解讀
2.1 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的輸出。
位置編碼 PositionalEncoding:
實(shí)現(xiàn)的是式(5)的位置編碼。
MultiHeadAttention:
實(shí)現(xiàn)圖13,14的多頭self-attention。
前向傳播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 xEncoderLayer:
實(shí)現(xiàn)圖26中的一個(gè)EncoderLayer,具體的結(jié)構(gòu)如圖19所示。
DecoderLayer:
實(shí)現(xiàn)圖26中的一個(gè)DecoderLayer,具體的結(jié)構(gòu)如圖19所示。
Encoder:
實(shí)現(xiàn)圖26,19左側(cè)的Encoder:
Decoder:
實(shí)現(xiàn)圖26,19右側(cè)的Decoder:
整體結(jié)構(gòu):
實(shí)現(xiàn)圖26,19整體的Transformer:
產(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_masksrc_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不做任何處理。
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的Key和Value就是編碼器輸出的編碼向量(如圖30所示),而Query就是Object queries,就是我們的100個(gè)格子。
Query可以視作代表不同?的信息,而Key和Value可以視作代表圖像的全局信息。
現(xiàn)在通過(guò)注意力模塊將Query和Key計(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類。
作者定義了一種數(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)。
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 outputObject Queries:可學(xué)習(xí)的位置編碼:
注釋中已經(jīng)注明了變量的shape的變化過(guò)程,最終輸出的是與Positional Encoding維度相同的位置編碼,維度是(b,H,W,256),只是現(xiàn)在這個(gè)位置編碼是可學(xué)習(xí)的了。
Transformer的一個(gè)Decoder Layer:
注意變量的命名:
object queries(query_pos)
Encoder的位置編碼(pos)
Encoder的輸出(memory)
有了一個(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)部所有的?。
在得到匹配關(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)題。
- 上一篇: win7系统更改密码策略的设置方法
- 下一篇: 【深度学习】遗传算法优化GAN