1.嵌入(Input Embedding)
讓我用一個更具體的例子來解釋輸入嵌入(Input Embedding)。
背景
假設我們有一個非常小的詞匯表,其中包含以下 5 個詞:
- "I"
- "love"
- "machine"
- "learning"
- "!"
假設我們想把這句話 "I love machine learning !" 作為輸入。
步驟 1:創建詞匯表(Vocabulary)
我們給每個詞分配一個唯一的索引號:
- "I" -> 0
- "love" -> 1
- "machine" -> 2
- "learning" -> 3
- "!" -> 4
步驟 2:創建嵌入矩陣(Embedding Matrix)
假設我們選擇每個詞的向量維度為 3(實際應用中維度會更高)。我們初始化一個大小為 5x3 的嵌入矩陣,如下所示:
嵌入矩陣(Embedding Matrix):
[[0.1, 0.2, 0.3], // "I" 的向量表示[0.4, 0.5, 0.6], // "love" 的向量表示[0.7, 0.8, 0.9], // "machine" 的向量表示[1.0, 1.1, 1.2], // "learning" 的向量表示[1.3, 1.4, 1.5] // "!" 的向量表示
]
步驟 3:查找表操作(Lookup Table Operation)
當我們輸入句子 "I love machine learning !" 時,我們首先將每個詞轉換為其對應的索引:
- "I" -> 0
- "love" -> 1
- "machine" -> 2
- "learning" -> 3
- "!" -> 4
然后,我們使用這些索引在嵌入矩陣中查找相應的向量表示:
輸入句子嵌入表示:
[[0.1, 0.2, 0.3], // "I" 的向量表示[0.4, 0.5, 0.6], // "love" 的向量表示[0.7, 0.8, 0.9], // "machine" 的向量表示[1.0, 1.1, 1.2], // "learning" 的向量表示[1.3, 1.4, 1.5] // "!" 的向量表示
]
步驟 4:輸入嵌入過程
????????通過查找表操作,我們把原本的句子 "I love machine learning !" 轉換成了一個二維數組,每一行是一個詞的嵌入向量。
碼示例代
讓我們用 Python 和 PyTorch 來實現這個過程:
import torch
import torch.nn as nn# 假設詞匯表大小為 5,嵌入維度為 3
vocab_size = 5
embedding_dim = 3# 創建一個嵌入層
embedding_layer = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)# 初始化嵌入矩陣(為了便于理解,這里手動設置嵌入矩陣的值)
embedding_layer.weight = nn.Parameter(torch.tensor([[0.1, 0.2, 0.3], # "I"[0.4, 0.5, 0.6], # "love"[0.7, 0.8, 0.9], # "machine"[1.0, 1.1, 1.2], # "learning"[1.3, 1.4, 1.5] # "!"
]))# 輸入句子對應的索引
input_indices = torch.tensor([0, 1, 2, 3, 4])# 獲取輸入詞的嵌入表示
embedded = embedding_layer(input_indices)print(embedded)
輸出:?
tensor([[0.1000, 0.2000, 0.3000],[0.4000, 0.5000, 0.6000],[0.7000, 0.8000, 0.9000],[1.0000, 1.1000, 1.2000],[1.3000, 1.4000, 1.5000]], grad_fn=<EmbeddingBackward>)
????????這樣我們就完成了輸入嵌入的過程,把離散的詞轉換為了連續的向量表示。
????????當你完成了詞嵌入,將離散的詞轉換為連續的向量表示后,位置編碼步驟如下:
2. 理解位置編碼
????????位置編碼(Positional Encoding)通過生成一組特殊的向量,表示詞在序列中的位置,并將這些向量添加到詞嵌入上,使模型能夠識別詞序。
2.1 位置編碼公式
????????位置編碼使用正弦和余弦函數生成。具體公式如下:
其中:
- ?
是詞在序列中的位置。
- ?i是詞嵌入向量的維度索引。
- ?d是詞嵌入向量的總維度。
2.2 生成位置編碼向量
????????以下是 Python 代碼示例,展示如何生成位置編碼向量,并將其添加到詞嵌入上:
????????生成位置編碼向量
import numpy as np
import torchdef get_positional_encoding(max_len, d_model):"""生成位置編碼向量:param max_len: 序列的最大長度:param d_model: 詞嵌入向量的維度:return: 形狀為 (max_len, d_model) 的位置編碼矩陣"""pos = np.arange(max_len)[:, np.newaxis]i = np.arange(d_model)[np.newaxis, :]angle_rates = 1 / np.power(10000, (2 * (i // 2)) / np.float32(d_model))angle_rads = pos * angle_rates# 采用正弦函數應用于偶數索引 (2i)angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])# 采用余弦函數應用于奇數索引 (2i+1)angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])return torch.tensor(angle_rads, dtype=torch.float32)# 示例參數
max_len = 100 # 假設最大序列長度為 100
d_model = 512 # 假設詞嵌入維度為 512# 生成位置編碼矩陣
positional_encoding = get_positional_encoding(max_len, d_model)
print(positional_encoding.shape) # 輸出: torch.Size([100, 512])
2.3 添加位置編碼到詞嵌入
????????假設你已經有一個詞嵌入張量?embedded
,它的形狀為 (batch_size, seq_len, d_model),可以將位置編碼添加到詞嵌入中:
class TransformerEmbedding(nn.Module):def __init__(self, vocab_size, d_model, max_len):super(TransformerEmbedding, self).__init__()self.token_embedding = nn.Embedding(vocab_size, d_model)self.positional_encoding = get_positional_encoding(max_len, d_model)self.dropout = nn.Dropout(p=0.1)def forward(self, x):# 獲取詞嵌入token_embeddings = self.token_embedding(x)# 添加位置編碼seq_len = x.size(1)position_embeddings = self.positional_encoding[:seq_len, :]# 詞嵌入和位置編碼相加embeddings = token_embeddings + position_embeddings.unsqueeze(0)return self.dropout(embeddings)# 示例參數
vocab_size = 10000 # 假設詞匯表大小為 10000
d_model = 512 # 詞嵌入維度
max_len = 100 # 最大序列長度# 實例化嵌入層
embedding_layer = TransformerEmbedding(vocab_size, d_model, max_len)# 假設輸入序列為一批大小為 2,序列長度為 10 的張量
input_tensor = torch.tensor([[1, 2, 3, 4, 5, 6, 7, 8, 9, 10],[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]], dtype=torch.long)# 獲取嵌入表示
output_embeddings = embedding_layer(input_tensor)
print(output_embeddings.shape) # 輸出: torch.Size([2, 10, 512])
2.4. 繼續進行 Transformer 模型的前向傳播
????????有了詞嵌入和位置編碼之后,接下來的步驟就是將這些嵌入輸入到 Transformer 模型的編碼器和解碼器中,進行進一步處理。Transformer 模型的編碼器和解碼器由多層注意力機制和前饋神經網絡組成。
????????位置編碼步驟通過生成一組正弦和余弦函數的向量,并將這些向量添加到詞嵌入上,使 Transformer 模型能夠捕捉序列中的位置信息。
import torch
import torch.nn as nnclass MultiHeadSelfAttention(nn.Module):def __init__(self, d_model, nhead):super(MultiHeadSelfAttention, self).__init__()assert d_model % nhead == 0, "d_model 必須能被 nhead 整除"self.d_model = d_modelself.d_k = d_model // nheadself.nhead = nheadself.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.fc = nn.Linear(d_model, d_model)self.dropout = nn.Dropout(0.1)self.scale = torch.sqrt(torch.FloatTensor([self.d_k]))def forward(self, x):batch_size = x.size(0)seq_len = x.size(1)# 線性變換得到 Q, K, VQ = self.W_q(x)K = self.W_k(x)V = self.W_v(x)# 分成多頭Q = Q.view(batch_size, seq_len, self.nhead, self.d_k).transpose(1, 2)K = K.view(batch_size, seq_len, self.nhead, self.d_k).transpose(1, 2)V = V.view(batch_size, seq_len, self.nhead, self.d_k).transpose(1, 2)# 計算注意力權重attn_weights = torch.matmul(Q, K.transpose(-2, -1)) / self.scaleattn_weights = torch.nn.functional.softmax(attn_weights, dim=-1)attn_weights = self.dropout(attn_weights)# 加權求和attn_output = torch.matmul(attn_weights, V)# 拼接多頭輸出attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)# 最后的線性變換output = self.fc(attn_output)return output# 示例參數
d_model = 8
nhead = 2# 輸入張量
x = torch.rand(2, 5, d_model)# 實例化多頭自注意力層
multi_head_attn = MultiHeadSelfAttention(d_model, nhead)# 前向傳播
output = multi_head_attn(x)
print("多頭自注意力輸出:\n", output)
解釋
- 線性變換:使用 nn.Linear 實現線性變換,將輸入張量??通過三個不同的線性層得到查詢、鍵和值向量。
- 分成多頭:使用 view 和 transpose 方法將查詢、鍵和值向量分成多頭,形狀變為?。
- 計算注意力權重:通過點積計算查詢和鍵的相似度,并通過 softmax 歸一化得到注意力權重。
- 加權求和:使用注意力權重對值向量進行加權求和,得到每個頭的輸出。
- 拼接多頭輸出:將多頭的輸出拼接起來,并通過一個線性層進行變換,得到最終的輸出。
????????查詢、鍵和值向量的生成是多頭自注意力機制的關鍵步驟,通過線性變換將輸入向量轉換為查詢、鍵和值向量,然后使用這些向量計算注意力權重,捕捉輸入序列中不同位置的相關性。