BERT finetune
本文代码部分参考github项目:
https://github.com/BSlience/search-engine-zerotohero/tree/main/public/bert_wwm_pretrain
项目概述
本文的主要内容是基于huggingface transformer的chinese-bert-wwm模型,在自己的语料集上进行finetune的整体步骤和代码实现。
关于chinese-bert-wwm:
https://huggingface.co/hfl/chinese-bert-wwm
https://github.com/ymcui/Chinese-BERT-wwm
主要步骤包括:预处理和训练两个部分
预处理(pre-processing)
- 下载chinese-bert-wwm模型的预训练词表(vocab.txt)、config.json和pytorch_model.bin;
- 读取自己的原始数据集(比如大量的文章、文本),做句子分割,然后保存成语料集;
- 根据自己的语料集进行分词(BERT是分割成单个字),并将自己语料集中相比原始的词表多的字(或者词)添加到原始词表中(就是一个扩充操作),然后就生成了自己的词表;
步骤1的下载地址:https://huggingface.co/hfl/chinese-bert-wwm/tree/main
如果生成的语料集比较大,为了后续加载方便可以存储至内存型的数据库中(比如Redis)
训练(train)
- 加载BertTokenizer,读取语料集,生成数据;
- 针对1中得到的数据,进行填充、截断和mask等操作,借助data collator类;
- 加载chinese-bert-wwm模型的预训练权重文件,基于当前数据开始训练(微调);
- 保存模型,测试效果。
关于data collator的实现可以查看我的上一篇文章whole word mask;
步骤3加载的预训练权重文件就是上述预处理步骤1下载的,不过要注意把config.json和pytorch_model.bin放在同一个目录下,然后加载这个目录即可。
预处理
这里原始数据(大量的知乎文章)存储在mongodb中,我们读取出来,然后执行预处理步骤2、3
1 | def clean_html_tag(data: str): |
这里句子分割单独封装了一个类SplitSentence,具体实现如下
1 | def replace_with_separator(text, separator, regexs): |
上面是pre_processing()方法的实现,接下来还有保存语料到文件和生成词表的操作
1 | def save_corpus(data, corpus_file_path): |
上面的代码是把所有的语料都放到列表中,然后全部操作完再一条条的写到文件中,很容易在执行的时候超出内存卡死,所以我优化了下,读取一条、处理一条、保存一条,运行瞬间丝滑。
1 | """ |
不过这里需要注意的是,扩充原词表时,不要改变原始词表的顺序,保持原词表顺序不变,在后面添加新词,新添加的顺序无所谓。
训练
先配置一个整体的配置文件,方便后面管理使用
1 | CONFIG = { |
训练代码
1 | def seed_everyone(seed_): |
训练的时候可能会出现这个问题
Failed to create a directory: data/whole_word_mask_bert_output\runs\Jun21_16-47-59_user; No such file or directory
我在whole_word_mask_bert_output文件夹下又创建了一个runs文件夹解决了。
另外,如果你的debug配置忘记改为False,那么传入trainer.train()的数据只有2000条,是会报错的,IndexError: index out of range in self
报这个错误是embedding层的张量输入超过了合法范围,embedding层的合法张量输入数值范围应该在[0, num_embeddings-1]的范围内,过大过小都会报错。
关于tokenizer.encode_plus
1 | tokenizer = BertTokenizer.from_pretrained(CONFIG['vocab_file_path'], local_file_only=True) |
[CLS] 标志放在第一个句子的首位,经过 BERT 得到的的表征向量 C 可以用于后续的分类任务。
[SEP] 标志用于分开两个输入句子,例如输入句子 A 和 B,要在句子 A,B 后面增加 [SEP] 标志。
[UNK]标志指的是未知字符
[MASK] 标志用于遮盖句子中的一些单词,将单词用 [MASK] 遮盖之后,再利用 BERT 输出的 [MASK] 向量预测单词是什么。
特征抽取
训练(finetune)完成后,就可以使用训练得到的模型来抽取文本的特征,其实这里所说的抽取文本的特征实际就是把自然语言文本转为向量,我们直接使用原始的模型也是可以进行特征抽取的,只不过在自己的数据集上finetune之后效果会更好,而具体效果要在下游的实际任务中才能评估,仅通过finetune后的模型来将文本转为特征向量无法评估效果的好坏。
特征抽取代码示例
1 | """ |