一种解决bert长文本匹配的方法

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一种解决bert长文本匹配的方法相关的知识,希望对你有一定的参考价值。

参考技术A

bert [1] 提出来后打开了迁移学习的大门,首先通过无监督的语料训练通用的语言模型,然后基于自己的语料微调(finetune)模型来达到不同的业务需求。我们知道bert可以支持的最大token长度为512,如果最大长度超过512,该怎么样处理呢?下面这边论文提供一种简单有效的解决思路。

201903发表

bert大招很好用,但是其最大长度为512以及其性能这两个缺点给我们的线上部署提出了挑战。我们在做document级别的召回的时候,其文本长度远超bert可以处理的长度,本文提出了一种简单并且有效的解决思路。将长的document分解成多个短的句子,每个句子在bert上独立推断,然后将这些句子的得分聚合得到document的得分。

作者先以短文本匹配任务-社交媒体的帖子来做召回实验,通过query来召回相关的帖子,一般帖子的长度是较短的文本,在bert可以处理的范围内。实验的评价指标为两个 平均召回(AP) top30的召回率(P30) ,下表是最近的深度模型在这个数据集上的结果。

我觉得上述实验数据主要说一点:

长文本的docment匹配一般解决方法:

本文的方法

针对新闻语料的长文本召回问题,本文首先利用NLTK工具将长文本分为短的句子,不同于考虑最匹配的句子,本文考虑 top n 个句子。最终长文本docment的匹配得分计算公司如下:

其中S doc 是原始的长文本得分( 文本得分 ),例如BM25得分,S i 表示第i个top的基于bert句子的匹配得分( 语义得分 ),其中参数a的参数范围[0,1],w 1 的值为1,w i 参数范围[0,1],基于gridsearch去调参,获得一个比较好的性能。

finetune的数据

我们的原始的微调数据是查询query和长文本document的关系,而我们将长文本拆分为n个短句子后,不是所有的句子和当前的query是强相关的(正样本),因此我们不能简单依赖现在的长文本数据了。本论文的解决方法是基于外部语料,基于QA或者Microblog数据,首先bert基于通用的无监督语料学习到了词语以及句子的表征,所以基于少量的数据微调也可以获得较好的效果,因此本文选择外部相关的语料进行微调。具体效果如下表,我们发现长文本的匹配基于本文的方法可以取得比较好的效果。

总结

思考

Simple Applications of BERT for Ad Hoc Document Retrieval

BERT的几个可能的应用

??BERT是谷歌公司于2018年11月发布的一款新模型,它一种预训练语言表示的方法,在大量文本语料(维基百科)上训练了一个通用的“语言理解”模型,然后用这个模型去执行想做的NLP任务。一经公布,它便引爆了整个NLP界,其在11个主流NLP任务中都取得优异的结果,因此成为NLP领域最吸引人的一个模型。简单来说,BERT就是在训练了大量的文本语料(无监督)之后,能够在对英语中的单词(或中文的汉字)给出一个向量表示,使得该单词(或汉字)具有一定的语义表示能力,因此,BERT具有一定的先验知识,在NLP任务中表现十分抢眼。
??在文章利用bert-serving-server搭建bert词向量服务(一) 中,作者简洁明了地介绍了如何利用bert-serving-server来获取中文汉字的词向量,这大大降低了一般从业者使用BERT的门槛。
??结合笔者这段时间的工作体会以及思考,笔者尝试着给出BERT的几个可能的应用,如下:

  • NLP基本任务
  • 查找相似词语
  • 提取文本中的实体
  • 问答中的实体对齐

由于笔者才疏学浅且撰写文章时间仓促,文章中有不足之处,请读者多多批评指正!

NLP基本任务

??BERT公布已经半年多了,现在已经成为NLP中的深度学习模型中必不可少的工具,一般会加载在模型中的Embedding层。由于篇幅原因,笔者不再介绍自己的BERT项目,而是介绍几个BERT在基本任务中的Github项目:

可以看到,BERT已经广泛应用于NLP基本任务中,在开源项目中导出可以见到它的身影,并且这些项目的作者也写了非常细致的代码工程,便于上手。


??在具体讲述下面的三个应用前,我们先了解下BERT应用的项目结构,如下:

技术图片

其中,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词向量,它在一定程度上可以作为补充策略,帮助我们定位到事件的元素。具体的想法如下:

  • 指定事件元素模板
  • 句子分词,对词语做n-gram
  • 查询每个n-gram与模板的相似度
  • 按相似度对n-gram排序,取相似度最高的n-gram

在这里,我们的事件元素为恐怖组织,指定的模板为“伊斯兰组织”,完整的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), 欢迎大家关注~

以上是关于一种解决bert长文本匹配的方法的主要内容,如果未能解决你的问题,请参考以下文章

Python 插入长文本至Oracle

ACL 2021|美团提出基于对比学习的文本表示模型,效果相比BERT-flow提升8%

从Siamse孪生网络到Sentence-BERT综述

ACL 2021|美团提出基于对比学习的文本表示模型,效果提升8%

多标签文本分类MSML-BERT模型的层级多标签文本分类方法研究

iOS text的长文本换行