?
一、說明
????????在本教程中,我們將使用 PyTorch 從頭開始構建一個基本的轉換器模型。Vaswani等人在論文“注意力是你所需要的一切”中引入的Transformer模型是一種深度學習架構,專為序列到序列任務而設計,例如機器翻譯和文本摘要。它基于自我注意機制,已成為許多最先進的自然語言處理模型的基礎,如GPT和BERT。
二、準備活動
????????若要生成轉換器模型,我們將按照以下步驟操作:
- 導入必要的庫和模塊
- 定義基本構建塊:多頭注意力、位置前饋網絡、位置編碼
- 構建編碼器和解碼器層
- 組合編碼器和解碼器層以創建完整的轉換器模型
- 準備示例數據
- 訓練模型
????????讓我們從導入必要的庫和模塊開始。
import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import math
import copy
現在,我們將定義轉換器模型的基本構建基塊。
三、多頭注意力
圖2.多頭注意力(來源:作者創建的圖像)
????????多頭注意力機制計算序列中每對位置之間的注意力。它由多個“注意頭”組成,用于捕獲輸入序列的不同方面。
class MultiHeadAttention(nn.Module):def __init__(self, d_model, num_heads):super(MultiHeadAttention, self).__init__()assert d_model % num_heads == 0, "d_model must be divisible by num_heads"self.d_model = d_modelself.num_heads = num_headsself.d_k = d_model // num_headsself.W_q = nn.Linear(d_model, d_model)self.W_k = nn.Linear(d_model, d_model)self.W_v = nn.Linear(d_model, d_model)self.W_o = nn.Linear(d_model, d_model)def scaled_dot_product_attention(self, Q, K, V, mask=None):attn_scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)if mask is not None:attn_scores = attn_scores.masked_fill(mask == 0, -1e9)attn_probs = torch.softmax(attn_scores, dim=-1)output = torch.matmul(attn_probs, V)return outputdef split_heads(self, x):batch_size, seq_length, d_model = x.size()return x.view(batch_size, seq_length, self.num_heads, self.d_k).transpose(1, 2)def combine_heads(self, x):batch_size, _, seq_length, d_k = x.size()return x.transpose(1, 2).contiguous().view(batch_size, seq_length, self.d_model)def forward(self, Q, K, V, mask=None):Q = self.split_heads(self.W_q(Q))K = self.split_heads(self.W_k(K))V = self.split_heads(self.W_v(V))attn_output = self.scaled_dot_product_attention(Q, K, V, mask)output = self.W_o(self.combine_heads(attn_output))return output
????????MultiHeadAttention 代碼使用輸入參數和線性變換層初始化模塊。它計算注意力分數,將輸入張量重塑為多個頭部,并將所有頭部的注意力輸出組合在一起。前向方法計算多頭自我注意,允許模型專注于輸入序列的某些不同方面。
四、位置前饋網絡
class PositionWiseFeedForward(nn.Module):def __init__(self, d_model, d_ff):super(PositionWiseFeedForward, self).__init__()self.fc1 = nn.Linear(d_model, d_ff)self.fc2 = nn.Linear(d_ff, d_model)self.relu = nn.ReLU()def forward(self, x):return self.fc2(self.relu(self.fc1(x)))
PositionWiseFeedForward 類擴展了 PyTorch 的 nn。模塊并實現按位置的前饋網絡。該類使用兩個線性轉換層和一個 ReLU 激活函數進行初始化。forward 方法按順序應用這些轉換和激活函數來計算輸出。此過程使模型能夠在進行預測時考慮輸入元素的位置。
五、位置編碼
????????位置編碼用于注入輸入序列中每個令牌的位置信息。它使用不同頻率的正弦和余弦函數來生成位置編碼。
class PositionalEncoding(nn.Module):def __init__(self, d_model, max_seq_length):super(PositionalEncoding, self).__init__()pe = torch.zeros(max_seq_length, d_model)position = torch.arange(0, max_seq_length, dtype=torch.float).unsqueeze(1)div_term = torch.exp(torch.arange(0, d_model, 2).float() * -(math.log(10000.0) / d_model))pe[:, 0::2] = torch.sin(position * div_term)pe[:, 1::2] = torch.cos(position * div_term)self.register_buffer('pe', pe.unsqueeze(0))def forward(self, x):return x + self.pe[:, :x.size(1)]
PositionalEncoding 類使用 d_model 和 max_seq_length 輸入參數進行初始化,從而創建一個張量來存儲位置編碼值。該類根據比例因子div_term分別計算偶數和奇數指數的正弦和余弦值。前向方法通過將存儲的位置編碼值添加到輸入張量中來計算位置編碼,從而使模型能夠捕獲輸入序列的位置信息。
現在,我們將構建編碼器層和解碼器層。
六、編碼器層
圖3.變壓器網絡的編碼器部分(來源:圖片來自原文)
編碼器層由多頭注意力層、位置前饋層和兩個層歸一化層組成。
class EncoderLayer(nn.Module):def __init__(self, d_model, num_heads, d_ff, dropout):super(EncoderLayer, self).__init__()self.self_attn = MultiHeadAttention(d_model, num_heads)self.feed_forward = PositionWiseFeedForward(d_model, d_ff)self.norm1 = nn.LayerNorm(d_model)self.norm2 = nn.LayerNorm(d_model)self.dropout = nn.Dropout(dropout)def forward(self, x, mask):attn_output = self.self_attn(x, x, x, mask)x = self.norm1(x + self.dropout(attn_output))ff_output = self.feed_forward(x)x = self.norm2(x + self.dropout(ff_output))return x
類使用輸入參數和組件進行初始化,包括一個多頭注意模塊、一個 PositionWiseFeedForward 模塊、兩個層規范化模塊和一個 dropout 層。前向方法通過應用自注意、將注意力輸出添加到輸入張量并規范化結果來計算編碼器層輸出。然后,它計算按位置的前饋輸出,將其與歸一化的自我注意輸出相結合,并在返回處理后的張量之前對最終結果進行歸一化。
七、解碼器層
圖4.變壓器網絡的解碼器部分(Souce:圖片來自原始論文)
解碼器層由兩個多頭注意力層、一個位置前饋層和三個層歸一化層組成。
class DecoderLayer(nn.Module):def __init__(self, d_model, num_heads, d_ff, dropout):super(DecoderLayer, self).__init__()self.self_attn = MultiHeadAttention(d_model, num_heads)self.cross_attn = MultiHeadAttention(d_model, num_heads)self.feed_forward = PositionWiseFeedForward(d_model, d_ff)self.norm1 = nn.LayerNorm(d_model)self.norm2 = nn.LayerNorm(d_model)self.norm3 = nn.LayerNorm(d_model)self.dropout = nn.Dropout(dropout)def forward(self, x, enc_output, src_mask, tgt_mask):attn_output = self.self_attn(x, x, x, tgt_mask)x = self.norm1(x + self.dropout(attn_output))attn_output = self.cross_attn(x, enc_output, enc_output, src_mask)x = self.norm2(x + self.dropout(attn_output))ff_output = self.feed_forward(x)x = self.norm3(x + self.dropout(ff_output))return x
解碼器層使用輸入參數和組件進行初始化,例如用于屏蔽自我注意和交叉注意力的多頭注意模塊、PositionWiseFeedForward 模塊、三層歸一化模塊和輟學層。
轉發方法通過執行以下步驟來計算解碼器層輸出:
- 計算掩蔽的自我注意輸出并將其添加到輸入張量中,然后進行 dropout 和層歸一化。
- 計算解碼器和編碼器輸出之間的交叉注意力輸出,并將其添加到規范化的掩碼自注意力輸出中,然后進行 dropout 和層規范化。
- 計算按位置的前饋輸出,并將其與歸一化交叉注意力輸出相結合,然后是壓差和層歸一化。
- 返回已處理的張量。
這些操作使解碼器能夠根據輸入和編碼器輸出生成目標序列。
現在,讓我們組合編碼器和解碼器層來創建完整的轉換器模型。
八、變壓器型號
圖5.The Transformer Network(來源:圖片來源于原文)
將它們全部合并在一起:
class Transformer(nn.Module):def __init__(self, src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout):super(Transformer, self).__init__()self.encoder_embedding = nn.Embedding(src_vocab_size, d_model)self.decoder_embedding = nn.Embedding(tgt_vocab_size, d_model)self.positional_encoding = PositionalEncoding(d_model, max_seq_length)self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff, dropout) for _ in range(num_layers)])self.fc = nn.Linear(d_model, tgt_vocab_size)self.dropout = nn.Dropout(dropout)def generate_mask(self, src, tgt):src_mask = (src != 0).unsqueeze(1).unsqueeze(2)tgt_mask = (tgt != 0).unsqueeze(1).unsqueeze(3)seq_length = tgt.size(1)nopeak_mask = (1 - torch.triu(torch.ones(1, seq_length, seq_length), diagonal=1)).bool()tgt_mask = tgt_mask & nopeak_maskreturn src_mask, tgt_maskdef forward(self, src, tgt):src_mask, tgt_mask = self.generate_mask(src, tgt)src_embedded = self.dropout(self.positional_encoding(self.encoder_embedding(src)))tgt_embedded = self.dropout(self.positional_encoding(self.decoder_embedding(tgt)))enc_output = src_embeddedfor enc_layer in self.encoder_layers:enc_output = enc_layer(enc_output, src_mask)dec_output = tgt_embeddedfor dec_layer in self.decoder_layers:dec_output = dec_layer(dec_output, enc_output, src_mask, tgt_mask)output = self.fc(dec_output)return output
類組合了以前定義的模塊以創建完整的轉換器模型。在初始化期間,Transformer 模塊設置輸入參數并初始化各種組件,包括源序列和目標序列的嵌入層、位置編碼模塊、用于創建堆疊層的編碼器層和解碼器層模塊、用于投影解碼器輸出的線性層以及 dropout 層。
generate_mask 方法為源序列和目標序列創建二進制掩碼,以忽略填充標記并防止解碼器處理將來的令牌。前向方法通過以下步驟計算轉換器模型的輸出:
- 使用 generate_mask 方法生成源掩碼和目標掩碼。
- 計算源和目標嵌入,并應用位置編碼和丟棄。
- 通過編碼器層處理源序列,更新enc_output張量。
- 通過解碼器層處理目標序列,使用enc_output和掩碼,并更新dec_output張量。
- 將線性投影層應用于解碼器輸出,獲取輸出對數。
這些步驟使轉換器模型能夠處理輸入序列,并根據其組件的組合功能生成輸出序列。
九、準備樣本數據
????????在此示例中,我們將創建一個用于演示目的的玩具數據集。實際上,您將使用更大的數據集,預處理文本,并為源語言和目標語言創建詞匯映射。
src_vocab_size = 5000
tgt_vocab_size = 5000
d_model = 512
num_heads = 8
num_layers = 6
d_ff = 2048
max_seq_length = 100
dropout = 0.1transformer = Transformer(src_vocab_size, tgt_vocab_size, d_model, num_heads, num_layers, d_ff, max_seq_length, dropout)# Generate random sample data
src_data = torch.randint(1, src_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
tgt_data = torch.randint(1, tgt_vocab_size, (64, max_seq_length)) # (batch_size, seq_length)
十、訓練模型
????????現在,我們將使用示例數據訓練模型。在實踐中,您將使用更大的數據集并將其拆分為訓練集和驗證集。
criterion = nn.CrossEntropyLoss(ignore_index=0)
optimizer = optim.Adam(transformer.parameters(), lr=0.0001, betas=(0.9, 0.98), eps=1e-9)transformer.train()for epoch in range(100):optimizer.zero_grad()output = transformer(src_data, tgt_data[:, :-1])loss = criterion(output.contiguous().view(-1, tgt_vocab_size), tgt_data[:, 1:].contiguous().view(-1))loss.backward()optimizer.step()print(f"Epoch: {epoch+1}, Loss: {loss.item()}")
????????我們可以使用這種方式在 Pytorch 中從頭開始構建一個簡單的轉換器。所有大型語言模型都使用這些轉換器編碼器或解碼器塊進行訓練。因此,了解啟動這一切的網絡非常重要。希望本文能幫助所有希望深入了解LLM的人。