1. 程式人生 > 實用技巧 >NLP中的標識化

NLP中的標識化

作者|ARAVIND PAI
編譯|VK
來源|Analytics Vidhya

概述

  • 標識化是處理文字資料的一個關鍵

  • 我們將討論標識化的各種細微差別,包括如何處理詞彙表外單詞(OOV)

介紹

從零開始掌握一門新的語言令人望而生畏。如果你曾經學過一種不是你母語的語言,你就會理解!有太多的層次需要考慮,例如語法需要考慮。這是一個相當大的挑戰。

為了讓我們的計算機理解任何文字,我們需要用機器能夠理解的方式把這個詞分解。這就是自然語言處理(NLP)中標識化的概念。

簡單地說,標識化(Tokenization)對於處理文字資料十分重要。

下面是關於標識化的有趣的事情,它不僅僅是分解文字。標識化在處理文字資料中起著重要的作用。因此,在本文中,我們將探討自然語言處理中的標識化,以及如何在Python中實現它。

目錄

  1. 標識化

  2. 標識化背後的真正原因

  3. 我們應該使用哪種(單詞、字元或子單詞)?

  4. 在Python中實現Byte Pair編碼

標識化

標識化(Tokenization)是自然語言處理(NLP)中的一項常見任務。這是傳統NLP方法(如Count Vectorizer)和高階的基於深度學習的體系結構(如Transformers)的基本步驟。

單詞是自然語言的組成部分。

標識化是一種將文字分割成稱為標識的較小單元的方法。在這裡,標識可以是單詞、字元或子單詞。因此,標識化可以大致分為三種類型:單詞、字元和子單詞(n-gram字元)標識化。

例如,想想這句話:“Never give up”。

最常見的詞的形成方式是基於空間。假設空格作為分隔符,句子的標識化會產生3個詞,Never-give-up。由於每個標識都是一個單詞,因此它成為單詞標識化的一個示例。

類似地,標識(token)可以是字元或子單詞。例如,讓我們考慮smarter”:

  1. 字元標識:s-m-a-r-t-e-r

  2. 子單詞(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的步驟

  1. 附加結尾符號

  2. 用語料庫中的唯一字元初始化詞彙

  3. 計算語料庫中pair或字元序列的頻率

  4. 合併語料庫中最頻繁的pair

  5. 把最好的pair保留到詞彙表中

  6. 對一定數量的迭代重複步驟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單詞的表示過程:

  1. 追加後將OOV單詞拆分為字元

  2. 計算一個單詞中的pair或字元序列

  3. 選擇學習過的存在的pair

  4. 合併最常見的pair

  5. 重複步驟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/