前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >文本相似度,一件可大可小的事情

文本相似度,一件可大可小的事情

作者头像
段清华DEAN
发布2020-06-24 10:38:16
3500
发布2020-06-24 10:38:16
举报
文章被收录于专栏:AI工程AI工程

问题出现,为什么需要文本相似度

于小文是一个普通程序员,业余的时候会出于做一些自己的网站,最近他做了一个问答社区,就是大家有什么问题都可以在上面问,然后也会有热心网友来解答的网站。

最近他发现一个问题,很多人在提问的时候都会重复,所以他希望有一个功能,就是假设新问题跟已经有的问题相似,就直接推给答案,就避免了重复提问和解答,以及找到解答的时间了。

那么问题就很清楚的定义了,新问题(文本)与已有的问题(文本)之间怎么算重复问题?

我们能否通过设计一个相似度函数,通过调用 Similar(新问题,老问题),把每个老问题都计算一边,就判断出是否相似。

于小文心里开始盘算怎么做。

相似度的实现思考

第一种思考

于小文发现“日本多大”,“日本大小”,我们把第一个问题进行一些步骤的修改,就可以变成第二个问题。

例如我们去掉第一个词的“多”字,变成“日本大”,然后我们再最后插入一个“小”字,就变成了“日本大小”。

也就是说一个文本总能通过进行N种如增加一个字、删掉一个字、修改一个字等等这样的方法就能变成另一个方法。

那么显然两个文本越相似,需要进行的这样的操作就越少。然后于小文查了一下发现这种方法被称为:Edit Distance

代码语言:javascript
复制
>>> # !pip install editdistance
>>> import editdistance
>>> editdistance.eval('日本多大', '日本大小')
2

第二种思考

假设我们说文本“日本多大的面积”,和文本“日本面积多大”,一个想法是,这两个文本中有很多字一样,那么我们假设文本是由单个的字组成的集合,那么最简单的的方法是,判断两个集合交集来多少。我们知道集合中的元素是不能重复的,也就是说两个文本中相同字符的数量除以全部字符的数量,不就是一个相似度了吗?似乎这个想法不错。

代码语言:javascript
复制
a = '日本多大的面积'
b = '日本面积多大'
len(set(a) & set(b)) / len(set(a) | set(b))

0.8571428571428571

后来于小文上网查了下,发现这种方法有个很高大上的名字,叫:Jaccard Distance

中文是有词,但又未分词的

我们知道英文大部分情况是以空格分割的一个一个单词,但是中文并没有,中文只是有时候会有一些标点符号而已。那么就带来一个问题,比如说有人想问时间,那么“日本时间”和“本日时间”显然不是一个时间,但是如果按照刚才的算法,它们相似度就是100%了。

所以我们有两种可能的方法:

  1. 用某个方法将中文分词切割,不以字符为最小单位,以词为最小单位
  2. 我们发现字符和字符之间的关联,是有意义的,比如我们把“日本时间”,每两个字连一起,就是有了“日本,本时,时间”,其中“日本”和“时间”都是有意义的,而如果把“本日时间”也这样分割得到的就是“本日,日时,时间”,也就是本来100%的相似度,在2元字符组合的情况下只有一个词“时间”相同了。我们把这种方法一般称为2-gram,相对的,也有3-gram,或者N-gram。

因为分词需要引入额外的分词工具,所以于小文决定先试试2的效果,然后于小文很喜欢正则表达式的各种技巧,决定装个逼。

代码语言:javascript
复制
>>> re.findall('(?=(.{2}))', '日本时间')
['日本', '本时', '时间']
>>> re.findall('(?=(.{2}))', '本日时间')
['本日', '日时', '时间']
>>> 
>>> a = set(re.findall('(?=(.{2}))', '日本时间'))
>>> b = set(re.findall('(?=(.{2}))', '本日时间'))
>>> len(a & b) / len(a | b)
0.2

用2-gram方法分了5个词,只有“时间”是一样的,所以相似度一下来就从100%降到了20%

词与词不同权

于小文发现了一个问题,有些问题中有很多“废话”,例如

  • 日本的面积到底有多大?
  • 日本的大小?
  • 我家的面积到底有多大?

显然第一个问题和第二个问题是一个问题,但是如果按照上面的算法,夏然第一个问题和第三个问题重复的词更多。

于小文脑海中的第一个想法:去掉“没用的”词,例如去掉“到底”,“有”,“的”,这种经常在问题中出现但是又没什么实际意义的词。

如果这么做确实能解决一部分问题,不过引申出另外的问题,一个是很难判断哪些词真的没用,例如“游戏打到底是什么?”,这里的“到底”就似乎很有用。另一个问题是,这些词可能有很多很多,人工添加很累。

然后于小文发现,有用的词往往是不常见的,没用的词往往是常见的,例如“的”,“有”都很常见,但是“日本”,“面积”在日常说话中并不常见。

所以一个显而易见的方法就是给不常见的词一个很高的权重,常见的词给一个低权重。这个权重只要通过对所有已有的问题都统计一下就可以了,可以以中文分词为基础也可以以2-gram为基础,如果完全没出现的词可以给一个比较高的权重。

于小文的另一个发现,是问题中,经常提及的词,应该是重要的,例如一个问题是:“switch到底哪好了我发现好多人都买了switch”,这里switch提到了两次,应该是一个比较重要的词。如果我们仅仅用所有问题统计的词权重就忽略了问题(当前文本)本身中的特性(权重),于是决定把这两个特性结合一下。

然后于小文搜了一下发现这个叫TFIDF。

语义相似度

于小文发现有些问题中的重要词完全不一样,但是确是指一个东西?

有人问:“厕所怎么装修”,有人问“洗手间怎么装修”,“洗手间”和“厕所”应该是一个意思,吧?但是显然它们从文本上没有任何相似性。

也就是说这两个词从“字符”级别上完全没相似性,但是从“含义”上有相似性。

那么怎么判断这个“含义”相似性呢?

还是先来朴素的想法,于小文最容易想到的方法是,我维护一个列表,这里面列出所有的能找到的同义词,例如WoW可能跟魔兽世界是一个词,厕所和跟洗手间,然后在判断问题相似性的时候把此表中出现的词都尝试一下,也许每次选取一个在遍历尝试过程中最高的相似度就好。

这个方法显然是有问题的,一个最直接的问题是同义词并没有那么好找,而且人们也在不断的发明新词。

必须,得换一个高级点的思路了……

高级思路警告。

假设我们有一个神奇的函数,例如叫他T,T可以把输入的词,都转换为一个真实且唯一的含义的某个符号,也就是说它有一个如下的性质:

T(厕所) = T(洗手间)

因为厕所和洗手间的含义相同,它们经过T转换后的符号也肯定相同。

那么T还有什么性质呢,就是我们从句子的其他词(上下文)中是可以预测到T的。

那么怎么找到T呢?这里需要一个填空题一样的技巧。

例如我们说填空题:“我来到了海边,放眼望去一片()”,括号里面填什么?

我们假设答案是填“蓝”或者“蓝色”吧,因为填这两个都可以满足含义,所以我们可以知道 T(蓝) = T(蓝色)。

我们把这类填空题也定义为一个函数,例如叫V,所以可以得到下面的关系:

V(我来到了海边,放眼望去一片) = T(蓝色) = T(蓝)

当然在实际上我们其实并不需要完全等于,所以粗略的也可以把上面修改为:

V(我来到了海边,放眼望去一片) ≈ T(蓝色) ≈ T(蓝)

也就是说我们假设找到了函数T和V,就可以让“蓝色”、“蓝”,归一到一个一致或至少差不多的符号上,这样就可以用这两个字符之间的相似度来代替它们的字符相似度,也就是可以让“厕所”和“洗手间”等价。

我们把“差不多的符号”,修改为“差不多相似”的向量,把T函数定义为一个从符号集合到向量集合的单射函数,把函数V定义为某种线性或非线性函数。而这两个函数都是可以通过机器学习的反向传播算法得到的。

那么为什么需要填空题呢,因为填空题不需要太多额外的处理,只要我们从一个完整句子挖空一个词就可以了,非常容易得到。

而且我们也不需要设计T(蓝色) ≈ T(蓝)的部分,只要我们通过训练能使得函数T和函数V满足:

V(我来到了海边,放眼望去一片) ≈ T(蓝色)

V(我来到了海边,放眼望去一片) ≈ T(蓝)

自然可以得到 T(蓝色) ≈ T(蓝)

以上是Word2vector的某种解释。

V(我来到了海边,放眼望去一片) ≈ T(蓝) 可以理解为基于V得到T(蓝)的概率,我们一般把这样构建词之间概率关系称为语言模型。

相似度的扩展

分类

于小文遇到一个问题,有很多人不仅仅是为了提问,也是会去看看别人问的问题都有什么。这个时候最好能把问题做一些分类。

但是于小文只会用相似度,于是想,什么是分类?

假设我们有分类A、B、C,新来一个问题x,那么所谓分类不就是看x跟A、B、C哪个相似?或者说跟A、B、C里面的哪些问题相似?

所以一个最朴素的想法是这样的:假设问题x跟A、B、C里面所有的问题都比较一下相似度,然后看排前n个最相似的问题都是分别属于A、B、C哪些里面的,投个票,就好了。

这个算法基本上是K-Nearest-Neighbour

聚类

那么我现在没有A、B、C这些个分类啊?

一个方法是于小文辛苦一点,自己先把问题都过一遍,按照某个标准,分个几万个问题,先把A、B、C都先大概分出来。

但是于小文很懒,真的很懒。

代码语言:javascript
复制
相似的问题总是相似的 —— 于小文

我们假设我们要把问题分成10类,假设我们每类先随机放入一个问题。第11个问题,看它跟这10个分类里面的问题哪个最相似,哪个最相似就放入里面,这样不断的往里放。当然还要注意就是分类里面已有的问题,如果它其实跟本身分类的问题都不太相似,反而跟别的分类的一些问题更相似,就挑出来放到别的分类里面。

重复上面两个过程,不就是把相似的都放一起了?

然后于小文只需要大概看看每个分类,给一些明确的分类起个名字就好了

这个算法基本上是K-means

搜索

搜索和聚类、分类其实差不多,我们可以认为语义搜索从某种意义上来说,是拿搜索中的“查询语句”去找已有资料库中的最相似的资料。

这方面首先我们需要有一种判断相似度的方法,其次是可能构建分层次的搜索方法。

例如先进行粗力度的大范围搜索,例如用annoy库,或者jina这样的引擎。

然后可以再进行某些细粒度的排序,例如用某种Siamese Networks的复杂模型,最终得到结果。

实体相似度

“大家给我推荐个笔记本呗?能玩游戏的”

“大家给我推荐个笔记本呗?要书写流畅的”

上面两个笔记本是一个意思吗?当然是有可能的了,只靠纸笔也是可以玩龙与地下城跑团游戏的,也有笔记本因为带有触摸屏幕也是涉及到书写是否流畅的。但是,显然更可能的是,第一个问题是希望推荐笔记本电脑,第二个是希望推荐书写用的纸质笔记本。

那么怎么区别上面两个问题呢,那就需要我们首先知道我们要对比的是“笔记本”这个实体,在这个基础上判断两句话的相似度。因为相似度实际上是我们的目标定义的,例如我们可以说两个句子都是推荐东西时就是相似的,这样定义的话,上面两句话其实是相似的。我们也可以定义,两个句子提到的实体是一致的时候才是相似的,这就需要对具体实体进行判断了。

假设我们对实体进行相似度判断,就需要这个实体的上下文,在这里可以简单理解为句子里的其他词汇,因为这些词汇可以帮助我们定义这个实体到底是什么,所以是我们所需的上下文。

这个任务也很类似实体链接(Entity Linking)和分面情感分析(Aspect-base Sentiment Analysis)。

广义语义相似度

广义语义相似度,于小文发明的词?

当我们把语义相似度扩展到广义,比如说文字的语义相似度可以用一个复杂的函数(模型)表达的时候,相似度实际上就可以是很多东西了。

例如我们说文本分类模型,本质上是对新来的样本,是模型去判断它和学过的样本的相似度对比并最终打分、聚合而得到的结果。

例如我们说记忆与联想,本质上是我们在脑中进行某种相似度搜索,而得到的答案。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 问题出现,为什么需要文本相似度
  • 相似度的实现思考
    • 第一种思考
      • 第二种思考
        • 中文是有词,但又未分词的
          • 词与词不同权
            • 语义相似度
            • 相似度的扩展
              • 分类
                • 聚类
                  • 搜索
                    • 实体相似度
                      • 广义语义相似度
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档