Transformers 文件

分詞器概述

Hugging Face's logo
加入 Hugging Face 社群

並獲得增強的文件體驗

開始使用

分詞器概述

在本頁中,我們將更深入地瞭解分詞。

正如我們在預處理教程中看到的,對文字進行分詞是將其拆分為單詞或子詞,然後透過查詢表將其轉換為ID。將單詞或子詞轉換為ID是直接的,因此在本概述中,我們將重點關注將文字拆分為單詞或子詞(即對文字進行分詞)。更具體地說,我們將介紹🤗 Transformers中使用的三種主要分詞器型別:位元組對編碼 (BPE)WordPieceSentencePiece,並展示哪些模型使用了哪種分詞器型別的示例。

請注意,在每個模型頁面上,您可以檢視相關分詞器的文件,以瞭解預訓練模型使用了哪種分詞器型別。例如,如果我們檢視BertTokenizer,我們可以看到該模型使用WordPiece

簡介

將文字拆分成更小的塊比看起來更難,並且有多種方法可以做到。例如,我們來看句子`"Don't you love 🤗 Transformers? We sure do."`

一種簡單的文字分詞方法是按空格拆分,這將得到

["Don't", "you", "love", "🤗", "Transformers?", "We", "sure", "do."]

這是合理的第一步,但如果我們檢視標記`"Transformers?"`和`"do."`,我們會發現標點符號附著在單詞`"Transformer"`和`"do"`上,這是次優的。我們應該考慮標點符號,這樣模型就不必學習單詞的不同表示以及可能跟隨它的每個可能的標點符號,這會使模型必須學習的表示數量暴增。考慮標點符號,對我們的示例文字進行分詞將得到

["Don", "'", "t", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]

更好。然而,這種分詞處理單詞`"Don't"`的方式不利。`"Don't"`代表`"do not"`,所以將其分詞為`["Do", "n't"]`會更好。這就是事情開始變得複雜的地方,也是每個模型都有自己分詞器型別的部分原因。根據我們應用於文字分詞的規則,相同的文字會生成不同的分詞輸出。預訓練模型只有在您輸入的資料使用了與其訓練資料相同的分詞規則時才能正常執行。

spaCyMoses 是兩種流行的基於規則的分詞器。將它們應用於我們的示例,*spaCy* 和 *Moses* 將輸出類似以下內容:

["Do", "n't", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]

如上所示,這裡使用了空格和標點符號分詞,以及基於規則的分詞。空格和標點符號分詞以及基於規則的分詞都是詞元分詞的例子,其廣義定義是將句子拆分為單詞。雖然這是將文字拆分為更小塊最直觀的方式,但這種分詞方法對於大型文字語料庫可能會導致問題。在這種情況下,空格和標點符號分詞通常會生成一個非常大的詞彙表(所有獨特單詞和標記的集合)。*例如*,Transformer XL 使用空格和標點符號分詞,導致詞彙量大小為 267,735!

如此龐大的詞彙量迫使模型在輸入和輸出層擁有一個巨大的嵌入矩陣,這會導致記憶體和時間複雜度的增加。一般來說,transformer 模型的詞彙量很少超過 50,000,特別是如果它們僅在單一語言上進行預訓練。

那麼,如果簡單的空格和標點符號分詞不令人滿意,為什麼不直接按字元分詞呢?

雖然字元分詞非常簡單,並且可以大大降低記憶體和時間複雜度,但它使得模型更難學習有意義的輸入表示。例如,學習字母`"t"`的有意義的上下文無關表示比學習單詞`"today"`的上下文無關表示要困難得多。因此,字元分詞通常伴隨著效能的損失。因此,為了兩全其美,transformer 模型使用一種介於詞級和字元級分詞之間的混合方法,稱為**子詞**分詞。

子詞分詞

子詞分詞演算法基於以下原則:常用詞不應拆分為更小的子詞,但稀有詞應分解為有意義的子詞。例如,`"annoyingly"`可能被認為是一個稀有詞,可以分解為`"annoying"`和`"ly"`。`"annoying"`和`"ly"`作為獨立子詞會更頻繁地出現,同時`"annoyingly"`的含義透過`"annoying"`和`"ly"`的組合含義得以保留。這在土耳其語等粘著語中特別有用,在這些語言中,可以透過將子詞串聯起來形成(幾乎)任意長的複雜詞。

子詞分詞允許模型擁有合理的詞彙量,同時能夠學習有意義的上下文無關表示。此外,子詞分詞使模型能夠透過將以前從未見過的單詞分解為已知子詞來處理它們。例如,BertTokenizer 將 `"I have a new GPU!"` 分詞如下:

>>> from transformers import BertTokenizer

>>> tokenizer = BertTokenizer.from_pretrained("google-bert/bert-base-uncased")
>>> tokenizer.tokenize("I have a new GPU!")
["i", "have", "a", "new", "gp", "##u", "!"]

因為我們考慮的是 uncased 模型,所以句子首先被小寫。我們可以看到單詞 `["i", "have", "a", "new"]` 存在於分詞器的詞彙表中,但單詞 `"gpu"` 不存在。因此,分詞器將 `"gpu"` 拆分為已知子詞:`["gp" 和 "##u"]`。`"##"` 意味著令牌的其餘部分應與前一個令牌連線,不帶空格(用於解碼或反轉分詞)。

再舉一個例子,XLNetTokenizer 將我們之前的示例文字分詞如下:

>>> from transformers import XLNetTokenizer

>>> tokenizer = XLNetTokenizer.from_pretrained("xlnet/xlnet-base-cased")
>>> tokenizer.tokenize("Don't you love 🤗 Transformers? We sure do.")
["▁Don", "'", "t", "▁you", "▁love", "▁", "🤗", "▁", "Transform", "ers", "?", "▁We", "▁sure", "▁do", "."]

當我們檢視 SentencePiece 時,我們將回到那些 `"` 的含義。正如所看到的,稀有詞 `"Transformers"` 已被拆分為更頻繁的子詞 `"Transform"` 和 `"ers"`。

現在讓我們看看不同的子詞分詞演算法是如何工作的。請注意,所有這些分詞演算法都依賴於某種形式的訓練,通常是在相應模型將要訓練的語料庫上進行的。

位元組對編碼 (BPE)

位元組對編碼(BPE)在《使用子詞單元的稀有詞神經機器翻譯》(Sennrich 等人,2015)中被引入。BPE 依賴於一個預分詞器,該預分詞器將訓練資料拆分為單詞。預分詞可以像空格分詞一樣簡單,例如 GPT-2RoBERTa。更高階的預分詞包括基於規則的分詞,例如 XLMFlauBERT,它們對大多數語言使用 Moses,或者 GPT,它使用 spaCy 和 ftfy,來計算訓練語料庫中每個單詞的頻率。

預分詞後,將建立一個唯一的單詞集,並確定每個單詞在訓練資料中出現的頻率。接下來,BPE 建立一個由唯一單詞集中出現的所有符號組成的基本詞彙表,並學習合併規則以從基本詞彙表中的兩個符號形成新符號。它會一直這樣做,直到詞彙表達到所需的詞彙量大小。請注意,所需的詞彙量大小是分詞器訓練前要定義的超引數。

例如,假設在預分詞之後,已經確定了以下單詞集及其頻率:

("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

因此,基本詞彙表是 `["b", "g", "h", "n", "p", "s", "u"]`。將所有單詞拆分為基本詞彙表中的符號,我們得到:

("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)

BPE 然後計算每個可能的符號對的頻率,並選擇出現頻率最高的符號對。在上面的示例中,`"h"` 後跟 `"u"` 出現了 10 + 5 = 15 次(在 `"hug"` 的 10 次出現中出現 10 次,在 `"hugs"` 的 5 次出現中出現 5 次)。然而,最頻繁的符號對是 `"u"` 後跟 `"g"`,總共出現了 10 + 5 + 5 = 20 次。因此,分詞器學習的第一個合併規則是將所有 `"u"` 符號後跟 `"g"` 符號的分詞組合在一起。接下來,`"ug"` 被新增到詞彙表中。單詞集 then becomes

("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)

BPE 接著識別下一個最常見的符號對。它是 `"u"` 後跟 `"n"`,出現了 16 次。`"u"`、`"n"` 合併為 `"un"` 並新增到詞彙表中。下一個最常見的符號對是 `"h"` 後跟 `"ug"`,出現了 15 次。同樣,該對被合併,並且 `"hug"` 可以新增到詞彙表中。

在此階段,詞彙表為 `["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]`,我們的唯一詞集表示為

("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)

假設位元組對編碼訓練到此停止,那麼所學習的合併規則將被應用於新詞(只要這些新詞不包含不在基本詞彙表中的符號)。例如,單詞`"bug"`將被分詞為`["b", "ug"]`,但`"mug"`將被分詞為`["<unk>", "ug"]`,因為符號`"m"`不在基本詞彙表中。一般來說,像`"m"`這樣的單個字母不會被`"<unk>"`符號替換,因為訓練資料通常至少包含每個字母的一次出現,但對於表情符號等非常特殊的字元,這種情況很可能發生。

如前所述,詞彙量大小,即基本詞彙量大小+合併次數,是一個需要選擇的超引數。例如,GPT 的詞彙量大小為 40,478,因為它們有 478 個基本字元,並且選擇在 40,000 次合併後停止訓練。

位元組級BPE

如果例如所有 Unicode 字元都被視為基本字元,則包含所有可能基本字元的基本詞彙表可能會非常大。為了擁有更好的基本詞彙表,GPT-2 使用位元組作為基本詞彙表,這是一個巧妙的技巧,可以將基本詞彙表的大小強制為 256,同時確保每個基本字元都包含在詞彙表中。透過一些額外的規則來處理標點符號,GPT2 的分詞器可以對每個文字進行分詞,而無需使用 <unk> 符號。GPT-2 的詞彙表大小為 50,257,這對應於 256 個位元組基本標記,一個特殊的文字結束標記以及透過 50,000 次合併學習到的符號。

WordPiece

WordPiece 是用於 BERTDistilBERTElectra 的子詞分詞演算法。該演算法在 《日語和韓語語音搜尋》(Schuster 等人,2012)中進行了概述,並且與 BPE 非常相似。WordPiece 首先將詞彙表初始化為包含訓練資料中存在的每個字元,並逐步學習給定數量的合併規則。與 BPE 不同,WordPiece 不選擇最頻繁的符號對,而是選擇新增到詞彙表後能使訓練資料似然度最大化的符號對。

那麼這到底意味著什麼呢?回到之前的例子,最大化訓練資料的似然度等同於找到這樣一個符號對,其機率除以其第一個符號和第二個符號的機率,在所有符號對中最大。例如,`"u"` 後面跟著 `"g"` 只有在 `"ug"` 的機率除以 `"u"` 和 `"g"` 的機率大於任何其他符號對時才會被合併。直觀地說,WordPiece 與 BPE 略有不同,因為它會評估合併兩個符號會*損失*什麼,以確保*值得*。

Unigram

Unigram是一種子詞分詞演算法,由《子詞正則化:透過多子詞候選項改進神經網路翻譯模型》(Kudo,2018)引入。與BPE或WordPiece不同,Unigram將其基本詞彙表初始化為大量符號,並逐步修剪每個符號以獲得更小的詞彙表。基本詞彙表可以例如對應於所有預分詞的單詞和最常見的子字串。Unigram並未直接用於transformer中的任何模型,但它與SentencePiece結合使用。

在每個訓練步驟中,Unigram演算法根據當前詞彙表和一元語言模型定義訓練資料上的損失(通常定義為對數似然)。然後,對於詞彙表中的每個符號,演算法計算如果將該符號從詞彙表中移除,總損失會增加多少。Unigram然後移除 p (通常 p 為 10% 或 20%) 的符號,這些符號的損失增加最低,即那些對訓練資料總損失影響最小的符號。此過程重複進行,直到詞彙表達到所需的大小。Unigram演算法始終保留基本字元,以便任何單詞都可以被分詞。

由於Unigram不基於合併規則(與BPE和WordPiece不同),該演算法在訓練後有多種方式對新文字進行分詞。例如,如果一個訓練好的Unigram分詞器顯示詞彙表為

["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"],

`"hugs"` 可以被分詞為 `["hug", "s"]`、`["h", "ug", "s"]` 或 `["h", "u", "g", "s"]`。那麼該選擇哪一個呢?Unigram 除了儲存詞彙表外,還儲存了訓練語料庫中每個標記的機率,以便在訓練後計算每個可能分詞的機率。該演算法在實踐中簡單地選擇最可能的分詞,但也提供了根據其機率對可能分詞進行取樣的可能性。

這些機率由分詞器訓練的損失定義。假設訓練資料由以下單片語成:x1,,xNx_{1}, \dots, x_{N}並且所有可能分詞的集合為單詞xix_{i}定義為S(xi)S(x_{i}),則總損失定義為L=i=1Nlog(xS(xi)p(x))\mathcal{L} = -\sum_{i=1}^{N} \log \left ( \sum_{x \in S(x_{i})} p(x) \right )

SentencePiece

目前為止描述的所有分詞演算法都有相同的問題:它們假設輸入文字使用空格分隔單詞。然而,並非所有語言都使用空格分隔單詞。一種可能的解決方案是使用特定語言的預分詞器,例如XLM 使用特定的中文、日文和泰文預分詞器。為了更普遍地解決這個問題,《SentencePiece:一種用於神經文字處理的簡單且獨立於語言的子詞分詞器和去分詞器》(Kudo 等人,2018)將輸入視為原始輸入流,從而將空格包含在要使用的字元集中。然後它使用 BPE 或 Unigram 演算法來構建適當的詞彙表。

XLNetTokenizer 例如使用 SentencePiece,這也是為什麼在之前的示例中 `"` 字元被包含在詞彙表中。使用 SentencePiece 進行解碼非常簡單,因為所有標記都可以簡單地連線起來,並且 `"` 被替換為空格。

庫中所有使用 SentencePiece 的 transformer 模型都將其與 unigram 結合使用。使用 SentencePiece 的模型示例包括 ALBERTXLNetMarianT5

< > 在 GitHub 上更新

© . This site is unofficial and not affiliated with Hugging Face, Inc.