很多网友在使用 Lucene.net (Lucene java 版本也是一样)后会感觉Lucene.net 的匹配相关度存在问题,搜索得到的结果往往不是希望的结果,不完全匹配的记录往往比完全匹配的记录排序还要靠前,很多人试图通过分词来解决,中文环境搜索,分词确实能解决一些问题,但不能根本解决问题,而英文环境下,分词根本无法解决任何问题。问题的本质是由于Lucene的得分算法缺陷造成的,不改进得分算法根本不能根本解决问题。HubbleDotNet的得分算法参考了Lucene的得分算法并做了重大改进,匹配相关度比Lucene.net 有了显著提高。本文结合一个极端的例子来分析两者得分算法的异同,并从原理上讲解为什么HubbleDotNet 的匹配相关度要比Lucene.net 的高。
先看例子
我们对下面两条记录分别用 Lucene.net 2.9.1 和 HubbleDotNet 0.9.7.1 进行索引。
记录1
教育问题一直是国家最关心的,我们要长抓不懈
记录2
教育独生子女问题,这是很多家长要关心的问题
分词采用盘古分词,分词参数中关闭多元分词。
两个句子的分词结果分别为:
教育/问题/一直/是/国家/最/关心/的/我们/要/长抓/不懈/
教育/独生子女/问题/这/是/很多/家长/要/关心/的/问题/
要搜索的句子是:教育问题
其分词结果为:教育/问题/
从直观上看,记录1 是完全匹配,应该得分比记录2高,这也是我们希望的排序结果,即记录1排在第一个,记录2排第二个。
下面看看实际的排序结果:
Lucene.net 的排序结果:(这是盘古分词带的Lucene.net 的例子稍作改动后(将得分输出了)的输出结果) 从结果我们可以看出记录2被排在了第一位,得分
为 0.042 而记录1 的得分为 0.034 排第二位,这个显然不是我们希望的结果。
再看HubbleDotNet的结果
这里我们看到记录1被排在第一位,得分为 390218522
记录2 排第二位,得分 85937
原因分析
要分析两者匹配相关度的差异,我们需要比较两者的基础得分算法
Lucene 的基础得分算法
coord(q,d): 文档d中,q中命中的项数除以查询q的项总数
queryNorm(q): 只在不同query比较时影响score的normalizing因素
tf(t in d):单文本词汇频率,t在文档d中出现的次数除以d中所有的项总数的平方根
idf(t):逆文本频率指数,log(numDocs/docFreq+1)+1.0
If the document has multiple fields with the same name, all their boosts are multiplied together
从Lucene的得分算法公式我们可以看出,得分算法和单词在文档中的位置没有任何关系,也就是说Lucene 的得分算法只关心单词分量的出现频率,不关心出现位置。这就不难理解为什么文档2的得分比文档1高了,因为文档2中 “教育”分量出现了1次,“问题”分量出现了2次,而文档1中这两个分量各出现了一次,另外idf 和 norm(t,d) 在当前环境中又几乎相等,于是文档2的得分就超过了文档1。这是Lucene得分算法的重大缺陷,因为文档的匹配相关度不仅与频率有关还与位置有关。
HubbleDotNet 的基础得分算法
HubbleDotNet 在设计得分算法时充分考虑到了Lucene 的这个缺陷,在得分算法中加入了位置函数 fp(t,d,q) ,这个位置函数的加入使HubbleDotNet 的匹配相关度比Lucene有了大幅的提高。
HubbleDotNet 的基础得分算法公式如下:
这个算法其实是以 TF-IDF 算法为基础并增加了位置函数 fp(t,d,q)
其中
- FieldRank 为字段权值
- Rank(t,q) 为单词分量(term)的在查询字符串中的权值,即 教育^1^0 中的 1
- Rank(t,d) 为单词分量(term)所在文档的权值,默认为1,如果要指定文档权值,需要在表中增加一个 rank int untokenized 字段。
- TF(t,d) : 为单文本词汇频率,要查询的单词分量(term)在文档中的出现的次数除以文档所有单词分量出现的次数。
公式如下:
- IDF(t) 为逆文本频率指数。
公式如下:
|D|: 文本集合的文档总数
HubbleDotNet 的 tf idf 算法是根据标准算法来写的,和Lucene 的算法有不同。参考 tf-idf
- Sum(t) = 单词分量(term) 在所有文档中出现的总数的平方根。
公式如下:
- fp(t,d,q) 是单词分量在文档中的位置与在查询字符串中的位置关系函数,位置越接近,则返回值越大。
除去 fp(t,d,q) 以外的部分和 Lucene 的得分算法是近似的,都是基于余弦定理来做的,只是在实现上有点区别而已。
而 fp(t,d,q) 则是Lucene 得分算法所没有的,这个函数是单词分量在文档中的位置与在查询字符串中的位置关系函数,位置越接近,则返回值越大。
就拿上面的例子来说,教育 和 问题 这两个单词分量在文档1 中的位置关系和查询字符串 “教育问题” 的位置关系是一致的,这时 fp(t,d,q) 函数的返回值就会很大,而文档2中,两个单词分量的位置关系和查询字符串“教育问题” 的位置关系不一致,这时返回值就比较小。这也就是我们看到文档1的得分要比文档2大几个数量级的原因。
关于fp(t,d,q)这个函数的实现原理我将在另外的文章中阐述,主要思路就是计算相同向量在文档中和查询字符串中的向量夹角然后求积,不过说起来简单,这里面要考虑的问题还是比较多,比如如何控制返回值不能太大,查询字符串中有多个相同单词分量怎么处理等等。
相关文章
HubbleDotNet 和 Lucene.net 性能对比测试