1. 背景與基礎(chǔ)
在使用GPT BERT模型輸入詞語常常會(huì)先進(jìn)行tokenize 咙鞍,tokenize具體目標(biāo)與粒度是什么呢房官?tokenize也有許多類別及優(yōu)缺點(diǎn),這篇文章總結(jié)一下各個(gè)方法及實(shí)際案例续滋。
tokenize的目標(biāo)是把輸入的文本流翰守,切分成一個(gè)個(gè)子串,每個(gè)子串相對(duì)有完整的語義疲酌,便于學(xué)習(xí)embedding表達(dá)和后續(xù)模型的使用蜡峰。
tokenize有三種粒度:word/subword/char
word/詞,詞朗恳,是最自然的語言單元湿颅。對(duì)于英文等自然語言來說,存在著天然的分隔符粥诫,如空格或一些標(biāo)點(diǎn)符號(hào)等油航,對(duì)詞的切分相對(duì)容易。但是對(duì)于一些東亞文字包括中文來說臀脏,就需要某種分詞算法才行劝堪。順便說一下,Tokenizers庫(kù)中揉稚,基于規(guī)則切分部分秒啦,采用了spaCy和Moses兩個(gè)庫(kù)。如果基于詞來做詞匯表搀玖,由于長(zhǎng)尾現(xiàn)象的存在余境,這個(gè)詞匯表可能會(huì)超大。像Transformer XL庫(kù)就用到了一個(gè)26.7萬個(gè)單詞的詞匯表灌诅。這需要極大的embedding matrix才能存得下芳来。embedding matrix是用于查找取用token的embedding vector的。這對(duì)于內(nèi)存或者顯存都是極大的挑戰(zhàn)猜拾。常規(guī)的詞匯表即舌,一般大小不超過5萬。
char/字符挎袜,即最基本的字符顽聂,如英語中的'a','b','c'或中文中的'你'肥惭,'我','他'等紊搪。而一般來講蜜葱,字符的數(shù)量是少量有限的。這樣做的問題是耀石,由于字符數(shù)量太小牵囤,我們?cè)跒槊總€(gè)字符學(xué)習(xí)嵌入向量的時(shí)候,每個(gè)向量就容納了太多的語義在內(nèi)滞伟,學(xué)習(xí)起來非常困難揭鳞。
subword/子詞級(jí),它介于字符和單詞之間梆奈。比如說'Transformers'可能會(huì)被分成'Transform'和'ers'兩個(gè)部分汹桦。這個(gè)方案平衡了詞匯量和語義獨(dú)立性,是相對(duì)較優(yōu)的方案鉴裹。它的處理原則是,常用詞應(yīng)該保持原狀钥弯,生僻詞應(yīng)該拆分成子詞以共享token壓縮空間径荔。
2. 常用tokenize算法
最常用的三種tokenize算法:BPE(Byte-Pair Encoding),WordPiece和SentencePiece
2.1 Byte-Pair Encoding (BPE) / Byte-level BPE
2.1.1 BPE
BPE脆霎,即字節(jié)對(duì)編碼总处。其核心思想在于將最常出現(xiàn)的子詞對(duì)合并,直到詞匯表達(dá)到預(yù)定的大小時(shí)停止睛蛛。
首先鹦马,它依賴于一種預(yù)分詞器pretokenizer來完成初步的切分。pretokenizer可以是簡(jiǎn)單基于空格的忆肾,也可以是基于規(guī)則的荸频;
分詞之后,統(tǒng)計(jì)每個(gè)詞出現(xiàn)的頻次客冈,供后續(xù)計(jì)算使用旭从。例如,我們統(tǒng)計(jì)到了5個(gè)詞的詞頻
("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)
- 建立基礎(chǔ)詞匯表场仲,包括所有的字符和悦,即:
["b", "g", "h", "n", "p", "s", "u"]
- 根據(jù)規(guī)則,我們分別考察2-gram渠缕,3-gram的基本字符組合鸽素,把高頻的ngram組合依次加入到詞匯表中,直到詞匯表達(dá)到預(yù)定大小停止亦鳞。比如馍忽,我們計(jì)算出ug/un/hug三種組合出現(xiàn)頻次分別為20棒坏,16和15,加入到詞匯表中舵匾。
- 最終詞匯表的大小= 基礎(chǔ)字符詞匯表大小 + 合并串的數(shù)量俊抵,比如像GPT,它的詞匯表大小 40478 = 478(基礎(chǔ)字符) + 40000(merges)坐梯。添加完后徽诲,我們?cè)~匯表變成:
["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]
實(shí)際使用中,如果遇到未知字符用<unk>代表吵血。
2.1.2 Byte-level BPE
BPE的一個(gè)問題是谎替,如果遇到了unicode芝囤,基本字符集可能會(huì)很大护锤。一種處理方法是我們以一個(gè)字節(jié)為一種“字符”,不管實(shí)際字符集用了幾個(gè)字節(jié)來表示一個(gè)字符措伐。這樣的話侦另,基礎(chǔ)字符集的大小就鎖定在了256秩命。
例如,像GPT-2的詞匯表大小為50257 = 256 + <EOS> + 50000 mergers褒傅,<EOS>是句子結(jié)尾的特殊標(biāo)記弃锐。
2.2 WordPiece
WordPiece,從名字好理解殿托,它是一種子詞粒度的tokenize算法subword tokenization algorithm霹菊,很多著名的Transformers模型,比如BERT/DistilBERT/Electra都使用了它支竹。
它的原理非常接近BPE旋廷,不同之處在于它做合并時(shí),并不是直接找最高頻的組合礼搁,而是找能夠最大化訓(xùn)練數(shù)據(jù)似然的merge饶碘。即它每次合并的兩個(gè)字符串A和B,應(yīng)該具有最大的值馒吴。合并AB之后熊镣,所有原來切成A+B兩個(gè)tokens的就只保留AB一個(gè)token,整個(gè)訓(xùn)練集上最大似然變化量與成正比募书。
2.3 Unigram
與BPE或者WordPiece不同绪囱,Unigram的算法思想是從一個(gè)巨大的詞匯表出發(fā),再逐漸刪除trim down其中的詞匯莹捡,直到size滿足預(yù)定義鬼吵。
初始的詞匯表可以采用所有預(yù)分詞器分出來的詞,再加上所有高頻的子串篮赢。
每次從詞匯表中刪除詞匯的原則是使預(yù)定義的損失最小齿椅。訓(xùn)練時(shí)琉挖,計(jì)算loss的公式為:
假設(shè)訓(xùn)練文檔中的所有詞分別為 ,而每個(gè)詞tokenize的方法是一個(gè)集合涣脚。
當(dāng)一個(gè)詞匯表確定時(shí)示辈,每個(gè)詞tokenize的方法集合就是確定的,而每種方法對(duì)應(yīng)著一個(gè)概率遣蚀。
如果從詞匯表中刪除部分詞矾麻,則某些詞的tokenize的種類集合就會(huì)變少,log(*)中的求和項(xiàng)就會(huì)減少芭梯,從而增加整體loss险耀。
Unigram算法每次會(huì)從詞匯表中挑出使得loss增長(zhǎng)最小的10%~20%的詞匯來刪除。
一般Unigram算法會(huì)與SentencePiece算法連用玖喘。
2.4 SentencePiece
SentencePiece甩牺,顧名思義,它是把一個(gè)句子看作一個(gè)整體累奈,再拆成片段贬派,而沒有保留天然的詞語的概念。一般地澎媒,它把空格space也當(dāng)作一種特殊字符來處理赠群,再用BPE或者Unigram算法來構(gòu)造詞匯表。
比如旱幼,XLNetTokenizer就采用了_來代替空格,解碼的時(shí)候會(huì)再用空格替換回來突委。
目前柏卤,Tokenizers庫(kù)中,所有使用了SentencePiece的都是與Unigram算法聯(lián)合使用的匀油,比如ALBERT缘缚、XLNet、Marian和T5.
3. 切分實(shí)例與代碼分析
3.1 BertTokenizer/ WordPiece
先試一個(gè)BertTokenizer敌蚜,它基于WordPiece算法桥滨,base版本的詞匯表大小為21128
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
tokens = t.encode(...).tokens
切分效果為:
Tokenizer: <class 'transformers.models.bert.tokenization_bert.BertTokenizer'>
Text: The problems of your past are your business. The problems of your future are my privilege.
Tokens: [UNK],pro,##ble,##ms,of,your,pa,##st,are,your,business,.,[UNK],pro,##ble,##ms,of,your,future,are,my,pr,##i,##vi,##le,##ge,.
Text: 你的過去我不愿過問,那是你的事情弛车。你的未來我希望參與齐媒,這是我的榮幸。
Tokens: 你,的,過,去,我,不,愿,過,問,纷跛,,那,是,你,的,事,情,喻括。,你,的,未,來,我,希,望,參,與,,,這,是,我,的,榮,幸,贫奠。
Text: Don’t make the user feel stupid.
Tokens: [UNK],[UNK],t,make,the,user,feel,st,##up,##id,.
Text: 中國(guó)語言研究院正式宣布唬血,“筆畫最多的漢字”的桂冠屬于“龖(dá)”字望蜡!
Tokens: 中,國(guó),語,言,研,究,院,正,式,宣,布,,,[UNK],筆,畫,最,多,的,漢,字,[UNK],的,桂,冠,屬,于,[UNK],[UNK],(,[UNK],),[UNK],字,拷恨!
其中脖律,
- BertTokenizer中,用##符號(hào)表示非開頭的子詞腕侄,比如第1句中的problems被拆分成了三部分小泉,pro/##ble/##ms;
- 標(biāo)點(diǎn)符號(hào)兜挨、生僻字等未出現(xiàn)的token被[UNK]代替
- 中文基本拆分成了字的形式膏孟,并沒有看到多字詞的形式
分詞流程與代碼分析如下:
BertTokenizer類關(guān)系如下
在代碼中查看
主要做了兩件事情:
- 根據(jù)參數(shù)控制來對(duì)輸入文本做基礎(chǔ)分詞 (basic_tokenizer)
- 對(duì)于切分出來的單個(gè)詞,再切分(wordpiece_tokenizer)
basic_tokenizer
是把句子切分成詞拌汇,仍然可以對(duì)著代碼看一下:
特別要注意的在 401 行:如果 tokenize_chinese_chars 參數(shù)為 True柒桑,那么所有的中文詞都會(huì)被切成字符級(jí)別!T胍ā魁淳!參數(shù)傳來的 never_split 并不會(huì)讓這些中文詞不被切分。
wordpiece_tokenizer則是將詞切成字符級(jí)別与倡,例如 doing->['do', '###ing']界逛。
這里的做法就是把一個(gè)詞送入 BERT 中做最大匹配(類似于 Jieba 分詞的正向最大匹配算法),如果前面已經(jīng)有匹配纺座,則后面的詞都會(huì)加 ’##‘息拜。
而中文,因?yàn)橐呀?jīng)在上一步被切分成字符級(jí)別净响,所以不會(huì)有任何改變少欺。
3.2 T5Tokenizer / SentencePiece
T5模型是基于SentencePiece的,我們看看它的切分效果馋贤。我用的這個(gè)版本詞匯表大小是250112赞别。
Tokenizer: <class 'transformers.models.t5.tokenization_t5.T5Tokenizer'>
Text: The problems of your past are your business. The problems of your future are my privilege.
Tokens: ▁The,▁problems,▁of,▁your,▁past,▁are,▁your,▁business,.,▁The,▁problems,▁of,▁your,▁future,▁are,▁my,▁,privilege,.
Text: 你的過去我不愿過問,那是你的事情配乓。你的未來我希望參與仿滔,這是我的榮幸。
Tokens: ▁,你的,過去,我不,愿,過,問,,,那是,你,的事情,犹芹。,你的,未來,我,希望,參與,,,這是,我的,榮,幸,崎页。
Text: Don’t make the user feel stupid.
Tokens: ▁Don,’,t,▁make,▁the,▁user,▁feel,▁stupid,.
Text: 中國(guó)語言研究院正式宣布,“筆畫最多的漢字”的桂冠屬于“龖(dá)”字腰埂!
Tokens: ▁,中國(guó),語言,研究院,正式,宣布,,“,筆,畫,最多,的,漢,字,”,的,桂,冠,屬于,“,<0xE9>,<0xBE>,<0x96>,(,dá,),”,字,!
其中实昨,
- 最明顯的,可以看到下劃線被引入盐固,代替了空格和句子開頭特殊符號(hào)
- 中文可以看到一些多字詞荒给,比如“未來”丈挟,“研究院”等,但有些詞其實(shí)不符合一般的分詞習(xí)慣志电,比如“的事情”曙咽、“我不”等等
- 生僻字龖被拆成了三個(gè)基礎(chǔ)字節(jié)形式的token