ssh小型購物網(wǎng)站開發(fā)重慶seo務(wù)
文章目錄
- 前言
- 一、Transformer
- 1.Transformer概覽
- 2.Self-Attention
- 3.Multi-head Attention
- 4.Position-wise Feed-Forward Networks(位置前饋網(wǎng)絡(luò))
- 5.殘差連接和層歸一化
- 6.Positional Encodings(位置編碼)
- 二、Vision Transformer
- 1.Vision Transformer概覽
- 2.Embedding層結(jié)構(gòu)
- 🥇 Image Patching(圖像分塊)
- 🥈Patch Embedding(圖像塊嵌入)
- 🥉Class token
- 🏅Position Embedding
- 3.Transformer Encoder
- 4.MLP Head
- 5.ViT B/16網(wǎng)絡(luò)結(jié)構(gòu)
- 三、Hybrid混合模型
- 四、ViT網(wǎng)絡(luò)實現(xiàn)
- 1.構(gòu)建ViT網(wǎng)絡(luò)
- 2.訓(xùn)練和測試模型
- 五、實現(xiàn)圖像分類
- 結(jié)束語
- 💂 個人主頁:風(fēng)間琉璃
- 🤟 版權(quán): 本文由【風(fēng)間琉璃】原創(chuàng)、在CSDN首發(fā)、需要轉(zhuǎn)載請聯(lián)系博主
- 💬 如果文章對你有
幫助
、歡迎關(guān)注
、點贊
、收藏(一鍵三連)
和訂閱專欄
哦
前言
Vision Transformer(ViT)
是將Transformer模型應(yīng)用于計算機視覺領(lǐng)域的方法,用于圖像分類任務(wù)。與傳統(tǒng)的卷積神經(jīng)網(wǎng)絡(luò)(CNN)不同,ViT通過將圖像分成固定大小的圖塊(Image Patches)并展平成序列,然后將序列輸入Transformer模型進行處理。在Transformer中,Self-Attention結(jié)構(gòu)被用來捕捉序列中不同位置的關(guān)聯(lián)信息
。通過多層的Transformer編碼器,ViT能夠從輸入圖像中學(xué)習(xí)到更高級的特征表示
,最終輸出圖像的類別預(yù)測結(jié)果。
一、Transformer
Transformer是2017年Google在Computation and Language上發(fā)表的,當(dāng)時主要是針對自然語言處理領(lǐng)域提出的。RNN模型記憶長度有限且無法并行化,只有計算完 t i t_i ti?時刻后的數(shù)據(jù)才能計算 t i + 1 t_{i+1} ti+1?時刻的數(shù)據(jù),而Transformer都可以做到。
1.Transformer概覽
Transformer模型
是一種廣泛應(yīng)用于各個領(lǐng)域的深度學(xué)習(xí)模型,它是一種基于自注意力機制的編碼器-解碼器架構(gòu)
。與傳統(tǒng)的編碼器-解碼器模型不同,Transformer模型沒有使用傳統(tǒng)的卷積神經(jīng)網(wǎng)絡(luò)(CNN)和循環(huán)神經(jīng)網(wǎng)絡(luò)(RNN)方法和模塊。
首先,讓我們先將 Transformer 模型視為一個黑盒,如圖所示。
在機器翻譯任務(wù)中,將一種語言的一個句子作為輸入,然后將其翻譯成另一種語言的一個句子作為輸出。
Transformer模型的核心思想是通過自注意力機制來捕捉輸入序列中不同位置之間的依賴關(guān)系
。Transformer 本質(zhì)上是一個Encoder-Decoder 架構(gòu)
。因此中間部分的 Transformer 可以分為兩個部分:編碼組件和解碼組件。
在編碼器部分,輸入序列經(jīng)過多個相同的編碼器層進行處理,每個編碼器層由一個多頭自注意力機制和一個前饋神經(jīng)網(wǎng)絡(luò)組成。在解碼器部分,輸出序列的每個位置通過注意力機制對編碼器部分的輸出進行查找,以便生成正確的輸出。
其中,編碼組件由多層編碼器(Encoder)組成(在論文中作者使用了 6 層編碼器,在實際使用過程中可以嘗試其他層數(shù))。解碼組件也是由相同層數(shù)的解碼器(Decoder)組成(在論文也使用了 6 層)。如圖所示:
每個編碼器
由兩個子層組成:Self-Attention 層(自注意力層)
和 Position-wise Feed Forward Network(前饋網(wǎng)絡(luò),縮寫為 FFN)
如下圖所示。每個編碼器的結(jié)構(gòu)都是相同的,但是它們使用不同的權(quán)重參數(shù)。
編碼器的輸入會先流入 Self-Attention 層,它可以讓編碼器在對特定詞進行編碼時使用輸入句子中的其他詞的信息,可以理解為:當(dāng)我們翻譯一個詞時,不僅只關(guān)注當(dāng)前的詞,而且還會關(guān)注其他詞的信息。然后,Self-Attention 層的輸出會流入前饋網(wǎng)絡(luò)。
解碼器
也有編碼器中這兩層,但是它們之間還有一個注意力層,即 Encoder-Decoder Attention
,其用來幫忙解碼器關(guān)注輸入句子的相關(guān)部分。
Transformer模型的優(yōu)點包括并行計算能力強、捕捉長距離依賴關(guān)系能力強、易于訓(xùn)練和擴展性好
等。這些特性使得Transformer模型在自然語言處理、機器翻譯、語音識別等領(lǐng)域取得了顯著的成果。
一個典型的 Transformer 結(jié)構(gòu)如下圖所示:
Transformer由一個編碼器和一個解碼器組成 。每個編碼器塊主要由一個多頭 self-attention 模塊和一個位置前饋網(wǎng)絡(luò)(FFN)組成。為了構(gòu)建更深的模型,每個模塊周圍都采用了殘差連接
,然后是層歸一化模塊。與編碼器塊相比,解碼器塊在多頭 self-attention 模塊和位置方面 FFN 之間額外插入了 cross-attention 模塊
。此外,解碼器中的 self-attention 模塊用于防止每個位置影響后續(xù)位置。
2.Self-Attention
在論文中作者提出了Self-Attention
的概念,然后在此基礎(chǔ)上提出Multi-Head Attention
。
首先通過一個例子,來對 Self-Attention 有一個直觀的認(rèn)識。假如,我們要翻譯下面這個句子:
The animal didn’t cross the street because it was too tired
這個句子中的 it 指的是什么?是指 animal 還是 street ?對我們來說,這是一個簡單的問題,但是算法來說卻不那么簡單。當(dāng)模型在處理 it 時,Self-Attention 機制
使其能夠?qū)?it 和 animal 關(guān)聯(lián)起來。
當(dāng)模型處理每個詞(輸入序列中的每個位置)時,Self-Attention 機制使得模型不僅能夠關(guān)注當(dāng)前位置的詞,而且能夠關(guān)注句子中其他位置的詞,從而可以更好地編碼這個詞。
如果你熟悉 RNN,想想如何維護隱狀態(tài),使 RNN 將已處理的先前詞/向量的表示與當(dāng)前正在處理的詞/向量進行合并。Transformer 使用 Self-Attention 機制將其他詞的理解融入到當(dāng)前詞中。
當(dāng)編碼器對單詞”it“進行編碼時,有一部分注意力集中在”The animal“上,并將它們的部分信息融入到”it“的編碼中。
Self-Attention其基本結(jié)構(gòu)如下圖所示:
對于 Self Attention 來講,Q(Query),K(Key)和 V(Value)三個矩陣
均來自同一輸入,并按照以下步驟計算:
? \star ? 首先計算 Q 和 K 之間的點積,為了防止其結(jié)果過大,會除以 d k \sqrt{d_{k}} dk?? ,其中 d k d_{k} dk?為 Key 向量的維度。
? \star ? 然后利用 Softmax 操作將其結(jié)果歸一化為概率分布,再乘以矩陣 V 就得到權(quán)重求和的表示。
整個計算過程表示如下:
A t t e n t i o n ( Q , K , V ) = s o f t m a x ( Q K T d k ) V Attention(Q,K,V) = softmax(\cfrac{QK^T}{\sqrt{d_k}})V Attention(Q,K,V)=softmax(dk??QKT?)V
如上圖所示,QKV矩陣是在自注意力機制(Self-Attention Mechanism)中用于計算注意力權(quán)重的三個矩陣
。這三個矩陣通常是通過對輸入序列進行線性變換得到的
。它們分別是:
? \star ? Q矩陣(Query Matrix): Q矩陣用于生成查詢向量
,每個查詢向量代表一個小塊(Patch)。在注意力機制中的查詢,即用尋找與當(dāng)前小塊相關(guān)的信息。
? \star ? K矩陣(Key Matrix): K矩陣用于生成鍵向量
,每個鍵向量代表一個小塊(Patch)。在注意力機制中的鍵,即用于表示當(dāng)前小塊與其他小塊之間的關(guān)系。
? \star ? V矩陣(Value Matrix): V矩陣用于生成值向量
,每個值向量代表一個小塊(Patch。在注意力機制中的值,即用于表示當(dāng)前小塊的特征信息。
在自注意力機制中,輸入序列首先通過三個不同的線性變換
,分別得到查詢矩陣Q、鍵矩陣K和值矩陣V。這三個矩陣將用于計算注意力權(quán)重
,從而對輸入序列進行加權(quán)求和
,得到最終的表示。其中,Q和K的點乘得到的矩陣就是注意力權(quán)重矩陣A
。假設(shè)如果只有V矩陣,不經(jīng)過Q和K的過程,那么這就算是普通的網(wǎng)絡(luò),沒有加入注意力機制。
舉例:假設(shè)輸入的序列長度為2,輸入就兩個節(jié)點 x 1 , x 2 x_1,x_2 x1?,x2?,然后通過Input Embedding
,即圖中的f(x)將輸入映射
到 a 1 , a 2 a_1,a_2 a1?,a2?。緊接著分別將 a 1 , a 2 a_1,a_2 a1?,a2?分別通過三個變換矩陣$W_q,W_k,W_v$
(這三個參數(shù)是可訓(xùn)練的,是共享的)得到對應(yīng)的 q i , k i , v i q^i,k^i,v^i qi,ki,vi(這里在源碼中是直接使用全連接層實現(xiàn)的,這里為了方便理解,忽略偏執(zhí))。
? \star ? q代表query,后續(xù)會去和每一個k進行匹配
? \star ? k代表key,后續(xù)會被每個q匹配
? \star ? v代表從a中提取得到的信息
,即輸入的數(shù)據(jù)
? \star ? q和k匹配的過程可以理解成計算兩者的相關(guān)性,相關(guān)性越大對應(yīng)v的權(quán)重也就越大
,q和k一系列運算都是為了計算v的權(quán)重。
假設(shè): a 1 = ( 1 , 1 ) , a 2 = ( 1 , 0 ) , W q = ( 1 1 0 1 ) a_1=(1,1),a_2=(1,0), W^q = \begin{pmatrix} 1 & 1 \\ 0 & 1 \end{pmatrix}\\ a1?=(1,1),a2?=(1,0),Wq=(10?11?)
則(Transformer可以并行化):
( q 1 q 2 ) \qquad \qquad \qquad \qquad \qquad \begin{pmatrix} q^1 \\ q^2 \end{pmatrix} (q1q2?) = ( 1 1 1 0 ) \begin{pmatrix} 1&1 \\ 1&0 \end{pmatrix} (11?10?) ( 1 1 0 1 ) \begin{pmatrix} 1&1 \\ 0&1 \end{pmatrix} (10?11?) = ( 1 2 1 1 ) \begin{pmatrix} 1&2 \\ 1&1 \end{pmatrix} (11?21?)
( q 1 q 2 ) \begin{pmatrix} q^1 \\ q^2 \end{pmatrix} (q1q2?)即為論文公式中的Q, ( k 1 k 2 ) \begin{pmatrix} k^1 \\ k^2 \end{pmatrix} (k1k2?)即為論文公式中的K, ( v 1 v 2 ) \begin{pmatrix} v^1 \\ v^2 \end{pmatrix} (v1v2?)即為論文公式中的V。然后用 q 1 q_1 q1?和每個k進行點乘操作,并除以 d \sqrtvxwlu0yf4 d?就可以得到對應(yīng)的 α \alpha α, 其中d代表向量 k i k^i ki的長度(k=2),除以 d \sqrtvxwlu0yf4 d?的原因是因為進行點乘后的數(shù)值很大,會導(dǎo)致通過softmax后梯度變得很小,所以除以 d \sqrtvxwlu0yf4 d?進行縮放。比如 α 1 , 1 \alpha_{1,1} α1,1?的計算:
α 1 , 1 = q 1 ? k 1 d = 1 ? 1 + 2 ? 0 2 = 0.71 \alpha_{1,1}=\cfrac{q^1·k^1}{\sqrtvxwlu0yf4} = \cfrac{1·1+2·0}{\sqrt{2}} = 0.71 α1,1?=d?q1?k1?=2?1?1+2?0?=0.71
使用矩陣計算:
( α 1 , 1 α 1 , 2 α 2 , 1 α 2 , 2 ) = ( q 1 q 2 ) ( k 1 k 2 ) T d \begin{pmatrix} \alpha_{1,1} & \alpha_{1,2} \\ \alpha_{2,1} & \alpha_{2,2} \end{pmatrix} = \cfrac{\begin{pmatrix} q^1 \\ q^2 \end{pmatrix} \begin{pmatrix} k^1 \\ k^2 \end{pmatrix}^{T} }{ \sqrtvxwlu0yf4} (α1,1?α2,1??α1,2?α2,2??)=d?(q1q2?)(k1k2?)T?
完成步驟1,接著對每一行即( α 1 , 1 , α 1 , 2 \alpha_{1,1} ,\alpha_{1,2} α1,1?,α1,2?)( α 2 , 1 , α 2 , 2 \alpha_{2,1} ,\alpha_{2,2} α2,1?,α2,2?)分別進行softmax處理
得到( α ^ 1 , 1 , α ^ 1 , 2 \widehat{\alpha}_{1,1} ,\widehat{\alpha}_{1,2} α 1,1?,α 1,2?)( α ^ 2 , 1 , α ^ 2 , 2 \widehat{\alpha}_{2,1} ,\widehat{\alpha}_{2,2} α 2,1?,α 2,2?), 其中$\widehat{\alpha}$相當(dāng)于計算得到針對每個v的權(quán)重
。至此,完成Attention(Q,K,V)公式的 s o f t m a x ( Q K T d k ) softmax(\cfrac{QK^T}{\sqrt{d_k}}) softmax(dk??QKT?)部分計算。
具體計算流程如下圖所示:
上面已經(jīng)計算得到 α \alpha α,即針對每個v的權(quán)重,接著進行加權(quán)得到最終結(jié)果
:
( b 1 b 2 ) \qquad \qquad \qquad \qquad \qquad \begin{pmatrix} b^1 \\ b^2 \end{pmatrix} (b1b2?) = ( α ^ 1 , 1 α ^ 1 , 2 α ^ 2 , 1 α ^ 2 , 2 ) \begin{pmatrix} \widehat{\alpha}_{1,1} &\widehat{\alpha}_{1,2} \\ \widehat{\alpha}_{2,1} &\widehat{\alpha}_{2,2} \end{pmatrix} (α 1,1?α 2,1??α 1,2?α 2,2??) ( v 1 v 2 ) \begin{pmatrix} v^1 \\ v^2 \end{pmatrix} (v1v2?)
至此,Self-Attention的公式計算完成。
3.Multi-head Attention
在 Transformer 論文中,通過添加一種多頭注意力機制
,使用多頭注意力機制能夠聯(lián)合來自不同head部分學(xué)習(xí)到的信息, 進一步完善了自注意力層。具體做法:首先,通過h個不同的線性變換對 Query、Key 和 Value 進行映射;然后,將不同的 Attention 拼接起來;最后,再進行一次線性變換?;窘Y(jié)構(gòu)如圖所示:
每一組注意力用于將輸入映射到不同的子表示空間,這使得模型可以在不同子表示空間中關(guān)注不同的位置。整個計算過程可表示為:
M u l t i H e a d ( Q , K , V ) = C o n c a t ( h e a d 1 1 , . . . , h e a d h ) W O MultiHead(Q,K,V) = Concat(head1_1,...,head_h)W^O MultiHead(Q,K,V)=Concat(head11?,...,headh?)WO w h e r e h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) where \qquad head_i = Attention(QW_i^Q,KW_i^K,VW_i^V) whereheadi?=Attention(QWiQ?,KWiK?,VWiV?) 其中 W i Q ∈ R d m o d e l × d k W_i^Q \in \Bbb{R}^{d_{model} \times d_k} WiQ?∈Rdmodel?×dk?, W i K ∈ R d m o d e l × d k W_i^K \in \Bbb{R}^{d_{model} \times d_k} WiK?∈Rdmodel?×dk?, W i V ∈ R d m o d e l × d v W_i^V \in \Bbb{R}^{d_{model} \times d_v} WiV?∈Rdmodel?×dv?和 W i O ∈ R h d v × d m o d e l W_i^O \in \Bbb{R}^{hd_v \times d_{model}} WiO?∈Rhdv?×dmodel?。
在論文中,指定h=8,即使用8個注意力頭。 d k = d v = d m o d e l / h = 64 d_k = d_v = d_{model} / h = 64 dk?=dv?=dmodel?/h=64
注意:
? \star ? d m o d e l d_{model} dmodel?表示Multi-Head Self-Attention輸入輸出的token維度(向量長度)
? \star ? d k , d v d_k, d_v dk?,dv? 表示Multi-Head Self-Attention中每個head的key(K)以及query(Q)的維度
舉例:首先和Self-Attention模塊一樣將 a i a_i ai?分別通過 W q , W k , W v W^q,W^k,W^v Wq,Wk,Wv得到對應(yīng)的 q i , k i , v i q^i,k^i,v^i qi,ki,vi,然后根據(jù)使用的head的數(shù)目h進一步把得到的 q i , k i , v i q^i,k^i,v^i qi,ki,vi均分成h份。
上圖假設(shè)h=2,將得到的 q 1 = 1 , 1 , 0 , 1 q^1={1,1,0,1} q1=1,1,0,1分為 q 1 , 1 = ( 1 , 1 ) , q 1 , 2 = ( 0 , 1 ) q^{1,1} = (1,1),q^{1,2} = (0,1) q1,1=(1,1),q1,2=(0,1), q 1 , 1 q^{1,1} q1,1屬于head1, q 1 , 2 q^{1,2} q1,2屬于head2。
但是在論文中 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ?,WiK?,WiV?映射$得到每一個head的 Q i , K i , V i Q_i,K_i, V_i Qi?,Ki?,Vi? : h e a d i = A t t e n t i o n ( Q W i Q , K W i K , V W i V ) head_i = Attention(QW_i^Q,KW_i^K,VW_i^V) headi?=Attention(QWiQ?,KWiK?,VWiV?)
其實可以進行簡單的均分,也可以通過 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ?,WiK?,WiV?設(shè)置成對應(yīng)值來實現(xiàn)均分,如下圖:
通過上述方法就能得到每個 h e a d i head_i headi?對應(yīng)的 W i Q , W i K , W i V W_i^Q,W_i^K, W_i^V WiQ?,WiK?,WiV?。接下來針對每個head使用和Self-Attention中相同的方法即可得到對應(yīng)的結(jié)果。
接著將每個head得到的結(jié)果進行concat拼接
,比如上圖中 b 1 , 1 b_{1,1} b1,1?和 b 2 , 1 b_{2,1} b2,1?拼接起來。如下圖所示:
接著將拼接后的結(jié)果通過 W O W^O WO(可學(xué)習(xí)的參數(shù))進行融合,如下圖所示,融合后得到最終的結(jié)果 b 1 , b 2 b_1,b_2 b1?,b2?。
至此Multi-Head Attention公式計算完畢。
4.Position-wise Feed-Forward Networks(位置前饋網(wǎng)絡(luò))
位置前饋網(wǎng)絡(luò)
就是一個全連接前饋網(wǎng)絡(luò)
,每個位置的詞都單獨經(jīng)過這個完全相同的前饋神經(jīng)網(wǎng)絡(luò)。其由兩個線性變換組成,即兩個全連接層組成,第一個全連接層的激活函數(shù)為 ReLU 激活函數(shù)
??梢员硎緸?#xff1a;
F F N ( x ) = m a x ( 0 , x W 1 + b 1 ) W 2 + b 2 FFN(x)=max(0, xW_1+b_1)W_2 + b_2 FFN(x)=max(0,xW1?+b1?)W2?+b2?
在每個編碼器和解碼器中,雖然這個全連接前饋網(wǎng)絡(luò)結(jié)構(gòu)相同,但是不共享參數(shù)。整個前饋網(wǎng)絡(luò)的輸入和輸出維度都是 d m o d e l d_{model} dmodel?=512,第一個全連接層的輸出和第二個全連接層的輸入維度為 d f f d_{ff} dff?=2048。
5.殘差連接和層歸一化
編碼器結(jié)構(gòu)中有一個需要注意的細(xì)節(jié):每個編碼器的每個子層(Self-Attention 層和 FFN 層)都有一個殘差連接,再執(zhí)行一個層標(biāo)準(zhǔn)化操作,整個計算過程可以表示為:
s u b l a y e r o u t p u t = L a y e r N o r m ( x + S u b L a y e r ( x ) ) sub_layer_output = LayerNorm(x + SubLayer(x)) subl?ayero?utput=LayerNorm(x+SubLayer(x))
將向量和自注意力層的層標(biāo)準(zhǔn)化操作可視化,如下圖所示:
上面的操作也適用于解碼器的子層。假設(shè)一個 Transformer 是由 2 層編碼器和 2 層解碼器組成,其如下圖所示
為了方便進行殘差連接,編碼器和解碼器中的所有子層和嵌入層的輸出維度需要保持一致
,在 Transformer 論文中 d m o d e l d_{model} dmodel? = 512。
6.Positional Encodings(位置編碼)
到目前為止,我們所描述的模型中缺少一個東西:表示序列中詞順序的方法
。為了解決這個問題,Transformer 模型為每個輸入的詞嵌入向量添加一個向量。這些向量遵循模型學(xué)習(xí)的特定模式,有助于模型確定每個詞的位置,或序列中不同詞之間的距離。
從上圖可知位置編碼t是直接加在輸入
的x上的,然后再進行編碼的。
位置編碼是直接加在輸入
的a={ a 1 , . . . , a n a_1,...,a_n a1?,...,an?}上的,所以pe={ p e 1 , . . . , p e n pe_1,...,pe_n pe1?,...,pen?}和a={ a 1 , . . . , a n a_1,...,a_n a1?,...,an?}有相同的維度大小。
關(guān)于位置編碼在原論文中有提出兩種方案,一種是原論文中使用的固定編碼,即論文中給出的sine and cosine functions方法
,按照該方法可計算出位置編碼;另一種是可訓(xùn)練的位置編碼
。
transformer就介紹到這里,有了上面的大概了解,下面進入今天的主題Vision Transformer。
transformer參考文章:transformer
二、Vision Transformer
1.Vision Transformer概覽
ViT是2020年Google團隊提出的將Transformer應(yīng)用在圖像分類的模型,其模型簡單且效果好,可擴展性強(scalable,模型越大效果越好),成為了transformer在CV領(lǐng)域應(yīng)用的里程碑著作,也引爆了后續(xù)相關(guān)研究。
ViT原論文中最核心的結(jié)論是:當(dāng)擁有足夠多的數(shù)據(jù)進行預(yù)訓(xùn)練的時候,ViT的表現(xiàn)就會超過CNN,突破transformer缺少歸納偏置的限制,可以在下游任務(wù)中獲得較好的遷移效果。但是當(dāng)訓(xùn)練數(shù)據(jù)集不夠大的時候,ViT的表現(xiàn)通常比同等大小的ResNets要差一些
,因為Transformer和CNN相比缺少歸納偏置(inductive bias),即一種先驗知識,提前做好的假設(shè)。
CNN具有兩種歸納偏置
,一種是局部性
(locality/two-dimensional neighborhood structure),即圖片上相鄰的區(qū)域具有相似的特征;一種是平移不變形
(translation equivariance), f ( g ( x ) ) = g ( f ( x ) ) f(g(x)) = g(f(x)) f(g(x))=g(f(x))其中g(shù)代表卷積操作,f代表平移操作。當(dāng)CNN具有以上兩種歸納偏置,就有了很多先驗信息,需要相對少的數(shù)據(jù)就可以學(xué)習(xí)一個比較好的模型。
Vision Transformer(ViT)的模型框架
模型由三個模塊組成:
? \star ? Linear Projection of Flattened Patches(Embedding層)
? \star ? Transformer Encode
r(圖右側(cè))
? \star ? MLP Head
(最終用于分類的層結(jié)構(gòu))
按照上面的流程圖,一個ViT block可以分為以下幾個步驟:
(1) Patch Embedding
:假設(shè)輸入圖片大小為224x224,將圖片分為固定大小的patch,然后將每個 Patch 拉成一維向量,patch大小為16x16,則每張圖像會生成224x224/16x16=196個patch,即輸入序列長度為196,每個patch維度16x16x3=768。
考慮到一維向量維度較大,需要將拉伸后的 Patch 序列經(jīng)過線性投影 (nn.Linear) 壓縮維度
,同時也可以實現(xiàn)特征變換功能,這兩個步驟可以稱為圖片 Token 化過程 (Patch Embedding)
。
線性投射層的維度為768xN (N=768),因此輸入通過線性投射層之后的維度依然為196x768,即一共有196個token,每個token的維度是768。為了方便后續(xù)分類,作者還額外引入一個可學(xué)習(xí)的 Class Token
,該 Token 插入到圖片 token 化后所得序列的開始位置。現(xiàn)在,已經(jīng)通過Patch Embedding將一個視覺問題轉(zhuǎn)化為了一個seq2seq問題
。
(2) Positional Encoding
(standard learnable 1D position embeddings):ViT同樣需要加入位置編碼,位置編碼可以理解為一張表,表一共有N行,N的大小和輸入序列長度相同,每一行代表一個向量,向量的維度和輸入序列embedding的維度相同(768)。注意位置編碼的操作是sum
,而不是concat。加入位置編碼信息之后,維度依然是197x768。
(3) LN/multi-head attention/LN
:LN輸出維度依然是197x768。多頭自注意力時,先將輸入映射到q,k,v,如果只有一個頭,qkv的維度都是197x768,如果有12個頭(768/12=64),則qkv的維度是197x64,一共有12組qkv,最后再將12組qkv的輸出拼接起來,輸出維度是197x768,然后在過一層LN,維度依然是197x768
(4) MLP
:將維度放大再縮小回去,197x768放大為197x3072,再縮小變?yōu)?97x768。一個block之后維度依然和輸入相同,都是197x768,因此可以堆疊多個block。將最后一個 Transformer 編碼器輸出序列的第 0 位置( Class Token 位置對應(yīng)輸出)提取出來
,后面接 MLP 分類后,然后正常分類即可。
2.Embedding層結(jié)構(gòu)
🥇 Image Patching(圖像分塊)
對于標(biāo)準(zhǔn)的Transformer模塊,要求輸入的是token(向量)序列
,即二維矩陣[num_token, token_dim]
,如下圖,token0-9對應(yīng)的都是向量,以ViT-B/16為例,每個token向量長度為768。
對于圖像數(shù)據(jù),其數(shù)據(jù)格式為[H, W, C],不滿足Transformer輸入要求。所以需要先通過Image Patching來對圖像數(shù)據(jù)處理,將圖像劃分為固定大小的patch
。如下圖所示,首先將一張圖片按給定大小分成一堆Patches。圖像分塊(Image Patches)過程如下圖所示:
以ViT-B/16為例,將輸入圖片(224x224)按照16x16大小的Patch進行劃分,劃分后會得到 ( 224 / 16 ) 2 (224/16)^2 (224/16)2=196個Patches。
將圖像分成小塊(即Patch)可以帶來的優(yōu)勢:
? \star ? 特征提取
: 在一些任務(wù)中,特定區(qū)域的信息比整個圖像更有用。通過對每個Patch進行特征提取,可以獲得更細(xì)粒度的信息,有助于更好地理解圖像內(nèi)容。
? \star ? 處理大尺寸圖像
: 對于非常大的圖像,可能會遇到計算和存儲方面的限制。將圖像分成小的Patch可以幫助降低計算復(fù)雜度,并且可以更輕松地處理這些小尺寸的塊。
? \star ? 自適應(yīng)性
: 在一些自適應(yīng)處理的算法中,對于不同的圖像區(qū)域采取不同的策略是很常見的。將圖像劃分成Patch可以使算法在局部區(qū)域上更加靈活和自適應(yīng)。
🥈Patch Embedding(圖像塊嵌入)
Patch Embedding與圖像處理和卷積神經(jīng)網(wǎng)絡(luò)(CNN)相關(guān)。CNN對圖像數(shù)據(jù)進行處理是在像素級上的處理
,通過卷積核在圖像上滑動進行特征提取。而Patch Embedding,則引入了更高級的特征表示方式。它先將一張圖片按給定大小分成一堆Patches,然后將每個小塊轉(zhuǎn)換為低維的向量表示
。這種向量表示可以用作后續(xù)任務(wù)的輸入。
Patch Embedding的目的在于降低計算復(fù)雜度并提高特征提取的效率
。在卷積神經(jīng)網(wǎng)絡(luò)中,相鄰的像素通常會有大量重疊,而Patch Embedding將圖像分成塊后,可以減少冗余計算,同時保留了重要的特征信息。
Patch Embedding過程如下圖所示:
通過線性映射將每個Patch映射到一維向量中,以ViT-B/16為例,每個Patche數(shù)據(jù)shape為[16, 16, 3]通過映射得到一個長度為768的向量(后面都直接稱為token)。[16, 16, 3] -> [768]
在代碼實現(xiàn)中,直接通過一個卷積層來實現(xiàn)
。 以ViT-B/16為例,使用一個卷積核大小為16x16,步距為16,卷積核個數(shù)為768的卷積來實現(xiàn)。通過卷積[224, 224, 3] -> [14, 14, 768],然后把H以及W兩個維度展平即可[14, 14, 768] -> [196, 768],此時正好變成了一個二維矩陣,符合Transformer輸入數(shù)據(jù)要求。
🥉Class token
前面說過,為了方便后續(xù)分類,作者還額外引入一個可學(xué)習(xí)的 Class Token
,是一個可訓(xùn)練的參數(shù)
,數(shù)據(jù)格式和其他token一樣都是一個向量,用于表示整個圖像的類別信息,以輔助后續(xù)的圖像分類或生成任務(wù),該 Class Token 插入到圖片 token 化后(Patch Embedding操作后)所得序列的開始位置。
在Transformer模型中,Patch Embedding操作后,Class foken通常被添加在輸入序列的開頭,并且在訓(xùn)練過程中會經(jīng)過特定的注意力機制,以使得模型能夠?qū)︻悇e信息進行編碼和利用
。
以ViT-B/16為例,假設(shè)Patch Embedding后得到196個向量,添加Class Token(一個長度為768的向量)后輸入序列為:[Class Token,v1,v2,…,v196]。整個輸入序列的第一個向量就是Class Token,它包含了整個圖像的類別信息,網(wǎng)絡(luò)模型在訓(xùn)練過程中可以利用這個類別信息,進行圖像分類任務(wù)。
🏅Position Embedding
在Vision Transformer 模型中,PE表示位置編碼(Positonal Encoding)
,用于將圖像中的每個Patch Embedding 向量與其位置信息相關(guān)聯(lián),將整個圖像的全局位置信息引入到Transformer模型中。
Position Embedding和Transformer中講到的Positional Encoding一樣,這里的Position Embedding采用的是一個可訓(xùn)練的參數(shù)(1D Pos. Emb.)
,直接疊加在tokens上的(add),所以shape要求相同。
以ViT-B/16為例,輸入序列添加Class Token后shape是[197, 768],則Position Embedding的shape也要是[197, 768]。
Position Embedding作用:為了給Transformer模型提供輸入序列中的位置信息。 在Transformer模型沒有像卷積神經(jīng)網(wǎng)絡(luò)那樣顯式地保留位置信息
。在自然語言處理任務(wù)中,輸入是一個詞語序列,為了保留詞語的位置信息,通常會添加位置編碼。同理在ViT中,輸入是圖像的Patch Embedding 序列,為了保留Patch的位置信息,也需要添加位置編碼。
對于Position Embedding作者做了一系列對比試驗,在源碼中默認(rèn)使用的是1D Pos. Emb.
,對比不使用Position Embedding準(zhǔn)確率提升了0.3。
3.Transformer Encoder
Transformer Encoder是重復(fù)堆疊Encoder Block L次,Encoder Block如下圖所示,
主要由以下幾個部分組成:
? \star ? Layer Norm
,這種Normalization方法主要是針對NLP領(lǐng)域提出的,這里是對每個token進行Norm處理,在圖像處理領(lǐng)域中BN比LN是更有效的,但現(xiàn)在越來越多的人將自然語言領(lǐng)域的模型用來處理圖像,比如Vision Transformer,此時還是會涉及到LN。
? \star ? ·Multi-Head Attention·,這個結(jié)構(gòu)在講前面Transformer中講過。
? \star ? ·Dropout/DropPath·,在原論文的代碼中是直接使用的Dropout層,在但rwightman大佬實現(xiàn)的代碼中使用的是DropPath(stochastic depth),可能后者會更好一點。
? \star ? ·MLP Block·,如圖右側(cè)所示,全連接+GELU激活函數(shù)+Dropout組成,需要注意的是第一個全連接層會把輸入節(jié)點個數(shù)翻4倍[197, 768] -> [197, 3072],第二個全連接層會還原回原節(jié)點個數(shù)[197, 3072] -> [197, 768]
4.MLP Head
Transformer Encoder后輸出的shape和輸入的shape是保持不變的,以ViT-B/16為例,輸入的是[197, 768]輸出的還是[197, 768]。
在Transformer Encoder后還有一個Layer Norm
,這里我們只是需要分類的信息,所以只需要提取出Class Token生成的對應(yīng)結(jié)果就行
,即[197, 768]中抽取出Class Token對應(yīng)的[1, 768]。接著通過MLP Head得到的分類結(jié)果
。
MLP Head原論文中說在訓(xùn)練ImageNet21K時是由Linear+tanh激活函數(shù)+Linear組成。但是遷移到ImageNet1K上或者自己的數(shù)據(jù)上時,只用一個Linear即可。
5.ViT B/16網(wǎng)絡(luò)結(jié)構(gòu)
ViT B/16 的網(wǎng)絡(luò)結(jié)構(gòu)如上圖所示,假設(shè)輸入圖為 224 × 224 × 3的RGB彩色圖片。
Embedding層:首先經(jīng)過一個16x16大小的卷積核、步距為16的卷積層,得到14x14x768的特征圖,然后進行高度和寬度方向的展平處理,得到196x768的特征向量。緊接著 concat 一個 Class token,其尺寸變?yōu)?97x768,再加上 Position Embedding 的相加操作,因為尺寸完全相同,可以理解為數(shù)值上的相加,這里的 Position Embedding 也是可訓(xùn)練的參數(shù)。
Transformer Encoder:將以上的輸入序列經(jīng)過 Dropout 后輸入 12 個堆疊的 Encoder Block。Encoder 輸出經(jīng)過 LN 層得到的輸出為 197 × 768,即是不變的。然后切片提取Class token信息,切片之后即變成了 1 × 768。
MLP Head:將提取Class token輸入 MLP Head層得到最終的輸出。如果在 ImageNet21K 預(yù)訓(xùn)練的時候,Pre-Logits 是由一個全連接層+tanh 激活函數(shù)構(gòu)成,然后通過一個全連接層得到最終的輸出。如果是在 ImageNet1k 或者自己的數(shù)據(jù)集上的時候訓(xùn)練的時候,可以不需要 Pre-Logits。
在論文中給出三個模型(Base/ Large/ Huge)的參數(shù),ViT B 對應(yīng)的就是 ViT-Base,ViT L 對應(yīng)的是 ViT-Large,ViT H 對應(yīng)的是 ViT-Huge。
? \star ? patch size 是圖片切片大小(源碼中還有 32 × 32 )
? \star ? Layers是Transformer Encoder中重復(fù)堆疊Encoder Block的次數(shù)
? \star ? Hidden Size就是對應(yīng)通過Embedding層后每個token的dim(向量的長度)
? \star ? MLP size是Transformer Encoder中MLP Block第一個全連接的節(jié)點個數(shù)(是Hidden Size的四倍)
? \star ? Heads代表Transformer中Multi-Head Attention的heads數(shù)
三、Hybrid混合模型
混合模型
是指首先使用傳統(tǒng)的卷積神經(jīng)網(wǎng)絡(luò)提取特征,然后通過Vit模型得到最終的結(jié)果
。
ResNet50 + ViT-B/16網(wǎng)絡(luò)結(jié)構(gòu)如下所示:
上圖以ResNet50作為特征提取器的混合模型,但這里的Resnet與之前講的Resnet有些不同。首先這里的R50的卷積層采用的StdConv2d
不是Conv2d,然后將所有的BatchNorm層替換成GroupNorm層
。在原Resnet50網(wǎng)絡(luò)中,stage1重復(fù)堆疊3次,stage2重復(fù)堆疊4次,stage3重復(fù)堆疊6次,stage4重復(fù)堆疊3次,但在這里的R50中,把stage4中的3個Block移至stage3中,所以stage3中共重復(fù)堆疊9次。
通過R50 Backbone進行特征提取后,得到的特征矩陣shape是[14, 14, 1024],接著再輸入Patch Embedding層,注意Patch Embedding中卷積層Conv2d的kernel_size和stride都變成了1,只是用來調(diào)整channel,經(jīng)過1x1卷積核變?yōu)?4x14x768,然后經(jīng)過Flatten就得到token。后面的部分和ViT處理流程一樣。
上表是論文用來對比ViT,Resnet以及Hybrid模型的效果。通過對比發(fā)現(xiàn),在訓(xùn)練epoch較少時Hybrid優(yōu)于ViT,但當(dāng)epoch增大后ViT優(yōu)于Hybrid。因此,如果訓(xùn)練迭代次數(shù)少,混合模型的效果表現(xiàn)比較好。如果訓(xùn)練迭代次數(shù)較多的話,純ViT的效果更佳
。
四、ViT網(wǎng)絡(luò)實現(xiàn)
1.構(gòu)建ViT網(wǎng)絡(luò)
def drop_path(x, drop_prob: float = 0., training: bool = False):"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).This is the same as the DropConnect impl I created for EfficientNet, etc networks, however,the original name is misleading as 'Drop Connect' is a different form of dropout in a separate paper...See discussion: https://github.com/tensorflow/tpu/issues/494#issuecomment-532968956 ... I've opted forchanging the layer and argument names to 'drop path' rather than mix DropConnect as a layer name and use'survival rate' as the argument."""if drop_prob == 0. or not training:return xkeep_prob = 1 - drop_probshape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNetsrandom_tensor = keep_prob + torch.rand(shape, dtype=x.dtype, device=x.device)random_tensor.floor_() # binarizeoutput = x.div(keep_prob) * random_tensorreturn outputclass DropPath(nn.Module):"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks)."""def __init__(self, drop_prob=None):super(DropPath, self).__init__()self.drop_prob = drop_probdef forward(self, x):return drop_path(x, self.drop_prob, self.training)class PatchEmbed(nn.Module):"""2D Image to Patch Embedding"""def __init__(self, img_size=224, patch_size=16, in_c=3, embed_dim=768, norm_layer=None):super().__init__()img_size = (img_size, img_size)patch_size = (patch_size, patch_size)self.img_size = img_sizeself.patch_size = patch_sizeself.grid_size = (img_size[0] // patch_size[0], img_size[1] // patch_size[1]) # [14,14](224/16)self.num_patches = self.grid_size[0] * self.grid_size[1] # 14x14 = 196self.proj = nn.Conv2d(in_c, embed_dim, kernel_size=patch_size, stride=patch_size)self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()def forward(self, x):B, C, H, W = x.shapeassert H == self.img_size[0] and W == self.img_size[1], \f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."# flatten: [B, C, H, W] -> [B, C, HW]# transpose: [B, C, HW] -> [B, HW, C]x = self.proj(x).flatten(2).transpose(1, 2)x = self.norm(x)return x# 多頭自注意力機制
class Attention(nn.Module):def __init__(self,dim, # 輸入token的dimnum_heads=8,qkv_bias=False,qk_scale=None,attn_drop_ratio=0.,proj_drop_ratio=0.):super(Attention, self).__init__()# 每個heda對應(yīng)qkv的dimself.num_heads = num_headshead_dim = dim // num_headsself.scale = qk_scale or head_dim ** -0.5# 得到qkvself.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)self.attn_drop = nn.Dropout(attn_drop_ratio)self.proj = nn.Linear(dim, dim)self.proj_drop = nn.Dropout(proj_drop_ratio)def forward(self, x):# [batch_size, num_patches + 1(Class token), total_embed_dim]B, N, C = x.shape# qkv(): -> [batch_size, num_patches + 1, 3 * total_embed_dim]# reshape: -> [batch_size, num_patches + 1, 3, num_heads, embed_dim_per_head]# permute: -> [3, batch_size, num_heads, num_patches + 1, embed_dim_per_head] 調(diào)整維度qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)# [batch_size, num_heads, num_patches + 1, embed_dim_per_head]q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)# transpose: -> [batch_size, num_heads, embed_dim_per_head, num_patches + 1]# @: multiply -> [batch_size, num_heads, num_patches + 1, num_patches + 1]attn = (q @ k.transpose(-2, -1)) * self.scale # scale是除以根號那一部分attn = attn.softmax(dim=-1) # dim=-1:每一行進行softmax處理attn = self.attn_drop(attn)# @: multiply -> [batch_size, num_heads, num_patches + 1, embed_dim_per_head]# transpose: -> [batch_size, num_patches + 1, num_heads, embed_dim_per_head]# reshape: -> [batch_size, num_patches + 1, total_embed_dim]x = (attn @ v).transpose(1, 2).reshape(B, N, C)x = self.proj(x)x = self.proj_drop(x)return x# 這里是Encoder Block 中的 MLP Block
class Mlp(nn.Module):"""MLP as used in Vision Transformer, MLP-Mixer and related networks"""def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):super().__init__()out_features = out_features or in_featureshidden_features = hidden_features or in_featuresself.fc1 = nn.Linear(in_features, hidden_features)self.act = act_layer()self.fc2 = nn.Linear(hidden_features, out_features)self.drop = nn.Dropout(drop)def forward(self, x):x = self.fc1(x)x = self.act(x)x = self.drop(x)x = self.fc2(x)x = self.drop(x)return x# Encoder Block
class Block(nn.Module):def __init__(self,dim,num_heads,mlp_ratio=4.,qkv_bias=False,qk_scale=None,drop_ratio=0.,attn_drop_ratio=0.,drop_path_ratio=0.,act_layer=nn.GELU,norm_layer=nn.LayerNorm):super(Block, self).__init__()self.norm1 = norm_layer(dim)self.attn = Attention(dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,attn_drop_ratio=attn_drop_ratio, proj_drop_ratio=drop_ratio)# NOTE: drop path for stochastic depth, we shall see if this is better than dropout hereself.drop_path = DropPath(drop_path_ratio) if drop_path_ratio > 0. else nn.Identity()self.norm2 = norm_layer(dim)mlp_hidden_dim = int(dim * mlp_ratio)self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop_ratio)def forward(self, x):x = x + self.drop_path(self.attn(self.norm1(x)))x = x + self.drop_path(self.mlp(self.norm2(x)))return xclass VisionTransformer(nn.Module):def __init__(self, img_size=224, patch_size=16, in_c=3, num_classes=1000,embed_dim=768, depth=12, num_heads=12, mlp_ratio=4.0, qkv_bias=True,qk_scale=None, representation_size=None, distilled=False, drop_ratio=0.,attn_drop_ratio=0., drop_path_ratio=0., embed_layer=PatchEmbed, norm_layer=None,act_layer=None):"""Args:img_size (int, tuple): input image sizepatch_size (int, tuple): patch sizein_c (int): number of input channelsnum_classes (int): number of classes for classification headembed_dim (int): embedding dimensiondepth (int): depth of transformernum_heads (int): number of attention headsmlp_ratio (int): ratio of mlp hidden dim to embedding dimqkv_bias (bool): enable bias for qkv if Trueqk_scale (float): override default qk scale of head_dim ** -0.5 if setrepresentation_size (Optional[int]): enable and set representation layer (pre-logits) to this value if setdistilled (bool): model includes a distillation token and head as in DeiT modelsdrop_ratio (float): dropout rateattn_drop_ratio (float): attention dropout ratedrop_path_ratio (float): stochastic depth rateembed_layer (nn.Module): patch embedding layernorm_layer: (nn.Module): normalization layer"""super(VisionTransformer, self).__init__()self.num_classes = num_classesself.num_features = self.embed_dim = embed_dim # num_features for consistency with other modelsself.num_tokens = 2 if distilled else 1norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)act_layer = act_layer or nn.GELUself.patch_embed = embed_layer(img_size=img_size, patch_size=patch_size, in_c=in_c, embed_dim=embed_dim)num_patches = self.patch_embed.num_patches# class token:1x768self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) # Parameter使用可訓(xùn)練參數(shù)self.dist_token = nn.Parameter(torch.zeros(1, 1, embed_dim)) if distilled else None# Position Embedding :197x768self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + self.num_tokens, embed_dim))self.pos_drop = nn.Dropout(p=drop_ratio)dpr = [x.item() for x in torch.linspace(0, drop_path_ratio, depth)] # stochastic depth decay rule# 堆疊Encoder blockself.blocks = nn.Sequential(*[Block(dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,drop_ratio=drop_ratio, attn_drop_ratio=attn_drop_ratio, drop_path_ratio=dpr[i],norm_layer=norm_layer, act_layer=act_layer)for i in range(depth)])self.norm = norm_layer(embed_dim)# Representation layer(MLP Head)if representation_size and not distilled:self.has_logits = Trueself.num_features = representation_sizeself.pre_logits = nn.Sequential(OrderedDict([("fc", nn.Linear(embed_dim, representation_size)),("act", nn.Tanh())]))else: # MLP Head就沒有pre-logitsself.has_logits = Falseself.pre_logits = nn.Identity()# Classifier head(s)self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()self.head_dist = Noneif distilled:self.head_dist = nn.Linear(self.embed_dim, self.num_classes) if num_classes > 0 else nn.Identity()# Weight initnn.init.trunc_normal_(self.pos_embed, std=0.02)if self.dist_token is not None:nn.init.trunc_normal_(self.dist_token, std=0.02)nn.init.trunc_normal_(self.cls_token, std=0.02)self.apply(_init_vit_weights)def forward_features(self, x):# [B, C, H, W] -> [B, num_patches, embed_dim]x = self.patch_embed(x) # [B, 196, 768]# [1, 1, 768] -> [B, 1, 768]cls_token = self.cls_token.expand(x.shape[0], -1, -1)if self.dist_token is None:x = torch.cat((cls_token, x), dim=1) # [B, 197, 768]else:x = torch.cat((cls_token, self.dist_token.expand(x.shape[0], -1, -1), x), dim=1)x = self.pos_drop(x + self.pos_embed)x = self.blocks(x)x = self.norm(x)if self.dist_token is None:return self.pre_logits(x[:, 0])else:return x[:, 0], x[:, 1]def forward(self, x):x = self.forward_features(x)if self.head_dist is not None:x, x_dist = self.head(x[0]), self.head_dist(x[1])if self.training and not torch.jit.is_scripting():# during inference, return the average of both classifier predictionsreturn x, x_distelse:return (x + x_dist) / 2else:x = self.head(x) # 全連接層return xdef _init_vit_weights(m):"""ViT weight initialization:param m: module"""if isinstance(m, nn.Linear):nn.init.trunc_normal_(m.weight, std=.01)if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, nn.Conv2d):nn.init.kaiming_normal_(m.weight, mode="fan_out")if m.bias is not None:nn.init.zeros_(m.bias)elif isinstance(m, nn.LayerNorm):nn.init.zeros_(m.bias)nn.init.ones_(m.weight)def vit_base_patch16_224(num_classes: int = 1000, has_logits: bool = True):"""ViT-Base model (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:鏈接: https://pan.baidu.com/s/1zqb08naP0RPqqfSXfkB2EA 密碼: eu9f"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=768, # 16x16x3depth=12, # encoder block 重復(fù)堆疊的次數(shù)num_heads=12,representation_size=None, # MLP Head 中 pre-logits全連接層的個數(shù)num_classes=num_classes)return modeldef vit_base_patch16_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Base model (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_patch16_224_in21k-e5005f0a.pth"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=768,depth=12,num_heads=12,representation_size=768 if has_logits else None,num_classes=num_classes)return modeldef vit_base_patch32_224(num_classes: int = 1000):"""ViT-Base model (ViT-B/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:鏈接: https://pan.baidu.com/s/1hCv0U8pQomwAtHBYc4hmZg 密碼: s5hl"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=768,depth=12,num_heads=12,representation_size=None,num_classes=num_classes)return modeldef vit_base_patch32_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Base model (ViT-B/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_base_patch32_224_in21k-8db57226.pth"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=768,depth=12,num_heads=12,representation_size=768 if has_logits else None,num_classes=num_classes)return modeldef vit_large_patch16_224(num_classes: int = 1000):"""ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-1k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:鏈接: https://pan.baidu.com/s/1cxBgZJJ6qUWPSBNcE4TdRQ 密碼: qqt8"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=1024,depth=24,num_heads=16,representation_size=None,num_classes=num_classes)return modeldef vit_large_patch16_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_large_patch16_224_in21k-606da67d.pth"""model = VisionTransformer(img_size=224,patch_size=16,embed_dim=1024,depth=24,num_heads=16,representation_size=1024 if has_logits else None,num_classes=num_classes)return modeldef vit_large_patch32_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Large model (ViT-L/32) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.weights ported from official Google JAX impl:https://github.com/rwightman/pytorch-image-models/releases/download/v0.1-vitjx/jx_vit_large_patch32_224_in21k-9046d2e7.pth"""model = VisionTransformer(img_size=224,patch_size=32,embed_dim=1024,depth=24,num_heads=16,representation_size=1024 if has_logits else None,num_classes=num_classes)return modeldef vit_huge_patch14_224_in21k(num_classes: int = 21843, has_logits: bool = True):"""ViT-Huge model (ViT-H/14) from original paper (https://arxiv.org/abs/2010.11929).ImageNet-21k weights @ 224x224, source https://github.com/google-research/vision_transformer.NOTE: converted weights not currently available, too large for github release hosting."""model = VisionTransformer(img_size=224,patch_size=14,embed_dim=1280,depth=32,num_heads=16,representation_size=1280 if has_logits else None,num_classes=num_classes)return model
2.訓(xùn)練和測試模型
def main(args):# 檢測是否支持CUDA,如果支持則使用第一個可用的GPU設(shè)備,否則使用CPUdevice = torch.device(args.device if torch.cuda.is_available() else "cpu")print(args)print('Start Tensorboard with "tensorboard --logdir=runs", view at http://localhost:6006/')# tensorboard --logdir=F:/NN/Learn_Pytorch/ShuffleNetV2/runs/Oct11_13-22-17_DESKTOP-64L888R# 記錄訓(xùn)練過程中的指標(biāo)和可視化結(jié)果tb_writer = SummaryWriter()# 創(chuàng)建一個用于存儲模型權(quán)重文件的目錄if os.path.exists("./weights") is False:os.makedirs("./weights")# 獲取訓(xùn)練和驗證數(shù)據(jù)集的文件路徑和標(biāo)簽train_images_path, train_images_label, val_images_path, val_images_label = read_split_data(args.data_path)# 數(shù)據(jù)預(yù)處理/增強的操作data_transform = {"train": transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])]),"val": transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])}# 實例化訓(xùn)練數(shù)據(jù)集train_dataset = MyDataSet(images_path=train_images_path,images_class=train_images_label,transform=data_transform["train"])# 實例化驗證數(shù)據(jù)集val_dataset = MyDataSet(images_path=val_images_path,images_class=val_images_label,transform=data_transform["val"])batch_size = args.batch_sizenw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8]) # number of workersprint('Using {} dataloader workers every process'.format(nw))# 加載數(shù)據(jù)集,指定了批處理大小、是否打亂數(shù)據(jù)、數(shù)據(jù)加載的并行工作進程數(shù)(num_workers)# 以及如何合并批次數(shù)據(jù)的函數(shù)(collate_fn)train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size,shuffle=True,pin_memory=True,num_workers=nw,collate_fn=train_dataset.collate_fn)val_loader = torch.utils.data.DataLoader(val_dataset,batch_size=batch_size,shuffle=False,pin_memory=True,num_workers=nw,collate_fn=val_dataset.collate_fn)# 如果存在預(yù)訓(xùn)練權(quán)重則載入model = create_model(num_classes=args.num_classes, has_logits=False).to(device)if args.weights != "":assert os.path.exists(args.weights), "weights file: '{}' not exist.".format(args.weights)weights_dict = torch.load(args.weights, map_location=device)# 刪除不需要的權(quán)重del_keys = ['head.weight', 'head.bias'] if model.has_logits \else ['pre_logits.fc.weight', 'pre_logits.fc.bias', 'head.weight', 'head.bias']for k in del_keys:del weights_dict[k]print(model.load_state_dict(weights_dict, strict=False))if args.freeze_layers:for name, para in model.named_parameters():# 除head, pre_logits外,其他權(quán)重全部凍結(jié)if "head" not in name and "pre_logits" not in name:para.requires_grad_(False)else:print("training {}".format(name))# 創(chuàng)建一個包含所有需要進行梯度更新的參數(shù)的列表pg = [p for p in model.parameters() if p.requires_grad]optimizer = optim.SGD(pg, lr=args.lr, momentum=0.9, weight_decay=4E-5)# Scheduler https://arxiv.org/pdf/1812.01187.pdf# 學(xué)習(xí)率調(diào)度策略,將學(xué)習(xí)率在訓(xùn)練過程中按余弦函數(shù)的方式進行調(diào)整lf = lambda x: ((1 + math.cos(x * math.pi / args.epochs)) / 2) * (1 - args.lrf) + args.lrf # cosine# 根據(jù)余弦函數(shù)的形狀調(diào)整學(xué)習(xí)率scheduler = lr_scheduler.LambdaLR(optimizer, lr_lambda=lf)best_acc = 0.0for epoch in range(args.epochs):# trainmean_loss = train_one_epoch(model=model,optimizer=optimizer,data_loader=train_loader,device=device,epoch=epoch)scheduler.step()# validateacc = evaluate(model=model,data_loader=val_loader,device=device)print("[epoch {}] accuracy: {}".format(epoch, round(acc, 3)))tags = ["loss", "accuracy", "learning_rate"]tb_writer.add_scalar(tags[0], mean_loss, epoch)tb_writer.add_scalar(tags[1], acc, epoch)tb_writer.add_scalar(tags[2], optimizer.param_groups[0]["lr"], epoch)# 保存準(zhǔn)確率最高的權(quán)重if round(acc, 3) > best_acc:best_acc = round(acc, 3)torch.save(model.state_dict(), "./weights/model-{}.pth".format(epoch))if __name__ == '__main__':parser = argparse.ArgumentParser()parser.add_argument('--num_classes', type=int, default=5)parser.add_argument('--epochs', type=int, default=100)parser.add_argument('--batch-size', type=int, default=16)parser.add_argument('--lr', type=float, default=0.01)parser.add_argument('--lrf', type=float, default=0.1)# 數(shù)據(jù)集所在根目錄# https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgzparser.add_argument('--data-path', type=str,default=r"F:/NN/Learn_Pytorch/flower_photos")parser.add_argument('--model-name', default='', help='create model name')# 預(yù)訓(xùn)練權(quán)重路徑,如果不想載入就設(shè)置為空字符parser.add_argument('--weights', type=str, default='./vit_base_patch16_224_in21k.pth',help='initial weights path')# 是否凍結(jié)權(quán)重parser.add_argument('--freeze-layers', type=bool, default=True)parser.add_argument('--device', default='cuda:0', help='device id (i.e. 0 or 0,1 or cpu)')opt = parser.parse_args()main(opt)
這里使用了預(yù)訓(xùn)練權(quán)重,在其基礎(chǔ)上訓(xùn)練自己的數(shù)據(jù)集。訓(xùn)練100epoch的準(zhǔn)確率能到達98%左右。
五、實現(xiàn)圖像分類
這里使用花朵數(shù)據(jù)集,下載連接:https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
def main():device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")# 與訓(xùn)練的預(yù)處理一樣data_transform = transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])])# 加載圖片img_path = 'daisy2.jpg'assert os.path.exists(img_path), "file: '{}' does not exist.".format(img_path)image = Image.open(img_path)# image.show()# [N, C, H, W]img = data_transform(image)# 擴展維度img = torch.unsqueeze(img, dim=0)# 獲取標(biāo)簽json_path = 'class_indices.json'assert os.path.exists(json_path), "file: '{}' does not exist.".format(json_path)with open(json_path, 'r') as f:# 使用json.load()函數(shù)加載JSON文件的內(nèi)容并將其存儲在一個Python字典中class_indict = json.load(f)# create modelmodel = create_model(num_classes=5, has_logits=False).to(device)# load model weightsmodel_weight_path = "./weights/model-2.pth"model.load_state_dict(torch.load(model_weight_path, map_location=device))model.eval()with torch.no_grad():# 對輸入圖像進行預(yù)測output = torch.squeeze(model(img.to(device))).cpu()# 對模型的輸出進行 softmax 操作,將輸出轉(zhuǎn)換為類別概率predict = torch.softmax(output, dim=0)# 得到高概率的類別的索引predict_cla = torch.argmax(predict).numpy()res = "class: {} prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())draw = ImageDraw.Draw(image)# 文本的左上角位置position = (10, 10)# fill 指定文本顏色draw.text(position, res, fill='green')image.show()for i in range(len(predict)):print("class: {:10} prob: {:.3}".format(class_indict[str(i)], predict[i].numpy()))
分類結(jié)果:
結(jié)束語
感謝閱讀吾之文章,今已至此次旅程之終站 🛬。
吾望斯文獻能供爾以寶貴之信息與知識也 🎉。
學(xué)習(xí)者之途,若藏于天際之星辰🍥,吾等皆當(dāng)努力熠熠生輝,持續(xù)前行。
然而,如若斯文獻有益于爾,何不以三連為禮?點贊、留言、收藏 - 此等皆以證爾對作者之支持與鼓勵也 💞。