QA 改 1.pretrain

重现一下第一名的方案

这里先介绍第一部分的工作——pretrain一个bert

1.像是为数据集微调一个LM,在问答网站用StackExchange data训练是个不错的方案。

这里的数据都用xml形式存储,可利用的信息在“user.xml”和“Posts.xml”这里抓取数据的步骤就取消了,可以通过xml_to_pandas直接转换成csv的形式。得到的csv如下,选取我们需要的列。   

question里面,body和title当然是很重要的点。但一般来说会将更多的信息加入进去,比如用来表征问题的更多特征,比如说浏览次数,问题的分数,提问次数等,但是还会加入一些我认为是噪音的特征比如说id...

answer里面,answer的score,answer的评论当然是比较感觉比较相关的特征了。

制造噪音的特征,考虑为生成一个比较有鲁棒性的模型。

上述过程是预处理的第一阶段,预处理的第二阶段主要是数据的清理工作:

1.直接dropna掉空值行

2.增加新的行,is_answer_accepted

最终选出的数据如下:

 answer_max_score和answer_mean_score可作为quetion的特征,也可以作为answer的指标表征。

数据预处理包括:progress_apply。

html_pattern = re.compile(r'<.*?>')

question_body = all_questions['Body'].astype(str).progress_apply(lambda s: html_pattern.sub('', s))
question_title = all_questions['Title'].astype(str).progress_apply(lambda s: html_pattern.sub('', s))

 用compile 设置pattern,用pattern.sub进行正则化替换

 最终除了question_username、title、body、answer_username、answer其他都是float,再进行标准化处理

接下来导入tokenizer,导入词库,得到编号表,得到train和val的类。

构建我们的prerain model(用的词库是较大的词库),导入我们的config,对transformer参数的设置,从预训练模型中导出。

这里设置模型的embedding层用的是一个更小模型的embedding,然后扩充成一个大模型的embedding。因为这里用的词库是一个非常大的词库11w的词,bert-base只有28000,bert-large只有3w,所以我们需要扩充embedding层中的vacab。

最终形成我们的trainer:

其中model是在base的基础上对embeddding层进行扩充维度。

loss_func:

其中loss 除了预训练中用到的mlm loss和下一句预测loss,还包括了target预测的loss,由三个部分组成。

这里训练还是挺难的,理解了半天才明白:

这是我们设置的trainer,在Model类里面放着我们的模型,优化器,损失,评价指标。

  trainer_to(device)

 第三步就是最重要的!

 我们需要读取两个Dataloader,对Dataloader而言,我们需要在它下面放dataset。

对fit_generator而言,每次读取dataloader的一个batch的数据

 这里的__getitem__(self,index)是来自:class QuestMLMDataset(QuestDataset):

而Dataset是QuestDataset的父类,因此QuestMLMDataset类有方法__getitem__,可以取出某个index(也就是批次)下的数据。

1.再取出文本数据后,首先我们需要进行分词,编号化。

2.经过encode,得到的是这个batch下的3个encode,我们计算得到它们的长度,并且计算出了均衡化的answer长度和question长度(title+body),我们希望它们的长度尽可能均衡。

接下来我们需要加入bert可识别的令牌,我们将question的title和body用SEP分开

input_ids 长度为201(question_ids的长度+answer_ids的长度+3) 因为 build_inputs_with_special_tokens 如果输入为两个ids的话,自动加入令牌CLS+A+SEP+B+SEP,因此长度多了3,因此我们一个句子的token令牌分布是CLS+title+SEP+body+SEP+answer+SEP,这里主要强调的是answer和question的ids需要分成两个句子,作用是可以用来做句子对分类任务。

等于是训练的时候输入的是两个句子,用SEP分开。

padding成bert的词嵌入维度 

制造masked ids 选出15%的词为masked,masked词中80%的词被替换成masked的ids,10%的masked词被替换成别的词,10%的masked的词保留成原词。

我们只计算masked的词的损失

 让我们关注模型的输入输出:

我们在Dataloader中准备了input_ids、token_type_ids、attention_mask

outputs:

对于outputs,首先我们需要得到embeddings,经过embedding层

embedding层如下:

包括word_embedding,position_embedding,token_type_embedding

其中word_embedding:num_embeddings为vocab-size(110000),embedding_dim为hidden-size(768)

        position_embedding:max_position_size,hidden_size

        type_vocab_size:2

最终的embedding将三者进行相加

 得到embedding后再经过encoder层:

输入为embedding层的输出,和attention_mask

 encoder 执行的核心在于以上两行代码,关键模型为layer_module,每一个layer_module都是属于Bertlayer类的。hidden_states是上一层的输出,对应第一层的就是embedding的输出。attention_mask指的是extended_attention_mask。想要的词上是0,被masked的词是-10000。

encoder的理论基础如下:

首先我们需要经过连接层得到QKV,代码实现如下:

 我们的一个batch的size为(1,512,768),生成的QKV的size为(1,512,768),计算为(512,768)*(768,768)。再将(1,512,768)进行拆分成(1,512,12,64),再交换位置得到:(1,12,512,64) 对应batch,层数,(512,64)为输出size。

因为有12个encoder,每个encoder有64维度的hidden_size,这里是将12层concat计算了。

得到的是三个向量空间,可以认为是3个特征表征。

 接下来就是做运算了,并且使用我们的attention_mask了:

先做矩阵乘法, 得到一个(512,512)的attention,再加上我们的attention_mask,再经过softmax,根据softmax函数,对于mask掉的词它与自己、其他的词的“关联性”为0。我们对masked掉的词不赋予注意力,最后经过dropout。再将关系矩阵*value,此时的size回到(512,768)

ok,那这样我们就得到了我们的attention_outputs。这里attention_outputs==attention_output

在经过BertIntermediate,非常简单,经过一个dense,经过dropout,再x+attention,再LN,得到输出。

 再将得到的x+attention经过dense,dropout,再与attention相加做LN,感觉这里重复做是为了增加更多的参数,提高模型的拟合能力。自此我们的encoder就结束了。

最终来到我们的BertPooler:

觉得非常简单的做法,把每个句子的第一个token的hidden_states拿出来,作为pooled_output。

所以最终self.bert的输出就是 :outputs = (sequence_output, pooled_output,)

也就是12层之后输出的hidden_states和pooled_output

接下来做剩余的操作:

执行self.cls()

 先做predictions:把最高层的输出作为输入,经过两个步骤:1.transform和decoder,transform包括dense、gelu、LN,decoder是将transfor的输出变成(512,vocab-size)的形式,最终的输出为prediction_score。

seq_relationship是将每个句子的第一个token的state_states辨认是否属于next_sentence。

到这里self.cls就做好了,输出为:outputs = (prediction_scores, seq_relationship_score,)

这里还有个改进,我们在预训练模型的时候不仅考虑了MLM的训练loss,和isnextsentence的loss,还考虑了最终输出和target的loss,我们只需要将最高层的states进行求平均,再输出成一个5维的向量即可。

最后的最后我们再回到Dataset的__getitem__:

非常关键 !

我们return的是两个turple,第一个是训练的输入,第二个是作为targte。

首先,第一个target是labels,最终计算的损失函数是交叉熵损失函数。

其次token_type_ids里面answer和question被设置成0和1。sop_lable被初始化为0,但是其中如果title、body、answer的对应关系不对的话,sop_label被设置为1。因为我们在输入的时候有一半的数据对应关系是不对的。我们同样以交叉熵损失来计算损失。

其中numeric_targets就是5维的指标向量。用的是BCEwithLogitLoss,和bce类似,不过在输出的后面接了一个sigmoid,相当于省略了加上sigmoid的步骤。

自此一个完整的pretraining bert就搭建好了!

最终的训练时间训练一个完整的LM,大概需要60万条数据,大概训练20个epoch,3-4个batchsize。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值