為了預訓練第 15.8 節中實現的 BERT 模型,我們需要以理想的格式生成數據集,以促進兩項預訓練任務:掩碼語言建模和下一句預測。一方面,原始的 BERT 模型是在兩個巨大的語料庫 BookCorpus 和英文維基百科(參見第15.8.5 節)的串聯上進行預訓練的,這使得本書的大多數讀者難以運行。另一方面,現成的預訓練 BERT 模型可能不適合醫學等特定領域的應用。因此,在自定義數據集上預訓練 BERT 變得越來越流行。為了便于演示 BERT 預訓練,我們使用較小的語料庫 WikiText-2 ( Merity et al. , 2016 )。
與 15.3節用于預訓練word2vec的PTB數據集相比,WikiText-2(i)保留了原有的標點符號,適合下一句預測;(ii) 保留原始案例和編號;(iii) 大兩倍以上。
在 WikiText-2 數據集中,每一行代表一個段落,其中在任何標點符號及其前面的標記之間插入空格。保留至少兩句話的段落。為了簡單起見,為了拆分句子,我們只使用句點作為分隔符。我們將在本節末尾的練習中討論更復雜的句子拆分技術。
#@save
d2l.DATA_HUB['wikitext-2'] = (
'https://s3.amazonaws.com/research.metamind.io/wikitext/'
'wikitext-2-v1.zip', '3c914d17d80b1459be871a5039ac23e752a53cbe')
#@save
def _read_wiki(data_dir):
file_name = os.path.join(data_dir, 'wiki.train.tokens')
with open(file_name, 'r') as f:
lines = f.readlines()
# Uppercase letters are converted to lowercase ones
paragraphs = [line.strip().lower().split(' . ')
for line in lines if len(line.split(' . ')) >= 2]
random.shuffle(paragraphs)
return paragraphs
#@save
d2l.DATA_HUB['wikitext-2'] = (
'https://s3.amazonaws.com/research.metamind.io/wikitext/'
'wikitext-2-v1.zip', '3c914d17d80b1459be871a5039ac23e752a53cbe')
#@save
def _read_wiki(data_dir):
file_name = os.path.join(data_dir, 'wiki.train.tokens')
with open(file_name, 'r') as f:
lines = f.readlines()
# Uppercase letters are converted to lowercase ones
paragraphs = [line.strip().lower().split(' . ')
for line in lines if len(line.split(' . ')) >= 2]
random.shuffle(paragraphs)
return paragraphs
15.9.1。為預訓練任務定義輔助函數
下面,我們首先為兩個 BERT 預訓練任務實現輔助函數:下一句預測和掩碼語言建模。這些輔助函數將在稍后將原始文本語料庫轉換為理想格式的數據集以預訓練 BERT 時調用。
15.9.1.1。生成下一句預測任務
根據15.8.5.2 節的描述,該 _get_next_sentence
函數為二元分類任務生成一個訓練樣例。
#@save
def _get_next_sentence(sentence, next_sentence, paragraphs):
if random.random() < 0.5:
is_next = True
else:
# `paragraphs` is a list of lists of lists
next_sentence = random.choice(random.choice(paragraphs))
is_next = False
return sentence, next_sentence, is_next
以下函數paragraph
通過調用該 _get_next_sentence
函數從輸入生成用于下一句預測的訓練示例。這paragraph
是一個句子列表,其中每個句子都是一個標記列表。該參數 max_len
指定預訓練期間 BERT 輸入序列的最大長度。
#@save
def _get_nsp_data_from_paragraph(paragraph, paragraphs, vocab, max_len):
nsp_data_from_paragraph = []
for i in range(len(paragraph) - 1):
tokens_a, tokens_b, is_next = _get_next_sentence(
paragraph[i], paragraph[i + 1], paragraphs)
# Consider 1 '' token and 2 '' tokens
if len(tokens_a) + len(tokens_b) + 3 > max_len:
continue
tokens, segments = d2l.get_tokens_and_segments(tokens_a, tokens_b)
nsp_data_from_paragraph.append((tokens, segments, is_next))
return nsp_data_from_paragraph
#@save
def _get_nsp_data_from_paragraph(paragraph, paragraphs, vocab, max_len):
nsp_data_from_paragraph = []
for i in range(len(paragraph) - 1):
tokens_a, tokens_b, is_next = _get_next_sentence(
paragraph[i], paragraph[i + 1], paragraphs)
# Consider 1 '' token and 2 '' tokens
if len(tokens_a) + len(tokens_b) + 3 > max_len:
continue
tokens, segments = d2l.get_tokens_and_segments(tokens_a, tokens_b)
nsp_data_from_paragraph.append((tokens, segments, is_next))
return nsp_data_from_paragraph
15.9.1.2。生成掩碼語言建模任務
為了從 BERT 輸入序列為掩碼語言建模任務生成訓練示例,我們定義了以下 _replace_mlm_tokens
函數。在它的輸入中,tokens
是代表BERT輸入序列的token列表,candidate_pred_positions
是BERT輸入序列的token索引列表,不包括特殊token(masked語言建模任務中不預測特殊token),num_mlm_preds
表示預測(召回 15% 的隨機標記來預測)。遵循第 15.8.5.1 節中屏蔽語言建模任務的定義 ,在每個預測位置,輸入可能被特殊的“”標記或隨機標記替換,或者保持不變。最后,該函數返回可能替換后的輸入標記、發生預測的標記索引以及這些預測的標簽。
#@save
def _replace_mlm_tokens(tokens, candidate_pred_positions, num_mlm_preds,
vocab):
# For the input of a masked language model, make a new copy of tokens and
# replace some of them by '' or random tokens
mlm_input_tokens = [token for token in tokens]
pred_positions_and_labels = []
# Shuffle for getting 15% random tokens for prediction in the masked
# language modeling task
random.shuffle(candidate_pred_positions)
for mlm_pred_position in candidate_pred_positions:
if len(pred_positions_and_labels) >= num_mlm_preds:
break
masked_token = None
# 80% of the time: replace the word with the '' token
if random.random() < 0.8:
masked_token = ''
else:
# 10% of the time: keep the word unchanged
if random.random() < 0.5:
masked_token = tokens[mlm_pred_position]
# 10% of the time: replace the word with a random word
else:
masked_token = random.choice(vocab.idx_to_token)
mlm_input_tokens[mlm_pred_position] = masked_token
pred_positions_and_labels.append(
(mlm_pred_position, tokens[mlm_pred_position]))
return mlm_input_tokens, pred_positions_and_labels
#@save
def _replace_mlm_tokens(tokens, candidate_pred_positions, num_mlm_preds,
vocab):
# For the input of a masked language model, make a new copy of tokens and
# replace some of them by '' or random tokens
mlm_input_tokens = [token for token in tokens]
pred_positions_and_labels = []
# Shuffle for getting 15% random tokens for prediction in the masked
# language modeling task
random.shuffle(candidate_pred_positions)
for mlm_pred_position in candidate_pred_positions:
if len(pred_positions_and_labels) >= num_mlm_preds:
break
masked_token = None
# 80% of the time: replace the word with the '' token
if random.random() < 0.8:
masked_token = ''
else:
# 10% of the time: keep the word unchanged
if random.random() < 0.5:
masked_token = tokens[mlm_pred_position]
# 10% of the time: replace the word with a random word
else:
masked_token = random.choice(vocab.idx_to_token)
mlm_input_tokens[mlm_pred_position] = masked_token
pred_positions_and_labels.append(
(mlm_pred_position, tokens[mlm_pred_position]))
return mlm_input_tokens, pred_positions_and_labels
通過調用上述_replace_mlm_tokens
函數,以下函數將 BERT 輸入序列 ( tokens
) 作為輸入并返回輸入標記的索引(在可能的標記替換之后,如第15.8.5.1 節所述)、發生預測的標記索引和標簽這些預測的指標。
#@save
def _get_mlm_data_from_tokens(tokens, vocab):
candidate_pred_positions = []
# `tokens` is a list of strings
for i, token in enumerate(tokens):
# Special tokens are not predicted in the masked language modeling
# task
if token in ['', '']:
continue
candidate_pred_positions.append(i)
# 15% of random tokens are predicted in the masked language modeling task
num_mlm_preds = max(1, round(len(tokens) * 0.15))
mlm_input_tokens, pred_positions_and_labels = _replace_mlm_tokens(
tokens, candidate_pred_positions, num_mlm_preds, vocab)
pred_positions_and_labels = sorted(pred_positions_and_labels,
key=lambda x: x[0])
pred_positions <
評論