NLP中的標識化
作者|ARAVIND PAI
編譯|VK
來源|Analytics Vidhya
概述
-
標識化是處理文字資料的一個關鍵
-
我們將討論標識化的各種細微差別,包括如何處理詞彙表外單詞(OOV)
介紹
從零開始掌握一門新的語言令人望而生畏。如果你曾經學過一種不是你母語的語言,你就會理解!有太多的層次需要考慮,例如語法需要考慮。這是一個相當大的挑戰。
為了讓我們的計算機理解任何文字,我們需要用機器能夠理解的方式把這個詞分解。這就是自然語言處理(NLP)中標識化的概念。
簡單地說,標識化(Tokenization)對於處理文字資料十分重要。
下面是關於標識化的有趣的事情,它不僅僅是分解文字。標識化在處理文字資料中起著重要的作用。因此,在本文中,我們將探討自然語言處理中的標識化,以及如何在Python中實現它。
目錄
-
標識化
-
標識化背後的真正原因
-
我們應該使用哪種(單詞、字元或子單詞)?
-
在Python中實現Byte Pair編碼
標識化
標識化(Tokenization)是自然語言處理(NLP)中的一項常見任務。這是傳統NLP方法(如Count Vectorizer)和高階的基於深度學習的體系結構(如Transformers)的基本步驟。
單詞是自然語言的組成部分。
標識化是一種將文字分割成稱為標識的較小單元的方法。在這裡,標識可以是單詞、字元或子單詞。因此,標識化可以大致分為三種類型:單詞、字元和子單詞(n-gram字元)標識化。
例如,想想這句話:“Never give up”。
最常見的詞的形成方式是基於空間。假設空格作為分隔符,句子的標識化會產生3個詞,Never-give-up。由於每個標識都是一個單詞,因此它成為單詞標識化的一個示例。
類似地,標識(token)可以是字元或子單詞。例如,讓我們考慮smarter”:
-
字元標識:s-m-a-r-t-e-r
-
子單詞(subword)標識:smart-er
但這有必要嗎?我們真的需要標識化來完成這一切嗎?
標識化背後的真正原因
由於詞語是自然語言的構建塊,所以處理原始文字的最常見方式發生在單詞級別。
例如,基於Transformer的模型(NLP中的最新(SOTA)深度學習架構)在單詞級別處理原始文字。類似地,對於NLP最流行的深度學習架構,如RNN、GRU和LSTM,也在單詞級別處理原始文字。
如圖所示,RNN在特定的時間步接收和處理每個單詞。
因此,標識化是文字資料建模的首要步驟。對語料庫執行標識化以獲取單詞。然後使用以下單詞準備詞彙表。詞彙是指語料庫中出現過的單詞。請記住,詞彙表可以通過考慮語料庫中每個唯一的單詞或考慮前K個頻繁出現的單詞來構建。
建立詞彙表是標識化的最終目標。
提高NLP模型效能的一個最簡單的技巧是使用top K的單詞建立一個詞彙表。
現在,讓我們瞭解一下詞彙在傳統的和高階的基於深度學習的NLP方法中的用法。
-
傳統的NLP方法如單詞頻率計數和TF-IDF使用詞彙作為特徵。詞彙表中的每個單詞都被視為一個獨特的特徵:
-
在基於深度學習的高階NLP體系結構中,詞彙表用於建立輸入語句。最後,這些單詞作為輸入傳遞給模型
我們應該使用哪種(單詞、字元或子單詞)?
如前所述,標識化可以在單詞、字元或子單詞級別執行。這是一個常見的問題-在解決NLP任務時應該使用哪種標識化?讓我們在這裡討論這個問題。
單詞級標識化
詞標識化是最常用的標識化演算法。它根據特定的分隔符將一段文字(英文)拆分為單個單詞。根據分隔符的不同,將形成不同的字級標識。預訓練的單詞嵌入,如Word2Vec和GloVe屬於單詞標識化。
這種只有少量缺點。
單詞級標識化的缺點
單詞標識的主要問題之一是處理詞彙表外(OOV)單詞。OOV詞是指在測試中遇到的新詞。這些生詞在詞彙表中不存在。因此,這些方法無法處理OOV單詞。
但是,等等,不要妄下結論!
-
一個小技巧可以將單詞標識化器從OOV單詞中解救出來。訣竅是用前K個頻繁片語成詞彙表,並用未知標識(UNK)替換訓練資料中的稀有詞。這有助於模型使用UNK學習OOV單詞的表示
-
因此,在測試期間,詞彙表中不存在的任何單詞都將對映到UNK標識。這就是我們如何解決標識化器中的OOV問題。
-
這種方法的問題是,當我們將OOV對映到UNK單詞時,單詞的整個資訊都會丟失。單詞的結構可能有助於準確地表示單詞。另一個問題是每個OOV單詞都有相同的表示
單詞標識的另一個問題與詞彙表的大小有關。一般來說,預訓練的模型是在大量的文字語料庫上訓練的。所以,想象一下在這麼大的一個語料庫中用所有單詞構建詞彙表。這會大大增加詞彙量!
這打開了字元級標識化的大門。
字元級標識化
字元標識化將每個文字分割成一組字元。它克服了我們在上面看到的關於單詞標識化的缺點。
-
字元標識化器通過儲存單詞的資訊來連貫地處理OOV單詞。它將OOV單詞分解成字元,並用這些字元表示單詞
-
它也限制了詞彙量的大小。想猜猜詞彙量嗎?答案是26個。
字元標識化的缺點
字元標識解決了OOV問題,但是當我們將一個句子表示為一個字元序列時,輸入和輸出句子的長度會迅速增加。因此,學習單詞之間的關係以形成有意義的詞就變得很有挑戰性。
這將我們帶到另一個稱為子單詞標識化(Subword)的標識化,它介於字和字元標識化之間。
子單詞標識化
子單詞標識化將文字分割成子單詞(或n個字元)。例如,lower這樣的詞可以被分割為low-er,smartest和smart-est,等等。
基於轉換的模型(NLP中的SOTA)依賴於子單詞標識化演算法來準備詞彙表。現在,我將討論一種最流行的子單詞標識化演算法,稱為Byte Pair Encoding 位元組對編碼(BPE)。
使用BPE
Byte Pair 編碼,BPE是基於轉換器的模型中廣泛使用的一種標識化方法。BPE解決了單詞和字元標識化器的問題:
-
BPE有效地解決了OOV問題。它將OOV分割為子單詞,並用這些子單詞表示單詞
-
與字元標識化相比,BPE後輸入和輸出語句的長度更短
BPE是一種標識化演算法,它迭代合併最頻繁出現的字元或字元序列。下面是一個逐步學習BPE的教程。
學習BPE的步驟
-
附加結尾符號
-
用語料庫中的唯一字元初始化詞彙
-
計算語料庫中pair或字元序列的頻率
-
合併語料庫中最頻繁的pair
-
把最好的pair保留到詞彙表中
-
對一定數量的迭代重複步驟3到5
我們將通過一個例子來理解這些步驟。
考慮語料庫
1a)在語料庫中的每個單詞後面附加單詞的結尾符號(比如說):
1b)將語料庫中的單詞分為字元:
2.初始化詞彙表:
迭代1:
3.計算頻率:
4.合併最常見的pair:
5.儲存最佳pair:
從現在開始對每個迭代重複步驟3-5。讓我再演示一次迭代。
迭代2:
3.計算頻率:
4.合併最常見的pair:
5.儲存最佳pair:
經過10次迭代後,BPE合併操作如下所示:
很直截了當,對吧?
BPE在OOV詞中的應用
但是,我們如何在測試時使用BPE來表示OOV單詞呢?有什麼想法嗎?我們現在來回答這個問題。
在測試時,OOV單詞被分割成字元序列。然後應用所學的操作將字符合併成更大的已知符號。
下面是表示OOV單詞的表示過程:
-
追加後將OOV單詞拆分為字元
-
計算一個單詞中的pair或字元序列
-
選擇學習過的存在的pair
-
合併最常見的pair
-
重複步驟2和3,直到可以合併
接下來讓我們來看看這一切!
在Python中實現Byte Pair編碼
我們現在知道BPE是如何學習和應用OOV詞彙的。所以,是時候用Python實現了。
BPE的Python程式碼已經在原來的論文釋出的程式碼中可用。
讀取語料庫
我們將考慮一個簡單的語料庫來說明BPE的思想。然而,同樣的想法也適用於另一個語料庫:
#匯入庫
import pandas as pd
#正在讀取.txt檔案
text = pd.read_csv("sample.txt",header=None)
#將資料幀轉換為單個列表
corpus=[]
for row in text.values:
tokens = row[0].split(" ")
for token in tokens:
corpus.append(token)
文字預處理
將單詞分割為語料庫中的字元,並在每個單詞的末尾附加:
#初始化詞彙
vocab = list(set(" ".join(corpus)))
vocab.remove(' ')
#把這個詞分成字元
corpus = [" ".join(token) for token in corpus]
#追加</w>
corpus=[token+' </w>' for token in corpus]
學習BPE
計算語料庫中每個單詞的頻率:
import collections
#返回每個單詞的頻率
corpus = collections.Counter(corpus)
#將計數器物件轉換為字典
corpus = dict(corpus)
print("Corpus:",corpus)
輸出:
讓我們定義一個函式來計算pair或字元序列的頻率。它接受語料庫並返回頻率:
#pair或字元序列的頻率
#引數是語料並且返回每個pair的頻率
def get_stats(corpus):
pairs = collections.defaultdict(int)
for word, freq in corpus.items():
symbols = word.split()
for i in range(len(symbols)-1):
pairs[symbols[i],symbols[i+1]] += freq
return pairs
現在,下一個任務是合併語料庫中最頻繁的pair。我們將定義一個函式來接受語料庫、最佳pair,並返回修改後的語料庫:
#合併語料庫中最常見的pair
#接受語料庫和最佳pair
import re
def merge_vocab(pair, corpus_in):
corpus_out = {}
bigram = re.escape(' '.join(pair))
p = re.compile(r'(?<!\S)' + bigram + r'(?!\S)')
for word in corpus_in:
w_out = p.sub(''.join(pair), word)
corpus_out[w_out] = corpus_in[word]
return corpus_out
接下來,是學習BPE操作的時候了。由於BPE是一個迭代過程,我們將執行並理解一次迭代的步驟。讓我們計算bi-gram的頻率:
#bi-gram的頻率
pairs = get_stats(corpus)
print(pairs)
輸出:
找到最常見的:
#計算最佳pair
best = max(pairs, key=pairs.get)
print("Most Frequent pair:",best)
輸出:(‘e’, ‘s’)
最後,合併最佳pair並儲存到詞彙表中:
#語料庫中頻繁pair的合併
corpus = merge_vocab(best, corpus)
print("After Merging:", corpus)
#將元組轉換為字串
best = "".join(list(best))
#合併到merges和vocab
merges = []
merges.append(best)
vocab.append(best)
輸出:
我們將遵循類似的步驟:
num_merges = 10
for i in range(num_merges):
#計算bi-gram的頻率
pairs = get_stats(corpus)
#計算最佳pair
best = max(pairs, key=pairs.get)
#合併語料庫中的頻繁pair
corpus = merge_vocab(best, corpus)
#合併到merges和vocab
merges.append(best)
vocab.append(best)
#將元組轉換為字串
merges_in_string = ["".join(list(i)) for i in merges]
print("BPE Merge Operations:",merges_in_string)
輸出:
最有趣的部分還在後面呢!將BPE應用於OOV詞彙。
BPE在OOV詞彙中的應用
現在,我們將看到如何應用BPE在OOV單詞上。例如OOV單詞是“lowest”:
#BPE在OOV詞彙中的應用
oov ='lowest'
#將OOV分割為字元
oov = " ".join(list(oov))
#新增 </w>
oov = oov + ' </w>'
#建立字典
oov = { oov : 1}
將BPE應用於OOV單詞也是一個迭代過程。我們將執行本文前面討論的步驟:
i=0
while(True):
#計算頻率
pairs = get_stats(oov)
#提取keys
pairs = pairs.keys()
#找出之前學習中可用的pair
ind=[merges.index(i) for i in pairs if i in merges]
if(len(ind)==0):
print("\nBPE Completed...")
break
#選擇最常學習的操作
best = merges[min(ind)]
#合併最佳pair
oov = merge_vocab(best, oov)
print("Iteration ",i+1, list(oov.keys())[0])
i=i+1
輸出:
如你所見,OOV單詞“low est”被分割為low-est。
結尾
標識化是處理文字資料的一種強大方法。我們在本文中看到了這一點,並使用Python實現了標識化。
繼續在任何基於文字的資料集上嘗試這個方法。練習得越多,就越能理解標識化是如何工作的(以及為什麼它是一個如此關鍵的NLP概念)。
原文連結:https://www.analyticsvidhya.com/blog/2020/05/what-is-tokenization-nlp/
歡迎關注磐創AI部落格站:
http://panchuang.net/
sklearn機器學習中文官方文件:
http://sklearn123.com/
歡迎關注磐創部落格資源彙總站:
http://docs.panchuang.net/