论文地址:https://arxiv.org/pdf/1905.05583.pdf
论文年份:2019年05月
论文代码: https://github.com/xuyige/BERT4doc-Classification
论文引用量:1191 (截止2023-04-28)
论文阅读前提:熟悉NLP、深度学习、Transformer、BERT、多任务学习等。
现在NLP任务方式大多都是对BERT进行微调。例如:我们要做一个电影评论情感分类的任务,那就可以直接把BERT拿过来,换上自己的分类头,然后使用电影评论的数据集进行训练,最终就可以得到一个还不错的模型。(这个过程称为微调(fine-tune))
然而,目前针对“如何对BERT进行微调会比较好”的研究不多(例如学习率怎么选择、应该选择怎样的策略等等)。所以,该论文就使用分类任务对“如何微调BERT”进行了研究,这也是论文的题目“ How to Fine-Tune BERT for Text Classification?”。
该论文通过实验,得出了许多结论,可以供我们在使用BERT做特定任务时进行参考。
作者通过实验得出最好的微调流程为:
大多数人可能都忽略了前两步,直接使用“单任务微调”的方式训练BERT,最终的结果总是差那么点意思。
作者探究了微调BERT的一些基本的策略,例如学习率、长文本处理方式等。最终得出了以下结论:
Further Pretain是指在正式使用目标任务训练BERT前,先使用该领域(In-domain)的数据对BERT进行无监督训练(训练BERT时用的MLM任务和NSP任务)。
例如:我们要训练一个电影评论情感分类模型,那么就是收集一堆无标签的电影评论数据,然后使用MLM和NSP任务对BERT进行无监督训练。
作者的得出的结论如下:
上述就是论文得出的所有结论,接下来讲解论文中所做的实验,若不感兴趣,可以不看。
作者使用了7个数据集进行实验,共三种类型,分别为:情感分类(Sentiment Analysis)、问题分类(Question Classification)和主题分类(Topic Classification)。数据集如下表:
其中 IMDb 是二分类的电影评论情感分类, Yelp是商户评论情感分类(类似大众点评),Sogou News是中文数据集。
Further Pretrain BERT的实验使用的相关超参数如下:
fine-tune BERT的实验使用的相关超参数如下:
BERT一次能够接收最长的token数量为512,算上开始
和结束
这两个特殊的token,相当于一次能够接收510个token。
因此,对于那些token数超过510的文本,需要对其进行处理。作者实验了不同的处理方式,包括以下几种:
最终的结果如下表:
在IMDb和Sogou这两个数据集上,均为“head+tail”的方式效果最好。
众所周知,BERT是由12层Transformer Encoder堆叠而成的,每一层的输出都是输入的特征向量表示,通常我们使用的是最后一层的输出作为输入的最终特征,然后将其送给分类头进行预测。
而作者在想,用最后一层的Transformer Encoder作为特征向量真的就最好吗?所以它就使用每一层的特征向量进行了实验,最终得到如下表结果:
实验代码在
codes/fine-tuning/modeling_last_concat_avg.py
最终实验表示使用最后一层的Transformer特征效果最好,这和预期的一样。
TODO:论文中的 First/Last 4 Layers + concat/mean/max 表示什么意思我也不太清楚。论文没有解释,且提供的代码里也没有找到关于它们的影子。
灾难性遗忘(Catastrophic forgetting)是指预训练模型在学习新的知识后把过去老的知识给忘了。这是迁移学习中常见的一个问题。
通常该问题是因为学习率太大引起的。学习率太大会导致灾难性遗忘,学习率太小又会无法学会新的知识,所以要学选择一个合适的学习率。
作者在IMDb数据集上选择不同学习率进行了实验:
最终作者得出结论:当学习率为2e-5时,模型收敛最好,且不容易出现灾难性遗忘问题。
通常,越靠近输出层的网络应使用较高的学习率,所以作者探索了一下应该如何为BERT的每一层设计学习学习率。
作者使用的方式为根据衰减因子 ξ \xi ξ 进行学习率逐层衰减。例如,学习率取2e-5,衰减因子取0.95,则第12层的Transformer使用 2e-5 的学习率,第11层学习率为 (2e-5)*0.95 ,第10层为 (2e-5)*0.952,第9层为 (2e-5)*0.953,依次类推。
作者的实验结果如下:
实验结论为:学习率取2e-5,衰减因子为0.95时效果最好。
作者探索了如何进行Further Pretrain效果最好。
既然要对BERT进行更多的预训练,那训练多少次合适呢?作者在IMDb数据集上进行了实验:
结果显示,当step数为10w时,效果最好。
BERT-ITPT-FiT为“BERT +withIn-Task Pre-Training + Fine-Tuning”,意思是使用原始的BERT进行Further Pretraining,然后再进行fine-tune。
作者尝试了若Further Pretrain时使用交叉领域的数据会不会好一点呢?
作者的数据集有三种任务:情感分类、问题分类和主题分类。他将这三种看做是不同领域(different domain)然后进行实验。
实验结果如下表:
从上表可以得出如下结论:
作者比较了一些其他模型和使用不同BERT训练策略的结果,如下表:
数字表示错误率,越低越好。Avg为相比BERT-Feat,错误率下降的平均比率。
其他模型就不介绍了,不是重点。作者使用的BERT策略如下:
最终的出的结论为:BERT-IDPT-FiT效果最好。这和上面的实验结论也一致。
通常,多任务训练可以让模型学会更加通用的特征表示,使模型的泛化性更强,提高模型的性能。
作者也对多任务进行了探索。作者使用IMDb、Yelp P、AG、DBP四个数据集进行多任务学习。
多任务就是同时学习多个任务。对于作者的实验来说,就是使用一个BERT,但为四个数据集分别使用不同的分类头(它们的类别数不一样,当然就算一样也肯定是不同的分类头),然后将四个数据集的数据混到一块进行学习。
最终的结果如下表:
这里为什么没有“BERT-IDPT-MFiT-FiT”?这是因为作者选用的这四个数据集属于Cross-Domain的,如果要做In-Domain的实验,就不是这组数据了。作者没做相应的实验。
作者相应的部分实验代码如下(codes/fine-tuning/modeling_multitask.py
):
class BertForSequenceClassification(nn.Module):
...
def __init__(self, config, num_labels):
super(BertForSequenceClassification, self).__init__()
self.bert = BertModel(config)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.classifier_1 = nn.Linear(config.hidden_size, 2) # IMDb数据的分类头
self.classifier_2 = nn.Linear(config.hidden_size, 2) # Yelp数据的分类头
self.classifier_3 = nn.Linear(config.hidden_size, 4) # AG数据的分类头
self.classifier_4 = nn.Linear(config.hidden_size, 14) # DBP数据的分类头
...
def forward(self, input_ids, token_type_ids, attention_mask, labels=None, dataset_labels=None):
# 训练过程中,每个step过来的一批数据是IMDb、Yelp P、AG、DBP的其中一个。
_, pooled_output = self.bert(input_ids, token_type_ids, attention_mask)
pooled_output = self.dropout(pooled_output)
# 若数据是IMDb,则使用classifier_1分类头
if dataset_labels[0].item()==1:logits = self.classifier_1(pooled_output)
# 若数据是Yelp,则使用classifier_2分类头
if dataset_labels[0].item()==2:logits = self.classifier_2(pooled_output)
# 若数据是AG,则使用classifier_3分类头
if dataset_labels[0].item()==3:logits = self.classifier_3(pooled_output)
# 若数据是DBP,则使用classifier_4分类头
if dataset_labels[0].item()==4:logits = self.classifier_4(pooled_output)
if labels is not None:
loss_fct = CrossEntropyLoss()
loss = loss_fct(logits, labels)
return loss, logits
else:
return logits
通常我们的目标任务的训练数据集都是比较少的,作者针对这种情况做了对比实验。
作者的实验为:只使用IMDb数据集的一部分(例如10%),然后进行BERT-FiT和BERT-ITPT-FiT的对比实验。
结果如下图:
从结果中可以看到,但只取0.4%的IMDb数据集时,做withIn-task Pretraining和不做差距还是挺大的。不过随着数据集的增大,差距越来越小,不过总归是有差距。
实验结论为:当训练数据较小(few-shot)时,使用Further Pretrain可以得到较好的结果。
上面的实验已经证明了对BERT-base使用Further Pretraining是有效果的,但对BERT-Large是否还有同样的效果呢?作者也做了实验,实验结果如下表:
从实验结果看,BERT-Large增加ITPT后同样可以减小错误率。
作者中了一系列实验总结出微调BERT的一套方法论,即先进行In-Domain的Further Pretraining,然后进行多任务学习,最后再使用目标任务进行单任务微调。并且作者还对这些步骤的长文本处理、学习率选择等做了一些实验指导。