LLM 課程文件
分詞器
並獲得增強的文件體驗
開始使用
分詞器
分詞器是自然語言處理(NLP)管道的核心元件之一。它們只有一個目的:將文字轉換為模型可以處理的資料。模型只能處理數字,因此分詞器需要將我們的文字輸入轉換為數值資料。在本節中,我們將詳細探討分詞管道中發生的一切。
在自然語言處理任務中,通常處理的資料是原始文字。下面是一個這樣的文字示例:
Jim Henson was a puppeteer
然而,模型只能處理數字,所以我們需要找到一種方法將原始文字轉換為數字。這就是分詞器所做的工作,並且有很多方法可以實現這一目標。目標是找到最有意義的表示——也就是說,對模型來說最有意義的表示——如果可能的話,也是最小的表示。
讓我們來看一些分詞演算法的例子,並嘗試回答你可能對分詞產生的一些問題。
基於詞語
首先想到的分詞器型別是**基於詞語**的分詞器。它通常非常容易設定和使用,只需少量規則,並且通常能產生不錯的效果。例如,在下圖中,目標是將原始文字拆分為單詞,併為每個單詞找到一個數值表示。
有多種方法可以分割文字。例如,我們可以使用空格將文字分詞成單詞,透過應用 Python 的 `split()` 函式
tokenized_text = "Jim Henson was a puppeteer".split()
print(tokenized_text)
['Jim', 'Henson', 'was', 'a', 'puppeteer']
還有一些詞語分詞器的變體,它們對標點符號有額外的規則。使用這類分詞器,我們最終可能會得到相當大的“詞彙表”,其中詞彙表由我們語料庫中獨立詞符的總數定義。
每個單詞都被分配一個ID,從0開始,一直到詞彙表的大小。模型使用這些ID來識別每個單詞。
如果我們想用基於詞的分詞器完全覆蓋一種語言,我們需要為該語言中的每個詞提供一個識別符號,這將生成大量的詞元。例如,英語中有超過 50 萬個詞,所以要建立一個從每個詞到輸入 ID 的對映,我們需要跟蹤這麼多 ID。此外,像“dog”這樣的詞與像“dogs”這樣的詞表示不同,模型最初無法知道“dog”和“dogs”是相似的:它會將這兩個詞識別為不相關的。這同樣適用於其他相似的詞,如“run”和“running”,模型最初不會認為它們是相似的。
最後,我們需要一個自定義詞元來表示詞彙表中沒有的詞。這被稱為“未知”詞元,通常表示為“[UNK]”或“<unk>”。如果分詞器產生大量此類詞元,通常是一個不好的跡象,因為它無法檢索到詞的合理表示,並且在此過程中會丟失資訊。構建詞彙表的目標是,使分詞器將盡可能少的詞分詞為未知詞元。
減少未知詞元數量的一種方法是深入一層,使用**基於字元**的分詞器。
基於字元
基於字元的分詞器將文字分割成字元,而不是單詞。這有兩個主要優點:
- 詞彙量要小得多。
- 詞彙表外(未知)詞元要少得多,因為每個詞都可以由字元組成。
但這裡也出現了一些關於空格和標點符號的問題:
這種方法也不完美。由於現在的表示是基於字元而不是詞,有人可能會認為,直觀上,它意義較小:每個字元本身並沒有太多的意義,而詞則不同。然而,這又因語言而異;例如,在中文中,每個字元比拉丁語中的字元攜帶更多資訊。
另一個需要考慮的問題是,我們的模型最終需要處理大量的詞元:一個詞在使用基於詞的分詞器時只算一個詞元,但在轉換為字元時可以輕易變成10個或更多詞元。
為了兩全其美,我們可以使用第三種技術,它結合了這兩種方法:**子詞分詞**。
子詞分詞
子詞分詞演算法基於一個原則:常用詞不應拆分成更小的子詞,而稀有詞應分解成有意義的子詞。
例如,“annoyingly”可能被認為是一個罕見詞,可以分解成“annoying”和“ly”。這兩個詞作為獨立子詞出現的頻率可能更高,同時“annoyingly”的含義透過“annoying”和“ly”的組合意義得以保留。
這是一個示例,展示了子詞分詞演算法如何對序列“Let's do tokenization!”進行分詞:
這些子詞最終提供了很多語義意義:例如,在上面的例子中,“tokenization”被分割成“token”和“ization”,這兩個詞元具有語義意義,同時節省空間(只需兩個詞元即可表示一個長詞)。這使我們能夠以較小的詞彙量實現相對較好的覆蓋率,並且幾乎沒有未知詞元。
這種方法在粘著語中尤其有用,例如土耳其語,它可以透過串聯子詞形成(幾乎)任意長的複雜詞。
還有更多!
不出所料,還有許多其他技術。舉幾個例子:
- GPT-2 中使用的位元組級 BPE
- BERT 中使用的 WordPiece
- SentencePiece 或 Unigram,用於多種多語言模型
現在您應該對分詞器的工作原理有了足夠的瞭解,可以開始使用 API 了。
載入和儲存
載入和儲存分詞器就像載入和儲存模型一樣簡單。實際上,它基於相同的兩個方法:`from_pretrained()` 和 `save_pretrained()`。這些方法將載入或儲存分詞器使用的演算法(有點像模型的*架構*)以及其詞彙表(有點像模型的*權重*)。
載入與 BERT 相同檢查點訓練的 BERT 分詞器,方式與載入模型相同,只是我們使用 `BertTokenizer` 類。
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
與 `AutoModel` 類似,`AutoTokenizer` 類將根據檢查點名稱獲取庫中正確的標記器類,並且可以直接與任何檢查點一起使用。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
我們現在可以像上一節所示那樣使用分詞器了
tokenizer("Using a Transformer network is simple")
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102],
'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0],
'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
儲存分詞器與儲存模型相同。
tokenizer.save_pretrained("directory_on_my_computer")
我們將在第三章中詳細討論`token_type_ids`,並稍後解釋`attention_mask`鍵。首先,讓我們看看`input_ids`是如何生成的。為此,我們需要檢視分詞器的中間方法。
編碼
將文字轉換為數字稱為**編碼**。編碼分為兩個步驟:分詞,然後轉換為輸入 ID。
正如我們所見,第一步是將文字分割成單詞(或單詞的一部分、標點符號等),通常稱為**詞元**。這個過程可以由多種規則來管理,這就是為什麼我們需要使用模型的名稱來例項化分詞器,以確保我們使用與模型預訓練時相同的規則。
第二步是將這些詞元轉換為數字,這樣我們就可以用它們構建張量並將其輸入模型。為此,分詞器有一個**詞彙表**,這是我們使用 `from_pretrained()` 方法例項化時下載的部分。同樣,我們需要使用模型預訓練時使用的相同詞彙表。
為了更好地理解這兩個步驟,我們將分別探討它們。請注意,我們將使用一些單獨執行分詞管道某些部分的方法來向您展示這些步驟的中間結果,但在實踐中,您應該直接在輸入上呼叫分詞器(如第 2 節所示)。
分詞
分詞過程由分詞器的 `tokenize()` 方法完成。
from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
sequence = "Using a Transformer network is simple"
tokens = tokenizer.tokenize(sequence)
print(tokens)
此方法的輸出是一個字串列表,即詞元。
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
這個分詞器是一個子詞分詞器:它會不斷拆分單詞,直到獲得能夠用其詞彙表表示的詞元。這裡“transformer”的情況就是如此,它被拆分成兩個詞元:“transform”和“##er”。
從詞元到輸入ID
轉換為輸入 ID 由 `convert_tokens_to_ids()` 分詞器方法處理。
ids = tokenizer.convert_tokens_to_ids(tokens)
print(ids)
[7993, 170, 11303, 1200, 2443, 1110, 3014]
這些輸出,一旦轉換為適當的框架張量,就可以像本章前面所示那樣用作模型的輸入。
✏️ **試一試!** 對我們在第 2 節中使用的輸入句子(“我一生都在等待一門 HuggingFace 課程。”和“我太討厭這個了!”)重複最後兩個步驟(分詞和轉換為輸入 ID)。檢查你是否得到了我們之前得到的相同輸入 ID!
解碼
**解碼**是反向操作:我們希望從詞彙表索引中獲取字串。這可以透過 `decode()` 方法完成,如下所示:
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014])
print(decoded_string)
'Using a Transformer network is simple'
請注意,`decode` 方法不僅將索引轉換回詞元,還將屬於同一單詞的詞元組合在一起,以生成可讀的句子。當模型預測新文字時(無論是從提示生成的文字,還是用於翻譯或摘要等序列到序列問題),這種行為將非常有用。
現在您應該已經瞭解了分詞器能夠處理的基本操作:分詞、轉換為 ID 以及將 ID 轉換回字串。然而,我們僅僅觸及了冰山一角。在下一節中,我們將把我們的方法推向極限,並研究如何克服這些限制。
< > 在 GitHub 上更新