在Transformer結構中,輸入編碼是模型處理文本數據的關鍵步驟,其中**BPE(Byte Pair Encoding,字節對編碼)和PE(Positional Encoding,位置編碼)**是兩種重要的編碼方式,它們分別解決了分詞和位置信息的問題。以下是對這兩種編碼方式的詳細解析:
BPE(Byte Pair Encoding)
1. 背景與目的
- 傳統分詞問題:在自然語言處理中,傳統的分詞方式(如基于空格或標點符號的分詞)往往無法有效處理未知詞(OOV,Out-of-Vocabulary)或稀有詞。這些詞在訓練數據中未出現或出現頻率極低,導致模型難以學習到它們的表示。
- BPE的引入:BPE是一種數據壓縮算法,最初用于文本壓縮。在NLP中,BPE被用作一種子詞(subword)分詞算法,旨在將文本分解為更小的單元(子詞),從而更有效地處理未知詞和稀有詞。
2. 算法原理
- 初始化詞表:BPE從一個基礎詞表開始,通常包含所有單個字符。
- 迭代合并:BPE通過迭代地合并最頻繁出現的字符對(或子詞對)來擴展詞表。每次合并后,新的子詞被添加到詞表中,而原始的字符對被替換為新的子詞。
- 終止條件:合并過程持續進行,直到詞表大小達到預設的閾值或合并次數達到上限。
3. 優點
- 處理未知詞:BPE能夠將未知詞分解為已知的子詞組合,從而允許模型學習到這些詞的表示。
- 靈活性:BPE可以根據不同的任務和數據集調整詞表大小,以平衡分詞粒度和模型性能。
BPE代碼
import tiktokendef main():print("Hello from hello-world!")# 獲取編碼enc = tiktoken.get_encoding("cl100k_base")# 編碼文本tokens = enc.encode("tiktoken是OpenAI開源的一個快速分詞工具。它將一個文本字符串(例如“tiktoken很棒!”)和一個編碼(例如“cl100k_base”)作為輸入,然后將字符串拆分為標記列表。-----aa aa aa bb bb bb cc cc aabbcc aabb ")print(tokens) # 輸出: [24912, 2375]# 解碼文本text = enc.decode(tokens)print(text) # 輸出: hello worldif __name__ == "__main__":main()
PE(Positional Encoding)
1. 背景與目的
- Transformer的局限性:Transformer模型基于自注意力機制,能夠并行處理輸入序列中的所有元素。然而,這種并行性導致模型無法直接捕捉到序列中元素的位置信息。
- 位置編碼的引入:為了解決這個問題,Transformer引入了位置編碼(PE),為輸入序列中的每個元素添加位置信息。
2. 實現方式
-
正弦和余弦函數:Transformer使用正弦和余弦函數來生成位置編碼。對于位置 p o s pos pos和維度 i i i,位置編碼的值由以下公式給出:
P E ( p o s , 2 i ) = sin ? ( p o s 1000 0 2 i / d model ) PE(pos, 2i) = \sin\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) PE(pos,2i)=sin(100002i/dmodel?pos?)
P E ( p o s , 2 i + 1 ) = cos ? ( p o s 1000 0 2 i / d model ) PE(pos, 2i+1) = \cos\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right) PE(pos,2i+1)=cos(100002i/dmodel?pos?)其中, d model d_{\text{model}} dmodel?是模型的維度。
-
添加到輸入嵌入:生成的位置編碼被添加到輸入序列的詞嵌入(word embedding)中,形成最終的輸入表示。
3. 優點
- 捕捉位置信息:位置編碼允許Transformer模型捕捉到輸入序列中元素的位置信息,從而更準確地理解序列的結構和語義。
- 可學習性:雖然位置編碼是預先計算的,但它與詞嵌入一起被輸入到模型中,允許模型在訓練過程中學習到更優的位置表示。
BP代碼
PyTorch提供了torch.nn.Embedding和自定義操作,可以更高效地實現PE:
import torch
import mathclass PositionalEncoding(torch.nn.Module):def __init__(self, d_model, max_len=5000):super(PositionalEncoding, self).__init__()self.d_model = d_model# 創建位置編碼矩陣pe = torch.zeros(max_len, d_model)position = torch.arange(0, max_len, 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) # 奇數維度pe = pe.unsqueeze(0) # 添加batch維度,形狀: (1, max_len, d_model)self.register_buffer('pe', pe) # 注冊為緩沖區,不參與訓練def forward(self, x):""":param x: 輸入張量 (batch_size, seq_len, d_model):return: 添加位置編碼后的張量"""x = x + self.pe[:, :x.size(1)] # 截取與輸入序列長度匹配的PEreturn x# 示例:生成長度為10,維度為512的位置編碼
d_model = 512
pe_layer = PositionalEncoding(d_model)
x = torch.zeros(32, 10, d_model) # 假設batch_size=32, seq_len=10
x_with_pe = pe_layer(x)
print(x_with_pe.shape) # 輸出: torch.Size([32, 10, 512])