海口什么网站建设,广州的互联网公司,舆情报告制度,建设邯郸网站#x1f468;#x1f393;作者简介#xff1a;一位即将上大四#xff0c;正专攻机器学习的保研er #x1f30c;上期文章#xff1a;机器学习深度学习——针对序列级和词元级应用微调BERT #x1f4da;订阅专栏#xff1a;机器学习深度学习 希望文… 作者简介一位即将上大四正专攻机器学习的保研er 上期文章机器学习深度学习——针对序列级和词元级应用微调BERT 订阅专栏机器学习深度学习 希望文章对你们有所帮助 NLP实战自然语言推断——微调BERT实现 引入加载预训练的BERT微调BERT的数据集微调BERT小结 引入
在之前已经为SNLI数据集上的自然语言推断任务设计了一个基于注意力的结构文章链接 机器学习深度学习——NLP实战自然语言推断——注意力机制实现 现在我们通过微调BERT来重新审视这项任务。正如上一节讨论的那样自然语言推断是一个序列级别的文本对分类问题而微调BERT只需要一个额外的基于多层感知机的架构如下图所示 这边将下载一个已经预训练好的小版本BERT然后对其进行微调一遍在SNLI数据集上进行自然语言推断。
import json
import multiprocessing
import os
import torch
from torch import nn
from d2l import torch as d2l加载预训练的BERT
原始的BERT模型有数以亿计的参数。在下面我们提供了两个版本的预训练BERT“bert.base”与原始BERT基础模型一样大需要大量计算资源才能进行微调而“bert.small”是一个小版本以便于演示。
d2l.DATA_HUB[bert.base] (d2l.DATA_URL bert.base.torch.zip,225d66f04cae318b841a13d32af3acc165f253ac)
d2l.DATA_HUB[bert.small] (d2l.DATA_URL bert.small.torch.zip,c72329e68a732bef0452e4b96a1c341c8910f81f)两个预训练好的BERT模型都包含一个定义词表的“vocab.json”文件和一个预训练参数的“pretrained.params”文件。我们实现了以下load_pretrained_model函数来加载预先训练好的BERT参数。
def load_pretrained_model(pretrained_model, num_hiddens, ffn_num_hiddens,num_heads, num_layers, dropout, max_len, devices):data_dir d2l.download_extract(pretrained_model)# 定义空词表以加载预定义词表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, norm_shape[256],ffn_num_input256, ffn_num_hiddensffn_num_hiddens,num_heads4, num_layers2, dropout0.2,max_lenmax_len, key_size256, query_size256,value_size256, hid_in_features256,mlm_in_features256, nsp_in_features256)# 加载预训练BERT参数bert.load_state_dict(torch.load(os.path.join(data_dir,pretrained.params)))return bert, vocab为了便于在大多数机器上演示我们将在本节中加载和微调经过预训练BERT的小版本“bert.small”。在练习中我们将展示如何微调大得多的“bert.base”以显著提高测试精度。
devices d2l.try_all_gpus()
bert, vocab load_pretrained_model(bert.small, num_hiddens256, ffn_num_hiddens512, num_heads4,num_layers2, dropout0.1, max_len512, devicesdevices)微调BERT的数据集
对于SNLI数据集的下游任务自然语言推断我们定义了一个定制的数据集类SNLIBERTDataset。在每个样本中前提和假设形成一对文本序列并被打包成一个BERT输入序列。片段索引用于区分BERT输入序列中的前提和假设。利用预定义的BERT输入序列的最大长度max_len持续移除输入文本对中较长文本的最后一个标记直到满足max_len。为了加速生成用于微调BERT的SNLI数据集我们使用4个工作进程并行生成训练或测试样本。
class SNLIBERTDataset(torch.utils.data.Dataset):def __init__(self, dataset, max_len, vocabNone):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 vocabself.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) # 使用4个进程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, dtypetorch.long),torch.tensor(all_segments, dtypetorch.long),torch.tensor(valid_lens))def _mp_worker(self, premise_hypothesis_tokens):p_tokens, h_tokens premise_hypothesis_tokensself._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[pad]] \* (self.max_len - len(tokens))segments segments [0] * (self.max_len - len(segments))valid_len len(tokens)return token_ids, segments, valid_lendef _truncate_pair_of_tokens(self, p_tokens, h_tokens):# 为BERT输入中的CLS、SEP和SEP词元保留位置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)读取完SNLI数据集后我们通过实例化SNLIBERTDataset类来生成训练和测试样本。这些样本将在自然语言推断的训练和测试期间进行小批量读取。
# 如果出现显存不足错误请减少“batch_size”。在原始的BERT模型中max_len512
batch_size, max_len, num_workers 512, 128, d2l.get_dataloader_workers()
data_dir D:\Python\pytorch\data\snli_1.0\snli_1.0
train_set SNLIBERTDataset(d2l.read_snli(data_dir, True), max_len, vocab)
test_set SNLIBERTDataset(d2l.read_snli(data_dir, False), max_len, vocab)
train_iter torch.utils.data.DataLoader(train_set, batch_size, shuffleTrue,num_workersnum_workers)
test_iter torch.utils.data.DataLoader(test_set, batch_size,num_workersnum_workers)微调BERT
用于自然语言推断的微调BERT只需要一个额外的多层感知机该多层感知机由两个全连接层组成下面代码的self.hidden和self.output。这个多层感知机将特殊的“cls”词元的BERT表示进行了转换该词元同时编码前提和假设的信息为自然语言推断的三个输出蕴涵、矛盾和中性。
class BERTClassifier(nn.Module):def __init__(self, bert):super(BERTClassifier, self).__init__()self.encoder bert.encoderself.hidden bert.hiddenself.output nn.Linear(256, 3)def forward(self, inputs):tokens_X, segments_X, valid_lens_x inputsencoded_X self.encoder(tokens_X, segments_X, valid_lens_x)return self.output(self.hidden(encoded_X[:, 0, :]))在下文中预训练的BERT模型bert被送到用于下游应用的BERTClassifier实例net中。在BERT微调的常见实现中只有额外的多层感知机net.output的输出层的参数将从零开始学习。预训练BERT编码器net.encoder和额外的多层感知机的隐藏层net.hidden的所有参数都将进行微调。
net BERTClassifier(bert)回想一下在之前的文章 机器学习深度学习——BERT来自transformer的双向编码器表示 其中我们的MaskLM类和NextSentencePred类在其使用的多层感知机中都有一些参数。这些参数是预训练BERT模型bert中参数的一部分因此是net中参数的一部分。然而这些参数仅用于计算预训练过程中的遮蔽语言模型损失和下一句预测损失。这两个损失函数与微调下游应用无关因此当BERT微调时MaskLM和NextSentencePred中采用的多层感知机的参数不会更新陈旧的staled。 为了允许具有陈旧梯度的参数标志ignore_stale_gradTrue在step函数d2l.train_batch_ch13中被设置。我们通过该函数使用SNLI的训练集train_iter和测试集test_iter对net模型进行训练和评估。
lr, num_epochs 1e-4, 5
trainer torch.optim.Adam(net.parameters(), lrlr)
loss nn.CrossEntropyLoss(reductionnone)
d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs,devices)运行结果 loss 0.520, train acc 0.790, test acc 0.779 446.5 examples/sec on [device(type‘cpu’)] 运行图片 如果计算资源允许比如咱们去autodl平台上租借GPU以后可以微调一个更大的预训练BERT模型修改load_pretrained_model函数中的参数设置将“bert.small”替换为“bert.base”将num_hiddens256、ffn_num_hiddens512、num_heads4和num_layers2的值分别增加到768、3072、12和12。这样的测试精度应该是会高于0.86的。
小结
1、我们可以针对下游应用对预训练的BERT模型进行微调例如在SNLI数据集上进行自然语言推断。 2、在微调过程中BERT模型成为下游应用模型的一部分。仅与训练前损失相关的参数在微调期间不会更新。