昨天我們提到了兩種古典的分詞分式:Character tokenization 和 Word tokenization。然後我們很快發現其盲點,Character tokenization 很難模型得出有意義的結論,而 Word tokenization 很容易導致參數過大的問題,因此近年來發展出比較新的 tokenization 演算法,基本上就是 Subword Tokenization 的天下了。
Subword Tokenization 一言以蔽之,就是結合了 Character tokenization 和 Word tokenization。首先它保留常用的單字,讓整個 transformer 的權重不會因為單字的總數太大而爆炸。再來還也保留了 Character tokenization 的機制,可以把單字拆分成更小的單元,進而能夠處理罕用字和拼錯的字,所以才叫 subword。我們接著來分述幾種常見的Subword 演算法。
在我們第三天開始寫 Hugging Face 的程式碼時,我們用了 distilbert-base-uncased 這個 pre-trained model 來測試我們輸入的句子。這個模型就是大名鼎鼎的 BERT 家族下面的模型之一,而 BERT 家族使用的 Subword Tokenization,常常就是 WordPiece。
這個算法的論文是在 2012 年被提出來的,當年主要是在做日文和韓文的聲音搜尋。而且日文、韓文和中文,都和英文的單字不一樣,要做分詞實在不容易。最經典的例子相信大家國小都有讀過:「下雨天留客天留我不留」,這句子用不同的方式做分詞,會得到不同的意思。有興趣讀原始論文的朋友可以參考這裡。
在此簡單地幫大家摘錄論文的重點。
這種方法同時保有了 Character tokenization(中文或日文裡的一個字) 和 Word tokenization(中文或日文裡的一個詞)。更重要的是,可以很有效的避免 out of vocabulary 的問題。
BPE 是另一組 subword 的代表,用這個演算法的代表正好就是與 BERT 家族不一樣的另一支: GPT 家族。
BPE 最古老是在1994年被出提出,最早是被用來做壓縮的演算法,後來被拿來做 Tokenization。有興趣的朋友可以去這裡看古老的論文。第一次拿 BPE 來做自然語言處理,可以參考這篇論文。
我們可以直接看下面程式碼,這是從論文中截出來的,我們就能很容易知道 BPE 的原理了。
num_merges
改為 15,程式碼如下:import re, collections
def get_stats(vocab):
pairs = collections.defaultdict(int)
for word, freq in vocab.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i],symbols[i+1]] += freq
return pairs
def merge_vocab(pair, v_in):
v_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in v_in:
w_out = p.sub(''.join(pair), word)
v_out[w_out] = v_in[word]
return v_out
# 這裡的 vocab 裡的字都要用空格分開
vocab = {'l o w </w>' : 5, 'l o w e r </w>' : 2,
'n e w e s t </w>':6, 'w i d e s t </w>':3}
num_merges = 15
for i in range(num_merges):
pairs = get_stats(vocab)
best = max(pairs, key=pairs.get)
vocab = merge_vocab(best, vocab)
print(best)
('e', 's')
('es', 't')
('est', '</w>')
('l', 'o')
('lo', 'w')
('n', 'e')
('ne', 'w')
('new', 'est</w>')
('low', '</w>')
('w', 'i')
('wi', 'd')
('wid', 'est</w>')
('low', 'e')
('lowe', 'r')
('lower', '</w>')
這個算法和 WordPiece 很像,結合了 Character tokenization 和 Word tokenization,也能避免 out of vocabulary 的問題。
與 BPE 和 WordPiece 相反,Unigram 是從一個巨大的詞彙表開始,然後從中刪除不要的,直到達到所需的詞彙量大小。而這個詞彙表的建立,就可以用 BPE 和 WordPiece 來建立。有興趣的朋友可以參考這篇論文。
這篇論文很多數學,讀到頭都花了。簡單來說就是在訓練的每一步,Unigram 當前這個詞彙在語料庫中的 loss。然後計算如果刪除了當前這個詞彙,整體損失會增加多少,並尋找增加最少的、對語料庫的整體損失影響較小。因此可以得到那些比較不需要的 word ,然後就可以踢除。
採用 Unigram 的 Transformer 的代表就是 T5,這和 GPT 與 BERT 又是不一樣的類別了。為未來我們會再為這主流的三種型態的 Transformer 來做講解。
關於 Subword 的演算法,我們就先介紹到這裡。以上講得非常複雜,如果是自己重頭實作演算法的話一定是哭出來,但是透過 Hugging Face 我們可以很容易的做完。明天就來開始用 Hugging Face 的 Tokenizer 來寫程式吧!