1 引言
各位朋友大家好,欢迎来到月来客栈,我是掌柜空字符。
经过前面四篇文章(BERT原理与NSL和MLM[1]、BERT模型的分步实现[2]、基于BERT预训练模型的文本分类任务[3]、基于BERT预训练模型的文本蕴含任务[4])的介绍,相信大家对于BERT预训练模型的使用已经有了的认识。不过为了满足不同人群的学习需求,在这篇文章中掌柜将会介绍基于BERT预训练模型的第三个下游任务场景,即如何完成推理问答选择任务。所谓问答选择指的就是同时给模型输入一个问题和若干选项的答案,最后需要模型从给定的选项中选择一个最符合问题逻辑的答案。可问题在于我们应该怎么来构建这个模型呢?
通常来说,在NLP领域的很多场景中模型最后所做的基本上都是一个分类任务,虽然表面上看起来不是。例如:文本蕴含任务其实就是将两个序列拼接在一起,然后预测其所属的类别;基于神经网络的序列生成模型(翻译、文本生成等)本质就是预测词表中下一个最有可能出现的词,此时的分类类别就是词表的大小。因此,从本质上来说本文介绍的问答选择任务以及在下一篇文章中将要介绍的问题回答任务其实都是一个分类任务,而关键的地方就在于如何构建模型的输入和输出。
以下所有完整示例代码均可从仓库 https://github.com/moon-hotel/BertWithPretrained 中获取!
2 模型构建
2.1 构建原理
正如前面所说,对于问答选择这个任务场景来说其本质上依旧可以归结为分类任务,只是关键在于如何构建这个任务以及整个数据集。对于问答选择这个场景来说,其整体原理如图1所示。
如图1所示,是一个基于BERT预训练模型的四选一问答选择模型的原理图。从图中可以看出,原始数据的形式是一个问题和四个选项,模型需要做的就是从四个选项中给出最合理的一个,于是也就变成了一个四分类任务。同时,构建模型输入的方式就是将原始问题和每一个答案都拼接起来构成一个序列中间用[SEP]
符号隔开,然后再分别输入到BERT模型中进行特征提取得到四个特征向量形状为[4,hidden_size]
,最后再经过一个分类层进行分类处理得到预测选项。值得一提的是,通常情况下这里的四个特征都是直接取每个序列经BERT编码后的[CLS]
向量。
到此,对于问答选择整个模型的原理我们算是清楚了,下面首先来看如何构造数据集。
2.2 语料介绍
在这里,我们使用到的也是论文中所提到的SWAG(The Situations With Adversarial Generations )数据集[5] [6],即给定一个情景(一个问题或一句描述),任务是模型从给定的四个选项中预测最有可能的一个。
如下所示便是部分原始示例数据:
xxxxxxxxxx
31,video-id,fold-ind,startphrase,sent1,sent2,gold-source,ending0,ending1,ending2,ending3,label
20,anetv_NttjvRpSdsI,19391,The people are in robes. They,The people are in robes.,They,gold,are wearing colorful costumes.,are doing karate moves on the floor.,shake hands on their hips.,do a flip to the bag.,0
31,lsmdc3057_ROBIN_HOOD-27684,16344,She smirks at someone and rides off. He,She smirks at someone and rides off.,He,gold,smiles and falls heavily.,wears a bashful smile.,kneels down behind her.,gives him a playful glance.,1
如上所示数据集中一共有12个字段包含两个样本,我们这里需要用到的就是sent1,ending0,ending1,ending2,ending3,label这6个字段。例如对于第一个样本来说,其形式如下:
xxxxxxxxxx
51The people are in robes. They
2 A) wearing colorful costumes.# 正确选项
3 B) are doing karate moves on the floor.
4 C) shake hands on their hips.
5 D) do a flip to the bag.
同时,由于该数据集已经做了训练集、验证集和测试集(没有标签)的划分,所以后续我们也就不需要来手动划分了。
后台回复“数据集”即可获取网盘链接!
2.3 数据集预览
同样,在正式介绍如何构建数据集之前我们先通过一张图来了解一下整个大致构建的流程。假如我们现在有两个样本构成了一个batch,那么其整个数据的处理过程则如图2所示。
如图2所示,首先对于原始数据的每个样本(一个问题和四个选项),需要将问题同每个选项拼接在一起构造成为四个序列并添加上对应的分类符[CLS]
和分隔符[SEP]
,即图中的第①步重构样本。紧接着需要将第①步构造得到的序列转换得到Token id并进行padding处理,此时便得到了一个形状为[batch_size,num_choice,seq_len]
的三维矩阵,即图2中第2步处理完成后形状为[2,4,19]
的结果。同时,在第②步中还要根据每个序列构造得到相应的attention_mask
向量和token_types_ids
向量(图中未画出),并且两者的形状也是[batch_size,num_choice,seq_len]
。
其次是将第②步处理后的结果变形成[batch_size*num_choice,seq_len]
的二维形式,因为BERT模型接收输入形式便是一个二维的矩阵。在经过BERT模型进行特征提取后,将会得到一个形状为[batch_size*num_choice,hidden_size]
的二维矩阵,最后再乘上一个形状为[hidden_size,1]
的矩阵并变形成[batch_size,num_choice]
即可完成整个分类任务。
2.4 数据集构建
在说完数据集构造的整理思路后,下面我们就来正式编码实现整个数据集的构造过程。同样,对于数据预处理部分我们可以继续继承之前文本分类处理的这个类LoadSingleSentenceClassificationDataset
,然后再稍微修改其中的部分方法即可。同时,由于在前两个示例[3] [4]中已经就tokenize和词表构建等内容做了详细的介绍,所以这部分内容就不再赘述。
第1步:重构样本和Tokenize
如图2过程所示,需要对原始样本进行重构以及转换得到每个序列对应的Token id,下面首先是在data_process()
函数中来定义如何读取原始数据:
xxxxxxxxxx
151class LoadMultipleChoiceDataset(LoadSingleSentenceClassificationDataset):
2 def __init__(self, num_choice=4, **kwargs):
3 super(LoadMultipleChoiceDataset, self).__init__(**kwargs)
4 self.num_choice = num_choice
5
6 def data_process(self, filepath):
7 data = pd.read_csv(filepath)
8 questions = data['startphrase']
9 answers0, answers1 = data['ending0'], data['ending1']
10 answers2, answers3 = data['ending2'], data['ending3']
11 labels = [-1] * len(questions)
12 if 'label' in data: # 测试集中没有标签
13 labels = data['label']
14 all_data = []
15 max_len = 0
在上述代码中,第1-4行用于继承之前的LoadSingleSentenceClassificationDataset
类以及添加一个新的参数num_choice
也就是分类数;第7-10行则是根据文件路径来读取原始数据并按对应字段取得问题和答案;第11-13行则是用来判断是否存在正确标签,因为测试集中不含有标签;max_len
则是用来保存数据集中最长序列的长度。
下面则是进一步对数据进行处理:
xxxxxxxxxx
221 def data_process(self, filepath):
2 # ...接上面代码...
3 for i in tqdm(range(len(questions)), ncols=80):
4 # 将问题中的每个word转换为字典中的token id
5 t_q = [self.vocab[token] for token in self.tokenizer(questions[i])]
6 t_q = [self.CLS_IDX] + t_q + [self.SEP_IDX]
7 # 将答案中的每个word转换为字典中的token id
8 t_a0 = [self.vocab[token] for token in self.tokenizer(answers0[i])]
9 t_a1 = [self.vocab[token] for token in self.tokenizer(answers1[i])]
10 t_a2 = [self.vocab[token] for token in self.tokenizer(answers2[i])]
11 t_a3 = [self.vocab[token] for token in self.tokenizer(answers3[i])]
12 # 计算最长序列的长度
13 max_len = max(max_len, len(t_q) + max(len(t_a0), len(t_a1), len(t_a2), len(t_a3)))
14 seg_q = [0] * len(t_q)
15 # 加1表示还要加上问题和答案组合后最后一个[SEP]的长度
16 seg_a0 = [1] * (len(t_a0) + 1)
17 seg_a1 = [1] * (len(t_a1) + 1)
18 seg_a2 = [1] * (len(t_a2) + 1)
19 seg_a3 = [1] * (len(t_a3) + 1)
20 all_data.append((t_q, t_a0, t_a1, t_a2, t_a3, seg_q,
21 seg_a0, seg_a1, seg_a2, seg_a3, labels[i]))
22 return all_data, max_len
在上述代码中,第3行为一个循环用来遍历每一个问题以及对应的答案;第5-6行是将原始问题根据词表转换为对应的Token id,同时在序列的起止位置分别加上[CLS]
和[SEP]
符号;第8-13行是将每个问题对应的四个选项转换为对应的Token id,以及保存最大序列的长度;第14-19行是用来构造对应的token_type_ids向量;第20-22行是分别将每一个问题以及对应的四个选项处理后的结果保存和返回最后的结果。注意,这里还没有将每个问题同对应的四个选项进行拼接。
第2步:拼接与padding
在处理得到每个问题以及对应选项的Token id和token_type_ids
后,我们再来定义一个generate_batch()
方法对每个batch中的数据集进行拼接和padding处理,代码如下:
xxxxxxxxxx
211 def generate_batch(self, data_batch):
2 batch_qa, batch_seg, batch_label = [], [], []
3 def get_seq(q, a):
4 seq = q + a
5 if len(seq) > self.max_position_embeddings - 1:
6 seq = seq[:self.max_position_embeddings - 1]
7 return torch.tensor(seq + [self.SEP_IDX], dtype=torch.long)
8 for item in data_batch:
9 # 得到 每个问题组合其中一个答案的 input_ids 序列
10 tmp_qa = [get_seq(item[0], item[1]),
11 get_seq(item[0], item[2]),
12 get_seq(item[0], item[3]),
13 get_seq(item[0], item[4])]
14 # 得到 每个问题组合其中一个答案的 token_type_ids
15 tmp_seg = [torch.tensor(item[5] + item[6], dtype=torch.long),
16 torch.tensor(item[5] + item[7], dtype=torch.long),
17 torch.tensor(item[5] + item[8], dtype=torch.long),
18 torch.tensor(item[5] + item[9], dtype=torch.long)]
19 batch_qa.extend(tmp_qa)
20 batch_seg.extend(tmp_seg)
21 batch_label.append(item[-1])
在上述代码中,第3-7行的get_seq()
方法用于根据传入的问题Token id和答案Token id拼接得到一个完整的Token id并将超过长度的部分进行截取;第8-13行则是将每个问题分别与其对应的四个选项进行拼接;第15-18行是分别构造得到每个问题与其对应的四个选项所形成的token_type_ids
向量;最后3行则是保存每个Batch所有样本处理好的结果。
在完成上述处理后,接下来就是分别对各部分的输入进行padding处理并返回相应的结果,代码如下:
xxxxxxxxxx
181 def generate_batch(self, data_batch):
2 # 接上面代码##
3 batch_qa = pad_sequence(batch_qa, # [batch_size*num_choice,max_len]
4 padding_value=self.PAD_IDX,
5 batch_first=True,
6 max_len=self.max_sen_len)
7 batch_mask = (batch_qa == self.PAD_IDX).view(
8 [-1, self.num_choice, batch_qa.size(-1)])
9 # batch_qa: reshape 至 [batch_size, num_choice, max_len]
10 batch_qa = batch_qa.view([-1, self.num_choice, batch_qa.size(-1)])
11 batch_seg = pad_sequence(batch_seg, # [batch_size*num_choice,max_len]
12 padding_value=self.PAD_IDX,
13 batch_first=True,
14 max_len=self.max_sen_len)
15 # batch_seg: reshape 至 [batch_size, num_choice, max_len]
16 batch_seg = batch_seg.view([-1, self.num_choice, batch_seg.size(-1)])
17 batch_label = torch.tensor(batch_label, dtype=torch.long)
18 return batch_qa, batch_seg, batch_mask, batch_label
此处关于pad_sequence()
函数的详细介绍可以参见文章[3]。
第3步:使用示例
在完成上述两个步骤之后,整个数据集的构建就算是已经基本完成了,可以通过如下代码进行数据集的载入:
xxxxxxxxxx
321from utils.data_helpers import LoadMultipleChoiceDataset
2from Tasks.TaskForMultipleChoice import ModelConfig
3from transformers import BertTokenizer
4
5if __name__ == '__main__':
6 model_config = ModelConfig()
7 load_dataset = LoadMultipleChoiceDataset(vocab_path=model_config.vocab_path,
8 tokenizer=BertTokenizer.from_pretrained(
9 model_config.pretrained_model_dir).tokenize,
10 batch_size=2,
11 max_sen_len=None,
12 max_position_embeddings=512,
13 pad_index=0,
14 is_sample_shuffle=False,
15 num_choice=model_config.num_labels)
16 train_iter, test_iter, val_iter = \
17 load_dataset.load_train_val_test_data(model_config.train_file_path,
18 model_config.val_file_path,
19 model_config.test_file_path)
20 for qa, seg, mask, label in test_iter:
21 print(" ### input ids:")
22 print(qa.shape) # [batch_size,num_choice, max_len]
23 print(qa[0])
24 print(" ### attention mask:")
25 print(mask.shape)
26 print(mask[0])
27 print(" ### token type ids:")
28 print(seg.shape) # [batch_size,num_choice, max_len]
29 print(seg[0])
30 print(label.shape)
31 trans_to_words(qa[0], load_dataset.vocab.itos)
32 break
上述代码运行结束后的输出结果如下所示:
xxxxxxxxxx
281 ### input ids:
2torch.Size([2, 4, 19]) # [batch_size,num_choice, max_len]
3tensor([[ 101, 1996, 2111, 2024, 1999, 17925, 1012, 2027, 102, 2024,
4 4147, 14231, 12703, 1012, 102, 0, 0, 0, 0],
5 [ 101, 1996, 2111, 2024, 1999, 17925, 1012, 2027, 102, 2024,
6 2725, 16894, 5829, 2006, 1996, 2723, 1012, 102, 0],
7 [ 101, 1996, 2111, 2024, 1999, 17925, 1012, 2027, 102, 6073,
8 2398, 2006, 2037, 6700, 1012, 102, 0, 0, 0],
9 [ 101, 1996, 2111, 2024, 1999, 17925, 1012, 2027, 102, 2079,
10 1037, 11238, 2000, 1996, 4524, 1012, 102, 0, 0]])
11 ### attention mask:
12torch.Size([2, 4, 19]) # [batch_size,num_choice, max_len]
13tensor([[False, False, ....., False, True, True, True, True],
14 [False, False, ....., False, False, False, False, True],
15 [False, False, ....., False, False, True, True, True],
16 [False, False, ....., False, False, False, True, True]])
17 ### token type ids:
18torch.Size([2, 4, 19]) # [batch_size,num_choice, max_len]
19tensor([[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
20 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0],
21 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0],
22 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]])
23torch.Size([2])
24Question and Answer:
25[CLS] the people are in robes . they [SEP] are wearing colorful costumes . [SEP] [PAD] [PAD] [PAD] [PAD]
26[CLS] the people are in robes . they [SEP] are doing karate moves on the floor . [SEP] [PAD]
27[CLS] the people are in robes . they [SEP] shake hands on their hips . [SEP] [PAD] [PAD] [PAD]
28[CLS] the people are in robes . they [SEP] do a flip to the bag . [SEP] [PAD] [PAD]
在上述结果中,其中第25-28行为根据Token id再转换为字符串后的结果。
到此,对于整个数据集的构建过程就介绍完了,下面掌柜开始继续介绍问答选择模型的实现内容。
3 问答选择模型
3.1 前向传播
正如第1节内容所介绍,我们只需要在原始BERT模型的基础上再加一个分类层即可,因此这部分代码相对来说也比较容易理解。首先需要定义一个类以及相应的初始化函数,如下:
xxxxxxxxxx
131class BertForMultipleChoice(nn.Module):
2 """
3 用于类似SWAG数据集的下游任务
4 """
5 def __init__(self, config, bert_pretrained_model_dir=None):
6 super(BertForMultipleChoice, self).__init__()
7 self.num_choice = config.num_labels
8 if bert_pretrained_model_dir is not None:
9 self.bert = BertModel.from_pretrained(config, bert_pretrained_model_dir)
10 else:
11 self.bert = BertModel(config)
12 self.dropout = nn.Dropout(config.hidden_dropout_prob)
13 self.classifier = nn.Linear(config.hidden_size, 1)
在上述代码中,第8-11行便是根据相应的条件返回一个BERT模型,第13行则是定义了一个分类层。
然后再是定义完成整个前向传播过程,代码如下:
xxxxxxxxxx
231 def forward(self, input_ids,
2 attention_mask=None,
3 token_type_ids=None,
4 position_ids=None,
5 labels=None):
6 flat_input_ids = input_ids.view(-1, input_ids.size(-1)).transpose(0, 1)
7 flat_token_type_ids = token_type_ids.view(-1, token_type_ids.size(-1)).transpose(0, 1)
8 flat_attention_mask = attention_mask.view(-1, token_type_ids.size(-1))
9
10 pooled_output, _ = self.bert(
11 input_ids=flat_input_ids, # [src_len,batch_size*num_choice]
12 attention_mask=flat_attention_mask, # [batch_size*num_choice,src_len]
13 token_type_ids=flat_token_type_ids, # [src_len,batch_size*num_choice]
14 position_ids=position_ids)
15 pooled_output = self.dropout(pooled_output) # [batch_size*num_choice, hidden_size]
16 logits = self.classifier(pooled_output) # [batch_size*num_choice, 1]
17 shaped_logits = logits.view(-1, self.num_choice) # [batch_size, num_choice]
18 if labels is not None:
19 loss_fct = nn.CrossEntropyLoss()
20 loss = loss_fct(shaped_logits, labels.view(-1))
21 return loss, shaped_logits
22 else:
23 return shaped_logits
在上述代码中,第6-8行用于将三维的输入变成二维的输入(也就是图2中的第③步),这是因为BERT所接收的输入形式便是两个维度;同时根据需要还将src_len
这个维度放到了最前面。第10-14行则是通过原始的BERT模型提取得到每个序列(指的是每个问题和其中一个选项所构成的序列,即图2中第③步后的每一行)的特征表示,其形状为[batch_size*num_choice, hidden_size]
;第16-17行则是先进行分类处理,然后再变形得到每个问题所对应预测选项的logits
值,形状为[batch_size, num_choice]
;第18-23行则是根据相应的判断条件返回损失或者logits
值。
3.2 模型训练
首先,我们需要定义一个ModelConfig
类来对分类模型中的超参数以及其它变量进行管理,代码如下所示:
xxxxxxxxxx
331class ModelConfig:
2 def __init__(self):
3 self.project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
4 self.dataset_dir = os.path.join(self.project_dir, 'data', 'MultipleChoice')
5 self.pretrained_model_dir = os.path.join(self.project_dir, "bert_base_uncased_english")
6 self.vocab_path = os.path.join(self.pretrained_model_dir, 'vocab.txt')
7 self.device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
8 self.train_file_path = os.path.join(self.dataset_dir, 'train.csv')
9 self.val_file_path = os.path.join(self.dataset_dir, 'val.csv')
10 self.test_file_path = os.path.join(self.dataset_dir, 'test.csv')
11 self.model_save_dir = os.path.join(self.project_dir, 'cache')
12 self.logs_save_dir = os.path.join(self.project_dir, 'logs')
13 self.is_sample_shuffle = True
14 self.batch_size = 16
15 self.max_sen_len = None
16 self.num_labels = 4 # num_choice
17 self.learning_rate = 2e-5
18 self.epochs = 10
19 self.model_val_per_epoch = 2
20 logger_init(log_file_name='choice', log_level=logging.INFO,
21 log_dir=self.logs_save_dir)
22 if not os.path.exists(self.model_save_dir):
23 os.makedirs(self.model_save_dir)
24
25 # 把原始bert中的配置参数也导入进来
26 bert_config_path = os.path.join(self.pretrained_model_dir, "config.json")
27 bert_config = BertConfig.from_json_file(bert_config_path)
28 for key, value in bert_config.__dict__.items():
29 self.__dict__[key] = value
30 # 将当前配置打印到日志文件中
31 logging.info(" ### 将当前配置打印到日志文件中 ")
32 for key, value in self.__dict__.items():
33 logging.info(f"### {key} = {value}")
在上述代码中,第3-12行分别用来获取各个文件的所在路径;第13-19行则是设置模型对应的超参数;第20-33行则是日志打印的相关信息。
同时,为了展示训练时的预测结果,这里我们需要写一个函数来进行格式化:
xxxxxxxxxx
201def show_result(qas, y_pred, itos=None, num_show=5):
2 count = 0
3 num_samples, num_choice, seq_len = qas.size()
4 qas = qas.reshape(-1)
5 strs = np.array([itos[t] for t in qas]).reshape(-1, seq_len)
6 for i in range(num_samples): # 遍历每个样本
7 s_idx = i * num_choice
8 e_idx = s_idx + num_choice
9 sample = strs[s_idx:e_idx]
10 if count == num_show:
11 return
12 count += 1
13 for j, item in enumerate(sample): # 每个样本的四个答案
14 q, a, _ = " ".join(item[1:]).replace(" .", ".").replace(" ##", "").split('[SEP]')
15 if y_pred[i] == j:
16 a += " ## True"
17 else:
18 a += " ## False"
19 logging.info(f"[{count + 1}/{num_show}] ### {q + a}")
20 logging.info("\n")
在上述函数调用结束可以输出类似如下所示的结果:
xxxxxxxxxx
51[2021-11-11 21:10:38] - INFO: the people are in robes. they are wearing colorful costumes. ## False
2[2021-11-11 21:10:38] - INFO: the people are in robes. they are doing karate moves on the floor. ## True
3[2021-11-11 21:10:38] - INFO: the people are in robes. they shake hands on their hips. ## False
4[2021-11-11 21:10:38] - INFO: the people are in robes. they do a flip to the bag. ## False
5[2021-11-11 21:10:38] - INFO:
最后,我们便可以通过如下方法完成整个模型的微调:
xxxxxxxxxx
441def train(config):
2 model = BertForMultipleChoice(config,
3 config.pretrained_model_dir)
4 model_save_path = os.path.join(config.model_save_dir, 'model.pt')
5 if os.path.exists(model_save_path):
6 loaded_paras = torch.load(model_save_path)
7 model.load_state_dict(loaded_paras)
8 logging.info("## 成功载入已有模型,进行追加训练......")
9 # .....
10 bert_tokenize = BertTokenizer.from_pretrained(config.pretrained_model_dir).tokenize
11 data_loader = LoadMultipleChoiceDataset(
12 vocab_path=config.vocab_path,
13 # ......
14 )
15 train_iter, test_iter, val_iter = \
16 data_loader.load_train_val_test_data(config.train_file_path,
17 config.val_file_path,
18 config.test_file_path)
19 max_acc = 0
20 for epoch in range(config.epochs):
21 losses = 0
22 start_time = time.time()
23 for idx, (qa, seg, mask, label) in enumerate(train_iter):
24 qa = qa.to(config.device) # [src_len, batch_size]
25 label = label.to(config.device)
26 seg = seg.to(config.device)
27 mask = mask.to(config.device)
28 loss, logits = model(
29 input_ids=qa,
30 # ......
31 labels=label)
32 # ......
33 acc = (logits.argmax(1) == label).float().mean()
34 if idx % 10 == 0:
35 logging.info(f"Epoch: {epoch}, Batch[{idx}/{len(train_iter)}], "
36 f"Train loss :{loss.item():.3f}, Train acc: {acc:.3f}")
37 train_loss = losses / len(train_iter)
38 logging.info(f"Epoch: {epoch}, Train loss: "
39 f"{train_loss:.3f}, Epoch time = {(end_time - start_time):.3f}s")
40 if (epoch + 1) % config.model_val_per_epoch == 0:
41 acc, y_pred = evaluate(val_iter, model,
42 config.device, inference=False)
43 show_result(val_iter, y_pred, data_loader.vocab.itos)
44 logging.info(f"Accuracy on val {acc:.3f}")
在上述代码中,第2-3行用来根据指定预训练模型的路径初始化一个基于BERT的文本分类模型;第9-18行则是载入相应的数据集;第20-44行则是整个模型的训练过程,完整示例代码可在仓库[7]中进行获取。
如下便是网络的训练结果:
xxxxxxxxxx
121[2021-11-11 21:32:50] - INFO: Epoch: 0, Batch[0/4597], Train loss :1.433, Train acc: 0.250
2[2021-11-11 21:32:58] - INFO: Epoch: 0, Batch[10/4597], Train loss :1.277, Train acc: 0.438
3[2021-11-11 21:33:01] - INFO: Epoch: 0, Batch[20/4597], Train loss :1.249, Train acc: 0.438
4 ......
5[2021-11-11 21:58:34] - INFO: Epoch: 0, Batch[4590/4597], Train loss :0.489, Train acc: 0.875
6[2021-11-11 21:58:36] - INFO: Epoch: 0, Batch loss :0.786, Epoch time = 1546.173s
7[2021-11-11 21:28:55] - INFO: Epoch: 0, Batch[0/4597], Train loss :1.433, Train acc: 0.250
8[2021-11-11 21:30:52] - INFO: He is throwing darts at a wall. A woman, squats alongside flies side to side with his gun. ## False
9[2021-11-11 21:30:52] - INFO: He is throwing darts at a wall. A woman, throws a dart at a dartboard. ## False
10[2021-11-11 21:30:52] - INFO: He is throwing darts at a wall. A woman, collapses and falls to the floor. ## False
11[2021-11-11 21:30:52] - INFO: He is throwing darts at a wall. A woman, is standing next to him. ## True
12[2021-11-11 21:30:52] - INFO: Accuracy on val 0.794
到此,对于整个基于BERT预训练模型的SWAG数据集的问答模型就介绍完了!
5 总结
在这篇文章中,掌柜首先介绍了基于BERT网络的问答选择模型的基本思想,并详细地介绍了模型的构建原理;接着介绍了问答数据集SWAG的基本信息,以及如何一步一步地来构造整个数据集;最后详细介绍了选择模型的实现方式以及整个模型的训练过程。总的来讲,对于问答选择这一任务场景来说,只需要将每个问题与其对应的各个选项看成两个拼接在一起的序列,再输入到BERT模型中进行特征提取最后进行分类即可。在下一篇文章中,掌柜将会详细介绍如何在问题回答任务(即输入一段文本描述和一个问题,让模型给出答案在文本中的起止位置)场景下进行BERT预训练模型的微调。
本次内容就到此结束,感谢您的阅读!如果你觉得上述内容对你有所帮助,欢迎点赞转发分享!若有任何疑问与建议,请添加掌柜微信nulls8或加群进行交流。青山不改,绿水长流,我们月来客栈见!
引用
[5]https://rowanzellers.com/swag/
[6]https://github.com/rowanz/swagaf/tree/master/data
[7] https://github.com/moon-hotel/BertWithPretrained