在本章前面的部分中,我們為 SNLI 數據集上的自然語言推理任務(如第 16.4 節所述)設計了一個基于注意力的架構(第16.5節)。現在我們通過微調 BERT 重新審視這個任務。正如16.6 節所討論的 ,自然語言推理是一個序列級文本對分類問題,微調 BERT 只需要一個額外的基于 MLP 的架構,如圖 16.7.1所示。
在本節中,我們將下載預訓練的小型 BERT 版本,然后對其進行微調以在 SNLI 數據集上進行自然語言推理。
16.7.1。加載預訓練的 BERT
我們已經在第 15.9 節和第 15.10 節中解釋了如何在 WikiText-2 數據集上預訓練 BERT (請注意,原始 BERT 模型是在更大的語料庫上預訓練的)。如15.10 節所述,原始 BERT 模型有數億個參數。在下文中,我們提供了兩個版本的預訓練 BERT:“bert.base”與需要大量計算資源進行微調的原始 BERT 基礎模型差不多大,而“bert.small”是一個小版本方便演示。
預訓練的 BERT 模型都包含一個定義詞匯集的“vocab.json”文件和一個預訓練參數的“pretrained.params”文件。我們實現以下load_pretrained_model
函數來加載預訓練的 BERT 參數。
def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,
num_heads, num_blks, dropout, max_len, devices):
data_dir = d2l.download_extract(pretrained_model)
# Define an empty vocabulary to load the predefined vocabulary
vocab = d2l.Vocab()
vocab.idx_to_token = json.load(open(os.path.join(data_dir, 'vocab.json')))
vocab.token_to_idx = {token: idx for idx, token in enumerate(
vocab.idx_to_token)}
bert = d2l.BERTModel(
len(vocab), num_hiddens, ffn_num_hiddens=ffn_num_hiddens, num_heads=4,
num_blks=2, dropout=0.2, max_len=max_len)
# Load pretrained BERT parameters
bert.load_state_dict(torch.load(os.path.join(data_dir,
'pretrained.params')))
return bert, vocab
def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,
num_heads, num_blks, dropout, max_len, devices):
data_dir = d2l.download_extract(pretrained_model)
# Define an empty vocabulary to load the predefined vocabulary
vocab = d2l.Vocab()
vocab.idx_to_token = json.load(open(os.path.join(data_dir, 'vocab.json')))
vocab.token_to_idx = {token: idx for idx, token in enumerate(
vocab.idx_to_token)}
bert = d2l.BERTModel(len(vocab), num_hiddens, ffn_num_hiddens, num_heads,
num_blks, dropout, max_len)
# Load pretrained BERT parameters
bert.load_parameters(os.path.join(data_dir, 'pretrained.params'),
ctx=devices)
return bert, vocab
為了便于在大多數機器上進行演示,我們將在本節中加載和微調預訓練 BERT 的小型版本(“bert.small”)。在練習中,我們將展示如何微調更大的“bert.base”以顯著提高測試準確性。
Downloading ../data/bert.small.torch.zip from http://d2l-data.s3-accelerate.amazonaws.com/bert.small.torch.zip...
16.7.2。微調 BERT 的數據集
對于 SNLI 數據集上的下游任務自然語言推理,我們定義了一個自定義的數據集類SNLIBERTDataset
。在每個示例中,前提和假設形成一對文本序列,并被打包到一個 BERT 輸入序列中,如圖 16.6.2所示。回想第 15.8.4 節 ,段 ID 用于區分 BERT 輸入序列中的前提和假設。對于 BERT 輸入序列 ( max_len
) 的預定義最大長度,輸入文本對中較長者的最后一個標記會不斷被刪除,直到max_len
滿足為止。為了加速生成用于微調 BERT 的 SNLI 數據集,我們使用 4 個工作進程并行生成訓練或測試示例。
class SNLIBERTDataset(torch.utils.data.Dataset):
def __init__(self, dataset, max_len, vocab=None):
all_premise_hypothesis_tokens = [[
p_tokens, h_tokens] for p_tokens, h_tokens in zip(
*[d2l.tokenize([s.lower() for s in sentences])
for sentences in dataset[:2]])]
self.labels = torch.tensor(dataset[2])
self.vocab = vocab
self.max_len = max_len
(self.all_token_ids, self.all_segments,
self.valid_lens) = self._preprocess(all_premise_hypothesis_tokens)
print('read ' + str(len(self.all_token_ids)) + ' examples')
def _preprocess(self, all_premise_hypothesis_tokens):
pool = multiprocessing.Pool(4) # Use 4 worker processes
out = pool.map(self._mp_worker, all_premise_hypothesis_tokens)
all_token_ids = [
token_ids for token_ids, segments, valid_len in out]
all_segments = [segments for token_ids, segments, valid_len in out]
valid_lens = [valid_len for token_ids, segments, valid_len in out]
return (torch.tensor(all_token_ids, dtype=torch.long),
torch.tensor(all_segments, dtype=torch.long),
torch.tensor(valid_lens))
def _mp_worker(self, premise_hypothesis_tokens):
p_tokens, h_tokens = premise_hypothesis_tokens
self._truncate_pair_of_tokens(p_tokens, h_tokens)
tokens, segments = d2l.get_tokens_and_segments(p_tokens, h_tokens)
token_ids = self.vocab[tokens] + [self.vocab['']] \
* (self.max_len - len(tokens))
segments = segments + [0] * (self.max_len - len(segments))
valid_len = len(tokens)
return token_ids, segments, valid_len
def _truncate_pair_of_tokens(self, p_tokens, h_tokens):
# Reserve slots for '', '', and '' tokens for the BERT
# input
while len(p_tokens) + len(h_tokens) > self.max_len - 3:
if len(p_tokens) > len(h_tokens):
p_tokens.pop()
else:
h_tokens.pop()
def __getitem__(self, idx):
return (self.all_token_ids[idx], self.all_segments[idx],
self.valid_lens[idx]), self.labels[idx]
def __len__(self):
return len(self.all_token_ids)
class SNLIBERTDataset(gluon.data.Dataset):
def __init__(self, dataset, max_len, vocab=None):
all_premise_hypothesis_tokens = [[
p_tokens, h_tokens
評論
查看更多