iT邦幫忙

2022 iThome 鐵人賽

DAY 11
1

昨天我們提到了兩種古典的分詞分式:Character tokenization 和 Word tokenization。然後我們很快發現其盲點,Character tokenization 很難模型得出有意義的結論,而 Word tokenization 很容易導致參數過大的問題,因此近年來發展出比較新的 tokenization 演算法,基本上就是 Subword Tokenization 的天下了。

Subword Tokenization 的概念

Subword Tokenization 一言以蔽之,就是結合了 Character tokenization 和 Word tokenization。首先它保留常用的單字,讓整個 transformer 的權重不會因為單字的總數太大而爆炸。再來還也保留了 Character tokenization 的機制,可以把單字拆分成更小的單元,進而能夠處理罕用字和拼錯的字,所以才叫 subword。我們接著來分述幾種常見的Subword 演算法。

1. WordPiece

在我們第三天開始寫 Hugging Face 的程式碼時,我們用了 distilbert-base-uncased 這個 pre-trained model 來測試我們輸入的句子。這個模型就是大名鼎鼎的 BERT 家族下面的模型之一,而 BERT 家族使用的 Subword Tokenization,常常就是 WordPiece。

這個算法的論文是在 2012 年被提出來的,當年主要是在做日文和韓文的聲音搜尋。而且日文、韓文和中文,都和英文的單字不一樣,要做分詞實在不容易。最經典的例子相信大家國小都有讀過:「下雨天留客天留我不留」,這句子用不同的方式做分詞,會得到不同的意思。有興趣讀原始論文的朋友可以參考這裡

在此簡單地幫大家摘錄論文的重點。

  1. 使用基本的 Unicode 字符(日文漢字、平假名、片假名日文、韓文韓文)來建立 word 的庫存,包括所有 ASCII,日文總數約為 22,000,韓文總數約為 11,000。
  2. 使用 1 中的清單在訓練數據上構建語言模型。
  3. 將 word 庫存中的兩個 word 合併在一起,產生出新的 word(在中文裡,這就是詞的概念,兩個字以上就是一個詞)。再從這些合成出來的新 word 中挑選出來放回 training data 裡面,以增加 training data 的量。
  4. 回到第 2 動,直到達到 word 的量達到定義好的某個閾值為止。

這種方法同時保有了 Character tokenization(中文或日文裡的一個字) 和 Word tokenization(中文或日文裡的一個詞)。更重要的是,可以很有效的避免 out of vocabulary 的問題。

2. Byte Pair Encoding

BPE 是另一組 subword 的代表,用這個演算法的代表正好就是與 BERT 家族不一樣的另一支: GPT 家族。

BPE 最古老是在1994年被出提出,最早是被用來做壓縮的演算法,後來被拿來做 Tokenization。有興趣的朋友可以去這裡看古老的論文。第一次拿 BPE 來做自然語言處理,可以參考這篇論文

我們可以直接看下面程式碼,這是從論文中截出來的,我們就能很容易知道 BPE 的原理了。

  1. 是終止符號,而其冒號的後面是出現的頻率。
  2. 我們把程式碼中的 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)
  1. 會得到下面的結果,它學到了幾個新的字了!如 est(東方標準時間?),new,wid,lowe。
('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 的問題。

3. Unigram

與 BPE 和 WordPiece 相反,Unigram 是從一個巨大的詞彙表開始,然後從中刪除不要的,直到達到所需的詞彙量大小。而這個詞彙表的建立,就可以用 BPE 和 WordPiece 來建立。有興趣的朋友可以參考這篇論文

這篇論文很多數學,讀到頭都花了。簡單來說就是在訓練的每一步,Unigram 當前這個詞彙在語料庫中的 loss。然後計算如果刪除了當前這個詞彙,整體損失會增加多少,並尋找增加最少的、對語料庫的整體損失影響較小。因此可以得到那些比較不需要的 word ,然後就可以踢除。

採用 Unigram 的 Transformer 的代表就是 T5,這和 GPT 與 BERT 又是不一樣的類別了。為未來我們會再為這主流的三種型態的 Transformer 來做講解。


關於 Subword 的演算法,我們就先介紹到這裡。以上講得非常複雜,如果是自己重頭實作演算法的話一定是哭出來,但是透過 Hugging Face 我們可以很容易的做完。明天就來開始用 Hugging Face 的 Tokenizer 來寫程式吧!


上一篇
# Day10-Tokenizer 入門
下一篇
# Day12-Hugging Face Tokenizer
系列文
變形金剛與抱臉怪---NLP 應用開發之實戰30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言