mxs 2019-11-17
BERT是谷歌公司于2018年11月发布的一款新模型,它一种预训练语言表示的方法,在大量文本语料(维基百科)上训练了一个通用的“语言理解”模型,然后用这个模型去执行想做的NLP任务。一经公布,它便引爆了整个NLP界,其在11个主流NLP任务中都取得优异的结果,因此成为NLP领域最吸引人的一个模型。简单来说,BERT就是在训练了大量的文本语料(无监督)之后,能够在对英语中的单词(或中文的汉字)给出一个向量表示,使得该单词(或汉字)具有一定的语义表示能力,因此,BERT具有一定的先验知识,在NLP任务中表现十分抢眼。
在文章利用bert-serving-server搭建bert词向量服务(一) 中,作者简洁明了地介绍了如何利用bert-serving-server来获取中文汉字的词向量,这大大降低了一般从业者使用BERT的门槛。
结合笔者这段时间的工作体会以及思考,笔者尝试着给出BERT的几个可能的应用,如下:
由于笔者才疏学浅且撰写文章时间仓促,文章中有不足之处,请读者多多批评指正!
BERT公布已经半年多了,现在已经成为NLP中的深度学习模型中必不可少的工具,一般会加载在模型中的Embedding层。由于篇幅原因,笔者不再介绍自己的BERT项目,而是介绍几个BERT在基本任务中的Github项目:
可以看到,BERT已经广泛应用于NLP基本任务中,在开源项目中导出可以见到它的身影,并且这些项目的作者也写了非常细致的代码工程,便于上手。
其中,bert_client_lmj.py为调用BERT词向量服务,具体可参考文章利用bert-serving-server搭建bert词向量服务(一) ,完整的Python代码如下:
# -*- coding:utf-8 -*- from bert_serving.client import BertClient from sklearn.metrics.pairwise import cosine_similarity class Encoding(object): def __init__(self): self.server_ip = "127.0.0.1" self.bert_client = BertClient(ip=self.server_ip) def encode(self, query): tensor = self.bert_client.encode([query]) return tensor def query_similarity(self, query_list): tensors = self.bert_client.encode(query_list) return cosine_similarity(tensors)[0][1] if __name__ == "__main__": ec = Encoding() print(ec.encode("中国").shape) print(ec.encode("美国").shape) print("中国和美国的向量相似度:", ec.query_similarity(["中国", "美国"]))
利用词向量可以查找文章中与指定词语最相近的几个词语。具体的做法为:现将文章分词,对分词后的每个词,查询其与指定词语的相似度,最后按相似度输出词语即可。我们的示例文章为老舍的《养花》,内容如下:
我爱花,所以也爱养花。我可还没成为养花专家,因为没有工夫去研究和试验。我只把养花当做生活中的一种乐趣,花开得大小好坏都不计较,只要开花,我就高兴。在我的小院子里,一到夏天满是花草,小猫只好上房去玩,地上没有它们的运动场。
花虽然多,但是没有奇花异草。珍贵的花草不易养活,看着一棵好花生病要死,是件难过的事。北京的气候,对养花来说不算很好,冬天冷,春天多风,夏天不是干旱就是大雨倾盆,秋天最好,可是会忽然闹霜冻。在这种气候里,想把南方的好花养活,我还没有那么大的本事。因此,我只养些好种易活、自己会奋斗的花草。
不过,尽管花草自己会奋斗,我若是置之不理,任其自生自灭,大半还是会死的。我得天天照管它们,像好朋友似的关心它们。一来二去,我摸着一些门道:有的喜阴,就别放在太阳地里;有的喜干,就别多浇水。摸着门道,花草养活了,而且三年五载老活着、开花,多么有意思啊!不是乱吹,这就是知识呀!多得些知识决不是坏事。
我不是有腿病吗,不但不利于行,也不利于久坐。我不知道花草们受我的照顾,感谢我不感谢;我可得感谢它们。我工作的时候,我总是写一会儿就到院中去看看,浇浇这棵,搬搬那盆,然后回到屋里再写一会儿,然后再出去。如此循环,让脑力劳动和体力劳动得到适当的调节,有益身心,胜于吃药。要是赶上狂风暴雨或天气突变,就得全家动员,抢救花草,十分紧张。几百盆花,都要很快地抢到屋里去,使人腰酸腿疼,热汗直流。第二天,天气好了,又得把花都搬出去,就又一次腰酸腿疼,热汗直流。可是,这多么有意思呀!不劳动,连棵花也养不活,这难道不是真理吗?
送牛奶的同志进门就夸“好香”,这使我们全家都感到骄傲。赶到昙花开放的时候,约几位朋友来看看,更有秉烛夜游的味道——昙花总在夜里开放。花分根了,一棵分为几棵,就赠给朋友们一些。看着友人拿走自己的劳动果实,心里自然特别欢喜。
当然,也有伤心的时候,今年夏天就有这么一回。三百棵菊秧还在地上(没到移入盆中的时候),下了暴雨,邻家的墙倒了,菊秧被砸死三十多种,一百多棵。全家人几天都没有笑容。
有喜有忧,有笑有泪,有花有果,有香有色,既须劳动,又长见识,这就是养花的乐趣。
指定词语为“开心”,查询《养花》一文中与“开心”最为接近的5个词语,完整的Python代码如下:(find_similar_words.py)
# -*- coding:utf-8 -*- import jieba from bert_client_lmj import Encoding from operator import itemgetter # 读取文章 with open('./doc.txt', 'r', encoding='utf-8') as f: content = f.read().replace('\n', '') ec = Encoding() similar_word_dict = {} # 查找文章中与'开心'的最接近的词语 words = list(jieba.cut(content)) for word in words: print(word) if word not in similar_word_dict.keys(): similar_word_dict[word] = ec.query_similarity([word, '开心']) # 按相似度从高到低排序 sorted_dict = sorted(similar_word_dict.items(), key=itemgetter(1), reverse=True) print('与%s最接近的5个词语及相似度如下:' % '开心') for _ in sorted_dict[:5]: print(_)
输出的结果如下:
与开心最接近的5个词语及相似度如下: ('难过', 0.9070794) ('高兴', 0.89517105) ('乐趣', 0.89260685) ('骄傲', 0.87363803) ('我爱花', 0.86954254)
在事件抽取中,我们往往需要抽取一些指定的元素,比如在下面的句子中,
巴基斯坦当地时间2014年12月16日早晨,巴基斯坦塔利班运动武装分子袭击了西北部白沙瓦市一所军人子弟学校,打死141人,其中132人为12岁至16岁的学生。
我们需要抽取袭击者,也就是恐怖组织这个元素。
直接从句法分析,也许可以得到一定的效果,但由于事件描述方式多变,句法分析会显得比较复杂且效果不一定能保证。这时候,我们尝试BERT词向量,它在一定程度上可以作为补充策略,帮助我们定位到事件的元素。具体的想法如下:
在这里,我们的事件元素为恐怖组织,指定的模板为“伊斯兰组织”,完整的Python程序如下(find_similar_entity_in_sentence.py):
# -*- coding:utf-8 -*- import jieba from operator import itemgetter from bert_client_lmj import Encoding # 创建n-gram def compute_ngrams(sequence, n): lst = list(zip(*[sequence[index:] for index in range(n)])) for i in range(len(lst)): lst[i] = ''.join(lst[i]) return lst # 模板 template = '伊斯兰组织' # 示例句子 doc = "巴基斯坦当地时间2014年12月16日早晨,巴基斯坦塔利班运动武装分子袭击了西北部白沙瓦市一所军人子弟学校,打死141人,其中132人为12岁至16岁的学生。" words = list(jieba.cut(doc)) all_lst = [] for j in range(1, 5): all_lst.extend(compute_ngrams(words, j)) ec = Encoding() similar_word_dict = {} # 查找文章中与template的最接近的词语 for word in all_lst: print(word) if word not in similar_word_dict.keys(): similar_word_dict[word] = ec.query_similarity([word, template]) # 按相似度从高到低排序 sorted_dict = sorted(similar_word_dict.items(), key=itemgetter(1), reverse=True) print('与%s最接近的实体是: %s,相似度为 %s.' %(template, sorted_dict[0][0], sorted_dict[0][1]))
输出的结果如下:
与伊斯兰组织最接近的实体是: 塔利班运动武装分子,相似度为 0.8953854.
可以看到,该算法成功地帮助我们定位到了恐怖组织:塔利班运动武装分子,效果很好,但是由于是无监督产生的词向量,效果不一定可控,而且该算法运行速度较慢,这点可以从工程上加以改进。
在智能问答中,我们往往会采用知识图谱或者数据库存储实体,其中一个难点就是实体对齐。举个例子,我们在数据库中储存的实体如下:(entities.txt)
094型/晋级
052C型(旅洋Ⅱ级)
辽宁舰/瓦良格/Varyag
杰拉尔德·R·福特号航空母舰
052D型(旅洋III级)
054A型
CVN-72/林肯号/Lincoln
这样的实体名字很复杂,如果用户想查询实体“辽宁舰”,就会碰到困难,但是由于实体以储存在数据库或知识图谱中,实体不好直接修改。一种办法是通过关键字匹配定位实体,在这里,我们可以借助BERT词向量来实现,完整的Python代码如下:(Entity_Alignment.py)
# -*- coding:utf-8 -*- from bert_client_lmj import Encoding from operator import itemgetter with open('entities.txt', 'r', encoding='utf-8') as f: entities = [_.strip() for _ in f.readlines()] ec = Encoding() def entity_alignment(query): similar_word_dict = {} # 查找已有实体中与query最接近的实体 for entity in entities: if entity not in similar_word_dict.keys(): similar_word_dict[entity] = ec.query_similarity([entity, query]) # 按相似度从高到低排序 sorted_dict = sorted(similar_word_dict.items(), key=itemgetter(1), reverse=True) return sorted_dict[0] query = '辽宁舰' result = entity_alignment(query) print('查询实体:%s,匹配实体:%s 。' %(query, result)) query = '林肯号' result = entity_alignment(query) print('查询实体:%s,匹配实体:%s 。' %(query, result))
输出的结果如下:
查询实体:辽宁舰,匹配实体:('辽宁舰/瓦良格/Varyag', 0.8534695) 。 查询实体:林肯号,匹配实体:('CVN-72/林肯号/Lincoln', 0.8389378) 。
在这里,查询的速度应该不是困难,因为我们可以将已储存的实体以离线的方式查询其词向量并储存,这样进来一个查询到实体,只查询一次词向量,并计算其与离线的词向量的相似度。这种方法也存在缺陷,主要是由于词向量的无监督,实体对齐有时候不会很准,但作为一种补充策略,也许可以考虑。
本文介绍了笔者这段时间所思考的BERT词向量的几个应用,由于能力有限,文章中会存在考虑不当的地方,还请读者多多批评指正。
另外,笔者将会持续调研词向量方面的技术,比如腾讯词向量,百度词向量等,欢迎大家关注~
注意:不妨了解下笔者的微信公众号: Python爬虫与算法(微信号为:easy_web_scrape), 欢迎大家关注~