徹底理解GPT tokenizers
你可能已經聽說過GPT這個詞,它是一種人工智能模型,可以生成各種各樣的文本,比如小說、詩歌、對話、新聞等等。GPT的全稱是Generative Pre-trained Transformer,意思是生成式預訓練變換器。生成式表示它可以根據一些輸入(比如一個單詞或一句話)來創造新的內容,預訓練表示它在使用之前已經在大量的文本數據上進行了學習,變換器表示它使用了一種叫做Transformer的神經網絡結構。
要理解GPT的工作原理,我們需要先了解一個重要的概念:token。token是文本的最小單位,可以是一個字母、一個單詞、一個標點符號或者一個符號。比如,這句話:
Hello, world!
可以被分成五個token:
Hello , world !
GPT模型在處理文本時,需要先把文本分割成token,然后把每個token轉換成一個數字,這個數字就代表了這個token的含義。這個數字叫做token ID。比如,我們可以用下面的表格來表示每個token和它對應的token ID:
token | token ID |
Hello | 1 |
, | 2 |
world | 3 |
! | 4 |
那么,這句話就可以被轉換成一個數字序列:
1 2 3 4
GPT模型就是通過學習大量的這樣的數字序列,來掌握文本的規律和語義。
然后,當我們給它一個輸入(比如一個token ID或者一個數字序列),它就可以根據它學到的知識,來生成一個合理的輸出(比如一個新的token ID或者一個新的數字序列)。
但是,如果我們只用單個字母或單詞作為token,會有一些問題。首先,不同的語言有不同的詞匯量,有些語言可能有幾萬個單詞,有些語言可能有幾十萬甚至幾百萬個單詞。如果我們要給每個單詞分配一個唯一的token ID,那么我們需要很大的內存空間來存儲這些ID。其次,有些單詞可能很少出現在文本中,或者有些單詞可能是新造出來的,比如一些專有名詞、縮寫、網絡用語等等。如果我們要讓GPT模型能夠處理這些單詞,那么我們需要不斷地更新我們的token ID表格,并且重新訓練模型。
為了解決這些問題,GPT模型使用了一種叫做BPE(Byte Pair Encoding)的方法來分割文本。BPE是一種數據壓縮技術,它可以把一段文本分割成更小的子單元(subword),這些子單元可以是單個字母、字母組合、部分單詞或完整單詞。
BPE的原理是基于統計頻率來合并最常見的字母對或子單元對。比如,如果我們有下面這四個單詞:
lowlowernewestwidest
我們可以先把它們分割成單個字母:
l o wl o w e rn e w e s tw i d e s t
然后,我們可以統計每個字母對出現的次數,比如:
pair | count |
l o | 2 |
o w | 2 |
w e | 2 |
e r | 1 |
n e | 1 |
e w | 1 |
w i | 1 |
i d | 1 |
d e | 1 |
e s | 1 |
s t | 1 |
我們可以看到,l o,o w和w e都出現了兩次,是最常見的字母對。我們可以把它們合并成一個新的子單元,比如:
lowlow ern e westw i dest
這樣,我們就減少了一些token的數量。我們可以重復這個過程,直到達到我們想要的token的數量或者沒有更多的可合并的字母對。比如,我們可以繼續合并e r,n e,e w等等,得到:
lowlowernewestwidest
這樣,我們就把四個單詞分割成了六個子單元:
lowernewestwidest
這些子單元就是BPE的token。我們可以給它們分配token ID,比如:
token | token ID |
low | 5 |
er | 6 |
new | 7 |
est | 8 |
wid | 9 |
那么,這四個單詞就可以被轉換成下面的數字序列:
55 67 89 8
你可能會問,為什么要用BPE來分割文本呢?有什么好處呢?其實,BPE有以下幾個優點:
- 它可以減少token的數量,從而節省內存空間和計算資源。
- 它可以處理未知或罕見的單詞,只要把它們分割成已知的子單元就行了。比如,如果我們遇到一個新單詞lowerest,我們可以把它分割成low er est,然后用對應的token ID表示它。
- 它可以捕捉單詞的形態變化,比如復數、時態、派生等等。比如,如果我們遇到一個單詞lowering,我們可以把它分割成low er ing,然后用對應的token ID表示它。這樣,GPT模型就可以學習到這個單詞和其他形式的關系。
當然,BPE也有一些缺點,比如:
- 它可能會破壞一些有意義的子單元,比如把一個完整的單詞分割成兩個或多個部分。比如,如果我們遇到一個單詞tower,我們可能會把它分割成t ow er,而不是保留它作為一個整體。
- 它可能會導致一些歧義或混淆,比如把兩個不同的單詞分割成相同的子單元序列。比如,如果我們遇到兩個單詞tow er和tower,我們可能會把它們都分割成t ow er,而不是區分它們。
- 它可能會影響一些特殊的符號或標記的處理,比如HTML標簽、URL、郵箱地址等等。比如,如果我們遇到一個URLhttps://www.bing.com/, 我們可能會把它分割成多個子單元,比如:
https : / / www . bing . com /
這樣,可能會丟失一些原本的含義或格式。
所以,BPE并不是一種完美的方法,它只是一種權衡的方法,它在減少token數量和保留token含義之間尋找一個平衡點。不同的BPE方法可能會有不同的分割規則和結果,比如,我們可以設置一個最大的token數量,或者一個最小的合并頻率,來影響BPE的過程和輸出。
那么,GPT模型是如何使用BPE來分割文本的呢?實際上,GPT模型并不是直接使用BPE來分割文本,而是使用了一種叫做GPT-2 tokenizer的工具,這個工具是基于BPE的一種改進版本。GPT-2 tokenizer有以下幾個特點:
- 它使用了Unicode編碼來表示每個字符,而不是ASCII編碼。這樣,它可以支持更多的語言和符號,比如中文、日文、阿拉伯文、表情符號等等。
- 它使用了一個固定的token數量,即50257個。這個數字是根據GPT-2模型的輸入層的大小來確定的,每個輸入層可以容納50257個不同的token ID。
- 它使用了一個預先訓練好的BPE模型來分割文本,這個BPE模型是在一個大規模的文本數據集上訓練得到的,它包含了各種各樣的文本類型和語言。
上手實踐
如果你想使用GPT-2 tokenizer來分割文本,你可以參考以下的步驟:
- 首先,你需要安裝和導入transformers庫,這是一個提供了各種預訓練模型和工具的開源庫12。
- 然后,你需要從預訓練的gpt2模型中加載tokenizer和model,你可以使用AutoTokenizer和GPT2DoubleHeadsModel類來實現這一功能12。
- 接著,你需要給tokenizer添加一些特殊的token,比如[CLS]和[SEP],這些token可以幫助模型識別文本的開始和結束12。
- 最后,你可以使用tokenizer的encode或encode_plus方法來把文本轉換成token ID的序列,并且使用model的forward方法來得到模型的輸出123。
下面是一個簡單的Python代碼示例:
# 導入transformers庫
from transformers import AutoTokenizer, GPT2DoubleHeadsModel
import torch
# 加載tokenizer和model
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = GPT2DoubleHeadsModel.from_pretrained("gpt2")
# 添加特殊的token
num_added_tokens = tokenizer.add_special_tokens({"cls_token": "[CLS]", "sep_token": "[SEP]"})
# 分割文本
text = "Hello, my dog is cute"
inputs = tokenizer.encode_plus(text, add_special_tokens=True, return_tensors="pt")
# 得到模型的輸出
outputs = model(**inputs)
last_hidden_states = outputs.last_hidden_state
一旦您了解了令牌,GPT 工具生成文本的方式就會變得更加有意義。
特別是,觀看 GPT-4 將其輸出作為獨立令牌流式傳輸回很有趣(GPT-4 比 3.5 略慢,因此更容易看到發生了什么)。
這是我得到的 - 使用我的 llm CLI 工具從 GPT-4 生成文本:llm -s 'Five names for a pet pelican' -4。
字典中不存在的“Pelly” 占用了多個token,而字典中存在的“Captain Gulliver”則能一次性輸出。
本文轉載自 ??AI小智??,作者: AI小智
