bert pytorch英文文本分类使用Huggingface bert用于文本分类_数据


最近使用 BERT 做文本二分类,为了 finetune 出高准确度的模型趋于崩溃。

我的数据特点是文本较短、包含网络用语、句子结构不完整、混杂缩写和错别字,和中文 BERT 预训练使用的 wiki 语料实在是差得太远了。因此,我一方面扩充数据,一方面调研领域适配的方案。

这期间读到了邱锡鹏老师在 NIPS-2020 的一篇 workshop,专门介绍 BERT 用于中文文本分类的各种 trick。我选取其中部分策略进行了尝试。原文在这里:

How to Fine-Tune BERT for Text Classification? - https://arxiv.org/abs/1905.05583

这篇文章主要介绍了两部分的内容,一是 fine-tuning,而是 further-pretraining。我先摘要一下主要的优化点,再介绍已尝试的部分。


bert pytorch英文文本分类使用Huggingface bert用于文本分类_基线_02


1. Fine-Tuning

  • 超参数设置:
batch_size = 24; dropout = 0.1; learning-rate=2e-5; warm-up proportion = 0.1; max_epoch = 4;


作者的学习率衰减策略,叫作 slanted triangular(继承自 ULM-Fit)。它和 BERT 的原版方案类似,都是带 warmup 的先增后减。通常来说,这类方案对初始学习率的设置并不敏感。但是,在 fine-tune阶段使用过大的学习率,会打乱 pretrain 阶段学习到的句子信息,造成“灾难性遗忘”。


bert pytorch英文文本分类使用Huggingface bert用于文本分类_数据_03

不同学习率下的训练曲线

比如上面的图,最右边的 4e-4 已经完全无法收敛了,而 1e-4 的 loss 曲线明显不如 2e-5 和 5e-5 的低。

根据作者的数据集大小推测,整个 finetune 过程在 1W 步左右。实测发现,小数据上更多的 epoch 并不能带来效果的提升,不如早一点终止训练省时间。

  • 长句处理:

BERT 使用的是 position-embedding,因此存在最大句长为 512 个 token 的限制(删去 CLS 和 SEP 就是 510 了)。如果不想舍弃超长的句子,那么可以对其截断。作者尝试了head-only([0:510]),tail-only([-510:])和 head+tail([0:128] + [-382:])三种切片方法,实验证实第三种在错误率上有 0.15~0.20% 的优势。也许是因为重要信息在首尾比较多中间比较少?我的数据集里没有长句子,因此没有深入验证。

  • 分类层位置

想降低计算量,取中间层的结果来分类?别做梦了,老老实实用第12层吧,总体错误率相差1.4% 呢。

  • 学习率逐层衰减

设置相邻两层的学习率比例,让低层更新幅度更小。


bert pytorch英文文本分类使用Huggingface bert用于文本分类_bert 文本分类_04

,系数控制在 0.90 ~ 0.95,不能再小了。整体优化效果不明显,不建议尝试。

bert pytorch英文文本分类使用Huggingface bert用于文本分类_基线_02


2. Further Pretraining

最有价值的部分在这里。当自己的数据集和 BERT 领域不适配,并且标注数据有限但可以获得大量无标注数据时,先对基线做进一步的 pretrain,能够帮助 finetune 效果提升。

  • 超参数设置:
batch_size = 32; max_length = 128; learning_rate = 5e-5; warmup_steps = 1W; steps = 10W;


需要注意的是,pretrain 为了提升训练效率,使用的是偏短的 128 个词句子;学习率仍然是带 warmup 的衰减方式,初始值不用像 finetune 那样设置得那么小。整个训练过程为 10W 步,这个值是作者实验测定的,太短或太长都会影响最终模型的准确率。

  • 数据源选择:

优先使用同领域的无标注数据,其次使用 finetune 的训练数据。什么都没有的话,混一点跨领域数据也可以。

  • 学习任务:

BERT 在做预训练时,使用的是长篇段落。数据格式为每句一行,段与段之间有额外空行。由于我的无标注数据同样都是短句,没有办法构造 NSP 任务需要的数据对,所以只保留了 MLM 任务。句子形式仍然是 [CLS] + token_a + [SEP] + token_b + [SEP],但 token_a 和 token_b 都是随机选取的。


bert pytorch英文文本分类使用Huggingface bert用于文本分类_基线_02


不知道是不是因为句子太短了并且句法不规范,不如段落文本那么规整,我的预训练准确度并不高,15W 步只有 60% 左右。


bert pytorch英文文本分类使用Huggingface bert用于文本分类_bert 文本分类_07

我的 BERT-base 的预训练结果

使用新基线做 finetune 后,可以观察到明显的收敛加快,但没有获得显著的分类准确率提升。不过,我在参数规模只有 12% 的 ELECTRA-small 上做 further pretraining 时,效果却十分明显(忽略震荡):


bert pytorch英文文本分类使用Huggingface bert用于文本分类_数据_08