主頁 >  其他 > 【實戰篇】是時候徹底弄懂BERT模型了(建議收藏)

【實戰篇】是時候徹底弄懂BERT模型了(建議收藏)

2021-09-12 11:19:35 其他

引言

本文是【理論篇】是時候徹底弄懂BERT模型了的姊妹篇,在本文中,我們通過🤗的transformers庫來實戰使用預訓練的BERT模型,

我們主要會實戰文本分類中的情緒識別任務和自然語言推理中的問答任務,

注意,文中提到的嵌入表示嵌入嵌入向量向量表示表示說的都是同一個東西,

探索預訓練的BERT模型

在理論篇中,我們知道了如何使用屏蔽語言建模和下一句預測任務來預訓練BERT模型,

但是從開開始預訓練BERT模型是很耗資源的,所以我們可以下載預訓練好的BERT模型,Google開源了預訓練的BERT模型,我們可以從https://github.com/google-research/bert中下載,他們開源了多種配置,如下圖所示, L L L代表編碼器層數, H H H代表隱藏單元大小:

d472a0ac-2567-4769-85eb-fa3e84da9f33

預訓練模型也可用于 BERT-uncased 和 BERT-cased 格式,在BERT-uncased中,所有的標記都是小寫的,但是在BERT-cased中的標記沒有轉換為小寫,而是直接用來訓練,

其中BERT-uncased模型是最常用的,但是如果我們在像命名物體識別任務時,此時我們應該保留大小寫,所以我們應該使用BERT-cased模型,除此之外,Google還釋放了使用整詞屏蔽(whole word masking,WWM)方法預訓練的模型,

我們可以以下面兩個方式使用預訓練模型:

  • 作為抽取嵌入表示的特征抽取器
  • 通過在下游任務像文本分類、問答等任務的微調預訓練的BERT模型

在后文中我們會學習這兩種方式是如何使用的,

從預訓練的BERT中抽取嵌入表示

我們通過一個實體來理解,考慮一個句子I love Paris,假設我們要抽取該句子中的每個標記的背景關系嵌入,首先,我們對句子分詞然后喂給預訓練的BERT模型,它會回傳每個標記的嵌入表示,除了可以獲取標記級(單詞級)的嵌入表示,我們還可以獲取句子級的表示,

在本節中,我們來學習具體如何從預訓練的BERT模型中抽取單詞級和句子級的嵌入表示,

假設我們想要完成一個情感分析任務,然后我們有下面的資料集:

cedc0b57-6cca-48e1-9df9-b5bc5cfbc7c7

我們有句子以及對應的標簽,其中 1 1 1代表正向情感而 0 0 0代表負向情感,我們可以使用給定的資料集訓練一個分類器來分類句子的情感,

首先,我們需要對資料集中的文本進行向量化,我們可以使用諸如TF-IDF、word2vec等方法進行向量化,但既然我們已經知道BERT可以學到單詞的背景關系嵌入表示,我們何不直接使用預訓練的BERT模型去向量化資料集中的句子呢?

考慮我們資料集中第一個句子:I love Paris,首先,我們通過WordPiece分詞器進行分詞并得到分詞后的單詞(標記):

tokens = [I, love, Paris]

然后增加[CLS]SEP標記:

tokens = [ [CLS], I, love, Paris, [SEP] ]

類似的,我們就可以對訓練集中的所有句子進行同樣的操作,但是每個句子的長度是不固定的,所以我們需要保證所有的單詞串列長度是一致的,假設我們保持資料集中的句子長度為 7 7 7,如果我們查看上面的單詞串列,它的長度為 5 5 5,為了滿足長度為 7 7 7的要求,我們需要增加一個新的填充標記叫[PAD],這樣我們得到的單詞串列如下:

tokens = [ [CLS], I, love, Paris, [SEP], [PAD], [PAD] ]

這樣我們的單詞串列長度就變成 7 7 7了,下一步就是讓我們的模型理解[PAD]標記是用于填充而不是實際的標記,因此我們引入一個注意力屏蔽(mask),我們將注意力mask中所有實際標記位置都設為 1 1 1,而[PAD]標記位置都設為 0 0 0,我們就有了下面的注意力mask:

attention_mask =  [ 1,1,1,1,1,0,0]

接下來我們映射所有標記到一個獨立的標記ID,假設用映射后的標記ID如下:

token_ids = [101, 1045, 2293, 3000, 102, 0, 0]

它的意思是標記[CLS]的ID是 101 101 101;標記I的DI是 1045 1045 1045等等,

現在,我們將token_ids以及attention_mask作為BERT模型的輸入,然后獲取每個標記的相應嵌入表示,

下圖顯示我們如何使用預訓練的BERT模型來獲得嵌入表示的,為了清晰起見,我們畫出了標記本身而不是它的ID,一旦我們將標記作為輸入喂給BERT,編碼器1(Encoder 1)計算所有標記的嵌入表示然后傳給下一個編碼器——編碼器2,編碼器2將編碼器1計算的嵌入表示作為輸入,然后輸出一個新的嵌入表示,接著繼續傳遞給下一個編碼器,這樣每個編碼器都把自己輸出的嵌入表示傳遞到它上面的下一個編碼器,而最后的編碼器,回傳的是我們句子中所有標記的最終嵌入表示,

00c50c7b-aed2-46d7-adcc-2660c3d9efb9

這樣 R [CLS] R_{\text{[CLS]}} R[CLS]?就是標記[CLS]的嵌入, R I R_I RI?是標記I的嵌入,等等,

在本例中,每個標記的嵌入表示大小為768,

我們現在得到了句子中每個單詞的嵌入表示,那我們如何得到整個句子的嵌入表示呢?

我們在句首位置有[CLS]標記,該標記的輸出會保存了整個完整句子的聚合資訊,所以,我們可以忽略所有其他標記的嵌入,僅用[CLS]標記的輸出嵌入來作為我們句子的表示,

使用同樣的方式,我們可以計算訓練集中所有句子的向量表示,一旦我們有了這些句子表示,我們就可以把它們作為輸入去訓練一個分類器完成情緒識別任務,

請注意使用[CLS]標記的輸出向量作為句子表示并不總是一個好主意,獲得句子表示的有效方法是對所有標記進行平均或池化(pooling),

在下節中,我們會學習如何使用Hugging Face🤗提供的transformers庫來實作我們上面說的這些事情,

Hugging Face transformers

Hugging Face🤗總部位于紐約,是一家專注于自然語言處理、人工智能和分布式系統的創業公司,他們所提供的聊天機器人技術一直頗受歡迎,但更出名的是他們在NLP開源社區上的貢獻,Hugging Face一直致力于自然語言處理NLP技術的平民化(democratize),希望每個人都能用上最先進(SOTA, state-of-the-art)的NLP技術,而非困窘于訓練資源的匱乏,他們的transformers庫同時提供了TesnforFlow和PyTorch版,

我們可以使用pip安裝transformers

pip install transformers==4.10.0

生成BERT嵌入

本節中,我們會學習如何從預訓練的BERT模型中抽取嵌入表示,考慮句子:I love Paris,我們來看看如何獲得句子中所有單詞的背景關系單詞嵌入,

首先我們引入需要的包:

from transformers import BertModel, BertTokenizer
import torch

下面我們下載預訓練好的BERT模型,我們可以從https://huggingface.co/models頁面查看所有可用的預訓練模型,我們使用的是bert-base-uncased模型,它基于12個編碼器層、并且在小寫的標記中訓練,表示向量的大小為768,

下載并加載預訓練的bert-base-uncased模型:

model = BertModel.from_pretrained('bert-base-uncased')

下面我們下載并加載用來預訓練bert-base-uncased模型的分詞器:

tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

現在,我們看看如何處理輸入,

預處理輸入

定義句子:

sentence = 'I love Paris'

分詞并獲得分詞后的標記:

tokens = tokenizer.tokenize(sentence)
print(tokens)

輸出:

['i', 'love', 'paris']

接著,我們增加[CLS]標記到標記串列頭部;增加[SEP]到標記串列尾部:

tokens = ['[CLS]'] + tokens + ['[SEP]']
print(tokens)

輸出:

['[CLS]', 'i', 'love', 'paris', '[SEP]']

現在我們的標記串列tokens大小為 5 5 5,假設我們想保持tokens長度為 7 7 7;此時,我們增加兩個填充標記[PAD]tokens末尾:

tokens = tokens + ['[PAD]'] + ['[PAD]']
tokens

輸出:

['[CLS]', 'i', 'love', 'paris', '[SEP]', '[PAD]', '[PAD]']

下面,我們創建一個注意力屏蔽attention_mask

attention_mask = [1 if i!= '[PAD]' else 0 for i in tokens]
attention_mask

輸出:

[1, 1, 1, 1, 1, 0, 0]

接著,我們將所有的標記轉換為對應的ID:

token_ids = tokenizer.convert_tokens_to_ids(tokens)token_ids

輸出:

[101, 1045, 2293, 3000, 102, 0, 0]

現在,我們通過將token_idsattention_mask轉換為Tensor:

token_ids = torch.tensor(token_ids).unsqueeze(0)attention_mask = torch.tensor(attention_mask).unsqueeze(0)

接下來,我們把這兩個變數喂給預訓練的BERT模型得到嵌入表示,

獲得嵌入表示

如下面的代碼所示,我們將token_idsattention_mask傳入模型得到嵌入表示,注意模型回傳的輸出是有兩個值的元組,第一個值代表隱藏狀態表示hidden_rep,它包含從最終編碼器(encoder 12)獲取的所有標記的嵌入表示;第二個值cls_head包含[CLS]標記的嵌入表示:

hidden_rep, cls_head = model(token_ids, attention_mask=attention_mask,return_dict=False)

hidden_rep包含輸入中所有單詞的嵌入表示,我們列印一下它的形狀:

print(hidden_rep.shape)

輸出:

torch.Size([1, 7, 768]) #  [batch_size, sequence_length, hidden_size]

我們的批大小為 1 1 1;序列長度就是標記串列長度,為 7 7 7;隱藏大小是嵌入表示大小,為 768 768 768

我們可以像下面的方式獲取每個表示的嵌入表示:

  • hidden_rep[0][0]回傳第一個標記[CLS]的嵌入表示
  • hidden_rep[0][1]回傳第二個標記I的嵌入表示
  • hidden_rep[0][2]回傳第三個標記love的嵌入表示

這樣,我們就得到了所有標記的背景關系表示,這基本上是給定句子中所有單詞的背景關系詞嵌入表示,

現在,我們來看下cls_head,它包含[CLS]標記的嵌入表示,我們列印一下它的形狀:

print(cls_head.shape)

輸出:

torch.Size([1, 768]) # [batch_size, hidden_size]

我們知道cls_head包含整個句子的聚合表示,所以我們可以使用該物件作為句子I love Paris的嵌入表示,

我們學習了如何從預訓練的 BERT 模型中提取嵌入, 但這些是僅從 BERT 的最頂層編碼器層(即encoder 12)獲得的嵌入,我們是否也可以從 BERT 的所有編碼器層中提取嵌入?

我們將在下一節中了解如何做到這一點,

從BERT所有編碼器層中抽取嵌入

我們已經知道如何從預訓練的BERT中抽取嵌入,那如何從BERT的所有編碼器層中抽取嵌入呢?

假設輸入層為 h 0 h_0 h0?,第一個編碼器層為 h 1 h_1 h1?,第二個編碼器層為 h 2 h_2 h2?,以此類推,最后一個編碼器層為 h 12 h_{12} h12?

img

BERT的研究者已經實驗過從不同的編碼器層中提取嵌入,

比如,以NER任務來說,研究者已經使用與訓練的BERT模型來抽取特征,他們實驗了不同編碼器層的特征,然后得到了如下的F1值:

img

如上表所示,拼接最后4個編碼器層的表示(conncat last four hidden)可以得到最好的F1值——96.1%,這說明了,不僅僅是抽取最后一個編碼器層的輸出,我們也可以嘗試使用其他編碼器層,

現在, 我們接下來會學習如何使用transformers從所有的編碼器層中抽取嵌入表示,

抽取嵌入表示

首先,我們匯入必須的包:

from transformers import BertModel, BertTokenizer
import torch

接著,下載預訓練的BERT模型和分詞器,正如我們看到的,在下載預訓練的BERT模型時,我們需要設定output_hidden_states=True,這樣我們就能獲得所有編碼器層的嵌入:

model = BertModel.from_pretrained('bert-base-uncased', output_hidden_states = True)
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')

下面,我們對輸入進行一些預處理,

預處理輸入

假設我們還是考慮上小節看到的例子,首先,我們對句子進行分詞并增加[CLS][SEP]標記:

sentence = 'I love Paris'
tokens = tokenizer.tokenize(sentence)
tokens = ['[CLS]'] + tokens + ['[SEP]']

假設我們需要保證標記串列的長度為 7 7 7,所以,我們增加填充標記[PAD]同時定義注意力mask:

tokens = tokens + ['[PAD]'] + ['[PAD]']attention_mask = [1 if i!= '[PAD]' else 0 for i in tokens]

下面我們將tokens轉換為對應的ID:

token_ids = tokenizer.convert_tokens_to_ids(tokens)

現在, 我們轉換token_idsattention_mask到Tensor:

token_ids = torch.tensor(token_ids).unsqueeze(0)
attention_mask = torch.tensor(attention_mask).unsqueeze(0)

這樣我們就處理完畢了,下面可以獲取嵌入了,

得到嵌入表示

由于我們在定義模型時設定了 output_hidden_states = True 以從所有編碼器層獲取嵌入,現在模型回傳一個三個元素的元組:

last_hidden_state, pooler_output, hidden_states = model(token_ids, attention_mask = attention_mask, return_dict = False)

在上面的代碼中,分析如下:

  • 第一個值,last_hidden_state包含所有標記的嵌入表示,但是僅來自最后一個編碼器層(encoder 12)
  • pooler_output代表從最后的編碼器層得到的[CLS]標記對應的嵌入表示,它可以進一步地通過一個線性和tanh激活函式處理
  • hidden_states包含從所有編碼器層得到的所有標記的嵌入表示

現在,我們逐個了解下每個值,

首先,我們看一下last_hidden_state,我們輸出它的形狀:

last_hidden_state.shape.shape

輸出:

torch.Size([1, 7, 768]) # [batch_size, sequence_length, hidden_size]

我們的批大小為 1 1 1,序列長度就是標記串列長度,為 7 7 7,隱藏大小是嵌入表示的大小,這里是 768 768 768

我們可以通過類似下面的方法獲取每個標記的嵌入表示:

  • last_hidden_state[0][0]輸出第一個標記[CLS]的嵌入表示

  • last_hidden_state[0][1]輸出第二個標記I的嵌入表示

  • last_hidden_state[0][2]輸出第三個標記love的嵌入表示

類似地,我們就可以從最后一個編碼器層得到所有標記的嵌入表示,

下面,我們來看pooler_output,它包含最后的編碼器層得到的[CLS]標記對應的嵌入表示,我們列印它的形狀:

pooler_output.shape

輸出:

torch.Size([1, 768]) # [batch_size, hidden_size]

我們知道[CLS]標記保存的是整個句子的聚合表示,我們就可以使用pooler_output來作為句子I love Paris的句子表示,

最后,我們來看一下hidden_states,它是一個包含13個值的元組,保存了從輸入層 h 0 h_0 h0?到最后一個編碼器層 h 12 h_{12} h12?的所有嵌入表示:

len(hidden_states)

輸出:

13

我們可以看到,這13個值包含了所有層的表示:

  • hidden_states[0]包含輸入嵌入層 h 0 h_0 h0?的所有標記的嵌入表示

  • hidden_states[1]包含第一個編碼器嵌入層 h 1 h_1 h1?的所有標記的嵌入表示

  • hidden_states[2]包含第二個編碼器嵌入層 h 2 h_2 h2?的所有標記的嵌入表示

  • hidden_states[12]包含最后一個編碼器嵌入層 h 1 2 h_12 h1?2的所有標記的嵌入表示

我們接著列印hidden_states[0]的形狀:

hidden_states[0].shape

輸出:

torch.Size([1, 7, 768]) # [batch_size, sequence_length, hidden_size]

然后,列印hidden_states[1]的形狀:

hidden_states[1].shape

輸出:

torch.Size([1, 7, 768])

這樣我們就可以得到所有編碼器層的標記對應的嵌入表示,下面我們來學習下如何把預訓練的BERT模型

應用到下游任務,比如情緒分析,

為下游任務微調BERT

注意微調意味著我們不會從頭開始訓練BERT,而是使用預訓練的BERT,然后根據任務來更新它的權重引數,

在本節中,我們會學習到如何為以下的下游任務微調BERT模型:

  • 文本分類

  • 自然語言推理

  • 命名物體識別

  • 問答任務

文本分類

我們先來學習如何為一個文本分類任務微調BERT模型,假設我們具體要做的是情緒識別,在情緒識別任務中,我們的目標是判斷一個句子到底是正向的還是負向的,假設我們有一個包含句子和對應標簽的資料集,

考慮句子:I love Paris,首先,當然還是分詞,然后增加[CLS][SEP]標記,

然后我們將這些標記輸入到BERT模型,得到每個標記的嵌入表示,

下面我們只拿[CLS]標記對應的嵌入表示 R [CLS] R_{\text{[CLS]}} R[CLS]?,而忽略所有其他的標記,因為我們知道[CLS]的嵌入包含了整個句子的聚合表示,我們將 R [CLS] R_{\text{[CLS]}} R[CLS]?喂給一個分類器(帶有softmax函式的前饋神經網路),然后訓練該分類器去做情緒識別任務,

等等!這和我們在本節開頭看到的有什么不同?微調預訓練的 BERT 模型與使用預先訓練的 BERT 模型作為特征提取器有何不同?

從預訓練的BERT中抽取嵌入表示小節中,我們知道在提取句子的嵌入后,我們將 R [CLS] R_{\text{[CLS]}} R[CLS]?提供給分類器并訓練分類器執行分類,類似地,在微調期間,我們也將 R [CLS] R_{\text{[CLS]}} R[CLS]?喂給一個分類并訓練分類器執行分類,

不同點在于當我們微調預訓練的BERT模型時,我們在更新分類器的引數時也會更新BERT的引數,但當我們使用預訓練的BERT模型作為特征抽取器時,我們只更新分類器的引數,而不更新預訓練的BERT模型,

在微調期間,我們可以通過以下兩個方式調整BERT模型的引數:

  • 隨著分類器一起更新預訓練的BERT模型的引數
  • 僅更新分類器的引數而不更新預訓練BERT模型的,此時,這種方式類似將預訓練的BERT模型作為特征抽取器的情況,

下圖闡述了我們如何為一個情緒分類任務微調預訓練的BERT模型:

img

正如我們看到的,我們將標記串列喂給BERT模型并得到這些標記的嵌入,然后我們拿[CLS]標記的嵌入作為前饋神經網路的輸入,并進行分類任務,

在下一小節中,讓我們通過代碼對預訓練的 BERT 模型進行微調來更好地了解微調的作業原理,

為情緒識別微調BERT

讓我們探索如何使用 IMDB 資料集為情感分析任務微調預訓練的 BERT 模型, IMDB 資料集由電影評論以及評論對應情緒組成,

引入依賴

首先我們安裝必要的依賴:

!pip install nlp==0.4.0
!pip install transformers==4.10.0

引入必要的包:

from transformers import BertForSequenceClassification, BertTokenizerFast, Trainer, TrainingArguments
from nlp import load_dataset
import torch
import numpy as np

加載模型和資料集

首先,我們使用nlp包下載并加載資料集:

!gdown https://drive.google.com/uc?id=11_M4ootuT7I1G0RlihcC0cA3Elqotlc-
dataset = load_dataset('csv', data_files='./imdbs.csv', split='train')

我們查看資料型別:

type(dataset)

輸出:

nlp.arrow_dataset.Dataset

下面,將資料集拆分為訓練和測驗集:

dataset = dataset.train_test_split(test_size=0.3)

我們輸出資料集dataset

{'test': Dataset(features: {'text': Value(dtype='string', id=None), 'label': Value(dtype='int64', id=None)}, num_rows: 30),
 'train': Dataset(features: {'text': Value(dtype='string', id=None), 'label': Value(dtype='int64', id=None)}, num_rows: 70)}

現在我們創建訓練和測驗集:

train_set = dataset['train']
test_set = dataset['test']

接下來,我們下載并加載預訓練的BERT模型,在本例中,我們使用預訓練的bert-base-uncased模型,因為我們在做序列分類任務,我們就可以使用BertForSequenceClassification類:

model = BertForSequenceClassification.from_pretrained('bert-base-uncased')

下面,我們下載并加載用于預訓練bert-base-uncased模型的分詞器,

這里我們使用BertTokenizerFast類創建分詞器而不是BertTokenizer,因為BertTokenizerFast相比BertTokenizer具有很多優勢,我們下節會探討這一點,

tokenizer = BertTokenizerFast.from_pretrained('bert-base-uncased')

現在我們已經加載好了資料集赫爾模型,讓我們開始預處理資料集吧,

預處理資料集

我們可以使用我們的分詞器快速地預處理資料集,比如,考慮句子:I love Paris

首先,我們分詞并增加[CLS][SEP]標記:

tokens = [ '[CLS]', 'I', 'love', 'Paris', '[SEP]' ]

下面,我們映射標記到獨立的輸入ID,假設我們得到下面的ID:

input_ids = [101, 1045, 2293, 3000, 102]

然后,我們需要增加片段ID(segment ID,標記型別ID),??什么片段ID?假設我們的輸入有兩個句子,此時,片段ID用于區分這兩個句子,所有來自第一個句子的標記會映射到0;所有來自第二個句子的標記會映射到1,因為我們這里只有一個句子,所以所有的標記都會映射到0

token_type_ids = [0, 0, 0, 0, 0]

現在我們需要創建注意力mask,我們知道注意力mask用于區分真實標記和填充標記[PAD],假設我們的標記串列長度應該為 5 5 5,我們這里的標記串列的長度應是 5 5 5了,所以不需要增加[PAD]標記,那么我們的注意力mask就會如下:

attention_mask = [1, 1, 1, 1, 1]

我們可以使用分詞器來為我們做上面這些手動的步驟,只需要將句子傳遞給分詞器即可:

tokenizer('I love Paris')

輸出如下,我們可以看到,輸入句子被分詞了并且映射到input_idstoken_type_ids,同時還有attention_mask

{
'input_ids': [101, 1045, 2293, 3000, 102], 
'token_type_ids': [0, 0, 0, 0, 0], 
'attention_mask': [1, 1, 1, 1, 1]
}

通過分詞器,我們也可以傳入任意數量的句子并動態地執行填充,我們執行需要設定paddingTrue和最大的句子長度max_length,比如,下面的代碼中,我們傳入3個句子,并將max_length設為5

tokenizer(['I love Paris', 'birds fly','snow fall'], padding = True, max_length=5)

前面的代碼將回傳以下內容, 如我們所見,所有句子都映射到 input_idstoken_type_idsattention_mask, 第二句和第三句只有兩個標記,加上[CLS][SEP]后,就會有4個標記, 由于我們將 padding 設定為 True 并將 max_length 設定為 5,因此第二個和第三個句子中會添加一個額外的[PAD]標記,這就是我們在第二個和第三個句子的注意力mask中值為 0 的原因:

{
'input_ids': [[101, 1045, 2293, 3000, 102], [101, 5055, 4875, 102, 0], [101, 4586, 2991, 102, 0]], 
'token_type_ids': [[0, 0, 0, 0, 0], [0, 0, 0, 0, 0], [0, 0, 0, 0, 0]], 'attention_mask': [[1, 1, 1, 1, 1], [1, 1, 1, 1, 0], [1, 1, 1, 1, 0]]
}

看,通過分詞器我們就可以很容易的預處理資料集,所以我們可以定義如下函式來預處理資料集:

def preprocess(data):
  return tokenizer(data['text'], padding=True, truncation=True)

現在,我們使用preprocess函式預處理訓練和測驗資料集:

train_set = train_set.map(preprocess, batched=True, 
                          batch_size=len(train_set))
test_set = test_set.map(preprocess, batched=True, batch_size=len(test_set))

下面,我們使用set_format函式來選擇我們在資料集中需要的列:

train_set.set_format('torch', 
                      columns=['input_ids', 'attention_mask', 'label'])
test_set.set_format('torch', 
                     columns=['input_ids', 'attention_mask', 'label'])

這樣,我們的資料集準備好了,可以開始訓練模型了,

訓練模型

我們先定義批大小和epoch大小:

batch_size = 8
epochs = 2

定義熱身步數和權重衰減:

warmup_steps = 500
weight_decay = 0.01

定義訓練引數:

training_args = TrainingArguments(
    output_dir='./results',
    num_train_epochs=epochs,
    per_device_train_batch_size=batch_size,
    per_device_eval_batch_size=batch_size,
    warmup_steps=warmup_steps,
    weight_decay=weight_decay,
    logging_dir='./logs'
)

然后定義訓練器:

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_set,
    eval_dataset=test_set
)

開始訓練:

trainer.train()

在訓練完成之后,我們可以通過evaluate函式評估模型:

trainer.evaluate()

輸出為:

{'epoch': 2.0,
 'eval_loss': 0.6827354431152344,
 'eval_runtime': 2.131,
 'eval_samples_per_second': 14.078,
 'eval_steps_per_second': 1.877}

這樣我們就能微調預訓練的BERT模型,既然我們已經學會了如何為一個文本分類任務微調BERT模型,在下一小節中,我們看看如何為自然語言推理任務微調BERT模型,

自然語言推理

在自然語言推理中,我們模型的目標是在給定前提(premise)的情況下,確定假設(hypothesis)是蘊涵(true)、矛盾(false)還是不確定(neutral), 讓我們通過微調 BERT 來學習如何解決自然語言推理任務,

考慮下面的資料集,

我們有一個前提和一個帶有標簽的假設,表明它們是蘊涵、矛盾還是不確定:

img

現在,我們模型的目標是確定句子對(前提-假設對)是蘊涵、矛盾還是不確定, 讓我們通過一個例子來了解如何做到這一點, 考慮以下前提-假設對:

Premise: He is playing
Hypothesis: He is sleeping 

首先我們對句子分詞,然后增加[CLS][SEP]標記:

tokens = [ [CLS], He, is, playing, [SEP], He, is, sleeping [SEP]]

現在, 我們將這些標記喂給預訓練的BERT模型并得到每個單詞的嵌入,

我們拿出[CLS]標記的嵌入表示 R [CLS] R_{\text{[CLS]}} R[CLS]?,并喂給一個分類器(前饋網路+softmax),該分類器會回傳句子是蘊含、矛盾還是不確定的概率:

img

為問答任務微調BERT

在本節中,我們學習如何用一個預訓練的問答BERT模型來做問答任務,首先,我們匯入必要的包:

from transformers import BertForQuestionAnswering, BertTokenizer
import torch

現在,我們下載并加載模型,我們使用bert-large-uncased-whole-word-masking-finetuned-squad模型,它是在SQUAD資料集上微調過的:

model = BertForQuestionAnswering.from_pretrained('bert-large-uncased-whole-word-masking-finetuned-squad')

然后,我們下載并加載分詞器:

tokenizer = BertTokenizer.from_pretrained('bert-large-uncased-whole-word-masking-finetuned-squad')

預處理輸入

首先,我們定義BERT的輸入,為問題和文本段落:

question = "What is the immune system?"
paragraph = "The immune system is a system of many biological structures and processes within an organism that protects against disease. To function properly, an immune system must detect a wide variety of agents, known as pathogens, from viruses to parasitic worms, and distinguish them from the organism's own healthy tissue."

增加[CLS][SEP]標記到問題和段落中:

question = '[CLS] ' + question + '[SEP]'
paragraph = paragraph + '[SEP]'

然后,對問題和段落進行分詞:

question_tokens = tokenizer.tokenize(question)
paragraph_tokens = tokenizer.tokenize(paragraph)

組合問題和段落標記,并將它們轉換為input_ids

tokens = question_tokens + paragraph_tokens 
input_ids = tokenizer.convert_tokens_to_ids(tokens)

接下來,定義segment_ids,這里,segment_ids會把所有來自問題的標記映射為0;把所有來自段落的標記映射為1

segment_ids = [0] * len(question_tokens)
segment_ids += [1] * len(paragraph_tokens)

然后,把input_idssegment_ids轉換為Tensor:

input_ids = torch.tensor([input_ids])
segment_ids = torch.tensor([segment_ids])

獲取答案

我們將input_idssegment_ids輸入到模型中,它會回傳所有標記作為答案開始位置和結束位置的得分:

start_scores, end_scores = model(input_ids, token_type_ids = segment_ids, return_dict = False)

接著,我們找到start_index,就是最高答案開始位置得分對應的標記索引,和end_index,為最高答案結束位置得分對應的標記索引:

start_index = torch.argmax(start_scores)
end_index = torch.argmax(end_scores)

好了,我們就可以輸出開始索引和結束索引之間的問題片段了:

print(' '.join(tokens[start_index:end_index+1]))

輸出:

a system of many biological structures and processes within an organism that protects against disease

命名物體識別

在命名物體識別(NER)中,我們的目標是將命名物體分類到預設好的類別中,比如,考慮句子:Jeremy lives in Paris,其中的Jeremy應該被分類為人名,Paris應該分類為地點,

現在,我們學習如何微調預訓練的BERT模型去做NER任務,首先,我們對句子分詞,然后增加[CLS][SEP]標記,接著,我們將這些標記喂給預訓練的BERT模型,得到每個標記的嵌入表示,然后,將這些嵌入表示都喂給一個分類器(前饋網路+softmax函式),最后,該分類器回傳每個命名物體對應的類別,

如下圖所示:

img

參考

Getting Started with Google BERT

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/299460.html

標籤:AI

上一篇:如何閱讀NLP論文

下一篇:《深度學習100例》資料和代碼

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more