46、縮放點積注意力模塊
論文《Attention Is All You Need》
1、作用:
縮放點積注意力(Scaled Dot-Product Attention)是 Transformer 模型的核心組件,旨在解決序列建模中長距離依賴關系捕捉的問題。傳統的循環神經網絡(RNN)在處理長序列時存在梯度消失或爆炸的問題,且并行性較差。該模塊通過計算查詢(Query)、鍵(Key)和值(Value)之間的相似度,實現對輸入序列中重要信息的聚焦,同時支持高效的并行計算,為 Transformer 在自然語言處理、計算機視覺等領域的成功奠定了基礎。
2、機制
縮放點積注意力的核心機制是通過向量點積計算查詢與鍵的相似度,再經過縮放和 softmax 歸一化得到注意力權重,最后用權重對值進行加權求和。具體步驟如下:
- 計算查詢(Q)與鍵(K)的點積,得到原始注意力分數:scores=QKT
- 對原始分數進行縮放,除以鍵維度的平方根(dk??),避免因維度過高導致的分數值過大,從而穩定 softmax 的梯度:scaled_scores=scores/dk??
- 對縮放后的分數應用 softmax 函數,得到歸一化的注意力權重:attention_weights=softmax(scaled_scores)
- 用注意力權重對值(V)進行加權求和,得到最終的注意力輸出:output=attention_weights×V
此外,為了處理掩碼場景(如解碼時的序列掩碼),可在 softmax 前加入掩碼(mask),將無效位置的分數設為負無窮。
3、獨特優勢
- 并行性強:相較于 RNN 的串行計算方式,縮放點積注意力可對序列中所有位置的關系進行并行計算,大幅提升訓練和推理速度。
- 長距離依賴捕捉能力:通過直接計算任意兩個位置之間的注意力權重,能夠有效捕捉長序列中的依賴關系,優于 RNN 和卷積神經網絡(CNN)的局部感受野限制。
- 靈活性高:可通過多頭注意力(Multi-Head Attention)擴展為多個并行的注意力子空間,捕捉不同維度的特征關系,進一步提升模型性能。
在機器翻譯任務中,基于該模塊的 Transformer 模型在 WMT 2014 英德翻譯任務上實現了 28.4 BLEU 的分數,顯著優于當時的主流模型。
4、代碼
import torch
import torch.nn as nn
import torch.nn.functional as Fclass ScaledDotProductAttention(nn.Module):"""縮放點積注意力模塊(Scaled Dot-Product Attention)實現查詢、鍵、值之間的注意力計算"""def __init__(self):super().__init__()def forward(self, q, k, v, mask=None):"""參數說明:q: 查詢張量,形狀為 [batch_size, n_heads, seq_len_q, d_k]k: 鍵張量,形狀為 [batch_size, n_heads, seq_len_k, d_k]v: 值張量,形狀為 [batch_size, n_heads, seq_len_v, d_v](通常seq_len_k = seq_len_v)mask: 掩碼張量,形狀為 [batch_size, 1, seq_len_q, seq_len_k] 或類似,用于掩蓋無效位置返回:output: 注意力輸出,形狀為 [batch_size, n_heads, seq_len_q, d_v]attn_weights: 注意力權重,形狀為 [batch_size, n_heads, seq_len_q, seq_len_k]"""d_k = q.size(-1) # 鍵的維度# 計算Q與K的點積并縮放scores = torch.matmul(q, k.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))# 應用掩碼(若有)if mask is not None:scores = scores.masked_fill(mask == 0, -1e9) # 掩碼位置設為負無窮# 計算注意力權重attn_weights = F.softmax(scores, dim=-1)# 加權求和得到輸出output = torch.matmul(attn_weights, v)return output, attn_weights# 測試代碼
if __name__ == '__main__':# 實例化注意力模塊model = ScaledDotProductAttention()# 生成隨機輸入(batch_size=2, n_heads=8, seq_len=10, d_k=d_v=64)q = torch.randn(2, 8, 10, 64)k = torch.randn(2, 8, 10, 64)v = torch.randn(2, 8, 10, 64)# 生成掩碼(掩蓋后5個位置)mask = torch.ones(2, 1, 10, 10)mask[:, :, :, 5:] = 0# 前向傳播output, attn_weights = model(q, k, v, mask)# 驗證輸出形狀print(f"輸出形狀: {output.shape}") # 預期: torch.Size([2, 8, 10, 64])print(f"注意力權重形狀: {attn_weights.shape}") # 預期: torch.Size([2, 8, 10, 10])
47、深度可分離卷積模塊
論文《MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications》
1、作用:
深度可分離卷積(Depthwise Separable Convolution)是為移動端和嵌入式設備設計的高效卷積操作,旨在解決傳統卷積神經網絡計算量大、參數過多的問題。傳統卷積在提取空間特征和通道特征時存在冗余計算,該模塊通過將卷積操作分解為深度卷積(Depthwise Convolution)和逐點卷積(Pointwise Convolution),在保持相似特征提取能力的同時,大幅減少計算量(FLOPs)和模型參數,使神經網絡能夠在資源受限的設備上高效運行。
2、機制
深度可分離卷積由兩個連續的操作組成,具體機制如下:
- 深度卷積:對輸入特征圖的每個通道單獨應用一個卷積核(即每個通道對應一個卷積核),用于捕捉該通道內的空間特征。假設輸入特征圖的通道數為Cin?,卷積核大小為K×K,則深度卷積的卷積核總數為Cin?,輸出特征圖的通道數仍為Cin?。其計算量為Cin?×H×W×K×K(H、W為特征圖的高和寬)。
- 逐點卷積:使用1×1的卷積核對深度卷積的輸出進行跨通道特征融合。假設需要輸出的通道數為Cout?,則逐點卷積的卷積核數量為Cout?,每個卷積核的輸入通道數為Cin?。其計算量為Cout?×H×W×Cin?×1×1。
相比之下,傳統卷積的計算量為Cout?×H×W×K×K×Cin?,深度可分離卷積的計算量約為傳統卷積的1/Cout?+1/(K2),當K=3時,計算量可減少至約 1/9~1/8。
3、獨特優勢
- 高效性:在相同感受野的情況下,計算量和參數數量遠低于傳統卷積,例如 MobileNet 使用深度可分離卷積后,模型大小僅為 AlexNet 的 1/10,計算量減少至 1/8,卻能保持相近的 ImageNet 分類準確率。
- 靈活性:深度卷積和逐點卷積可分別獨立優化,例如在深度卷積后加入批歸一化和激活函數,提升特征提取能力。
- 適用性廣:不僅適用于移動端視覺任務(如圖像分類、目標檢測),還可用于輕量級模型設計,如 MobileNet 系列、Xception 等,在工業界得到廣泛應用。
4、代碼
import torch
import torch.nn as nnclass DepthwiseSeparableConv(nn.Module):"""深度可分離卷積模塊(Depthwise Separable Convolution)由深度卷積和逐點卷積組成,減少計算量和參數"""def __init__(self, in_channels, out_channels, kernel_size=3, stride=1, padding=1):"""參數說明:in_channels: 輸入特征圖的通道數out_channels: 輸出特征圖的通道數kernel_size: 深度卷積的卷積核大小stride: 深度卷積的步長padding: 深度卷積的填充"""super().__init__()# 深度卷積:每個通道單獨卷積self.depthwise = nn.Conv2d(in_channels=in_channels,out_channels=in_channels, # 輸出通道數與輸入相同kernel_size=kernel_size,stride=stride,padding=padding,groups=in_channels, # 分組數等于輸入通道數,實現每個通道單獨卷積bias=False)# 逐點卷積:1x1卷積融合通道特征self.pointwise = nn.Conv2d(in_channels=in_channels,out_channels=out_channels,kernel_size=1,stride=1,padding=0,bias=False)# 批歸一化和激活函數self.bn = nn.BatchNorm2d(out_channels)self.relu = nn.ReLU(inplace=True)def forward(self, x):# 深度卷積x = self.depthwise(x)# 逐點卷積x = self.pointwise(x)# 批歸一化和激活x = self.bn(x)x = self.relu(x)return x# 測試代碼
if __name__ == '__main__':# 實例化深度可分離卷積模塊(輸入通道32,輸出通道64,3x3卷積)model = DepthwiseSeparableConv(in_channels=32, out_channels=64, kernel_size=3).cuda()# 創建隨機輸入張量 [batch_size=2, channels=32, height=64, width=64]input_tensor = torch.randn(2, 32, 64, 64).cuda()# 前向傳播output_tensor = model(input_tensor)# 驗證輸出形狀print(f"輸入形狀: {input_tensor.shape}")print(f"輸出形狀: {output_tensor.shape}") # 預期: torch.Size([2, 64, 64, 64])
48、視覺 Transformer(ViT)的 Patch Embedding 模塊
論文《An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale》
1、作用: Patch Embedding 是視覺 Transformer(Vision Transformer, ViT)中將圖像轉換為 Transformer 可處理序列的核心模塊。傳統的 Transformer 模型主要用于處理文本序列,而圖像是二維網格結構,無法直接輸入 Transformer。該模塊通過將圖像分割為固定大小的非重疊補丁(Patches),并將每個補丁線性投影為固定維度的向量,從而將圖像轉換為一維序列,使 Transformer 能夠應用于圖像識別等視覺任務,打破了卷積神經網絡在視覺領域的壟斷地位。
2、機制 Patch Embedding 的具體機制如下:
- 圖像分塊:將輸入圖像(形狀為\(H \times W \times C\),H、W為圖像高寬,C為通道數)分割為N個非重疊的補丁,每個補丁的大小為\(P \times P \times C\)。若\(H = W = P \times \sqrt{N}\)(通常取\(P=16\)),則補丁數量\(N = (H/P) \times (W/P)\)。
- 線性投影:將每個\(P \times P \times C\)的補丁展平為長度為\(P^2 \times C\)的向量,再通過一個線性層(全連接層)將其投影為維度為D的向量(即嵌入維度),得到N個嵌入向量,形成形狀為\(N \times D\)的序列。
- 添加位置編碼:由于 Transformer 的自注意力機制是位置無關的,需在補丁嵌入序列中加入位置編碼(Positional Encoding),以提供補丁的空間位置信息。位置編碼的形狀為\(N \times D\),與補丁嵌入序列逐元素相加。
例如,對于 224x224x3 的圖像,使用 16x16 的補丁分割,可得到\(14 \times 14 = 196\)個補丁,每個補丁展平后長度為\(16 \times 16 \times 3 = 768\),通過線性投影至\(D=768\)維度,最終得到 196+1(加上類別嵌入)個向量的序列。
3、獨特優勢
- 適配 Transformer:成功將二維圖像轉換為一維序列,使 Transformer 能夠直接處理視覺數據,充分利用 Transformer 捕捉全局依賴的能力。
- 減少冗余計算:相較于卷積神經網絡的局部感受野,Patch Embedding 通過分塊投影,避免了卷積操作中大量的重疊計算,在大規模數據集(如 ImageNet-21k)上表現更優。
- 靈活性高:補丁大小和嵌入維度可靈活調整,以平衡模型性能和計算成本。例如,ViT-B 在 ImageNet-1k 上的 top-1 準確率達到 85.8%,超過同期的 ResNet-152。
4、代碼
import torch
import torch.nn as nnclass PatchEmbedding(nn.Module):"""視覺Transformer的Patch Embedding模塊將圖像分割為補丁并轉換為嵌入序列"""def __init__(self, img_size=224, patch_size=16, in_channels=3, embed_dim=768):"""參數說明:img_size: 輸入圖像的大小(假設為正方形)patch_size: 每個補丁的大小(假設為正方形)in_channels: 圖像的通道數(如RGB圖像為3)embed_dim: 補丁嵌入的維度"""super().__init__()self.img_size = img_sizeself.patch_size = patch_size# 計算補丁數量self.num_patches = (img_size // patch_size) **2# 定義補丁投影層(使用卷積實現,等價于線性投影)self.proj = nn.Conv2d(in_channels=in_channels,out_channels=embed_dim,kernel_size=patch_size,stride=patch_size # 步長等于補丁大小,確保非重疊分塊)# 定義位置編碼(可學習的位置嵌入)self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, embed_dim))def forward(self, x):"""參數x: 輸入圖像張量,形狀為 [batch_size, in_channels, img_size, img_size]返回: 補丁嵌入序列,形狀為 [batch_size, num_patches, embed_dim]"""batch_size = x.shape[0]# 補丁投影:[batch_size, in_channels, H, W] -> [batch_size, embed_dim, num_patches^(1/2), num_patches^(1/2)]x = self.proj(x)# 展平為序列:[batch_size, embed_dim, num_patches] -> [batch_size, num_patches, embed_dim]x = x.flatten(2).transpose(1, 2)# 添加位置編碼x = x + self.pos_embedreturn x# 測試代碼
if __name__ == '__main__':# 實例化Patch Embedding模塊(224x224圖像,16x16補丁,3通道,768維嵌入)model = PatchEmbedding(img_size=224, patch_size=16, in_channels=3, embed_dim=768).cuda()# 創建隨機輸入張量 [batch_size=2, channels=3, height=224, width=224]input_tensor = torch.randn(2, 3, 224, 224).cuda()# 前向傳播output_tensor = model(input_tensor)# 驗證輸出形狀print(f"輸入形狀: {input_tensor.shape}")print(f"輸出形狀: {output_tensor.shape}") # 預期: torch.Size([2, 196, 768])(196=14x14)
49、倒置殘差塊(Inverted Residual Block)
論文《MobileNetV2: Inverted Residuals and Linear Bottlenecks》
1、作用: 倒置殘差塊是 MobileNetV2 中提出的核心模塊,旨在解決移動端神經網絡中特征表達能力與計算效率的平衡問題。傳統的殘差塊(如 ResNet 中的殘差塊)采用 “降維 - 卷積 - 升維” 的結構,而倒置殘差塊創新性地使用 “升維 - 卷積 - 降維” 的結構,配合線性瓶頸(Linear Bottleneck),在減少計算量的同時,有效緩解了特征信息在低維空間的損失,顯著提升了輕量級模型的性能。
2、機制 倒置殘差塊的機制主要包括以下步驟:
- 升維(Expansion):通過\(1 \times 1\)的逐點卷積將輸入特征的通道數從\(C_{in}\)提升至\(t \times C_{in}\)(t為擴展因子,通常取 6),目的是在深度卷積前增加特征維度,提升特征表達能力。
- 深度卷積(Depthwise Convolution):對升維后的特征圖應用\(3 \times 3\)的深度卷積(每個通道單獨卷積),捕捉空間特征,此時計算量為\(t \times C_{in} \times H \times W \times 3 \times 3\),由于深度卷積的特性,計算量仍保持較低水平。
- 降維(Projection):通過\(1 \times 1\)的逐點卷積將特征通道數從\(t \times C_{in}\)降維至\(C_{out}\),并移除非線性激活函數(使用線性層),形成線性瓶頸,減少通道冗余。
- 殘差連接(Residual Connection):當輸入輸出通道數和空間尺寸相同時,添加跳躍連接,將輸入特征與輸出特征相加,緩解梯度消失問題。
與傳統殘差塊相比,倒置殘差塊在高維空間進行卷積操作,避免了低維空間的信息損失,同時通過深度卷積保持計算效率。
3、獨特優勢
- 高效特征提取:通過升維操作增強特征表達能力,配合深度卷積減少計算量,在相同 FLOPs 下比傳統殘差塊具有更強的特征提取能力。
- 緩解信息損失:線性瓶頸設計避免了非線性激活函數在低維空間對特征信息的破壞,實驗表明 MobileNetV2 在 ImageNet 上的準確率比 MobileNetV1 提升了 3.2%。
- 輕量化設計:適用于移動端和嵌入式設備,MobileNetV2 的模型大小僅為 14MB,卻能在 COCO 目標檢測任務上達到與 ResNet-50 相當的性能。
4、代碼
import torch
import torch.nn as nnclass InvertedResidual(nn.Module):"""倒置殘差塊(Inverted Residual Block)采用"升維-深度卷積-降維"結構,配合殘差連接"""def __init__(self, in_channels, out_channels, stride, expansion_factor=6):"""參數說明:in_channels: 輸入特征通道數out_channels: 輸出特征通道數stride: 深度卷積的步長(1或2,決定是否改變空間尺寸)expansion_factor: 升維的擴展因子"""super().__init__()self.stride = strideself.use_residual = (stride == 1) and (in_channels == out_channels)# 升維通道數expanded_channels = in_channels * expansion_factor# 升維:1x1卷積self.expand = nn.Conv2d(in_channels=in_channels,out_channels=expanded_channels,kernel_size=1,stride=1,padding=0,bias=False)self.bn1 = nn.BatchNorm2d(expanded_channels)self.relu6 = nn.ReLU6(inplace=True) # 限制ReLU的最大值為6,增強穩定性# 深度卷積:3x3卷積(每個通道單獨卷積)self.depthwise = nn.Conv2d(in_channels=expanded_channels,out_channels=expanded_channels,kernel_size=3,stride=stride,padding=1,groups=expanded_channels, # 分組數等于輸入通道數bias=False)self.bn2 = nn.BatchNorm2d(expanded_channels)# 降維:1x1卷積(線性瓶頸,無激活函數)self.project = nn.Conv2d(in_channels=expanded_channels,out_channels=out_channels,kernel_size=1,stride=1,padding=0,bias=False)self.bn3 = nn.BatchNorm2d(out_channels)def forward(self, x):residual = x# 升維x = self.expand(x)x = self.bn1(x)x = self.relu6(x)# 深度卷積x = self.depthwise(x)x = self.bn2(x)x = self.relu6(x)# 降維(線性操作)x = self.project(x)x = self.bn3(x)# 殘差連接(若滿足條件)if self.use_residual:x += residualreturn x# 測試代碼
if __name__ == '__main__':# 實例化倒置殘差塊(輸入32通道,輸出16通道,步長1,擴展因子6)model = InvertedResidual(in_channels=32, out_channels=16, stride=1).cuda()# 創建隨機輸入張量 [batch_size=2, channels=32, height=64, width=64]input_tensor = torch.randn(2, 32, 64, 64).cuda()# 前向傳播output_tensor = model(input_tensor)# 驗證輸出形狀print(f"輸入形狀: {input_tensor.shape}")print(f"輸出形狀: {output_tensor.shape}") # 預期: torch.Size([2, 16, 64, 64])
45、MLP-Mixer 的 Mixer 塊
論文《MLP-Mixer: An all-MLP Architecture for Vision》
1、作用: Mixer 塊是 MLP-Mixer 模型的核心組件,該模型完全摒棄了卷積和自注意力機制,僅通過多層感知機(MLP)實現圖像識別任務。Mixer 塊的設計旨在證明純 MLP 架構也能有效捕捉圖像中的空間和通道特征,挑戰了卷積神經網絡和視覺 Transformer 在視覺領域的主導地位。其作用是通過兩種類型的 MLP(token-mixing MLP 和 channel-mixing MLP)分別建模空間維度和通道維度的特征交互,從而實現對圖像特征的有效提取。
2、機制 Mixer 塊的機制圍繞 “混合”(Mixing)操作展開,具體包括:
- 輸入準備:輸入為經過 Patch Embedding 后的補丁序列(形狀為\(N \times D\),N為補丁數量,D為嵌入維度),通常將其轉換為\(N \times D\)的矩陣。
- Token-Mixing MLP:用于建模不同補丁(token)之間的空間關系。首先對輸入矩陣進行轉置(\(D \times N\)),然后通過 MLP 處理:
- 第一個全連接層將維度從N擴展至h(隱藏維度,通常為4D);
- 應用 GELU 激活函數;
- 第二個全連接層將維度從h壓縮回N;
- 轉置回原形狀(\(N \times D\)),與輸入相加(殘差連接)并進行層歸一化(Layer Norm)。
- Channel-Mixing MLP:用于建模通道之間的關系。直接對 Token-Mixing 的輸出進行處理:
- 第一個全連接層將維度從D擴展至h;
- 應用 GELU 激活函數;
- 第二個全連接層將維度從h壓縮回D;
- 與輸入相加(殘差連接)并進行層歸一化。
通過交替進行 token-mixing 和 channel-mixing,Mixer 塊能夠同時捕捉空間和通道特征的交互信息。
3、獨特優勢
- 結構簡單:完全基于 MLP,無需卷積或自注意力機制,實現和訓練難度低,易于并行化。
- 計算高效:token-mixing 和 channel-mixing 的計算復雜度分別為\(O(D \times N^2)\)和\(O(N \times D^2)\),通過合理設置N和D,可在大規模數據上達到與 ViT 相當的性能。
- 泛化能力強:在 ImageNet-1k 上,Mixer-B 達到 84.8% 的 top-1 準確率,與 ViT-B 相當,且在遷移學習任務上表現優異,證明了純 MLP 架構在視覺任務中的潛力。
4、代碼
import torch
import torch.nn as nnclass MixerBlock(nn.Module):"""MLP-Mixer的Mixer塊包含Token-Mixing MLP和Channel-Mixing MLP,用于混合空間和通道特征"""def __init__(self, num_patches, hidden_dim, mlp_dim):"""參數說明:num_patches: 補丁(token)的數量(N)hidden_dim: 補丁嵌入的維度(D)mlp_dim: MLP的隱藏層維度"""super().__init__()# Token-Mixing MLP:混合不同補丁(空間維度)self.token_mixing = nn.Sequential(nn.LayerNorm(hidden_dim), # 層歸一化nn.Linear(num_patches, mlp_dim), # 擴展維度nn.GELU(),nn.Linear(mlp_dim, num_patches) # 壓縮回原維度)# Channel-Mixing MLP:混合不同通道self.channel_mixing = nn.Sequential(nn.LayerNorm(hidden_dim), # 層歸一化nn.Linear(hidden_dim, mlp_dim), # 擴展維度nn.GELU(),nn.Linear(mlp_dim, hidden_dim) # 壓縮回原維度)def forward(self, x):"""參數x: 輸入張量,形狀為 [batch_size, num_patches, hidden_dim]返回: 輸出張量,形狀與輸入相同"""# Token-Mixing:先轉置,處理后再轉置回來,添加殘差residual = xx = x.transpose(1, 2) # [batch_size, hidden_dim, num_patches]x = self.token_mixing(x)x = x.transpose(1, 2) # [batch_size, num_patches, hidden_dim]x += residual# Channel-Mixing:直接處理,添加殘差residual = xx = self.channel_mixing(x)x += residualreturn x# 測試代碼
if __name__ == '__main__':# 實例化Mixer塊(196個補丁,768維嵌入,3072維MLP隱藏層)model = MixerBlock(num_patches=196, hidden_dim=768, mlp_dim=3072).cuda()# 創建隨機輸入張量 [batch_size=2, num_patches=196, hidden_dim=768]input_tensor = torch.randn(2, 196, 768).cuda()# 前向傳播output_tensor = model(input_tensor)# 驗證輸出形狀print(f"輸入形狀: {input_tensor.shape}")print(f"輸出形狀: {output_tensor.shape}") # 預期: torch.Size([2, 196, 768])