1. 程式人生 > 其它 >Vision Transformer影象分類(MindSpore實現)

Vision Transformer影象分類(MindSpore實現)

Vision Transformer(ViT)簡介

近些年,隨著基於自注意(Self-Attention)結構的模型的發展,特別是Transformer模型的提出,極大的促進了自然語言處理模型的發展。由於Transformers的計算效率和可擴充套件性,它已經能夠訓練具有超過100B引數的空前規模的模型。

ViT則是自然語言處理和計算機視覺兩個領域的融合結晶。在不依賴卷積操作的情況下,依然可以在影象分類任務上達到很好的效果。

模型結構

ViT模型的主體結構是基於Transformer模型的Encoder部分(部分結構順序有調整,如:normalization的位置與標準Transformer不同),其結構圖如下:

模型特點

ViT模型是應用於影象分類領域。因此,其模型結構相較於傳統的Transformer有以下幾個特點:

  1. 資料集的原影象被劃分為多個patch後,將二維patch(不考慮channel)轉換為一維向量,再加上類別向量與位置向量作為模型輸入。
  2. 模型主體的Block基於Transformer的Encoder部分,但是調整了normaliztion的位置,其中,最主要的結構依然是Multi-head Attention結構。
  3. 模型在Blocks堆疊後接全連線層接受類別向量輸出用於分類。通常情況下,我們將最後的全連線層稱為Head,Transformer Encoder部分為backbone。

下面將通過程式碼例項來詳細解釋基於ViT實現ImageNet分類任務。

環境準備與資料讀取

本案例基於MindSpore-GPU版本,在單GPU卡上完成模型訓練和驗證。

首先匯入相關模組,配置相關超引數並讀取資料集,該部分程式碼在Vision套件中都有API可直接呼叫,詳情可以參考以下連結: 。

可通過: 進行資料集下載。

載入前先定義資料集路徑,請確保你的資料集路徑如以下結構。

.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中:

  1. 最初的輸入向量首先會經過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,