Vision Transformer影象分類(MindSpore實現)
Vision Transformer(ViT)簡介
近些年,隨著基於自注意(Self-Attention)結構的模型的發展,特別是Transformer模型的提出,極大的促進了自然語言處理模型的發展。由於Transformers的計算效率和可擴充套件性,它已經能夠訓練具有超過100B引數的空前規模的模型。
ViT則是自然語言處理和計算機視覺兩個領域的融合結晶。在不依賴卷積操作的情況下,依然可以在影象分類任務上達到很好的效果。
模型結構
ViT模型的主體結構是基於Transformer模型的Encoder部分(部分結構順序有調整,如:normalization的位置與標準Transformer不同),其結構圖如下:
模型特點
ViT模型是應用於影象分類領域。因此,其模型結構相較於傳統的Transformer有以下幾個特點:
- 資料集的原影象被劃分為多個patch後,將二維patch(不考慮channel)轉換為一維向量,再加上類別向量與位置向量作為模型輸入。
- 模型主體的Block基於Transformer的Encoder部分,但是調整了normaliztion的位置,其中,最主要的結構依然是Multi-head Attention結構。
- 模型在Blocks堆疊後接全連線層接受類別向量輸出用於分類。通常情況下,我們將最後的全連線層稱為Head,Transformer Encoder部分為backbone。
下面將通過程式碼例項來詳細解釋基於ViT實現ImageNet分類任務。
環境準備與資料讀取
本案例基於MindSpore-GPU版本,在單GPU卡上完成模型訓練和驗證。
首先匯入相關模組,配置相關超引數並讀取資料集,該部分程式碼在Vision套件中都有API可直接呼叫,詳情可以參考以下連結:https://gitee.com/mindspore/vision 。
可通過:http://image-net.org/ 進行資料集下載。
載入前先定義資料集路徑,請確保你的資料集路徑如以下結構。
.ImageNet/ ├── ILSVRC2012_devkit_t12.tar.gz ├── train/ └── val/
from mindspore import context
from mindvision.classification.dataset import ImageNet
context.set_context(mode=context.GRAPH_MODE, device_target='GPU')
data_url = './ImageNet/'
resize = 224
batch_size = 16
dataset_train = ImageNet(data_url,
split="train",
shuffle=True,
resize=resize,
batch_size=batch_size,
repeat_num=1,
num_parallel_workers=1).run()
模型解析
下面將通過程式碼來細緻剖析ViT模型的內部結構。
Transformer基本原理
Transformer模型源於2017年的一篇文章[2]。在這篇文章中提出的基於Attention機制的編碼器-解碼器型結構在自然語言處理領域獲得了巨大的成功。模型結構如下圖所示:
其主要結構為多個Encoder和Decoder模組所組成,其中Encoder和Decoder的詳細結構如下圖所示:
Encoder與Decoder由許多結構組成,如:多頭注意力(Multi-Head Attention)層,Feed Forward層,Normaliztion層,甚至殘差連線(Residual Connection,圖中的“add”)。不過,其中最重要的結構是多頭注意力(Multi-Head Attention)結構,該結構基於自注意力(Self-Attention)機制,是多個Self-Attention的並行組成。
所以,理解了Self-Attention就抓住了Transformer的核心。
Attention模組
以下是Self-Attention的解釋,其核心內容是為輸入向量的每個單詞學習一個權重。通過給定一個任務相關的查詢向量Query向量,計算Query和各個Key的相似性或者相關性得到注意力分佈,即得到每個Key對應Value的權重係數,然後對Value進行加權求和得到最終的Attention數值。
在Self-Attention中:
- 最初的輸入向量首先會經過Embedding層對映成Q(Query),K(Key),V(Value)三個向量,由於是並行操作,所以程式碼中是對映成為dim x 3的向量然後進行分割,換言之,如果你的輸入向量為一個向量序列( 1x1 1x1, 2x2 2x2, 3x3 3x3),其中的 1x1 1x1, 2x2 2x2, 3x3 3x3都是一維向量,那麼每一個一維向量都會經過Embedding層映射出Q,K,V三個向量,只是Embedding矩陣不同,矩陣引數也是通過學習得到的。這裡大家可以認為,Q,K,V三個矩陣是發現向量之間關聯資訊的一種手段,需要經過學習得到,至於為什麼是Q,K,V三個,主要是因為需要兩個向量點乘以獲得權重,又需要另一個向量來承載權重向加的結果,所以,最少需要3個矩陣,也是論文作者經過實驗得出的結論。
2. 自注意力機制的自注意主要體現在它的Q,K,V都來源於其自身,也就是該過程是在提取輸入的不同順序的向量的聯絡與特徵,最終通過不同順序向量之間的聯絡緊密性(Q與K乘積經過softmax的結果)來表現出來。Q,K,V得到後就需要獲取向量間權重,需要對Q和K進行點乘併除以維度的平方根 ⎯⎯√d ⎯⎯√d,對所有向量的結果進行Softmax處理,通過公式(2)的操作,我們獲得了向量之間的關係權重。
3.其最終輸出則是通過V這個對映後的向量與QK經過Softmax結果進行weight sum獲得,這個過程可以理解為在全域性上進行自注意表示。每一組QKV最後都有一個V輸出,這是Self-Attention得到的最終結果,是當前向量在結合了它與其他向量關聯權重後得到的結果。
通過下圖可以整體把握Self-Attention的全部過程。
多頭注意力機制就是將原本self-Attention處理的向量分割為多個Head進行處理,這一點也可以從程式碼中體現,這也是attention結構可以進行並行加速的一個方面。
總結來說,多頭注意力機制在保持引數總量不變的情況下,將同樣的query, key和value對映到原來的高維空間(Q,K,V)的不同子空間(Q_0,K_0,V_0)中進行自注意力的計算,最後再合併不同子空間中的注意力資訊。
所以,對於同一個輸入向量,多個注意力機制可以同時對其進行處理,即利用平行計算加速處理過程,又在處理的時候更充分的分析和利用了向量特徵。下圖展示了多頭注意力機制,其並行能力的主要體現在下圖中的$a_1$和$a_2$是同一個向量進行分割獲得的。
以下是vision套件中的Multi-Head Attention程式碼,結合上文的解釋,程式碼清晰的展現了這一過程。
import mindspore.nn as nn
class Attention(nn.Cell):
def __init__(self,
dim: int,
num_heads: int = 8,
keep_prob: float = 1.0,
attention_keep_prob: float = 1.0):
super(Attention, self).__init__()
self.num_heads = num_heads
head_dim = dim // num_heads
self.scale = Tensor(head_dim ** -0.5)
self.qkv = nn.Dense(dim, dim * 3)
self.attn_drop = nn.Dropout(attention_keep_prob)
self.out = nn.Dense(dim, dim)
self.out_drop = nn.Dropout(keep_prob)
self.mul = P.Mul()
self.reshape = P.Reshape()
self.transpose = P.Transpose()
self.unstack = P.Unstack(axis=0)
self.attn_matmul_v = P.BatchMatMul()
self.q_matmul_k = P.BatchMatMul(transpose_b=True)
self.softmax = nn.Softmax(axis=-1)
def construct(self, x):
"""Attention construct."""
b, n, c = x.shape
# 最初的輸入向量首先會經過Embedding層對映成Q(Query),K(Key),V(Value)三個向量
# 由於是並行操作,所以程式碼中是對映成為dim*3的向量然後進行分割
qkv = self.qkv(x)
#多頭注意力機制就是將原本self-Attention處理的向量分割為多個Head進行處理
qkv = self.reshape(qkv, (b, n, 3, self.num_heads, c // self.num_heads))
qkv = self.transpose(qkv,