早在 2018 年,谷歌就為 NLP 應(yīng)用程序開(kāi)發(fā)了一個(gè)基于 Transformer 的強(qiáng)大的機(jī)器學(xué)習(xí)模型,該模型在不同的基準(zhǔn)數(shù)據(jù)集中優(yōu)于以前的語(yǔ)言模型。這個(gè)模型被稱(chēng)為BERT。在這篇文章中,我們將使用來(lái)自 Hugging Face 的預(yù)訓(xùn)練 BERT 模型進(jìn)行文本分類(lèi)任務(wù)。一般而言,文本分類(lèi)任務(wù)中模型的主要目標(biāo)是將文本分類(lèi)為預(yù)定義的標(biāo)簽或標(biāo)簽之一。
本文中,我們使用 BBC 新聞分類(lèi)數(shù)據(jù)集,使用預(yù)訓(xùn)練的 BERT 模型來(lái)分類(lèi)新聞文章的文本是否可以分類(lèi)為體育、政治、商業(yè)、娛樂(lè)或科技類(lèi)別。
什么是 BERT
BERT 是 Bidirectional Encoder Representations from Transformers 的首字母縮寫(xiě)詞。

BERT 架構(gòu)由多個(gè)堆疊在一起的 Transformer 編碼器組成。每個(gè) Transformer 編碼器都封裝了兩個(gè)子層:一個(gè)自注意力層和一個(gè)前饋層。
有兩種不同的 BERT 模型:

- BERT base 模型,由 12 層 Transformer 編碼器、12 個(gè)注意力頭、768 個(gè)隱藏大小和 110M 參數(shù)組成。
- BERT large 模型,由 24 層 Transformer 編碼器、16 個(gè)注意力頭、1024 個(gè)隱藏大小和 340M 個(gè)參數(shù)組成。
BERT 是一個(gè)強(qiáng)大的語(yǔ)言模型至少有兩個(gè)原因:
- 它使用從 BooksCorpus (有 8 億字)和 Wikipedia(有 25 億字)中提取的未標(biāo)記數(shù)據(jù)進(jìn)行預(yù)訓(xùn)練。
- 顧名思義,它是通過(guò)利用編碼器堆棧的雙向特性進(jìn)行預(yù)訓(xùn)練的。這意味著 BERT 不僅從左到右,而且從右到左從單詞序列中學(xué)習(xí)信息。
論文
- Transformer: Attention Is All You Need:https:///abs/1706.03762
- BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding:https:///abs/1810.04805
源碼
- Transformer Pytorch源碼:https://github.com/jadore801120/attention-is-all-you-need-pytorch
- BERT Pytorch源碼:https://github.com/hichenway/CodeShare/tree/master/bert_pytorch_source_code
- HuggingFace Transformers:https://github.com/huggingface/transformers
BERT 輸入

BERT 模型需要一系列 tokens (words) 作為輸入。在每個(gè)token序列中,BERT 期望輸入有兩個(gè)特殊標(biāo)記:
[CLS]
:這是每個(gè)sequence的第一個(gè)token,代表分類(lèi)token。[SEP]
:這是讓BERT知道哪個(gè)token屬于哪個(gè)序列的token。這一特殊表征法主要用于下一個(gè)句子預(yù)測(cè)任務(wù)或問(wèn)答任務(wù)。如果我們只有一個(gè)sequence,那么這個(gè)token將被附加到序列的末尾。
為什么選它們([CLS]/[SEP]
)呢,因?yàn)榕c文本中已有的其它詞相比,這個(gè)無(wú)明顯語(yǔ)義信息的符號(hào)會(huì)更“公平”地融合文本中各個(gè)詞的語(yǔ)義信息,從而更好的表示整句話的語(yǔ)義。
具體來(lái)說(shuō),self-attention 是用文本中的其它詞來(lái)增強(qiáng)目標(biāo)詞的語(yǔ)義表示,但是目標(biāo)詞本身的語(yǔ)義還是會(huì)占主要部分的,因此,經(jīng)過(guò)BERT的12層,每次詞的 Embedding 融合了所有詞的信息,可以去更好的表示自己的語(yǔ)義。
而[CLS]
位本身沒(méi)有語(yǔ)義,經(jīng)過(guò)12層,得到的是attention后所有詞的加權(quán)平均,相比其他正常詞,可以更好的表征句子語(yǔ)義。
就像Transformer的普通編碼器一樣,BERT 將一系列單詞作為輸入,這些單詞不斷向上流動(dòng)。每一層都應(yīng)用自我注意,并將其結(jié)果通過(guò)前饋網(wǎng)絡(luò)傳遞,然后將其傳遞給下一個(gè)編碼器。

舉個(gè)簡(jiǎn)單的例子以更清楚說(shuō)明,假設(shè)我們有一個(gè)包含以下短句的文本:

第一步,需要將這個(gè)句子轉(zhuǎn)換為一系列tokens (words) ,這個(gè)過(guò)程稱(chēng)為tokenization
。

雖然已經(jīng)對(duì)輸入句子進(jìn)行了標(biāo)記,但還需要再做一步。在將其用作 BERT 模型的輸入之前,我們需要通過(guò)添加 [CLS] 和 [SEP] 標(biāo)記來(lái)對(duì) tokens 的 sequence 重新編碼。

其實(shí)我們只需要一行代碼(即使用BertTokenizer
)就可以將輸入句子轉(zhuǎn)換為 BERT 所期望的tokens 序列。
還需要注意的是,可以輸入 BERT 模型的最大tokens大小為 512。如果sequence中的tokens小于 512,我們可以使用填充來(lái)用 [PAD] 填充未使用的tokens。如果sequence中的tokens長(zhǎng)于 512,那么需要進(jìn)行截?cái)唷?/p>
BERT 輸出
每個(gè)位置輸出一個(gè)大小為 hidden_ size
的向量(BERT Base 中為 768)。對(duì)于我們?cè)谏厦婵吹降木渥臃诸?lèi)示例,我們只關(guān)注第一個(gè)位置的輸出(將特殊的 [CLS] token 傳遞到該位置)。

該向量現(xiàn)在可以用作我們選擇的分類(lèi)器的輸入。該論文僅使用單層神經(jīng)網(wǎng)絡(luò)作為分類(lèi)器就取得了很好的效果。

如果有更多標(biāo)簽,只需調(diào)整分類(lèi)器網(wǎng)絡(luò)以獲得更多輸出神經(jīng)元然后通過(guò)softmax輸出多標(biāo)簽分類(lèi)。
使用 BERT 進(jìn)行文本分類(lèi)

本文的主題是用 BERT 對(duì)文本進(jìn)行分類(lèi)。在這篇文章中,我們將使用kaggle上的BBC 新聞分類(lèi)數(shù)據(jù)集
。
數(shù)據(jù)集已經(jīng)是 CSV 格式,它有 2126 個(gè)不同的文本,每個(gè)文本都標(biāo)記在 5 個(gè)類(lèi)別中的一個(gè)下:sport(體育),business(商業(yè)),politics(政治),tech(科技),entertainment(娛樂(lè))
。
看一下數(shù)據(jù)集的樣子:

如上表所示,數(shù)據(jù)框只有兩列,category
將作為標(biāo)簽,text
將作為 BERT 的輸入數(shù)據(jù)。
預(yù)模型下載和使用
BERT 預(yù)訓(xùn)練模型的下載有許多方式,比如從github官網(wǎng)上下載(官網(wǎng)下載的是tensorflow版本的),還可以從源碼中找到下載鏈接,然后手動(dòng)下載,最后還可以從huggingface中下載。
從huggingface
下載預(yù)訓(xùn)練模型的地址:https:///models
在搜索框搜索到你需要的模型。

來(lái)到下載頁(yè)面:

注意,這里常用的幾個(gè)預(yù)訓(xùn)練模型,bert-base-cased、bert-base-uncased及中文bert-base-chinese。其中前兩個(gè)容易混淆。bert-base-cased是區(qū)分大小寫(xiě),不需要事先lower-case;而bert-base-uncased不能區(qū)分大小寫(xiě),因?yàn)樵~表只有小寫(xiě),需要事先lower-case。
基本使用示例:
from transformers import BertModel,BertTokenizer
BERT_PATH = './bert-base-cased'
tokenizer = BertTokenizer.from_pretrained(BERT_PATH)
print(tokenizer.tokenize('I have a good time, thank you.'))
bert = BertModel.from_pretrained(BERT_PATH)
print('load bert model over')
['I', 'have', 'a', 'good', 'time',
',', 'thank', 'you', '.']
load bert model over
預(yù)處理數(shù)據(jù)
現(xiàn)在我們基本熟悉了 BERT 的基本使用,接下來(lái)為其準(zhǔn)備輸入數(shù)據(jù)。一般情況下,在訓(xùn)練模型前,都需要對(duì)手上的數(shù)據(jù)進(jìn)行預(yù)處理,以滿足模型需要。
前面已經(jīng)介紹過(guò)了,模型輸入數(shù)據(jù)中,需要通過(guò)添加 [CLS] 和 [SEP] 這兩個(gè)特殊的token,將文本轉(zhuǎn)換為 BERT 所期望的格式。
首先,需要通過(guò) pip 安裝 Transformers 庫(kù):
%%capture
!pip install transformers
為了更容易理解得到的輸出tokenization
,我們以一個(gè)簡(jiǎn)短的文本為例。
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
example_text = 'I will watch Memento tonight'
bert_input = tokenizer(example_text,padding='max_length',
max_length = 10,
truncation=True,
return_tensors='pt')
# ------- bert_input ------
print(bert_input['input_ids'])
print(bert_input['token_type_ids'])
print(bert_input['attention_mask'])
tensor([[ 101, 146, 1209, 2824, 2508,
26173, 3568, 102, 0, 0]])
tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]])
tensor([[1, 1, 1, 1, 1, 1, 1, 1, 0, 0]])
下面是對(duì)上面BertTokenizer
參數(shù)的解釋?zhuān)?/p>
padding
:將每個(gè)sequence填充到指定的最大長(zhǎng)度。max_length
: 每個(gè)sequence的最大長(zhǎng)度。本示例中我們使用 10,但對(duì)于本文實(shí)際數(shù)據(jù)集,我們將使用 512,這是 BERT 允許的sequence 的最大長(zhǎng)度。truncation
:如果為T(mén)rue,則每個(gè)序列中超過(guò)最大長(zhǎng)度的標(biāo)記將被截?cái)唷?/section>return_tensors
:將返回的張量類(lèi)型。由于我們使用的是 Pytorch,所以我們使用pt
;如果你使用 Tensorflow,那么你需要使用tf
。
從上面的變量中看到的輸出bert_input
,是用于稍后的 BERT 模型。但是這些輸出是什么意思?
1. 第一行是 input_ids
,它是每個(gè) token 的 id 表示。實(shí)際上可以將這些輸入 id 解碼為實(shí)際的 token,如下所示:
example_text = tokenizer.decode(bert_input.input_ids[0])
print(example_text)
'[CLS] I will watch Memento tonight
[SEP] [PAD] [PAD]'
由上述結(jié)果所示,BertTokenizer
負(fù)責(zé)輸入文本的所有必要轉(zhuǎn)換,為 BERT 模型的輸入做好準(zhǔn)備。它會(huì)自動(dòng)添加 [CLS]、[SEP] 和 [PAD] token。由于我們指定最大長(zhǎng)度為 10,所以最后只有兩個(gè) [PAD] token。
2. 第二行是 token_type_ids
,它是一個(gè) binary mask,用于標(biāo)識(shí) token 屬于哪個(gè) sequence。如果我們只有一個(gè) sequence,那么所有的 token 類(lèi)型 id 都將為 0。對(duì)于文本分類(lèi)任務(wù),token_type_ids
是 BERT 模型的可選輸入?yún)?shù)。
3. 第三行是 attention_mask
,它是一個(gè) binary mask,用于標(biāo)識(shí) token 是真實(shí) word 還是只是由填充得到。如果 token 包含 [CLS]、[SEP] 或任何真實(shí)單詞,則 mask 將為 1。如果 token 只是 [PAD] 填充,則 mask 將為 0。
注意到,我們使用了一個(gè)預(yù)訓(xùn)練BertTokenizer
的bert-base-cased
模型。如果數(shù)據(jù)集中的文本是英文的,這個(gè)預(yù)訓(xùn)練的分詞器就可以很好地工作。
如果有來(lái)自不同語(yǔ)言的數(shù)據(jù)集,可能需要使用bert-base-multilingual-cased
。具體來(lái)說(shuō),如果你的數(shù)據(jù)集是德語(yǔ)、荷蘭語(yǔ)、中文、日語(yǔ)或芬蘭語(yǔ),則可能需要使用專(zhuān)門(mén)針對(duì)這些語(yǔ)言進(jìn)行預(yù)訓(xùn)練的分詞器。可以在此處查看相應(yīng)的預(yù)訓(xùn)練標(biāo)記器的名稱(chēng)[1]。特別地,如果數(shù)據(jù)集中的文本是中文的,需要使用bert-base-chinese
模型,以及其相應(yīng)的BertTokenizer
等。
數(shù)據(jù)集類(lèi)
現(xiàn)在我們知道從BertTokenizer
中獲得什么樣的輸出,接下來(lái)為新聞數(shù)據(jù)集構(gòu)建一個(gè)Dataset
類(lèi),該類(lèi)將作為一個(gè)類(lèi)來(lái)將新聞數(shù)據(jù)轉(zhuǎn)換成模型需要的數(shù)據(jù)格式。
上下滑動(dòng)查看更多源碼
import torch
import numpy as np
from transformers import BertTokenizer
tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
labels = {'business':0,
'entertainment':1,
'sport':2,
'tech':3,
'politics':4
}
class Dataset(torch.utils.data.Dataset):
def __init__(self, df):
self.labels = [labels[label] for label in df['category']]
self.texts = [tokenizer(text,
padding='max_length',
max_length = 512,
truncation=True,
return_tensors='pt')
for text in df['text']]
def classes(self):
return self.labels
def __len__(self):
return len(self.labels)
def get_batch_labels(self, idx):
# Fetch a batch of labels
return np.array(self.labels[idx])
def get_batch_texts(self, idx):
# Fetch a batch of inputs
return self.texts[idx]
def __getitem__(self, idx):
batch_texts = self.get_batch_texts(idx)
batch_y = self.get_batch_labels(idx)
return batch_texts, batch_y
在上面實(shí)現(xiàn)的代碼中,我們定義了一個(gè)名為 labels
的變量,它是一個(gè)字典,將DataFrame中的 category
映射到 labels
的 id 表示。注意,上面的__init__
函數(shù)中,還調(diào)用了BertTokenizer
將輸入文本轉(zhuǎn)換為 BERT 期望的向量格式。
定義Dataset類(lèi)后,將數(shù)據(jù)框拆分為訓(xùn)練集、驗(yàn)證集和測(cè)試集,比例為 80:10:10
。
np.random.seed(112)
df_train, df_val, df_test = np.split(df.sample(frac=1, random_state=42),
[int(.8*len(df)), int(.9*len(df))])
print(len(df_train),len(df_val), len(df_test))
1780 222 223
構(gòu)建模型
至此,我們已經(jīng)成功構(gòu)建了一個(gè) Dataset 類(lèi)來(lái)生成模型輸入數(shù)據(jù)?,F(xiàn)在使用具有 12 層 Transformer 編碼器的預(yù)訓(xùn)練 BERT 基礎(chǔ)模型構(gòu)建實(shí)際模型。
如果數(shù)據(jù)集中的文本是中文的,需要使用bert-base-chinese
模型。
from torch import nn
from transformers import BertModel
class BertClassifier(nn.Module):
def __init__(self, dropout=0.5):
super(BertClassifier, self).__init__()
self.bert = BertModel.from_pretrained('bert-base-cased')
self.dropout = nn.Dropout(dropout)
self.linear = nn.Linear(768, 5)
self.relu = nn.ReLU()
def forward(self, input_id, mask):
_, pooled_output = self.bert(input_ids= input_id, attention_mask=mask,return_dict=False)
dropout_output = self.dropout(pooled_output)
linear_output = self.linear(dropout_output)
final_layer = self.relu(linear_output)
return final_layer
從上面的代碼可以看出,BERT 模型輸出了兩個(gè)變量:
- 在上面的代碼中命名的第一個(gè)變量
_
包含sequence中所有 token 的 Embedding 向量層。 - 命名的第二個(gè)變量
pooled_output
包含 [CLS] token 的 Embedding 向量。對(duì)于文本分類(lèi)任務(wù),使用這個(gè) Embedding 作為分類(lèi)器的輸入就足夠了。
然后將pooled_output
變量傳遞到具有 ReLU
激活函數(shù)的線性層。在線性層中輸出一個(gè)維度大小為 5 的向量,每個(gè)向量對(duì)應(yīng)于標(biāo)簽類(lèi)別(運(yùn)動(dòng)、商業(yè)、政治、 娛樂(lè)和科技
)。
訓(xùn)練模型
接下來(lái)是訓(xùn)練模型。使用標(biāo)準(zhǔn)的 PyTorch 訓(xùn)練循環(huán)來(lái)訓(xùn)練模型。
from torch.optim import Adam
from tqdm import tqdm
def train(model, train_data, val_data, learning_rate, epochs):
# 通過(guò)Dataset類(lèi)獲取訓(xùn)練和驗(yàn)證集
train, val = Dataset(train_data), Dataset(val_data)
# DataLoader根據(jù)batch_size獲取數(shù)據(jù),訓(xùn)練時(shí)選擇打亂樣本
train_dataloader = torch.utils.data.DataLoader(train, batch_size=2, shuffle=True)
val_dataloader = torch.utils.data.DataLoader(val, batch_size=2)
# 判斷是否使用GPU
use_cuda = torch.cuda.is_available()
device = torch.device('cuda' if use_cuda else 'cpu')
# 定義損失函數(shù)和優(yōu)化器
criterion = nn.CrossEntropyLoss()
optimizer = Adam(model.parameters(), lr=learning_rate)
if use_cuda:
model = model.cuda()
criterion = criterion.cuda()
# 開(kāi)始進(jìn)入訓(xùn)練循環(huán)
for epoch_num in range(epochs):
# 定義兩個(gè)變量,用于存儲(chǔ)訓(xùn)練集的準(zhǔn)確率和損失
total_acc_train = 0
total_loss_train = 0
# 進(jìn)度條函數(shù)tqdm
for train_input, train_label in tqdm(train_dataloader):
train_label = train_label.to(device)
mask = train_input['attention_mask'].to(device)
input_id = train_input['input_ids'].squeeze(1).to(device)
# 通過(guò)模型得到輸出
output = model(input_id, mask)
# 計(jì)算損失
batch_loss = criterion(output, train_label)
total_loss_train += batch_loss.item()
# 計(jì)算精度
acc = (output.argmax(dim=1) == train_label).sum().item()
total_acc_train += acc
# 模型更新
model.zero_grad()
batch_loss.backward()
optimizer.step()
# ------ 驗(yàn)證模型 -----------
# 定義兩個(gè)變量,用于存儲(chǔ)驗(yàn)證集的準(zhǔn)確率和損失
total_acc_val = 0
total_loss_val = 0
# 不需要計(jì)算梯度
with torch.no_grad():
# 循環(huán)獲取數(shù)據(jù)集,并用訓(xùn)練好的模型進(jìn)行驗(yàn)證
for val_input, val_label in val_dataloader:
# 如果有GPU,則使用GPU,接下來(lái)的操作同訓(xùn)練
val_label = val_label.to(device)
mask = val_input['attention_mask'].to(device)
input_id = val_input['input_ids'].squeeze(1).to(device)
output = model(input_id, mask)
batch_loss = criterion(output, val_label)
total_loss_val += batch_loss.item()
acc = (output.argmax(dim=1) == val_label).sum().item()
total_acc_val += acc
print(
f'''Epochs: {epoch_num + 1}
| Train Loss: {total_loss_train / len(train_data): .3f}
| Train Accuracy: {total_acc_train / len(train_data): .3f}
| Val Loss: {total_loss_val / len(val_data): .3f}
| Val Accuracy: {total_acc_val / len(val_data): .3f}''')
我們對(duì)模型進(jìn)行了 5 個(gè) epoch 的訓(xùn)練,我們使用 Adam 作為優(yōu)化器,而學(xué)習(xí)率設(shè)置為1e-6。因?yàn)楸景咐惺翘幚矶囝?lèi)分類(lèi)問(wèn)題,則使用分類(lèi)交叉熵作為我們的損失函數(shù)。
建議使用 GPU 來(lái)訓(xùn)練模型,因?yàn)?BERT 基礎(chǔ)模型包含 1.1 億個(gè)參數(shù)。
EPOCHS = 5
model = BertClassifier()
LR = 1e-6
train(model, df_train, df_val, LR, EPOCHS)

顯然,由于訓(xùn)練過(guò)程的隨機(jī)性,每次可能不會(huì)得到與上面截圖類(lèi)似的損失和準(zhǔn)確率值。如果在 5 個(gè) epoch 之后沒(méi)有得到好的結(jié)果,可以嘗試將 epoch 增加到 10 個(gè),或者調(diào)整學(xué)習(xí)率。
在測(cè)試數(shù)據(jù)上評(píng)估模型
現(xiàn)在我們已經(jīng)訓(xùn)練了模型,我們可以使用測(cè)試數(shù)據(jù)來(lái)評(píng)估模型在未見(jiàn)數(shù)據(jù)上的性能。下面是評(píng)估模型在測(cè)試集上的性能的函數(shù)。
def evaluate(model, test_data):
test = Dataset(test_data)
test_dataloader = torch.utils.data.DataLoader(test, batch_size=2)
use_cuda = torch.cuda.is_available()
device = torch.device('cuda' if use_cuda else 'cpu')
if use_cuda:
model = model.cuda()
total_acc_test = 0
with torch.no_grad():
for test_input, test_label in test_dataloader:
test_label = test_label.to(device)
mask = test_input['attention_mask'].to(device)
input_id = test_input['input_ids'].squeeze(1).to(device)
output = model(input_id, mask)
acc = (output.argmax(dim=1) == test_label).sum().item()
total_acc_test += acc
print(f'Test Accuracy: {total_acc_test / len(test_data): .3f}')
evaluate(model, df_test)
運(yùn)行上面的代碼后,我從測(cè)試數(shù)據(jù)中得到了 0.994 的準(zhǔn)確率。由于訓(xùn)練過(guò)程中的隨機(jī)性,將獲得的準(zhǔn)確度可能會(huì)與我的結(jié)果略有不同。
討論兩個(gè)問(wèn)題
這里有個(gè)問(wèn)題:使用BERT預(yù)訓(xùn)練模型為什么最多只能輸入512個(gè)詞,最多只能兩個(gè)句子合成一句?
這是Google BERT預(yù)訓(xùn)練模型初始設(shè)置的原因,前者對(duì)應(yīng)Position Embeddings,后者對(duì)應(yīng)Segment Embeddings
在BERT中,Token,Position,Segment Embeddings 都是通過(guò)學(xué)習(xí)來(lái)得到的,pytorch代碼中它們是這樣的
self.word_embeddings = Embedding(config.vocab_size, config.hidden_size)
self.position_embeddings = Embedding(config.max_position_embeddings, config.hidden_size)
self.token_type_embeddings = Embedding(config.type_vocab_size, config.hidden_size)
而在BERT config中
'max_position_embeddings': 512
'type_vocab_size': 2
因此,在直接使用 Google 的 BERT 預(yù)訓(xùn)練模型時(shí),輸入最多512個(gè)詞(還要除掉[CLS]和[SEP]),最多兩個(gè)句子合成一句。這之外的詞和句子會(huì)沒(méi)有對(duì)應(yīng)的 Embedding 。
當(dāng)然,如果有足夠的硬件資源自己重新訓(xùn)練 BERT,可以更改 BERT config
,設(shè)置更大 max_position_embeddings
和 type_vocab_size
值去滿足自己的需求。
此外還有人問(wèn) BERT的三個(gè)Embedding直接相加會(huì)對(duì)語(yǔ)義有影響嗎?

這是一個(gè)非常有意思的問(wèn)題,蘇劍林老師也給出了回答,真的很妙?。?/p>
Embedding 的數(shù)學(xué)本質(zhì),就是以 one hot 為輸入的單層全連接。也就是說(shuō),世界上本沒(méi)什么 Embedding,有的只是one hot。
在這里想用一個(gè)例子再?lài)L試解釋一下:
假設(shè) token Embedding 矩陣維度是 [4,768];position Embedding 矩陣維度是 [3,768];segment Embedding 矩陣維度是 [2,768]。
對(duì)于一個(gè)字,假設(shè)它的 token one-hot 是[1,0,0,0];它的 position one-hot 是[1,0,0];它的 segment one-hot 是[1,0]。
那這個(gè)字最后的 word Embedding,就是上面三種 Embedding 的加和。
如此得到的 word Embedding,和concat后的特征:[1,0,0,0,1,0,0,1,0],再過(guò)維度為 [4+3+2,768] = [9, 768] 的全連接層,得到的向量其實(shí)就是一樣的。
再換一個(gè)角度理解:
直接將三個(gè)one-hot 特征 concat 起來(lái)得到的 [1,0,0,0,1,0,0,1,0] 不再是one-hot了,但可以把它映射到三個(gè)one-hot 組成的特征空間,空間維度是 432=24 ,那在新的特征空間,這個(gè)字的one-hot就是[1,0,0,0,0...] (23個(gè)0)。
此時(shí),Embedding 矩陣維度就是 [24,768],最后得到的 word Embedding 依然是和上面的等效,但是三個(gè)小 Embedding 矩陣的大小會(huì)遠(yuǎn)小于新特征空間對(duì)應(yīng)的 Embedding 矩陣大小。
當(dāng)然,在相同初始化方法前提下,兩種方式得到的 word Embedding 可能方差會(huì)有差別,但是,BERT還有Layer Norm,會(huì)把 Embedding 結(jié)果統(tǒng)一到相同的分布。
BERT的三個(gè)Embedding相加,本質(zhì)可以看作一個(gè)特征的融合,強(qiáng)大如 BERT 應(yīng)該可以學(xué)到融合后特征的語(yǔ)義信息的
這就是 BERT 期望的所有輸入。
然后,BERT 模型將在每個(gè)token中輸出一個(gè)大小為 768 的 Embedding 向量。我們可以將這些向量用作不同類(lèi)型 NLP 任務(wù)的輸入,無(wú)論是文本分類(lèi)、本文生成、命名實(shí)體識(shí)別 (NER) 還是問(wèn)答。
對(duì)于文本分類(lèi)任務(wù),我們將注意力集中在特殊 [CLS] token 的 embedding 向量輸出上。這意味著我們將使用具有 [CLS] token 的大小為 768 的 embedding 向量作為分類(lèi)器的輸入,然后它將輸出一個(gè)大小為分類(lèi)任務(wù)中類(lèi)別個(gè)數(shù)的向量。
寫(xiě)在最后
現(xiàn)在我們?nèi)鐚W(xué)會(huì)了何利用 Hugging Face 的預(yù)訓(xùn)練 BERT 模型進(jìn)行文本分類(lèi)任務(wù)的步驟。我希望在你開(kāi)始使用 BERT是,這篇文章能幫到你。我們不僅可以使用來(lái)自 BERT 的embedding向量來(lái)執(zhí)行句子或文本分類(lèi)任務(wù),還可以執(zhí)行更高級(jí)的 NLP 應(yīng)用,例如問(wèn)答、文本生成或命名實(shí)體識(shí)別 (NER)任務(wù)。
參考資料
[1]預(yù)訓(xùn)練標(biāo)記器的名稱(chēng): https:///transformers/pretrained_models.html