理解BERT:一個突破性NLP框架的綜合指南

磐創AI發表於2019-11-05

概述

  • Google的BERT改變了自然語言處理(NLP)的格局

  • 瞭解BERT是什麼,它如何工作以及產生的影響等

  • 我們還將在Python中實現BERT,為你提供動手學習的經驗

BERT簡介

想象一下——你正在從事一個非常酷的資料科學專案,並且應用了最新的最先進的庫來獲得一個好的結果!幾天後,一個新的最先進的框架出現了,它有可能進一步改進你的模型。

這不是一個假想的場景——這是在自然語言處理(NLP)領域工作的真正現實!過去的兩年的突破是令人興奮的。

谷歌的BERT就是這樣一個NLP框架。我敢說它可能是近代最有影響力的一個(我們很快就會知道為什麼)。

毫不誇張地說,BERT極大地改變了NLP的格局。想象一下,使用一個在大型未標記資料集上訓練的單一模型,然後在11個單獨的NLP任務上獲得SOTA結果。所有這些任務都需要fine-tuning。BERT是我們設計NLP模型的一個結構性轉變。

BERT啟發了許多最近的NLP架構、訓練方法和語言模型,如谷歌的TransformerXL、OpenAI的GPT-2、XLNet、ERNIE2.0、RoBERTa等。

我的目標是給你一個全面的指導,不僅BERT,還有它帶來的影響以及如何影響未來的NLP研究。

目錄

  1. 什麼是BERT?

  2. 從Word2Vec到BERT:NLP的學習語言表示的探索

  3. BERT如何工作?

  4. 使用BERT進行文字分類(Python程式碼)

  5. 超越BERT:NLP的最新技術

什麼是BERT?

你可能大概聽說過BERT,你看到過它是多麼不可思議,它是如何潛在地改變了NLP的前景。但是BERT到底是什麼呢?

BERT背後的研究團隊是這樣描述NLP框架的:

"BERT代表Transformers的雙向編碼器。它被設計為透過對左右的上下文的聯合來預訓練未標記文字得到深層的雙向表示。因此,只需一個額外的輸出層,就可以對預訓練的BERT模型進行微調,從而為各種NLP任務建立SOTA結果。"

作為一開始,這聽起來太複雜了。但是它確實總結了BERT的出色表現,因此讓我們對其進行細分。

首先,很容易理解BERT是Transformers的雙向編碼器表示。這裡的每個詞都有其含義,我們將在本文中逐一討論。這一行的關鍵是,BERT是基於Transformer架構的

其次,BERT是在大量未標記文字的預訓練,包括整個Wikipedia(有25億個詞!)和圖書語料庫(有8億個單詞)。

這個預訓練步驟是BERT成功背後的一半。這是因為當我們在大型文字語料庫上訓練模型時,我們的模型開始獲得對語言工作原理的更深入和深入的瞭解。這種知識幾乎可用於所有NLP任務

第三,BERT是"深度雙向"模型。雙向意味著BERT在訓練階段從目標詞的左右兩側上下文來學習資訊。

模型的雙向性對於真正理解語言的意義很重要。讓我們看一個例子來說明這一點。在此示例中,有兩個句子,並且兩個句子都包含單詞"bank":

理解BERT:一個突破性NLP框架的綜合指南

如果我們僅透過選擇左側或右側上下文來預測"bank"一詞的意義,那麼在兩個給定示例中至少有一個會出錯。

解決此問題的一種方法是在進行預測之前考慮左右上下文。這正是BERT所做的!我們將在本文的後面看到如何實現這一目標。

最後,BERT最令人印象深刻的方面。我們可以透過僅新增幾個其他輸出層來微調它,以建立用於各種NLP任務的最新模型。

從Word2Vec到BERT:NLP的學習語言表示的探索

"自然語言處理中的最大挑戰之一是訓練資料的短缺。由於NLP是一個具有許多不同任務的多元化領域,因此大多數特定於任務的資料集僅包含數千或數十萬個人標記的訓練示例。" – Google AI

Word2Vec和GloVe

透過在大型未標記文字資料上進行預訓練模型來學習語言表示的要求始於諸如Word2Vec和GloVe之類的Word Embedding。這些Embedding改變了我們執行NLP任務的方式。現在,我們有了Embedding,可以捕獲單詞之間的上下文關係。

理解BERT:一個突破性NLP框架的綜合指南

這些Embedding被用來訓練下游NLP任務的模型,並做出更好的預測。這可以透過利用來自Embedding本身的附加資訊,甚至使用較少的特定於任務的資料來完成。

這些Embedding的一個限制是使用非常淺的語言模型。這意味著他們能夠獲取的資訊的數量是有限的,這促使他們使用更深入、更復雜的語言模型(LSTMs和GRUs層)。

另一個關鍵的限制是,這些模型沒有考慮單詞的上下文。讓我們以上面的“bank”為例。同一個單詞在不同的上下文中有不同的意思。然而,像Word2Vec這樣的Embedding將在這兩個上下文中為“bank”提供相同的Word Embedding。

這是導致模型不準確的一個因素。

ELMO和ULMFiT

理解BERT:一個突破性NLP框架的綜合指南

ELMo是NLP社群對一詞多義問題的回應——相同的詞在不同的語境中有不同的含義。從訓練淺層前饋網路(Word2vec),逐步過渡到使用複雜的雙向LSTM體系結構的層來訓練Word Embedding。這意味著同一個單詞可以根據它所在的上下文有多個ELMO Embedding。

那是我們開始看到預訓練作為NLP的訓練機制的優勢。

理解BERT:一個突破性NLP框架的綜合指南

ULMFiT則更進一步。這個框架可以訓練語言模型,這些模型可以進行微調,從而在各種文件分類任務中,即使使用更少的資料(少於100個示例)也可以提供出色的結果。可以肯定地說,ULMFiT破解了NLP中遷移學習的密碼。

這就是我們在NLP中建立遷移學習黃金法則的時候:

NLP中的遷移學習 =預訓練和微調

ULMFIT之後的大多數NLP的突破調整了上述等式的組成部分,並獲得了最先進的基準

OpenAI的GPT

OpenAI的GPT擴充套件了ULMFiT和ELMo引入的預訓練和微調方法。GPT本質上用基於轉換的體系結構取代了基於lstm的語言建模體系結構。

GPT模型可以微調到文件分類之外的多個NLP任務,如常識推理、語義相似性和閱讀理解。

GPT還強調了Transformer框架的重要性,它具有更簡單的體系結構,並且比基於lstm的模型訓練得更快。它還能夠透過使用注意力機制來學習資料中的複雜模式。

OpenAI的GPT透過實現多個最先進的技術,驗證了Transformer架構的健壯性和有用性。

這就是Transformer如何啟發了BERT以及接下來在NLP領域的所有突破。

現在,還有一些其他的重要突破和研究成果我們還沒有提到,比如半監督序列學習。這是因為它們稍微超出了本文的範圍,但是你可以閱讀相關的論文來了解更多資訊。

BERT

因此,解決NLP任務的新方法變成了一個2步過程:

  1. 在大型無標籤文字語料庫(無監督或半監督)上訓練語言模型

  2. 將這個大型模型微調到特定的NLP任務,以利用這個大型知識庫訓練模型(監督)

在這樣的背景下,讓我們來理解BERT是如何從這裡開始建立一個模型,這個模型將在很長一段時間內成為NLP中優秀的基準

BERT如何工作?

讓我們仔細看一下BERT,瞭解為什麼它是一種有效的語言建模方法。我們已經知道BERT可以做什麼,但是它是如何做到的?我們將在本節中回答這個相關問題。

1. BERT的體系結構

BERT架構建立在Transformer之上。我們目前有兩個可用的版本:

  • BERT Base:12層transformer,12個attention heads和1.1億個引數

  • BERT Large:24層transformer,16個attention heads和3.4億個引數

理解BERT:一個突破性NLP框架的綜合指南

出於比較的目的,BERT基礎架構具有與OpenAI的GPT相同的模型大小。所有這些Transformer層都是隻使用Transformer的編碼器

現在我們已經瞭解了BERT的總體架構,接下來讓我們看看在進入模型構建階段之前需要哪些文字處理步驟。

2.文字預處理

理解BERT:一個突破性NLP框架的綜合指南

BERT背後的開發人員已經新增了一組特定規則來表示模型的輸入文字。其中許多是創造性的設計選擇,目的是使模型更好。

對於初學者,每個輸入的Embedding是3個嵌入的組合:

  1. 位置嵌入(Position Embeddings):BERT學習並使用位置嵌入來表達句子中單詞的位置。這些是為了克服Transformer的限制而新增的,Transformer與RNN不同,它不能捕獲“序列”或“順序”資訊

  2. 段嵌入(Segment Embeddings):BERT還可以將句子對作為任務的輸入(可用於問答)。這就是為什麼它學習第一和第二句話的獨特嵌入,以幫助模型區分它們。在上面的例子中,所有標記為EA的標記都屬於句子A(對於EB也是一樣)

  3. 目標詞嵌入(Token Embeddings):這些是從WordPiece詞彙表中對特定詞彙學習到的嵌入

對於給定的目標詞,其輸入表示是透過對相應的目標詞、段和位置的Embedding進行求和來構造的。

這樣一個綜合的Embedding方案包含了很多對模型有用的資訊。

這些預處理步驟的組合使BERT如此多才多藝。這意味著,不需要對模型的體系結構進行任何重大更改,我們就可以輕鬆地對它進行多種NLP任務的訓練。

3.預訓練任務

BERT已接受兩項NLP任務的預訓練:

  • 遮蔽語言建模

  • 下一句預測

讓我們更詳細地瞭解這兩個任務!

1. 遮蔽語言建模(雙向性)

雙向性

BERT被設計成一個深度雙向模型。網路有效地從第一層本身一直到最後一層捕獲來自目標詞的左右上下文的資訊。

傳統上,我們要麼訓練語言模型預測句子中的下一個單詞(GPT中使用的從右到左的上下文),要麼訓練語言模型預測從左到右的上下文。這使得我們的模型容易由於資訊丟失而產生錯誤。

理解BERT:一個突破性NLP框架的綜合指南

ELMo試圖透過在左到右和從右到左的上下文中訓練兩個LSTM語言模型並對其進行淺級連線來解決此問題。即使它在現有技術上有了很大的改進,但這還不夠。

"憑直覺,我們有理由相信,深層雙向模型比左向右模型或從左至右和從右至左模型的淺級連線嚴格更強大。" – BERT

這就是BERT在GPT和ELMo上都大大改進的地方。看下圖:

理解BERT:一個突破性NLP框架的綜合指南

箭頭指示從一層到下一層的資訊流。頂部的綠色框表示每個輸入單詞的最終上下文表示。

從上圖可以明顯看出:BERT是雙向的,GPT是單向的(資訊僅從左向右流動),而ELMO是淺雙向的。

關於遮蔽語言模型

假設我們有一句話——“我喜歡閱讀關於分析資料科學的部落格”。我們想要訓練一個雙向的語言模型。與其試圖預測序列中的下一個單詞,不如構建一個模型,從序列本身預測缺失的單詞。

讓我們把“分析”替換成“[MASK]”。這是表示被遮蔽的單詞。然後,我們將以這樣一種方式訓練該模型,使它能夠預測“分析”這個詞語,所以句子變為:“我喜歡閱讀關於[MASK]資料科學的部落格”

這是掩蔽語言模型的關鍵所在。BERT的作者還提出了一些注意事項,以進一步改進這項技術:

  • 為了防止模型過於關注一個特定的位置或被掩蓋的標記,研究人員隨機掩蓋了15%的單詞

  • 掩碼字並不總是被掩碼令牌[掩碼]替換,因為[掩碼]令牌在調優期間不會出現

  • 因此,研究人員採用了以下方法:

  • 80%的情況下,單詞被替換成帶面具的令牌[面具]

  • 10%的情況下,這些單詞被隨機替換

  • 有10%的時間單詞是保持不變的

2. 下一句預測

掩蔽語言模型(MLMs)學習理解單詞之間的關係。此外,BERT還接受了下一個句子預測任務的訓練,這些任務需要理解句子之間的關係

此類任務的一個很好的例子是問題回答系統。

任務很簡單。給定兩個句子——A和B, B是語料庫中A後面的下一個句子,還是一個隨機的句子?

由於它是一個二分類任務,因此可以透過將任何語料庫分成句子對來輕鬆生成資料。就像mlm一樣,作者在這裡也新增了一些注意事項。讓我們舉個例子:

假設我們有一個包含100,000個句子的文字資料集。因此,將有50,000個訓練例子或句子對作為訓練資料。

  • 對於50%的對來說,第二個句子實際上是第一個句子的下一個句子

  • 對於剩下的50%,第二句是語料庫中的一個隨機句子

  • 第一種情況的標籤是“IsNext”,而第二種情況的標籤是“NotNext

這就是為什麼BERT能夠成為一個真正的任務不可知的模型。它結合了掩蔽語言模型(MLM)和下一個句子預測(NSP)的預訓練任務。

在Python中實現BERT以進行文字分類

你的頭腦一定被BERT所開闢的各種可能性攪得團團轉。我們有許多方法可以利用BERT的大量知識來開發我們的NLP應用程式。

最有效的方法之一是根據你自己的任務和特定於任務的資料對其進行微調。然後我們可以使用BERT中的Embedding作為文字文件的Embedding。

在本節中,我們將學習如何在NLP任務中使用BERT的Embedding。我們將在以後的文章中討論對整個BERT模型進行微調的概念。

為了從BERT中提取Embedding,我們將使用一個非常有用的開源專案:https://github.com/hanxiao/bert-as-service

理解BERT:一個突破性NLP框架的綜合指南

這個開源專案如此有用的原因是它允許我們只需兩行程式碼使用BERT獲取每個句子的Embedding。

安裝BERT-As-Service

服務以一種簡單的方式工作。它建立了一個BERT伺服器。每次我們將一個句子列表傳送給它時,它將傳送所有句子的Embedding。

我們可以透過pip安裝伺服器和客戶機。它們可以單獨安裝,甚至可以安裝在不同的機器上:

pip install bert-serving-server  # server
pip install bert-serving-client  # client, independent of `bert-serving-server`

注意,伺服器必須在Python >= 3.5上執行,而TensorFlow >= 1.10

此外,由於執行BERT是一個GPU密集型任務,我建議在基於雲的GPU或其他具有高計算能力的機器上安裝BERT伺服器

現在,回到你的終端並下載下面列出的模型。然後,將zip檔案解壓縮到某個資料夾中,比如/tmp/english_L-12_H-768_A-12/。

以下是釋出的預訓練BERT模型列表:

BERT-Base, Uncased    12-layer, 768-hidden, 12-heads, 110M parameters
https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip

BERT-Large, Uncased    24-layer, 1024-hidden, 16-heads, 340M parameters
https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-24_H-1024_A-16.zip

BERT-Base, Cased    12-layer, 768-hidden, 12-heads, 110M parameters
https://storage.googleapis.com/bert_models/2018_10_18/cased_L-12_H-768_A-12.zip

BERT-Large, Cased    24-layer, 1024-hidden, 16-heads, 340M parameters
https://storage.googleapis.com/bert_models/2018_10_18/cased_L-24_H-1024_A-16.zip

BERT-Base, Multilingual Cased (New)    104 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
https://storage.googleapis.com/bert_models/2018_11_23/multi_cased_L-12_H-768_A-12.zip

BERT-Base, Multilingual Cased (Old)    102 languages, 12-layer, 768-hidden, 12-heads, 110M parameters
https://storage.googleapis.com/bert_models/2018_11_03/multilingual_L-12_H-768_A-12.zip

BERT-Base, Chinese    Chinese Simplified and Traditional, 12-layer, 768-hidden, 12-heads, 110M parameters
https://storage.googleapis.com/bert_models/2018_11_03/chinese_L-12_H-768_A-12.zip

我們將下載BERT Uncased,然後解壓縮zip檔案:

wget https://storage.googleapis.com/bert_models/2018_10_18/uncased_L-12_H-768_A-12.zip && unzip uncased_L-12_H-768_A-12.zip

將所有檔案提取到一個資料夾中之後,就可以啟動BERT服務了:

bert-serving-start -model_dir uncased_L-12_H-768_A-12/ -num_worker=2 -max_seq_len 50

現在,你可以從Python程式碼(使用客戶端庫)簡單地呼叫BERT-As-Service。讓我們直接進入程式碼!

開啟一個新的Jupyter notebook,試著獲取以下句子的Embedding:“I love data science and analytics vidhya”。

from bert_serving.client import BertClient

# 使用BERT伺服器的ip地址與它建立連線;如果是同一臺電腦,不用填寫IP
bc = BertClient(ip="SERVER_IP_HERE")
# 獲取embedding
embedding = bc.encode(["I love data science and analytics vidhya."])
# 檢查embedding的形狀,應該是 1x768
print(embedding.shape)

這裡,IP地址是你的伺服器或雲的IP。如果在同一臺計算機上使用,則不需要此欄位。

返回的embedding形狀為(1,768),因為BERT的架構中一個句子由768個隱藏單元表示。

問題:在Twitter上對不良言論進行分類

讓我們拿一個真實世界的資料集來看看BERT有多有效。我們將使用一個資料集,該資料集由一系列推文組成,這些推文被歸類為“不良言論”或非“不良言論”。

為了簡單起見,如果一條推文帶有種族主義或性別歧視的情緒,我們就說它包含不良言論。因此,我們的任務是將種族主義或性別歧視的推文與其他推文進行分類

資料集連結
https://datahack.analyticsvidhya.com/contest/practice-problem-twitter-sentiment-analysis/?utm_source=blog&utm_medium=demystifying-bert-groundbreaking-nlp-framework。

我們將使用BERT從資料集中的每個推特中提取Embedding,然後使用這些Embedding來訓練文字分類模型。

以下是該專案的整體結構:

理解BERT:一個突破性NLP框架的綜合指南

現在讓我們看一下程式碼:

import pandas as pd
import numpy as np

# 載入資料
train = pd.read_csv('BERT_proj/train_E6oV3lV.csv', encoding='iso-8859-1')
train.shape

你會熟悉大多數人是如何發推特的。有許多隨機的符號和數字(又名聊天語言!)我們的資料集也一樣。我們需要在透過BERT之前對它進行預處理:

import re

# 清理噪聲
def clean_text(text):
    # 只剩字元
    text = re.sub(r'[^a-zA-Z\']', ' ', text)

    # 去除unicode字元
    text = re.sub(r'[^\x00-\x7F]+', '', text)

    # 轉換成小寫
    text = text.lower()

    return text

train['clean_text'] = train.tweet.apply(clean_text)

現在資料集是乾淨的,它被分割成訓練集和驗證集:

from sklearn.model_selection import train_test_split

# 分割成訓練集和驗證集
X_tr, X_val, y_tr, y_val = train_test_split(train.clean_text, train.label, test_size=0.25, random_state=42)

print('X_tr shape:',X_tr.shape)

讓我們在訓練和驗證集中獲得所有推特的Embedding:

from bert_serving.client import BertClient

# 連線BERT伺服器
bc = BertClient(ip="YOUR_SERVER_IP")
# 編碼訓練集和驗證集
X_tr_bert = bc.encode(X_tr.tolist())
X_val_bert = bc.encode(X_val.tolist())

現在是建模時間!我們來訓練分類模型:

from sklearn.linear_model import LogisticRegression

# 使用LR模型
model_bert = LogisticRegression()
# 訓練
model_bert = model_bert.fit(X_tr_bert, y_tr)
# 預測
pred_bert = model_bert.predict(X_val_bert)

檢查分類精度:

from sklearn.metrics import accuracy_score

print(accuracy_score(y_val, pred_bert))

即使使用如此小的資料集,我們也可以輕鬆獲得大約95%的分類精度,這真的非常棒。

我鼓勵你繼續嘗試BERT對不同問題進行嘗試

超越BERT:目前最先進的NLP

BERT激發了人們對NLP領域的極大興趣,特別是NLP任務中Transformer的應用。這導致了研究實驗室和組織的數量激增,他們開始研究預訓練、BERT和fine-tuning的不同方面。

許多這樣的專案在多個NLP任務上都比BERT做得好。其中最有趣的是RoBERTa,這是Facebook人工智慧對BERT和DistilBERT的改進,而DistilBERT是BERT的精簡版和快速版。

相關文章