參考資料:https://github.com/datawhalechina/happy-llm?
在 Transformer 中,使用注意力機制的是其兩個核心組件——Encoder(編碼器)和 Decoder(解碼器)。
2.2.1 Seq2Seq 模型
Seq2Seq(序列到序列) 是一種經典的自然語言處理(NLP)任務,其目標是將一個自然語言序列 映射到另一個可能不等長的自然語言序列 output?=?(y_1,?y_2,?y_3...y_m)。Seq2Seq 是 NLP 中最通用的任務形式,幾乎所有 NLP 任務都可以視為 Seq2Seq 的特例,例如:
-
文本分類:輸出長度為 1 的目標序列(m=1)。
-
詞性標注:輸出與輸入序列等長的目標序列(m=n)。
-
機器翻譯:輸入和輸出序列長度可能不同,例如將中文句子“今天天氣真好”翻譯為英文句子“Today is a good day.”。
Seq2Seq 的一般思路:
-
編碼(Encoding):
-
將輸入的自然語言序列通過隱藏層編碼成能夠表征語義的向量(或矩陣),可以理解為更復雜的詞向量表示。
-
-
解碼(Decoding):
-
將編碼得到的向量或矩陣通過隱藏層輸出,再解碼成對應的自然語言目標序列。
-
Transformer 模型:
Transformer 是一個經典的 Seq2Seq 模型,最初被應用于機器翻譯任務。它由 Encoder(編碼器) 和 Decoder(解碼器) 組成,具體結構如下:
-
Encoder:
-
包含多個(通常是 6 個)Encoder Layer。
-
輸入源序列進入 Encoder 進行編碼,編碼結果輸出給 Decoder。
-
-
Decoder:
-
包含多個(通常是 6 個)Decoder Layer。
-
接收 Encoder 的編碼結果,并逐步解碼生成目標序列。
-
Encoder 和 Decoder 內部傳統神經網絡的經典結構有:前饋神經網絡(FNN)、層歸一化(Layer Norm)和殘差連接(Residual Connection)。
2.2.2 前饋神經網絡
前饋神經網絡(Feed Forward Neural Network,FFN)?是一種簡單的全連接網絡結構,用于對輸入數據進行非線性變換。
FFN 的結構:
-
兩個線性層:
-
輸入經過第一個線性層(全連接層)進行變換。
-
輸出再經過第二個線性層進行進一步變換。
-
-
ReLU 激活函數:
-
在兩個線性層之間加入 ReLU 激活函數,引入非線性。
-
ReLU 激活函數的公式為:ReLU(x)=max(0,x)。
-
-
Dropout 層:
-
在 FFN 的輸出后加入 Dropout 層,用于防止過擬合。
-
Dropout 通過隨機丟棄一部分神經元的輸出,增強模型的泛化能力。
-
class MLP(nn.Module):'''前饋神經網絡'''def __init__(self, dim: int, hidden_dim: int, dropout: float):super().__init__()# 定義第一層線性變換,從輸入維度到隱藏維度self.w1 = nn.Linear(dim, hidden_dim, bias=False)# 定義第二層線性變換,從隱藏維度到輸入維度self.w2 = nn.Linear(hidden_dim, dim, bias=False)# 定義dropout層,用于防止過擬合self.dropout = nn.Dropout(dropout)def forward(self, x):# 前向傳播函數# 首先,輸入x通過第一層線性變換和RELU激活函數# 然后,結果乘以輸入x通過第三層線性變換的結果# 最后,通過第二層線性變換和dropout層return self.dropout(self.w2(F.relu(self.w1(x))))
2.2.3 層歸一化
層歸一化(Layer Norm) 是一種深度學習中的歸一化操作,目的是讓不同層的輸入分布更加一致,從而穩定訓練過程并提高模型性能。它與批歸一化(Batch Norm)的主要區別在于統計量的計算方式。
歸一化的必要性
-
梯度爆炸/消失問題:
-
深度神經網絡中,每一層的輸入是上一層的輸出,隨著層數增加,輸入分布可能因參數變化而發生較大改變。
-
這種分布變化會導致梯度不穩定,影響模型的收斂速度和性能。
-
-
預測誤差:
-
預測的條件分布始終相同,但各層輸出分布不同,導致預測誤差增大。
-
批歸一化(Batch Norm)的局限性
-
小批量(mini-batch)問題:
-
當 mini-batch 較小時,計算的均值和方差不能反映全局統計分布,導致效果變差。
-
-
時間維度問題:
-
對于 RNN,不同句子的同一時間步分布可能不同,Batch Norm 的歸一化失去意義。
-
-
訓練與測試不一致:
-
訓練時需要保存每個 step 的統計信息,測試時可能出現比訓練集更長的句子,導致統計量缺失。
-
-
計算開銷:
-
每個 step 都需要保存和計算 batch 統計量,耗時且耗力。
-
代碼實現:
class LayerNorm(nn.Module):''' Layer Norm 層'''def __init__(self, features, eps=1e-6):super(LayerNorm, self).__init__()# 線性矩陣做映射self.a_2 = nn.Parameter(torch.ones(features))self.b_2 = nn.Parameter(torch.zeros(features))self.eps = epsdef forward(self, x):# 在統計每個樣本所有維度的值,求均值和方差mean = x.mean(-1, keepdim=True) # mean: [bsz, max_len, 1]std = x.std(-1, keepdim=True) # std: [bsz, max_len, 1]# 注意這里也在最后一個維度發生了廣播return self.a_2 * (x - mean) / (std + self.eps) + self.b_2
2.2.4 殘差連接
在 Transformer 模型中,殘差連接被廣泛應用于每個子層(如多頭自注意力層和前饋神經網絡層)。其主要作用是:
-
避免梯度消失:允許梯度直接回傳到更深層,減少梯度消失問題。
-
增強信息流動:讓高層專注于學習輸入與輸出之間的殘差,而不是直接學習輸出。
-
提高訓練效率:通過直接傳遞輸入,減少深層網絡的訓練難度。
Transformer 中的實現
在 Transformer 的 Encoder 和 Decoder 中,每個子層的輸出不僅包括上一層的輸出,還包括上一層的輸入。具體公式如下:
-
多頭自注意力層:
-
輸入 x 首先經過層歸一化(LayerNorm)。
-
然后通過多頭自注意力層(MultiHeadSelfAttention)。
-
最后將注意力層的輸出與原始輸入 x 相加,形成殘差連接。
-
-
前饋神經網絡層:
-
輸入 x 首先經過層歸一化(LayerNorm)。
-
然后通過前饋神經網絡(FNN)。
-
最后將 FNN 的輸出與原始輸入 x 相加,形成殘差連接。
-
代碼實現
# 注意力計算
h = x + self.attention.forward(self.attention_norm(x))
# 經過前饋神經網絡
out = h + self.feed_forward.forward(self.fnn_norm(h))
2.2.5 Encoder
Transformer 的 Encoder 是由多個 Encoder Layer 組成的模塊,每個 Encoder Layer 包含兩個主要部分:
-
多頭自注意力層(Multi-Head Attention):
-
用于捕捉輸入序列內部的依賴關系。
-
-
前饋神經網絡(Feed Forward Network,FFN):
-
用于對自注意力層的輸出進行非線性變換。
-
每個子層(多頭自注意力層和前饋神經網絡層)都使用 殘差連接 和 層歸一化(Layer Norm)。
Encoder Layer 的實現
class EncoderLayer(nn.Module):'''Encoder層'''def __init__(self, args):super().__init__()# 一個 Layer 中有兩個 LayerNorm,分別在 Attention 之前和 MLP 之前self.attention_norm = LayerNorm(args.n_embd)# Encoder 不需要掩碼,傳入 is_causal=Falseself.attention = MultiHeadAttention(args, is_causal=False)self.fnn_norm = LayerNorm(args.n_embd)self.feed_forward = MLP(args)def forward(self, x):# Layer Normnorm_x = self.attention_norm(x)# 自注意力h = x + self.attention.forward(norm_x, norm_x, norm_x)# 經過前饋神經網絡out = h + self.feed_forward.forward(self.fnn_norm(h))return out
-
輸入:
x
是輸入序列的嵌入表示。 -
層歸一化:在多頭自注意力層和前饋神經網絡之前分別應用層歸一化。
-
殘差連接:每個子層的輸出加上原始輸入,形成殘差連接。
-
多頭自注意力:
self.attention
對歸一化后的輸入進行自注意力計算。 -
前饋神經網絡:
self.feed_forward
對歸一化后的輸入進行非線性變換。
Encoder 的實現
整個 Encoder 由多個 Encoder Layer 組成,并在最后加入一個 Layer Norm 實現規范化:
class Encoder(nn.Module):'''Encoder 塊'''def __init__(self, args):super(Encoder, self).__init__() # 一個 Encoder 由 N 個 Encoder Layer 組成self.layers = nn.ModuleList([EncoderLayer(args) for _ in range(args.n_layer)])self.norm = LayerNorm(args.n_embd)def forward(self, x):"分別通過 N 層 Encoder Layer"for layer in self.layers:x = layer(x)return self.norm(x)
-
輸入:
x
是輸入序列的嵌入表示。 -
多層 Encoder Layer:輸入依次通過每個 Encoder Layer。
-
最終層歸一化:在所有 Encoder Layer 之后,對輸出進行一次層歸一化。
輸出
通過 Encoder 的輸出是輸入序列編碼后的結果,可以用于后續的解碼器(Decoder)或其他任務。
2.2.6 Decoder
Transformer 的 Decoder 由多個 Decoder Layer 組成,每個 Decoder Layer 包含三個主要部分:
-
掩碼自注意力層(Masked Multi-Head Attention):
-
使用掩碼(Mask)確保每個 token 只能使用該 token 之前的注意力分數。
-
-
多頭注意力層(Multi-Head Attention):
-
使用 Encoder 的輸出作為 Key 和 Value,當前 Decoder 的輸出作為 Query,計算注意力分數。
-
-
前饋神經網絡(Feed Forward Network,FFN):
-
對多頭注意力層的輸出進行非線性變換。
-
每個子層(掩碼自注意力層、多頭注意力層和前饋神經網絡層)都使用 殘差連接 和 層歸一化(Layer Norm)。
Decoder Layer 的實現
class DecoderLayer(nn.Module):'''解碼層'''def __init__(self, args):super().__init__()# 一個 Layer 中有三個 LayerNorm,分別在 Mask Attention 之前、Self Attention 之前和 MLP 之前self.attention_norm_1 = LayerNorm(args.n_embd)# Decoder 的第一個部分是 Mask Attention,傳入 is_causal=Trueself.mask_attention = MultiHeadAttention(args, is_causal=True)self.attention_norm_2 = LayerNorm(args.n_embd)# Decoder 的第二個部分是 類似于 Encoder 的 Attention,傳入 is_causal=Falseself.attention = MultiHeadAttention(args, is_causal=False)self.ffn_norm = LayerNorm(args.n_embd)# 第三個部分是 MLPself.feed_forward = MLP(args)def forward(self, x, enc_out):# Layer Normnorm_x = self.attention_norm_1(x)# 掩碼自注意力x = x + self.mask_attention.forward(norm_x, norm_x, norm_x)# 多頭注意力norm_x = self.attention_norm_2(x)h = x + self.attention.forward(norm_x, enc_out, enc_out)# 經過前饋神經網絡out = h + self.feed_forward.forward(self.fnn_norm(h))return out
-
輸入:
-
x
是 Decoder 的輸入序列的嵌入表示。 -
enc_out
是 Encoder 的輸出。
-
-
多層 Decoder Layer:輸入依次通過每個 Decoder Layer。
-
最終層歸一化:在所有 Decoder Layer 之后,對輸出進行一次層歸一化。
完整的 Transformer 模型
將 Encoder 和 Decoder 拼接起來,再加入 Embedding 層,就可以搭建出完整的 Transformer 模型:
class Decoder(nn.Module):'''解碼器'''def __init__(self, args):super(Decoder, self).__init__() # 一個 Decoder 由 N 個 Decoder Layer 組成self.layers = nn.ModuleList([DecoderLayer(args) for _ in range(args.n_layer)])self.norm = LayerNorm(args.n_embd)def forward(self, x, enc_out):"Pass the input (and mask) through each layer in turn."for layer in self.layers:x = layer(x, enc_out)return self.norm(x)