# TinyBERT: Distilling BERT for Natural Language Understanding 以下是本例的简要目录结构及说明: ``` . ├── task_distill.py # 在特定任务上下的蒸馏脚本 ├── data_augmentation.py # 离线数据增强脚本 └── README.md # 文档,本文件 ``` ## 简介 本目录下的实验主要参考论文[《TinyBERT: Distilling BERT for Natural Language Understanding》](https://arxiv.org/abs/1909.10351)实现。 TinyBERT 中蒸馏的整体过程:首先进行通用蒸馏,然后用数据增强后的数据,在特定任务上进行蒸馏,本文主要进行了第二阶段的蒸馏,模型是利用第一阶段得到的通用小模型`tinybert-6l-768d-v2`进行初始化。


TinyBERT 蒸馏流程图

在模型蒸馏中,较大的模型(在本例中是 BERT base)通常被称为教师模型,较小的模型(在本例中是层数为6的 BERT,下文都称 TinyBERT6)通常被称为学生模型。 知识的蒸馏通常是通过让学生模型学习相关的蒸馏相损失函数实现,在本实验中,蒸馏的学习目标由两个部分组成,分别是中间层的蒸馏损失和预测层的蒸馏损失。其中,中间层的蒸馏包括对 Embedding 层的蒸馏、对每个 Transformer layer 输出的蒸馏、以及对每个 Transformer 中 attention 矩阵(softmax 之前的结果)的蒸馏,三者均采用的是均方误差损失函数。而预测层蒸馏的学习目标则是学生模型输出的 logits 和教师模型输出的 logits 的交叉熵损失。 由于教师模型是12层,学生模型的层数少于教师模型的层数,因此需要选择一种 layer mapping 的方式。论文中采用了一种固定的映射方式,当学生模型的层数为教师模型的1/2时,学生第 i 层的 attention 矩阵,需要学习教师的第2i+1层的 attention 矩阵,Transformer layer 输出同理。 实验分为两个大的训练过程:先对 BERT-base 进行微调,得到教师模型,再进行蒸馏的训练。其中,蒸馏过程也分为两个步骤:先对中间层进行蒸馏多个 epochs(论文中针对具体任务可能是10、20或者30个),再对预测层蒸馏3个 epochs。 需要注意的是,在使用不同教师模型时,`tinybert-6l-768d-v2`、`tinybert-4l-312d-v2`这两个 v2版本的预训练模型中开放的从学生 embedding 输出、transformer 中间层输出到教师相应输出的转换矩阵是每层独立的,而其他的`tinybert-6l-768d`、`tinybert-4l-312d`、`tinybert-6l-768d-zh`、`tinybert-4l-312-zh`则是多层之间的参数共用一个转换矩阵的。 ## 数据、预训练模型介绍及获取 本实验使用 GLUE 中数据集中的训练集作为训练语料,用数据集中的验证集评估模型的效果。 运行本目录下的实验,数据集会被自动下载到`paddlenlp.utils.env.DATA_HOME` 路径下,例如在 linux 系统下,对于 GLUE 中的 QQP 数据集,默认存储路径是`~/.paddlenlp/datasets/Glue/QQP`。 对于 BERT 的 fine-tuning 任务,本实验中使用了预训练模型`bert-base-uncased`。同样,这几个模型在训练时会被自动下载到`paddlenlp.utils.env.MODEL_HOME`路径下。例如,对于`bert-base-uncased`模型,在 linux 系统下,会被下载到`~/.paddlenlp/models/bert-base-uncased`下。 ## 蒸馏实验过程 ### 对 BERT Fine-tuning 得到教师模型 以 GLUE 的 SST-2任务为例,调用 BERT fine-tune 的训练脚本,配置如下的参数,训练 SST-2任务: ```shell cd ../../examples/benchmark/glue/ export CUDA_VISIBLE_DEVICES=0 export TASK_NAME=SST-2 python -u ./run_glue.py \ --model_type bert \ --model_name_or_path bert-base-uncased \ --task_name $TASK_NAME \ --max_seq_length 128 \ --batch_size 32 \ --learning_rate 2e-5 \ --num_train_epochs 3 \ --logging_steps 1 \ --save_steps 500 \ --output_dir ./tmp/$TASK_NAME/ \ --device gpu \ ``` 训练完成之后,可将训练效果最好的模型保存在本项目下的 `$TEACHER_DIR` 下。模型目录下有`model_config.json`, `model_state.pdparams`, `tokenizer_config.json`及`vocab.txt`这几个文件。 ### 对 TinyBERT 在特定任务下蒸馏 先蒸馏中间层: ```shell export CUDA_VISIBLE_DEVICES=0 export TASK_NAME=SST-2 export TEACHER_DIR=teacher_models # Moves the best model to $TEACHER_DIR mv ../../examples/benchmark/glue/tmp/SST-2/sst-2_ft_model_xx.pdparams/ $TEACHER_DIR python task_distill.py \ --model_type tinybert \ --student_model_name_or_path tinybert-6l-768d-v2 \ --task_name $TASK_NAME \ --intermediate_distill \ --max_seq_length 64 \ --batch_size 32 \ --T 1 \ --teacher_model_type bert \ --teacher_path $TEACHER_DIR \ --learning_rate 5e-5 \ --num_train_epochs 20 \ --logging_steps 10 \ --save_steps 10 \ --output_dir ./tmp/$TASK_NAME/ \ --device gpu ``` 其中参数释义如下: - `model_type` 学生模型类型,默认且目前仅支持 tinybert。 - `student_model_name_or_path` 中间层蒸馏后,学生模型存放的目录 - `max_seq_length` 表示最大句子长度,超过该长度将被截断。默认:128 - `T` softmax 的温度,用于对 softmax 做平滑,在训练中起到放大负标签效果的作用。默认:1 - `teacher_model_type` 教师模型的类型,默认且目前仅支持 bert - `teacher_path` 教师 Fine-tuned 模型的目录 - `output_dir` 学生模型存放的目录 - `device` 表示运行该程序的设备,默认是 gpu 然后对预测层进行蒸馏: ```shell python task_distill.py \ --model_type tinybert \ --student_model_name_or_path tmp/$TASK_NAME/intermediate_distill_model_final.pdparams \ --task_name $TASK_NAME \ --max_seq_length 64 \ --batch_size 32 \ --T 1 \ --teacher_model_type bert \ --teacher_path $TEACHER_DIR \ --learning_rate 3e-5 \ --num_train_epochs 3 \ --logging_steps 10 \ --save_steps 10 \ --output_dir ./tmp/$TASK_NAME/ \ --device gpu ``` 其中参数释义如下: - `student_model_name_or_path` 中间层蒸馏后,学生模型存放的目录 其他参数说明同上。 ### 实验中使用的超参数 | | SST-2 | QQP | MRPC | CoLA | RTE | MNLI | QNLI | | -------------------------------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | | batch_size | 32 | 32 | 32 | 32 | 32 | 32 | 32 | | max_seq_length | 64 | 128 | 128 | 64 | 128 | 128 | 128 | | max_epochs_of_intermediate_layer | 20 | 10 | 20 | 50 | 20 | 10 | 10 | | max_epochs_of_prediction_layer | 3 | 3 | 3 | 3 | 3 | 3 | 3 | | learning_rate(inter/pred) | 5e-5/3e-5 | 5e-5/3e-5 | 5e-5/3e-5 | 5e-5/3e-5 | 5e-5/3e-5 | 5e-5/3e-5 | 5e-5/3e-5 | ## 蒸馏实验结果 本文档的实验基于 TinyBERT 的6层、hidden_size 为768的通用蒸馏得到的模型,用未使用数据增强的原始数据集训练,并基于验证集进行评价。得到以下实验结果: | | SST-2 | QQP(acc/f1) | MRPC(acc/f1) | CoLA | RTE | MNLI-m | MNLI-mm | QNLI | | ----------------- | ----- | ----------- | ------------ | ----- | ----- | ------ | ------- | ----- | | BERT-base | 93.00 | 90.58/87.35 | 88.23/91.67 | 59.56 | 73.65 | 84.42 | 84.83 | 91.78 | | TinyBERT(6l-768d) | 93.00 | 91.13/88.20 | 88.48/91.91 | 52.64 | 72.94 | 84.57 | 84.63 | 91.36 | ## 数据增强扩充训练集(推荐) TinyBERT 使用的数据增强需要用到 BERT 预训练模型和 Glove Embeddings 做词替换。 即对于样本中的词,有一定的概率会被近义词替换。对于 single-piece 的词,会利用 BERT 的预训练模型,把选中的词替换成 mask token,然后返回模型预测的 top k 个概率最大的词,最后随机选择其中一个词做替换;对于非 single-piece 的词,则使用 Glove Embedding,找到 top k 个最近似的词,随机选择一个做替换。 先下载 glove embeddings ``` wget http://nlp.stanford.edu/data/glove.6B.zip ``` 然后运行下面的命令对 GLUE 数据集进行扩展 ``` export TASK_NAME=SST-2 export GLOVE_EMB="glove/glove.6B.300d.txt" python data_augmentation.py --pretrained_bert_model bert-base-uncased \ --glove_embs $GLOVE_EMB \ --glue_dir /root/.paddlenlp/datasets/Glue/ \ --task_name $TASK_NAME ``` 运行结束后,在 glue_dir/$TASK_NAME 目录下,会生成`train_aug.tsv`的数据增强后的训练集文件。 利用`task_distill.py`时,带上--use_aug 这个参数,程序会读取`train_aug.tsv`作训练集进行训练。 经过实验,利用数据增强后的数据集,在 RTE 数据集上,Acc 由0.7148提升至0.7184。 ## 参考文献 Jiao X, Yin Y, Shang L, et al. [TinyBERT: Distilling BERT for Natural Language Understanding](https://arxiv.org/abs/1909.10351)[J]. arXiv preprint arXiv:1909.10351v5, 2020.