今天想簡(jiǎn)單聊聊在自然語(yǔ)言處理領(lǐng)域用得比較多锁摔,像BERT里烦,GPT等自然語(yǔ)言模型都會(huì)用到的技術(shù)损离,BPE哥艇,全稱(chēng)是Byte Pair Encoding。
這個(gè)技術(shù)呢僻澎,在面試實(shí)習(xí)生過(guò)程中貌踏,發(fā)現(xiàn)其實(shí)很多學(xué)生不太能解釋清楚十饥,所以我打算自己也沉淀一下。
為啥要BPE編碼祖乳?
現(xiàn)在的語(yǔ)言模型BERT逗堵,GPT,LLaMa等等眷昆,在預(yù)訓(xùn)練的時(shí)候都得tokenization蜒秤。最簡(jiǎn)單的一種tokenization,就是把每個(gè)單詞看成一個(gè)token亚斋,然后對(duì)訓(xùn)練語(yǔ)料進(jìn)行編碼作媚,也就是用一個(gè)整數(shù)id代表這個(gè)token。
但是呢帅刊,就英語(yǔ)來(lái)說(shuō)纸泡,通常都有幾萬(wàn),甚至幾十萬(wàn)的單詞厚掷。如果用上述的編碼方式的話弟灼,自然語(yǔ)言模型在算softmax的時(shí)候,就得在一個(gè)幾十萬(wàn)個(gè)單詞列表上計(jì)算一個(gè)概率分布冒黑,那顯然是相當(dāng)費(fèi)時(shí)。費(fèi)時(shí)也就算了勤哗,有些token可能就不怎么常見(jiàn)抡爹,硬是這樣算分布,模型也不會(huì)準(zhǔn)芒划。純純的事倍功半冬竟。
又譬如,現(xiàn)在的GPT民逼,不僅僅能看懂英文泵殴,中文,日文這些都能懂拼苍。那隨著集成的不同國(guó)家的語(yǔ)言越來(lái)越多笑诅,詞匯表肯定會(huì)大到驚人。所以肯定得想一個(gè)能高效的減少token數(shù)量的方法疮鲫。
沒(méi)錯(cuò)吆你,這個(gè)方法就是BPE。
BPE是怎么編碼的俊犯?
一句話總結(jié)BPE妇多。
它無(wú)非就是在反復(fù)迭代直到你想停為止,而每次迭代都在選取頻數(shù)最高
的相鄰subword單元對(duì)
合并成新的subword單元
燕侠。
實(shí)例是最好的老師者祖。來(lái)立莉,我們一起搞個(gè)例子。
假設(shè)有一份語(yǔ)料七问,經(jīng)過(guò)統(tǒng)計(jì)之后呢蜓耻,我們可以把這份語(yǔ)料表示成:
{
'estern': 6,
'widest': 7,
'longest': 4
}
step 1. 我們把單詞拆分成字母,也就是說(shuō)烂瘫,一開(kāi)始媒熊,每個(gè)字母就是一個(gè)subword單元,像這樣:
這里有個(gè)小細(xì)節(jié)是坟比,在將單詞拆分成字母的時(shí)候芦鳍,我在每個(gè)單詞的最后都加上了</w>。這是為啥呢葛账?主要是為了用它來(lái)表示中止柠衅,這樣在解碼的時(shí)候,看到這個(gè)符號(hào)籍琳,后續(xù)就可以用空格做個(gè)replace菲宴。
step 2. 我們現(xiàn)在盯著上述這個(gè)語(yǔ)料,看下哪個(gè)相鄰的subword單元對(duì)
出現(xiàn)的頻數(shù)最高趋急?
顯然一眼看過(guò)去喝峦,e
和s
這個(gè)相鄰的subword單元對(duì)頻繁出現(xiàn),總共出現(xiàn)了6 + 7 + 4 = 17次呜达。所以我們用es
這個(gè)新的subword單元替換語(yǔ)料中出現(xiàn)過(guò)的e
和s
相鄰單元對(duì)谣蠢。像這樣:
這里注意詞表的變化哦。
s
這個(gè)subword單元沒(méi)了查近,增加了一個(gè)es
的subword單元眉踱。(詞表加一減一,數(shù)量不變)
step 3. 好霜威,接下來(lái)谈喳。我們?cè)僦貜?fù)一次上述操作「昶茫基于最新的語(yǔ)料婿禽,哪個(gè)相鄰的subword單元對(duì)
出現(xiàn)頻數(shù)最高?
又顯然一下矮冬,es
和t
這個(gè)相鄰的subword單元對(duì)頻繁出現(xiàn)谈宛,總共出現(xiàn)了6 + 7 + 4 = 17次。所以我們用est
這個(gè)新的subword單元替換語(yǔ)料中出現(xiàn)過(guò)的es
和s
相鄰單元對(duì)胎署。像這樣:
又要注意詞表的變化哦吆录。 es
和t
這兩個(gè)subword單元沒(méi)了,增加了一個(gè)est
的subword單元琼牧。(詞表加一減二恢筝,數(shù)量減一)
step 4. 來(lái)來(lái)來(lái)哀卫,別氣餒。我們繼續(xù)撬槽,又基于最新的語(yǔ)料此改,哪個(gè)相鄰的subword單元對(duì)
出現(xiàn)頻數(shù)最高?
est
和</w>
總共出現(xiàn)了 7 + 4 = 11次侄柔。所以我們用est</w>
這個(gè)新的subword單元替換語(yǔ)料中出現(xiàn)過(guò)的est
和</w>
相鄰單元對(duì)共啃。像這樣:
再來(lái)關(guān)注一下詞表的變化。詞表增加了est</w>
的subword單元暂题,并且詞表里面移剪,est
和est</w>
同時(shí)存在,直觀感受就是est</w>
就只會(huì)出現(xiàn)在后綴上薪者,而est
可以出現(xiàn)在開(kāi)頭纵苛,也可以出現(xiàn)詞中。(詞表加一言津,數(shù)量加一)
step 5. 繼續(xù)像上面不停的迭代攻人,直到達(dá)到預(yù)設(shè)的subword詞表大小(或者下一個(gè)最高頻出現(xiàn)的單元對(duì)的頻數(shù)是1)
我們回顧一下悬槽,我們剛才在干啥怀吻?
我們每一步都在問(wèn)自己,當(dāng)前的語(yǔ)料中哪個(gè)相鄰的subword單元對(duì)
出現(xiàn)得最頻繁初婆?找到這樣的單元對(duì)后烙博,我們將這個(gè)單元對(duì)合并作為一個(gè)新的subword單元,并且替換語(yǔ)料中相應(yīng)的相鄰單元對(duì)烟逊。
那是不是一句話就能概括?反復(fù)迭代直到你想停為止铺根,而每次迭代都在選取出現(xiàn)頻數(shù)最高
的相鄰subword單元對(duì)
合并成新的subword單元
另外宪躯,細(xì)心觀察上述整個(gè)過(guò)程,會(huì)發(fā)現(xiàn)詞表的大小在每次迭代的時(shí)候可能不變位迂,可能增加访雪,也可能減少。
實(shí)際上掂林,隨著合并的次數(shù)增加臣缀,詞表大小通常是先增加后減少。
為啥呢泻帮?可能是人類(lèi)語(yǔ)言發(fā)展的特點(diǎn)吧精置,有些字母之間本身就是會(huì)固定搭配。中文也是一樣锣杂,像魑魅魍魎
脂倦,饕餮
這些詞番宁,語(yǔ)料中基本不會(huì)單獨(dú)出現(xiàn)饕
,也不會(huì)單獨(dú)出現(xiàn)餮
赖阻。正因?yàn)橛羞@種語(yǔ)言現(xiàn)象蝶押,BPE才能起到縮減詞表的作用。
用BPE編碼得到了詞表后火欧,怎么用呢棋电?
使用BPE編碼得到的詞表,無(wú)非就是弄懂怎么編碼苇侵,怎么解碼赶盔。
編碼過(guò)程,有種最長(zhǎng)字符串匹配的意味衅檀。具體來(lái)說(shuō):
編碼
step 1. 將詞表中的subword單元招刨,按照長(zhǎng)度從長(zhǎng)到短進(jìn)行排序;
step 2. 對(duì)于一個(gè)待編碼的單詞哀军,遍歷step 1中排好序的詞表的每個(gè)subword單元沉眶,
看看這個(gè)subword單元是不是待編碼單詞的子字符串。
- 如果不是杉适,那continue谎倔。
- 如果是,這個(gè)subword單元是最終編碼的一部分猿推;
- 然后待編碼單詞去掉subword部分片习,對(duì)剩余的單詞字符串繼續(xù)再重新遍歷一次詞表。
step 3. 如果遍歷完整個(gè)詞表蹬叭,還有子字符串沒(méi)有匹配藕咏,那就把剩余字符串替換成<unk>。
step 4. 最終待編碼的單詞秽五,就表示成上述過(guò)程中找到的subword的組合孽查。
好的,別說(shuō)你們坦喘。我描述完上述過(guò)程盲再,我都覺(jué)得很繞。
還是那句瓣铣,實(shí)例是最好的老師答朋。我們來(lái)搞個(gè)例子:
# 待編碼單詞:
'highest</w>'
# 按長(zhǎng)度排好序的subword詞表
['est</w>', 'hi', 'g', 'h']
所以最終
highest
這個(gè)單詞就表示成[est</w>
, hi
, g
, h
]
編碼的復(fù)雜度還是挺高的,實(shí)際實(shí)現(xiàn)中會(huì)增加cache棠笑。
解碼過(guò)程梦碗,就相對(duì)來(lái)說(shuō)很好理解。具體來(lái)說(shuō):
# 解碼
如果相鄰subword中間沒(méi)有</w>中止符,就將兩個(gè)subword直接拼接叉弦。
如果有</w>丐一,就用空格seperate。
# 編碼序列
['wedd', 'ing</w>', 'party']
# 最終解碼序列
"wedding party"
中文怎么處理呢淹冰?
好了库车。上面說(shuō)了一通,都在說(shuō)英文怎么用BPE樱拴。那中文呢柠衍?畢竟現(xiàn)在的大語(yǔ)言模型基本還是外國(guó)友人搞得牛逼一些。我們?nèi)绻胗弥形恼Z(yǔ)料搞個(gè)中文的大語(yǔ)言模型的話晶乔,怎么用BPE呢珍坊?
其實(shí)BPE是個(gè)通用方法,本質(zhì)上就是定義好初始的subword單元正罢,然后按照頻數(shù)阵漏,不停合并成常見(jiàn)的subword單元。
對(duì)于中文來(lái)說(shuō)翻具,我們完全可以把每個(gè)漢字看成初始的subword單元履怯,直接套用BPE就行。
而我這里想說(shuō)的是裆泳,當(dāng)前大多數(shù)GPT模型叹洲,都不是以漢字作為subword初始單元來(lái)進(jìn)行BPE。他們定義的初始單元是byte工禾,這樣做的好處是可以避免OOV运提,也能兼顧各種語(yǔ)言符號(hào),這也就是大家聽(tīng)到的Byte-Level BPE
闻葵。
好了民泵。我也是簡(jiǎn)單聊聊BPE,可能有些細(xì)節(jié)也是沒(méi)聊到的槽畔。Anyway洪灯,遇到細(xì)節(jié)問(wèn)題的時(shí)候再研究吧。
那BPE是啥竟痰?
一句話概括:反復(fù)迭代直到你想停為止,而每次迭代都在選取頻數(shù)最高
的相鄰subword單元對(duì)
合并成新的subword單元