作者丨苏剑林
研究方向丨NLP,神经网络
个人主页丨kexue.fm
Bert 是什么,估计也不用笔者来诸多介绍了。虽然笔者不是很喜欢Bert,但不得不说,Bert 确实在 NLP 界引起了一阵轩然大波。现在不管是中文还是英文,关于 Bert 的科普和解读已经满天飞了,隐隐已经超过了当年 Word2Vec 刚出来的势头了。有意思的是,Bert 是 Google 搞出来的,当年的 word2vec 也是 Google 搞出来的,不管你用哪个,都是在跟着 Google 大佬的屁股跑。
Bert 刚出来不久,就有读者建议我写个解读,但我终究还是没有写。一来,Bert 的解读已经不少了,二来其实 Bert 也就是基于 Attention 搞出来的大规模语料预训练的模型,本身在技术上不算什么创新,而关于 Google 的 Attention 我已经写过解读了,所以就提不起劲来写了。
▲ Bert的预训练和微调(图片来自Bert的原论文)
总的来说,我个人对 Bert 一直也没啥兴趣,直到上个月末在做信息抽取比赛时,才首次尝试了 Bert。毕竟即使不感兴趣,终究也是得学会它,毕竟用不用是一回事,会不会又是另一回事。再加上在 Keras 中使用(fine tune)Bert,似乎还没有什么文章介绍,所以就分享一下自己的使用经验。
当Bert遇上Keras
很幸运的是,已经有大佬封装好了 Keras 版的 Bert,可以直接调用官方发布的预训练权重,对于已经有一定 Keras 基础的读者来说,这可能是最简单的调用 Bert 的方式了。所谓“站在巨人的肩膀上”,就是形容我们这些 Keras 爱好者此刻的心情了。
keras-bert
个人认为,目前在 Keras 下对 Bert 最好的封装是:
keras-bert:
https://github.com/CyberZHG/keras-bert
本文也是以此为基础的。 顺便一提的是,除了 keras-bert 之外,CyberZHG 大佬还封装了很多有价值的 keras 模块,比如 keras-gpt-2(你可以用像用 Bert 一样用 GPT2 模型了)、keras-lr-multiplier(分层设置学习率)、keras-ordered-neurons(就是前不久介绍的 ON-LSTM)等等。看来也是一位 Keras 铁杆粉丝,致敬大佬。
汇总可以看:
https://github.com/CyberZHG/summary
事实上,有了 keras-bert 之后,再加上一点点 Keras 基础知识,而且 keras-bert 所给的 demo 已经足够完善,调用、微调 Bert 都已经变成了意见没有什么技术含量的事情了。所以后面笔者只是给出几个中文的例子,来让读者上手 keras-bert 的基本用法。
Tokenizer
正式讲例子之前,还有必要先讲一下 Tokenizer 相关内容。我们导入 Bert 的 Tokenizer 并重构一下它:
from keras_bert import load_trained_model_from_checkpoint, Tokenizerimport codecsconfig_path = '../bert/chinese_L-12_H-768_A-12/bert_config.json'checkpoint_path = '../bert/chinese_L-12_H-768_A-12/bert_model.ckpt'dict_path = '../bert/chinese_L-12_H-768_A-12/vocab.txt'token_dict = {}with codecs.open(dict_path, 'r', 'utf8') as reader: for line in reader: token = line.strip() token_dict[token] = len(token_dict)class OurTokenizer(Tokenizer): def _tokenize(self, text): R = [] for c in text: if c in self._token_dict: R.append(c) elif self._is_space(c): R.append('[unused1]') # space类用未经训练的[unused1]表示 else: R.append('[UNK]') # 剩余的字符是[UNK] return Rtokenizer = OurTokenizer(token_dict)tokenizer.tokenize(u'今天天气不错')# 输出是 ['[CLS]', u'今', u'天', u'天', u'气', u'不', u'错', '[SEP]']import load_trained_model_from_checkpoint, Tokenizer
import codecs
config_path = '../bert/chinese_L-12_H-768_A-12/bert_config.json'
checkpoint_path = '../bert/chinese_L-12_H-768_A-12/bert_model.ckpt'
dict_path = '../bert/chinese_L-12_H-768_A-12/vocab.txt'
token_dict = {}
with codecs.open(dict_path, 'r', 'utf8') as reader:
for line in reader:
token = line.strip()
token_dict[token] = len(token_dict)
class OurTokenizer(Tokenizer):
def _tokenize(self, text):
R = []
for c in text:
if c in self._token_dict:
R.append(c)
elif self._is_space(c):
R.append('[unused1]')