【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了
??作者丨科技猛獸
編輯丨極市平臺
導讀
?本文對Vision Transformer的原理和代碼進行了非常全面詳細的解讀,一切從Self-attention開始、Transformer的實現(xiàn)和代碼以及Transformer+Detection:引入視覺領域的首創(chuàng)DETR。>>加入極市CV技術交流群,走在計算機視覺的最前沿
Transformer 是 Google 的團隊在 2017 年提出的一種 NLP 經典模型,現(xiàn)在比較火熱的 Bert 也是基于 Transformer。Transformer 模型使用了 Self-Attention 機制,不采用RNN順序結構,使得模型可以并行化訓練,而且能夠擁有全局信息。本文將對Vision Transformer的原理和代碼進行非常全面的解讀??紤]到每篇文章字數(shù)的限制,每一篇文章將按照目錄的編排包含三個小節(jié),而且這個系列會隨著Vision Transformer的發(fā)展而長期更新。
目錄
(每篇文章對應一個Section,目錄持續(xù)更新。)
Section 1
1 一切從Self-attention開始
1.1 處理Sequence數(shù)據(jù)的模型
1.2 Self-attention
1.3 Multi-head Self-attention
1.4 Positional Encoding
2 Transformer的實現(xiàn)和代碼解讀 (NIPS2017)
(來自Google Research, Brain Team)
2.1 Transformer原理分析
2.2 Transformer代碼解讀
3 Transformer+Detection:引入視覺領域的首創(chuàng)DETR (ECCV2020)
(來自Facebook AI)
3.1 DETR原理分析
3.2 DETR代碼解讀
Section 2
4 Transformer+Detection:Deformable DETR:可變形的Transformer (ICLR2021)
(來自商湯代季峰老師組)
4.1 Deformable DETR原理分析
4.2 Deformable DETR代碼解讀
5 Transformer+Classification:用于分類任務的Transformer (ICLR2021)
(來自Google Research, Brain Team)
5.1 ViT原理分析
5.2 ViT代碼解讀
6 Transformer+Image Processing:IPT:用于底層視覺任務的Transformer
(來自北京華為諾亞方舟實驗室)
6.1 IPT原理分析
Section 3
7 Transformer+Segmentation:SETR:基于Transformer 的語義分割
(來自復旦大學,騰訊優(yōu)圖等)
7.1 SETR原理分析
8 Transformer+GAN:VQGAN:實現(xiàn)高分辨率的圖像生成
(來自德國海德堡大學)
8.1 VQGAN原理分析
8.2 VQGAN代碼解讀
9 Transformer+Distillation:DeiT:高效圖像Transformer
(來自Facebook AI)
9.1 DeiT原理分析
1 一切從Self-attention開始
1.1 處理Sequence數(shù)據(jù)的模型:
Transformer是一個Sequence to Sequence model,特別之處在于它大量用到了self-attention。
要處理一個Sequence,最常想到的就是使用RNN,它的輸入是一串vector sequence,輸出是另一串vector sequence,如下圖1左所示。
如果假設是一個single directional的RNN,那當輸出?時,默認?都已經看過了。如果假設是一個bi-directional的RNN,那當輸出?任意時,默認?都已經看過了。RNN非常擅長于處理input是一個sequence的狀況。
那RNN有什么樣的問題呢?它的問題就在于:RNN很不容易并行化 (hard to parallel)。
為什么說RNN很不容易并行化呢?假設在single directional的RNN的情形下,你今天要算出?,就必須要先看?再看?再看?再看?,所以這個過程很難平行處理。
所以今天就有人提出把CNN拿來取代RNN,如下圖1右所示。其中,橘色的三角形表示一個filter,每次掃過3個向量?,掃過一輪以后,就輸出了一排結果,使用橘色的小圓點表示。
這是第一個橘色的filter的過程,還有其他的filter,比如圖2中的黃色的filter,它經歷著與橘色的filter相似的過程,又輸出一排結果,使用黃色的小圓點表示。
圖1:處理Sequence數(shù)據(jù)的模型圖2:處理Sequence數(shù)據(jù)的模型所以,用CNN,你確實也可以做到跟RNN的輸入輸出類似的關系,也可以做到輸入是一個sequence,輸出是另外一個sequence。
但是,表面上CNN和RNN可以做到相同的輸入和輸出,但是CNN只能考慮非常有限的內容。比如在我們右側的圖中CNN的filter只考慮了3個vector,不像RNN可以考慮之前的所有vector。但是CNN也不是沒有辦法考慮很長時間的dependency的,你只需要堆疊filter,多堆疊幾層,上層的filter就可以考慮比較多的資訊,比如,第二層的filter (藍色的三角形)看了6個vector,所以,只要疊很多層,就能夠看很長時間的資訊。
而CNN的一個好處是:它是可以并行化的 (can parallel),不需要等待紅色的filter算完,再算黃色的filter。但是必須要疊很多層filter,才可以看到長時的資訊。所以今天有一個想法:self-attention,如下圖3所示,目的是使用self-attention layer取代RNN所做的事情。
圖3:You can try to replace any thing that has been done by RNNwith self attention所以重點是:我們有一種新的layer,叫self-attention,它的輸入和輸出和RNN是一模一樣的,輸入一個sequence,輸出一個sequence,它的每一個輸出?都看過了整個的輸入sequence,這一點與bi-directional RNN相同。但是神奇的地方是:它的每一個輸出?可以并行化計算。
1.2 Self-attention:
那么self-attention具體是怎么做的呢?
圖4:self-attention具體是怎么做的?首先假設我們的input是圖4的?,是一個sequence,每一個input (vector)先乘上一個矩陣?得到embedding,即向量?。接著這個embedding進入self-attention層,每一個向量?分別乘上3個不同的transformation matrix?,以向量?為例,分別得到3個不同的向量?。
圖5:self-attention具體是怎么做的?接下來使用每個query?去對每個key?做attention,attention就是匹配這2個向量有多接近,比如我現(xiàn)在要對?和?做attention,我就可以把這2個向量做scaled inner product,得到?。接下來你再拿?和?做attention,得到?,你再拿?和?做attention,得到?,你再拿?和?做attention,得到?。那這個scaled inner product具體是怎么計算的呢?
式中,?是?跟?的維度。因為?的數(shù)值會隨著dimension的增大而增大,所以要除以?的值,相當于歸一化的效果。
接下來要做的事如圖6所示,把計算得到的所有?值取?操作。
圖6:self-attention具體是怎么做的?取完?操作以后,我們得到了?,我們用它和所有的?值進行相乘。具體來講,把?乘上?,把?乘上?,把?乘上?,把?乘上?,把結果通通加起來得到?,所以,今天在產生?的過程中用了整個sequence的資訊 (Considering the whole sequence)。如果要考慮local的information,則只需要學習出相應的?,?就不再帶有那個對應分支的信息了;如果要考慮global的information,則只需要學習出相應的?,?就帶有全部的對應分支的信息了。
圖7:self-attention具體是怎么做的?同樣的方法,也可以計算出?,如下圖8所示,?就是拿query?去對其他的?做attention,得到?,再與value值?相乘取weighted sum得到的。
圖8:self-attention具體是怎么做的?經過了以上一連串計算,self-attention layer做的事情跟RNN是一樣的,只是它可以并行的得到layer輸出的結果,如圖9所示。現(xiàn)在我們要用矩陣表示上述的計算過程。
圖9:self-attention的效果首先輸入的embedding是?,然后用?乘以transformation matrix?得到?,它的每一列代表著一個vector?。同理,用?乘以transformation matrix?得到?,它的每一列代表著一個vector?。用?乘以transformation matrix?得到?,它的每一列代表著一個vector?。
圖10:self-attention的矩陣計算過程接下來是?與?的attention過程,我們可以把vector?橫過來變成行向量,與列向量?做內積,這里省略了?。這樣,?就成為了?的矩陣,它由4個行向量拼成的矩陣和4個列向量拼成的矩陣做內積得到,如圖11所示。
在得到?以后,如上文所述,要得到?, 就要使用?分別與?相乘再求和得到,所以?要再左乘?矩陣。
圖11:self-attention的矩陣計算過程到這里你會發(fā)現(xiàn)這個過程可以被表示為,如圖12所示:輸入矩陣?分別乘上3個不同的矩陣?得到3個中間矩陣?。它們的維度是相同的。把?轉置之后與?相乘得到Attention矩陣?,代表每一個位置兩兩之間的attention。再將它取?操作得到?,最后將它乘以?矩陣得到輸出vector?。
圖12:self-attention就是一堆矩陣乘法,可以實現(xiàn)GPU加速1.3 Multi-head Self-attention:
還有一種multi-head的self-attention,以2個head的情況為例:由?生成的?進一步乘以2個轉移矩陣變?yōu)?和?,同理由?生成的?進一步乘以2個轉移矩陣變?yōu)?和?,由?生成的?進一步乘以2個轉移矩陣變?yōu)?和?。接下來?再與?做attention,得到weighted sum的權重?,再與?做weighted sum得到最終的?。同理得到??,F(xiàn)在我們有了?和?,可以把它們concat起來,再通過一個transformation matrix調整維度,使之與剛才的?維度一致(這步如圖13所示)。
圖13:multi-head self-attention圖13:調整b的維度從下圖14可以看到 Multi-Head Attention 包含多個 Self-Attention 層,首先將輸入?分別傳遞到 2個不同的 Self-Attention 中,計算得到 2 個輸出結果。得到2個輸出矩陣之后,Multi-Head Attention 將它們拼接在一起 (Concat),然后傳入一個Linear層,得到 Multi-Head Attention 最終的輸出???梢钥吹?Multi-Head Attention 輸出的矩陣?與其輸入的矩陣?的維度是一樣的。
圖14:multi-head self-attention這里有一組Multi-head Self-attention的解果,其中綠色部分是一組query和key,紅色部分是另外一組query和key,可以發(fā)現(xiàn)綠色部分其實更關注global的信息,而紅色部分其實更關注local的信息。
圖15:Multi-head Self-attention的不同head分別關注了global和local的訊息1.4 Positional Encoding:
以上是multi-head self-attention的原理,但是還有一個問題是:現(xiàn)在的self-attention中沒有位置的信息,一個單詞向量的“近在咫尺”位置的單詞向量和“遠在天涯”位置的單詞向量效果是一樣的,沒有表示位置的信息(No position information in self attention)。所以你輸入"A打了B"或者"B打了A"的效果其實是一樣的,因為并沒有考慮位置的信息。所以在self-attention原來的paper中,作者為了解決這個問題所做的事情是如下圖16所示:
圖16:self-attention中的位置編碼具體的做法是:給每一個位置規(guī)定一個表示位置信息的向量?,讓它與?加在一起之后作為新的?參與后面的運算過程,但是這個向量?是由人工設定的,而不是神經網絡學習出來的。每一個位置都有一個不同的?。
那到這里一個自然而然的問題是:為什么是?與?相加?為什么不是concatenate?加起來以后,原來表示位置的資訊不就混到?里面去了嗎?不就很難被找到了嗎?
這里提供一種解答這個問題的思路:
如圖15所示,我們先給每一個位置的?append一個one-hot編碼的向量?,得到一個新的輸入向量?,這個向量作為新的輸入,乘以一個transformation matrix?。那么:
所以,與?相加就等同于把原來的輸入?concat一個表示位置的獨熱編碼?,再做transformation。
這個與位置編碼乘起來的矩陣?是手工設計的,如圖17所示。
圖17:與位置編碼乘起來的轉移矩陣WPTransformer 中除了單詞的 Embedding,還需要使用位置 Embedding 表示單詞出現(xiàn)在句子中的位置。因為 Transformer 不采用 RNN 的結構,而是使用全局信息,不能利用單詞的順序信息,而這部分信息對于 NLP 來說非常重要。所以 Transformer 中使用位置 Embedding 保存單詞在序列中的相對或絕對位置。
位置 Embedding 用 PE表示,PE 的維度與單詞 Embedding 是一樣的。PE 可以通過訓練得到,也可以使用某種公式計算得到。在 Transformer 中采用了后者,計算公式如下:
式中,?表示token在sequence中的位置,例如第一個token "我" 的?。
,或者準確意義上是?和?表示了Positional Encoding的維度,的取值范圍是:?。所以當?為1時,對應的Positional Encoding可以寫成:
式中,?。底數(shù)是10000。為什么要使用10000呢,這個就類似于玄學了,原論文中完全沒有提啊,這里不得不說說論文的readability的問題,即便是很多高引的文章,最基本的內容都討論不清楚,所以才出現(xiàn)像上面提問里的討論,說實話這些論文還遠遠沒有做到easy to follow。這里我給出一個假想:是一個比較接近1的數(shù)(1.018),如果用100000,則是1.023。這里只是猜想一下,其實大家應該完全可以使用另一個底數(shù)。
這個式子的好處是:
每個位置有一個唯一的positional encoding。
使?能夠適應比訓練集里面所有句子更長的句子,假設訓練集里面最長的句子是有 20 個單詞,突然來了一個長度為 21 的句子,則使用公式計算的方法可以計算出第 21 位的 Embedding。
可以讓模型容易地計算出相對位置,對于固定長度的間距?,任意位置的?都可以被?的線性函數(shù)表示,因為三角函數(shù)特性:
接下來我們看看self-attention在sequence2sequence model里面是怎么使用的,我們可以把Encoder-Decoder中的RNN用self-attention取代掉。
圖18:Seq2seq with Attention2 Transformer的實現(xiàn)和代碼解讀
2.1 Transformer原理分析:
Encoder:
這個圖19講的是一個seq2seq的model,左側為 Encoder block,右側為 Decoder block。紅色圈中的部分為Multi-Head Attention,是由多個Self-Attention組成的,可以看到 Encoder block 包含一個 Multi-Head Attention,而 Decoder block 包含兩個 Multi-Head Attention (其中有一個用到 Masked)。Multi-Head Attention 上方還包括一個 Add & Norm 層,Add 表示殘差連接 (Residual Connection) 用于防止網絡退化,Norm 表示 Layer Normalization,用于對每一層的激活值進行歸一化。比如說在Encoder Input處的輸入是機器學習,在Decoder Input處的輸入是<BOS>,輸出是machine。再下一個時刻在Decoder Input處的輸入是machine,輸出是learning。不斷重復知道輸出是句點(.)代表翻譯結束。
接下來我們看看這個Encoder和Decoder里面分別都做了什么事情,先看左半部分的Encoder:首先輸入?通過一個Input Embedding的轉移矩陣?變?yōu)榱艘粋€張量,即上文所述的?,再加上一個表示位置的Positional Encoding?,得到一個張量,去往后面的操作。
它進入了這個綠色的block,這個綠色的block會重復?次。這個綠色的block里面有什么呢?它的第1層是一個上文講的multi-head的attention。你現(xiàn)在一個sequence,經過一個multi-head的attention,你會得到另外一個sequence?。
下一個Layer是Add & Norm,這個意思是說:把multi-head的attention的layer的輸入?和輸出?進行相加以后,再做Layer Normalization,至于Layer Normalization和我們熟悉的Batch Normalization的區(qū)別是什么,請參考圖20和21。
圖20:不同Normalization方法的對比其中,Batch Normalization和Layer Normalization的對比可以概括為圖20,Batch Normalization強行讓一個batch的數(shù)據(jù)的某個channel的?,而Layer Normalization讓一個數(shù)據(jù)的所有channel的?。
圖21:Batch Normalization和Layer Normalization的對比接著是一個Feed Forward的前饋網絡和一個Add & Norm Layer。
所以,這一個綠色的block的前2個Layer操作的表達式為:
這一個綠色的block的后2個Layer操作的表達式為:
所以Transformer的Encoder的整體操作為:
Decoder:
現(xiàn)在來看Decoder的部分,輸入包括2部分,下方是前一個time step的輸出的embedding,即上文所述的?,再加上一個表示位置的Positional Encoding?,得到一個張量,去往后面的操作。它進入了這個綠色的block,這個綠色的block會重復?次。這個綠色的block里面有什么呢?
首先是Masked Multi-Head Self-attention,masked的意思是使attention只會attend on已經產生的sequence,這個很合理,因為還沒有產生出來的東西不存在,就無法做attention。
輸出是: 對應?位置的輸出詞的概率分布。
輸入是:?的輸出 和 對應?位置decoder的輸出。所以中間的attention不是self-attention,它的Key和Value來自encoder,Query來自上一位置?的輸出。
解碼:這里要特別注意一下,編碼可以并行計算,一次性全部Encoding出來,但解碼不是一次把所有序列解出來的,而是像?一樣一個一個解出來的,因為要用上一個位置的輸入當作attention的query。
明確了解碼過程之后最上面的圖就很好懂了,這里主要的不同就是新加的另外要說一下新加的attention多加了一個mask,因為訓練時的output都是Ground Truth,這樣可以確保預測第?個位置時不會接觸到未來的信息。
包含兩個 Multi-Head Attention 層。
第一個 Multi-Head Attention 層采用了 Masked 操作。
第二個 Multi-Head Attention 層的Key,Value矩陣使用 Encoder 的編碼信息矩陣?進行計算,而Query使用上一個 Decoder block 的輸出計算。
最后有一個 Softmax 層計算下一個翻譯單詞的概率。
下面詳細介紹下Masked Multi-Head Self-attention的具體操作,Masked在Scale操作之后,softmax操作之前。
圖22:Masked在Scale操作之后,softmax操作之前因為在翻譯的過程中是順序翻譯的,即翻譯完第?個單詞,才可以翻譯第?個單詞。通過 Masked 操作可以防止第?個單詞知道第?個單詞之后的信息。下面以 "我有一只貓" 翻譯成 "I have a cat" 為例,了解一下 Masked 操作。在 Decoder 的時候,是需要根據(jù)之前的翻譯,求解當前最有可能的翻譯,如下圖所示。首先根據(jù)輸入 "<Begin>" 預測出第一個單詞為 "I",然后根據(jù)輸入 "<Begin> I" 預測下一個單詞 "have"。
Decoder 可以在訓練的過程中使用 Teacher Forcing 并且并行化訓練,即將正確的單詞序列 (<Begin> I have a cat) 和對應輸出 (I have a cat <end>) 傳遞到 Decoder。那么在預測第?個輸出時,就要將第? 之后的單詞掩蓋住,注意 Mask 操作是在 Self-Attention 的 Softmax 之前使用的,下面用 0 1 2 3 4 5 分別表示 "<Begin> I have a cat <end>"。
圖23:Decoder過程注意這里transformer模型訓練和測試的方法不同:
測試時:
輸入<Begin>,解碼器輸出 I 。
輸入前面已經解碼的<Begin>和 I,解碼器輸出have。
輸入已經解碼的<Begin>,I, have, a, cat,解碼器輸出解碼結束標志位<end>,每次解碼都會利用前面已經解碼輸出的所有單詞嵌入信息。
Transformer測試時的解碼過程:
訓練時:
不采用上述類似RNN的方法 一個一個目標單詞嵌入向量順序輸入訓練,想采用類似編碼器中的矩陣并行算法,一步就把所有目標單詞預測出來。要實現(xiàn)這個功能就可以參考編碼器的操作,把目標單詞嵌入向量組成矩陣一次輸入即可。即:并行化訓練。
但是在解碼have時候,不能利用到后面單詞a和cat的目標單詞嵌入向量信息,否則這就是作弊(測試時候不可能能未卜先知)。為此引入mask。具體是:在解碼器中,self-attention層只被允許處理輸出序列中更靠前的那些位置,在softmax步驟前,它會把后面的位置給隱去。
Masked Multi-Head Self-attention的具體操作 如圖24所示。
Step1: 輸入矩陣包含 "<Begin> I have a cat" (0, 1, 2, 3, 4) 五個單詞的表示向量,Mask是一個 5×5 的矩陣。在Mask可以發(fā)現(xiàn)單詞 0 只能使用單詞 0 的信息,而單詞 1 可以使用單詞 0, 1 的信息,即只能使用之前的信息。輸入矩陣?經過transformation matrix變?yōu)?個矩陣:Query,Key?和Value?。
Step2:?得到 Attention矩陣?,此時先不急于做softmax的操作,而是先于一個?矩陣相乘,使得attention矩陣的有些位置 歸0,得到Masked Attention矩陣?。?矩陣是個下三角矩陣,為什么這樣設計?是因為想在計算?矩陣的某一行時,只考慮它前面token的作用。即:在計算?的第一行時,刻意地把?矩陣第一行的后面幾個元素屏蔽掉,只考慮?。在產生have這個單詞時,只考慮 I,不考慮之后的have a cat,即只會attend on已經產生的sequence,這個很合理,因為還沒有產生出來的東西不存在,就無法做attention。
Step3: Masked Attention矩陣進行 Softmax,每一行的和都為 1。但是單詞 0 在單詞 1, 2, 3, 4 上的 attention score 都為 0。得到的結果再與?矩陣相乘得到最終的self-attention層的輸出結果?。
Step4:?只是某一個head的結果,將多個head的結果concat在一起之后再最后進行Linear Transformation得到最終的Masked Multi-Head Self-attention的輸出結果?。
圖24:Masked Multi-Head Self-attention的具體操作第1個Masked Multi-Head Self-attention的?均來自Output Embedding。
第2個Multi-Head Self-attention的?來自第1個Self-attention layer的輸出,?來自Encoder的輸出。
為什么這么設計? 這里提供一種個人的理解:
來自Transformer Encoder的輸出,所以可以看做句子(Sequence)/圖片(image)的內容信息(content,比如句意是:"我有一只貓",圖片內容是:"有幾輛車,幾個人等等")。
表達了一種訴求:希望得到什么,可以看做引導信息(guide)。
通過Multi-Head Self-attention結合在一起的過程就相當于是把我們需要的內容信息指導表達出來。
Decoder的最后是Softmax 預測輸出單詞。因為 Mask 的存在,使得單詞 0 的輸出?只包含單詞 0 的信息。Softmax 根據(jù)輸出矩陣的每一行預測下一個單詞,如下圖25所示。
圖25:Softmax 根據(jù)輸出矩陣的每一行預測下一個單詞如下圖26所示為Transformer的整體結構。
圖26:Transformer的整體結構2.2 Transformer代碼解讀:
代碼來自:
https://github.com/jadore801120/attention-is-all-you-need-pytorch
ScaledDotProductAttention:
實現(xiàn)的是圖22的操作,先令?,再對結果按位乘以?矩陣,再做?操作,最后的結果與?相乘,得到self-attention的輸出。
位置編碼 PositionalEncoding:
實現(xiàn)的是式(5)的位置編碼。
MultiHeadAttention:
實現(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:
實現(xiàn)圖26中的一個EncoderLayer,具體的結構如圖19所示。
DecoderLayer:
實現(xiàn)圖26中的一個DecoderLayer,具體的結構如圖19所示。
Encoder:
實現(xiàn)圖26,19左側的Encoder:
Decoder:
實現(xiàn)圖26,19右側的Decoder:
整體結構:
實現(xiàn)圖26,19整體的Transformer:
產生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)
用于產生Encoder的Mask,它是一列Bool值,負責把標點mask掉。
trg_mask = get_pad_mask(trg_seq, self.trg_pad_idx) & get_subsequent_mask(trg_seq)
用于產生Decoder的Mask。它是一個矩陣,如圖24中的Mask所示,功能已在上文介紹。
3 Transformer+Detection:引入視覺領域的首創(chuàng)DETR
論文名稱:End-to-End Object Detection with Transformers
論文地址:
https://arxiv.org/abs/2005.12872arxiv.org
3.1 DETR原理分析:
網絡架構部分解讀
本文的任務是Object detection,用到的工具是Transformers,特點是End-to-end。
目標檢測的任務是要去預測一系列的Bounding Box的坐標以及Label, 現(xiàn)代大多數(shù)檢測器通過定義一些proposal,anchor或者windows,把問題構建成為一個分類和回歸問題來間接地完成這個任務。文章所做的工作,就是將transformers運用到了object detection領域,取代了現(xiàn)在的模型需要手工設計的工作,并且取得了不錯的結果。在object detection上DETR準確率和運行時間上和Faster RCNN相當;將模型 generalize 到 panoptic segmentation 任務上,DETR表現(xiàn)甚至還超過了其他的baseline。DETR第一個使用End to End的方式解決檢測問題,解決的方法是把檢測問題視作是一個set prediction problem,如下圖27所示。
圖27:DETR結合CNN和Transformer的結構,并行實現(xiàn)預測網絡的主要組成是CNN和Transformer,Transformer借助第1節(jié)講到的self-attention機制,可以顯式地對一個序列中的所有elements兩兩之間的interactions進行建模,使得這類transformer的結構非常適合帶約束的set prediction的問題。DETR的特點是:一次預測,端到端訓練,set loss function和二分匹配。
文章的主要有兩個關鍵的部分。
第一個是用transformer的encoder-decoder架構一次性生成?個box prediction。其中?是一個事先設定的、比遠遠大于image中object個數(shù)的一個整數(shù)。
第二個是設計了bipartite matching loss,基于預測的boxex和ground truth boxes的二分圖匹配計算loss的大小,從而使得預測的box的位置和類別更接近于ground truth。
DETR整體結構可以分為四個部分:backbone,encoder,decoder和FFN,如下圖28所示,以下分別解釋這四個部分:
圖28:DETR整體結構1 首先看backbone: CNN backbone處理?維的圖像,把它轉換為維的feature map(一般來說?或),backbone只做這一件事。
2 再看encoder: encoder的輸入是維的feature map,接下來依次進行以下過程:
通道數(shù)壓縮: 先用?convolution處理,將channels數(shù)量從?壓縮到?,即得到維的新feature map。
轉化為序列化數(shù)據(jù): 將空間的維度(高和寬)壓縮為一個維度,即把上一步得到的維的feature map通過reshape成維的feature map。
位置編碼: 在得到了維的feature map之后,正式輸入encoder之前,需要進行 Positional Encoding 。這一步在第2節(jié)講解transformer的時候已經提到過,因為在self-attention中需要有表示位置的信息,否則你的sequence = "A打了B" 還是sequence = "B打了A"的效果是一樣的。但是transformer encoder這個結構本身卻無法體現(xiàn)出位置信息。也就是說,我們需要對這個?維的feature map做positional encoding。
進行完位置編碼以后根據(jù)paper中的圖片會有個相加的過程,如下圖問號處所示。很多讀者有疑問的地方是:論文圖示中相加的2個張量,一個是input embedding,另一個是位置編碼維度看上去不一致,是怎么相加的?后面會解答。
圖:怎么相加的?原版Transformer和Vision Transformer (第4節(jié)講述)的Positional Encoding的表達式為:
式中,?就是這個?維的feature map的第一維,?。表示token在sequence中的位置,sequence的長度是?,例如第一個token 的?。
,或者準確意義上是?和?表示了Positional Encoding的維度,的取值范圍是:?。所以當?為1時,對應的Positional Encoding可以寫成:
式中,?。
第一點不同的是,原版Transformer只考慮?方向的位置編碼,但是DETR考慮了?方向的位置編碼,因為圖像特征是2-D特征。采用的依然是?模式,但是需要考慮?兩個方向。不是類似vision transoformer做法簡單的將其拉伸為?,然后從?進行長度為256的位置編碼,而是考慮了?方向同時編碼,每個方向各編碼128維向量,這種編碼方式更符合圖像特點。
Positional Encoding的輸出張量是:?,其中?代表位置編碼的長度,?代表張量的位置。意思是說,這個特征圖上的任意一個點?有個位置編碼,這個編碼的長度是256,其中,前128維代表?的位置編碼,后128維代表?的位置編碼。
假設你想計算任意一個位置?的Positional Encoding,把?代入(11)式的?式和?式可以計算得到128維的向量,它代表?的位置編碼,再把?代入(11)式的?式和?式可以計算得到128維的向量,它代表?的位置編碼,把這2個128維的向量拼接起來,就得到了一個256維的向量,它代表?的位置編碼。
計算所有位置的編碼,就得到了?的張量,代表這個batch的位置編碼。編碼矩陣的維度是?,也把它序列化成維度為?維的張量。
準備與維的feature map相加以后輸入Encoder。
值得注意的是,網上許多解讀文章沒有搞清楚 "轉化為序列化數(shù)據(jù)"這一步和 "位置編碼"的順序關系,以及變量的shape到底是怎樣變化的,這里我用一個圖表達,終結這個問題。
圖29:變量的shape的變化,變量一律使用方塊表達。所以,了解了DETR的位置編碼之后,你應該明白了其實input embedding和位置編碼維度其實是一樣的,只是論文圖示為了突出二位編碼所以畫的不一樣罷了,如下圖所示:
圖:input embedding與positional embedding的shape是一致的另一點不同的是,原版Transformer 只在Encoder之前使用了Positional Encoding,而且是在輸入上進行Positional Encoding,再把輸入經過transformation matrix變?yōu)镼uery,Key和Value這幾個張量。但是DETR在Encoder的每一個Multi-head Self-attention之前都使用了Positional Encoding,且只對Query和Key使用了Positional Encoding,即:只把維度為維的位置編碼與維度為維的Query和Key相加,而不與Value相加。
如圖30所示為DETR的Transformer的詳細結構,讀者可以對比下原版Transformer的結構,如圖19所示,為了閱讀的方便我把圖19又貼在下面了。
可以發(fā)現(xiàn),除了Positional Encoding設置的不一樣外,Encoder其他的結構是一致的。每個Encoder Layer包含一個multi-head self-attention 的module和一個前饋網絡Feed Forward Network。
Encoder最終輸出的是?維的編碼矩陣Embedding,按照原版Transformer的做法,把這個東西給Decoder。
總結下和原始transformer編碼器不同的地方:
輸入編碼器的位置編碼需要考慮2-D空間位置。
位置編碼向量需要加入到每個Encoder Layer中。
在編碼器內部位置編碼Positional Encoding僅僅作用于Query和Key,即只與Query和Key相加,Value不做任何處理。
3 再看decoder:
DETR的Decoder和原版Transformer的decoder是不太一樣的,如下圖30和19所示。
先回憶下原版Transformer,看下圖19的decoder的最后一個框:output probability,代表我們一次只產生一個單詞的softmax,根據(jù)這個softmax得到這個單詞的預測結果。這個過程我們表達為:predicts the output sequence one element at a time。
不同的是,DETR的Transformer Decoder是一次性處理全部的object queries,即一次性輸出全部的predictions;而不像原始的Transformer是auto-regressive的,從左到右一個詞一個詞地輸出。這個過程我們表達為:decodes the N objects in parallel at each decoder layer。
DETR的Decoder主要有兩個輸入:
Transformer Encoder輸出的Embedding與 position encoding 之和。
Object queries。
其中,Embedding就是上文提到的?的編碼矩陣。這里著重講一下Object queries。
Object queries是一個維度為?維的張量,數(shù)值類型是nn.Embedding,說明這個張量是可以學習的,即:我們的Object queries是可學習的。Object queries矩陣內部通過學習建模了100個物體之間的全局關系,例如房間里面的桌子旁邊(A類)一般是放椅子(B類),而不會是放一頭大象(C類),那么在推理時候就可以利用該全局注意力更好的進行解碼預測輸出。
Decoder的輸入一開始也初始化成維度為?維的全部元素都為0的張量,和Object queries加在一起之后充當?shù)?個multi-head self-attention的Query和Key。第一個multi-head self-attention的Value為Decoder的輸入,也就是全0的張量。
到了每個Decoder的第2個multi-head self-attention,它的Key和Value來自Encoder的輸出張量,維度為?,其中Key值還進行位置編碼。Query值一部分來自第1個Add and Norm的輸出,維度為?的張量,另一部分來自Object queries,充當可學習的位置編碼。所以,第2個multi-head self-attention的Key和Value的維度為?,而Query的維度為。
每個Decoder的輸出維度為?,送入后面的前饋網絡,具體的變量維度的變化見圖30。
到這里你會發(fā)現(xiàn):Object queries充當?shù)钠鋵嵤俏恢镁幋a的作用,只不過它是可以學習的位置編碼,所以,我們對Encoder和Decoder的每個self-attention的Query和Key的位置編碼做個歸納,如圖31所示,Value沒有位置編碼:
圖31:Transformer的位置編碼來自哪里?損失函數(shù)部分解讀
得到了Decoder的輸出以后,如前文所述,應該是輸出維度為?的張量。接下來要送入2個前饋網絡FFN得到class和Bounding Box。它們會得到?個預測目標,包含類別和Bounding Box,當然這個100肯定是大于圖中的目標總數(shù)的。如果不夠100,則采用背景填充,計算loss時候回歸分支分支僅僅計算有物體位置,背景集合忽略。所以,DETR輸出張量的維度為輸出的張量的維度是?和?。對應COCO數(shù)據(jù)集來說,?,?指的是每個預測目標歸一化的?。歸一化就是除以圖片寬高進行歸一化。
到這里我們了解了DETR的網絡架構,我們發(fā)現(xiàn),它輸出的張量的維度是 分類分支:和回歸分支:?,其中,前者是指100個預測框的類型,后者是指100個預測框的Bounding Box,但是讀者可能會有疑問:預測框和真值是怎么一一對應的?換句話說:你怎么知道第47個預測框對應圖片里的狗,第88個預測框對應圖片里的車?等等。
我們下面就來聊聊這個問題。
相比Faster R-CNN等做法,DETR最大特點是將目標檢測問題轉化為無序集合預測問題(set prediction)。論文中特意指出Faster R-CNN這種設置一大堆anchor,然后基于anchor進行分類和回歸其實屬于代理做法即不是最直接做法,目標檢測任務就是輸出無序集合,而Faster R-CNN等算法通過各種操作,并結合復雜后處理最終才得到無序集合屬于繞路了,而DETR就比較純粹了?,F(xiàn)在核心問題來了:輸出的?個檢測結果是無序的,如何和?計算loss?這就需要用到經典的雙邊匹配算法了,也就是常說的匈牙利算法,該算法廣泛應用于最優(yōu)分配問題。
一幅圖片,我們把第?個物體的真值表達為?,其中,?表示它的?,?表示它的?。我們定義?為網絡輸出的?個預測值。
假設我們已經了解了什么是匈牙利算法(先假裝了解了),對于第?個?,?為匈牙利算法得到的與?對應的prediction的索引。我舉個栗子,比如?,意思就是:與第3個真值對應的預測值是第18個。
那我能根據(jù)?匈牙利算法,找到?與每個真值對應的預測值是哪個,那究竟是如何找到呢?
我們看看這個表達式是甚么意思,對于某一個真值?,假設我們已經找到這個真值對應的預測值?,這里的?是所有可能的排列,代表從真值索引到預測值索引的所有的映射,然后用?最小化?和?的距離。這個?具體是:
意思是:假設當前從真值索引到預測值索引的所有的映射為?,對于圖片中的每個真值?,先找到對應的預測值?,再看看分類網絡的結果?,取反作為?的第1部分。再計算回歸網絡的結果?與真值的?的差異,即?,作為?的第2部分。
所以,可以使得?最小的排列?就是我們要找的排列,即:對于圖片中的每個真值?來講,?就是這個真值所對應的預測值的索引。
請讀者細品這個 尋找匹配的過程 ,這就是匈牙利算法的過程。是不是與Anchor或Proposal有異曲同工的地方,只是此時我們找的是一對一匹配。
接下來就是使用上一步得到的排列?,計算匈牙利損失:
式中的?具體為:
最常用的?對于大小?會有不同的標度,即使它們的相對誤差是相似的。為了緩解這個問題,作者使用了?和廣義IoU損耗?的線性組合,它是比例不變的。
Hungarian意思就是匈牙利,也就是前面的?,上述意思是需要計算?個?和?個輸預測出集合兩兩之間的廣義距離,距離越近表示越可能是最優(yōu)匹配關系,也就是兩者最密切。廣義距離的計算考慮了分類分支和回歸分支。
最后,再概括一下DETR的End-to-End的原理,前面那么多段話就是為了講明白這個事情,如果你對前面的論述還存在疑問的話,把下面一直到Experiments之前的這段話看懂就能解決你的困惑。
DETR是怎么訓練的?
訓練集里面的任何一張圖片,假設第1張圖片,我們通過模型產生100個預測框?,假設這張圖片有只3個?,它們分別是?。
問題是:我怎么知道這100個預測框哪個是對應?,哪個是對應?,哪個是對應??
我們建立一個?的矩陣,矩陣里面的元素就是?式的計算結果,舉個例子:比如左上角的?號元素的含義是:第1個預測框對應?的情況下的?值。我們用scipy.optimize 這個庫中的 linear_sum_assignment 函數(shù)找到最優(yōu)的匹配,這個過程我們稱之為:"匈牙利算法 (Hungarian Algorithm)"。
假設linear_sum_assignment 做完以后的結果是:第?個預測框對應?,第?個預測框對應?,第?個預測框對應?。
現(xiàn)在把第?個預測框挑出來,按照?式計算Loss,得到這個圖片的Loss。
把所有的圖片按照這個模式去訓練模型。
訓練完以后怎么用?
訓練完以后,你的模型學習到了一種能力,即:模型產生的100個預測框,它知道某個預測框該對應什么?,比如,模型學習到:第1個?對應?,第2個?對應?,第3個?對應?,第4個?對應?,第5個?對應?,第6-100個?對應?,等等。
以上只是我舉的一個例子,意思是說:模型知道了自己的100個預測框每個該做什么事情,即:每個框該預測什么樣的?。
為什么訓練完以后,模型學習到了一種能力,即:模型產生的100個預測框,它知道某個預測框該對應什么??
還記得前面說的Object queries嗎?它是一個維度為?維的張量,初始時元素全為?。實現(xiàn)方式是nn.Embedding(num_queries, hidden_dim),這里num_queries=100,hidden_dim=256,它是可訓練的。這里的?指的是batch size,我們考慮單張圖片,所以假設Object queries是一個維度為?維的張量。我們訓練完模型以后,這個張量已經訓練完了,那此時的Object queries究竟代表什么?
我們把此時的Object queries看成100個格子,每個格子是個256維的向量。訓練完以后,這100個格子里面注入了不同?的位置信息和類別信息。比如第1個格子里面的這個256維的向量代表著?這種?的位置信息,這種信息是通過訓練,考慮了所有圖片的某個位置附近的?編碼特征,屬于和位置有關的全局?統(tǒng)計信息。
測試時,假設圖片中有?三種物體,該圖片會輸入到編碼器中進行特征編碼,假設特征沒有丟失,Decoder的Key和Value就是編碼器輸出的編碼向量(如圖30所示),而Query就是Object queries,就是我們的100個格子。
Query可以視作代表不同?的信息,而Key和Value可以視作代表圖像的全局信息。
現(xiàn)在通過注意力模塊將Query和Key計算,然后加權Value得到解碼器輸出。對于第1個格子的Query會和Key中的所有向量進行計算,目的是查找某個位置附近有沒有?,如果有那么該特征就會加權輸出,對于第3個格子的Query會和Key中的所有向量進行計算,目的是查找某個位置附近有沒有?,很遺憾,這個沒有,所以輸出的信息里面沒有?。
整個過程計算完成后就可以把編碼向量中的?的編碼嵌入信息提取出來,然后后面接?進行分類和回歸就比較容易,因為特征已經對齊了。
發(fā)現(xiàn)了嗎?Object queries在訓練過程中對于?個格子會壓縮入對應的和位置和類別相關的統(tǒng)計信息,在測試階段就可以利用該Query去和某個圖像的編碼特征Key,Value計算,若圖片中剛好有Query想找的特征,比如?,則這個特征就能提取出來,最后通過2個?進行分類和回歸。所以前面才會說Object queries作用非常類似Faster R-CNN中的anchor,這個anchor是可學習的,由于維度比較高,故可以表征的東西豐富,當然維度越高,訓練時長就會越長。
這就是DETR的End-to-End的原理,可以簡單歸結為上面的幾段話,你讀懂了上面的話,也就明白了DETR以及End-to-End的Detection模型原理。
Experiments:
1. 性能對比:
圖32:DETR與Fast R-CNN的性能對比2. 編碼器層數(shù)對比實驗:
圖33:編碼器數(shù)目與模型性能可以發(fā)現(xiàn),編碼器層數(shù)越多越好,最后就選擇6。
下圖34為最后一個Encoder Layer的attention可視化,Encoder已經分離了instances,簡化了Decoder的對象提取和定位。
圖34:最后一個Encoder Layer的attention可視化3. 解碼器層數(shù)對比實驗:
圖35:每個Decoder Layer后的AP和AP 50性能。可以發(fā)現(xiàn),性能隨著解碼器層數(shù)的增加而提升,DETR本不需要NMS,但是作者也進行了,上圖中的NMS操作是指DETR的每個解碼層都可以輸入無序集合,那么將所有解碼器無序集合全部保留,然后進行NMS得到最終輸出,可以發(fā)現(xiàn)性能稍微有提升,特別是AP50。這可以通過以下事實來解釋:Transformer的單個Decoder Layer不能計算輸出元素之間的任何互相關,因此它易于對同一對象進行多次預測。在第2個和隨后的Decoder Layer中,self-attention允許模型抑制重復預測。所以NMS帶來的改善隨著Decoder Layer的增加而減少。在最后幾層,作者觀察到AP的一個小損失,因為NMS錯誤地刪除了真實的positive prediction。
圖36:Decoder Layer的attention可視化類似于可視化編碼器注意力,作者在圖36中可視化解碼器注意力,用不同的顏色給每個預測對象的注意力圖著色。觀察到,解碼器的attention相當局部,這意味著它主要關注對象的四肢,如頭部或腿部。我們假設,在編碼器通過全局關注分離實例之后,解碼器只需要關注極端來提取類和對象邊界。
3.2 DETR代碼解讀:
https://github.com/facebookresearch/detr
分析都注釋在了代碼中。
二維位置編碼:
DETR的二維位置編碼:
首先構造位置矩陣x_embed和y_embed,這里用到了python函數(shù)cumsum,作用是對一個矩陣的元素進行累加,那么累加以后最后一個元素就是所有累加元素的和,省去了求和的步驟,直接用這個和做歸一化,對應x_embed[:, :, -1:]和y_embed[:, -1:, :]。
這里我想著重強調下代碼中一些變量的shape,方便讀者掌握作者編程的思路:
值得注意的是,tensor_list的類型是NestedTensor,內部自動附加了mask,用于表示動態(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()方法應從哪個軸開始展開操作。
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)通過flatten()方法從第3個軸開始展平,變?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ù)結構:NestedTensor,里面打包存了兩個變量:x 和mask。
NestedTensor:
里面打包存了兩個變量: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時傳入的nn.Moduleclass的backbone,返回的layer:來自bool變量return_interm_layers self.body = IntermediateLayerGetter(backbone, return_layers=return_layers) self.num_channels = num_channelsdef forward(self, tensor_list: NestedTensor):#BackboneBase的輸入是一個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上下采樣,調整mask的size#to(torch.bool) 把mask轉化為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),再經過PositionEmbeddingSine輸出(b,H,W,256)。
Transformer的一個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): # 和標準做法有點不一樣,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)有了一個Encoder Layer的定義,再看Transformer的整個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): # 內部包括6個編碼器,順序運行 # src是圖像特征輸入,shape=hxw,b,256 output = src for layer in self.layers: # 每個編碼器都需要加入pos位置編碼 # 第一個編碼器輸入來自圖像特征,后面的編碼器輸入來自前一個編碼器輸出 output = layer(output, src_mask=mask, src_key_padding_mask=src_key_padding_mask, pos=pos) return outputObject Queries:可學習的位置編碼:
注釋中已經注明了變量的shape的變化過程,最終輸出的是與Positional Encoding維度相同的位置編碼,維度是(b,H,W,256),只是現(xiàn)在這個位置編碼是可學習的了。
Transformer的一個Decoder Layer:
注意變量的命名:
object queries(query_pos)
Encoder的位置編碼(pos)
Encoder的輸出(memory)
有了一個Decoder Layer的定義,再看Transformer的整個Decoder:
class TransformerDecoder(nn.Module):#值得注意的是:在使用TransformerDecoder時需要傳入的參數(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結構的實現(xiàn):
此處考慮到字數(shù)限制,省略了代碼。
實現(xiàn)了Transformer,還剩后面的FFN:
class MLP(nn.Module): """ Very simple multi-layer perceptron (also called FFN)""" 代碼略,簡單的Pytorch定義layer。匈牙利匹配HungarianMatcher類:
這個類的目的是計算從targets到predictions的一種最優(yōu)排列。
predictions比targets的數(shù)量多,但我們要進行1-to-1 matching,所以多的predictions將與?匹配。
這個函數(shù)整體在構建(13)式,cost_class,cost_bbox,cost_giou,對應的就是(13)式中的幾個損失函數(shù),它們的維度都是(b,100,m)。
m包含了這個batch內部所有的?。
在得到匹配關系后算loss就水到渠成了。loss_labels計算分類損失,loss_boxes計算回歸損失,包含?。
PS:作者將繼續(xù)更新Section2和Section3,請保持關注~
參考文獻:
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
◎作者檔案
作者:科技猛獸
往期精彩回顧適合初學者入門人工智能的路線及資料下載機器學習及深度學習筆記等資料打印機器學習在線手冊深度學習筆記專輯《統(tǒng)計學習方法》的代碼復現(xiàn)專輯 AI基礎下載機器學習的數(shù)學基礎專輯 獲取本站知識星球優(yōu)惠券,復制鏈接直接打開: https://t.zsxq.com/qFiUFMV 本站qq群704220115。加入微信群請掃碼:總結
以上是生活随笔為你收集整理的【深度学习】搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win7系统更改密码策略的设置方法
- 下一篇: Win11新特性:在平板锁屏界面还有酷炫