初次使用BERT的可視化指南

初次使用BERT的可視化指南

在這裏插入圖片描述
在過去幾年裏,處理語言的機器學習模型的進展一直在迅速加快。這一進步已經離開了研究實驗室,開始爲一些領先的數字產品提供動力。這方面的一個很好的例子是最近公佈的BERT模型如何成爲谷歌搜索背後的主要力量。谷歌認爲,這一步(即自然語言理解在搜索領域的應用進展)代表了過去五年最大的飛躍,也是搜索歷史上最大的飛躍之一。

這篇文章是關於如何使用BERT的變體對句子進行分類的簡單教程。作爲第一個介紹,這是一個足夠基本的示例,但也足夠高級,可以展示所涉及的一些關鍵概念。

數據集:SST2
在本例中,我們將使用的數據集是SST2,其中包含電影評論中的句子,每個句子都標記爲正樣本(值爲1)或負樣本(值爲0):
在這裏插入圖片描述
模型:情感分類
我們的目標是創建一個模型,該模型接受一個句子(就像我們的數據集中的那些句子一樣),並生成1(表示句子帶有積極情緒)或0(表示句子帶有消極情緒)。我們可以把它想象成這樣:
在這裏插入圖片描述
實際上,該模型是由兩個模型組成的。

  • DistilBERT 處理這個句子,並將從中提取的一些信息傳遞給下一個模型。DistilBERT 是BERT的一個小版本,由HuggingFace的團隊開發和開源。它是伯特的一個更輕、更快的版本,與它的性能大致相當。
  • 下一個模型是來自scikit learn的基本邏輯迴歸模型,它將接受DistilBERT處理的結果,並將句子分爲正或負(分別爲1和0)。

我們在兩個模型之間傳遞的數據是一個大小爲768的向量。我們可以把這個向量看作是我們可以用來分類的句子的嵌入。
在這裏插入圖片描述
訓練模型

雖然我們將使用兩個模型,但我們只訓練邏輯迴歸模型。對於 DistillBERT,我們將使用一個已經過預先訓練過並掌握了英語的模型。然而,這個模型既沒有經過訓練,也沒有經過 finetune 來進行句子分類。然而,從 BERT 通用目標的訓練中,我們得到了一些句子分類的能力。對於第一個位置(與[CLS] token 相關聯)的 BERT 輸出尤其如此。我認爲這是由於 BERT 的第二個訓練目標 — 下一個句子的分類。這個目標似乎是訓練模型將句子的意義壓縮到了第一個位置的輸出中。transformer庫爲我們提供了 DistilBERT 的實現以及模型的預訓練版本。
在這裏插入圖片描述
教程概述
這就是本教程的策略。我們將首先使用訓練好的distilBERT來生成2000個句子的嵌入。
在這裏插入圖片描述
在這一步之後,我們將不再接觸distilBERT。這些都是我從這裏學到的。我們做通常的訓練/測試劃分這個數據集:
在這裏插入圖片描述
然後在訓練集上訓練logistic迴歸模型:
在這裏插入圖片描述
如何計算單個預測
在深入研究代碼並解釋如何訓練模型之前,讓我們先看看訓練後的模型如何計算其預測。

讓我們試着把這句話“a visually stunning rumination on love”分類。第一步是使用 BERT tokenizer 將單詞首先分割成 tokens。然後,我們添加句子分類所需的特殊 tokens(在第一個位置是[CLS],在句子的末尾是[SEP])。
在這裏插入圖片描述
tokenizer 做的第三步是用嵌入表中的 id 替換每個 token,嵌入表是我們從訓練模型中得到的一個組件。
在這裏插入圖片描述
注意,tokenizer 在一行代碼中完成所有這些步驟:

tokenizer.encode("a visually stunning rumination on love", add_special_tokens=True)

我們的輸入語句現在是傳遞給 DistilBERT 的正確形狀。

這一步也可以用以下方式可視化:
在這裏插入圖片描述
DistilBERT 的數據流
通過 DistilBERT 傳遞輸入向量的工作方式與 BERT 一樣。輸出將是每個輸入 token 的向量。每個向量由 768 個數字(浮點數)組成。
在這裏插入圖片描述
因爲這是一個句子分類任務,所以除了第一個向量(與[CLS]token 相關聯的向量)外,我們忽略了所有其他向量。我們傳遞的這個向量作爲邏輯迴歸模型的輸入。
在這裏插入圖片描述
從這裏開始,邏輯迴歸模型的工作就是根據它從訓練階段學到的知識對這個向量進行分類。我們可以把預測計算想象成這樣:
在這裏插入圖片描述
我們將在下一節中討論訓練以及整個過程的代碼。
代碼
在本節中,我們將重點介紹訓練這個句子分類模型的代碼。

讓我們從 importing 工具開始。

import numpy as np
import pandas as pd
import torch
import transformers as ppb # pytorch transformers
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score
from sklearn.model_selection import train_test_split

數據集可以從 github 上得到,因此我們只需將其直接導入到 pandas dataframe 中。

df = pd.read_csv('https://github.com/clairett/pytorch-sentiment-classification/raw/master/data/SST2/train.tsv', delimiter='\t', header=None)

我們可以使用 df.head()查看 dataframe 的前五行,看看數據是什麼樣的。

df.head()

輸出:
在這裏插入圖片描述
導入預訓練的 DistilBERT 模型和 tokenizer

model_class, tokenizer_class, pretrained_weights = (ppb.DistilBertModel, ppb.DistilBertTokenizer, 'distilbert-base-uncased')

## Want BERT instead of distilBERT? Uncomment the following line:
#model_class, tokenizer_class, pretrained_weights = (ppb.BertModel, ppb.BertTokenizer, 'bert-base-uncased')

# Load pretrained model/tokenizer
tokenizer = tokenizer_class.from_pretrained(pretrained_weights)
model = model_class.from_pretrained(pretrained_weights)

我們現在可以 tokenize 數據集了。注意,這裏我們要做的事情與上面的示例稍有不同。上面的例子只處理了一個句子。在這裏,我們將使用批處理的方式 tokenize 和處理所有的句子(僅爲了資源考慮,notebook 將處理更小的一組示例,比如 2000 個示例)。
Tokenization

tokenized = df[0].apply((lambda x: tokenizer.encode(x, add_special_tokens=True)))

這樣就把每個句子都轉換成了 id 列表。
在這裏插入圖片描述
數據集當前是列表(或 panda 的 Series/DataFrame)的列表。在 DistilBERT 將其作爲輸入處理之前,我們需要使用 token id 0 填充更短的句子,從而使所有向量具有相同的大小。

填充之後,我們有了一個矩陣/張量,準備傳給 BERT:
在這裏插入圖片描述

max_len = 0
for i in tokenized.values:
    if len(i) > max_len:
        max_len = len(i)
print(max_len)
padded = np.array([i + [0]*(max_len-len(i)) for i in tokenized.values])
attention_mask = np.where(padded != 0, 1, 0)
print(attention_mask.shape)

使用 DistilBERT 處理
現在,我們從填充後的 token 矩陣中創建了一個輸入張量,並將其傳遞給 DistilBERT。

input_ids = torch.tensor(padded).long()
attention_mask = torch.tensor(attention_mask).long()

with torch.no_grad():
    last_hidden_states = model(input_ids, attention_mask=attention_mask)

運行此步驟後,last_hidden_states保存 DistilBERT 的輸出。在我們的例子中,這是個形狀爲(2000,66,768)的 tuple。2000(因爲我們只侷限於 2000 個例子),66(這是 2000 個例子中最長序列中的標記數),768(在 DistilBERT 模型中隱藏單元的數量)。
在這裏插入圖片描述
展開BERT輸出張量
我們來分解這個三維輸出張量。我們可以先從它的維度開始:
在這裏插入圖片描述
對句子做處理的歷程
輸入的每一行都與數據集中的一個句子相關聯。對第一句話處理路徑,我們可以把它想象成這樣:
在這裏插入圖片描述
對重要的部分切片
對於句子分類,我們只對 BERT 的[CLS] token 的輸出感興趣,所以我們選擇立方體的那一部分並放棄其他部分。
在這裏插入圖片描述
這就是我們切片三維張量得到我們感興趣的二維張量的方法:

# Slice the output for the first position for all the sequences, take all hidden unit outputs
features = last_hidden_states[0][:,0,:].numpy()

現在features是一個 2d numpy 數組,其中包含數據集中所有句子的嵌入。
在這裏插入圖片描述
邏輯迴歸的數據集
現在我們已經有了 BERT 的輸出,我們已經組裝了訓練邏輯迴歸模型所需的數據集。768 列是特徵,並且我們從初始數據集中獲得了標籤。
在這裏插入圖片描述
我們用來訓練邏輯迴歸的數據集。這些特徵是我們在前面的圖中分割的[CLS]token(位置#0)的BERT的輸出向量。每一行對應數據集中的一個句子,每一列對應Bert/DistilBERT模型頂層transformer block的前饋神經網絡的一個隱藏單元的輸出。

在完成傳統的機器學習訓練集/測試集劃分之後,我們可以構建邏輯迴歸模型並針對數據集進行訓練。

labels = df[1]
train_features, test_features, train_labels, test_labels = train_test_split(features, labels)

將數據集分割成訓練/測試集:
在這裏插入圖片描述
接下來,在訓練集上訓練邏輯迴歸模型。

lr_clf = LogisticRegression()
lr_clf.fit(train_features, train_labels)

現在模型已經訓練好了,我們可以根據測試集對它進行評估:

lr_clf.score(test_features, test_labels)

結果表明,該模型的準確率達 81%左右。
分數基準
作爲參考,這個數據集的最高準確率分數是96.8。DistilBERT 可以通過訓練來提高它在這個任務中的分數 —— 這個過程稱爲 finetune,它更新 BERT 的權重,使它在句子分類中獲得更好的性能(我們可以稱之爲“下游任務”)。finetune 後的DistilBERT達到了90.7的準確率分數。全尺寸的 BERT 模型可以達到94.9。

原文:https://jalammar.github.io/a-visual-guide-to-using-bert-for-the-first-time/
作者:Jay Alammar

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章