Ads by Lake Quincy Media

eaglet

本博专注于基于微软技术的搜索相关技术
posts - 174, comments - 2977, trackbacks - 26, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

很多网友在使用 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 排第二位,这个显然不是我们希望的结果。

image

 

再看HubbleDotNet的结果

image

这里我们看到记录1被排在第一位,得分为 390218522

记录2 排第二位,得分 85937

 

原因分析

 

要分析两者匹配相关度的差异,我们需要比较两者的基础得分算法

 

 

Lucene 的基础得分算法

image

 

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

image

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 的基础得分算法公式如下:

image

这个算法其实是以 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)在文档中的出现的次数除以文档所有单词分量出现的次数。

公式如下:

image

 

  • IDF(t) 为逆文本频率指数。

公式如下:

image

|D|: 文本集合的文档总数

image : 为含有单词分类(term)的文档总数

HubbleDotNet 的 tf idf 算法是根据标准算法来写的,和Lucene 的算法有不同。参考 tf-idf

 

  • Sum(t) = 单词分量(term) 在所有文档中出现的总数的平方根。

公式如下:

image

  • 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 性能对比测试

HubbleDotNet 海量数据测试报告

 

 

返回 Hubble.net 技术详解

Feedback

#1楼  回复 引用 查看   

2010-09-07 09:05 by 阿不      
看到这些公式,就相当的崇拜了。。。

#2楼  回复 引用 查看   

2010-09-07 09:06 by Axel      
hubble需要多做推广,这么好的东东。

#3楼  回复 引用 查看   

2010-09-07 09:09 by 温景良(Jason)      
呵呵,一面世就下来看过了,觉得很好

#4楼  回复 引用 查看   

2010-09-07 09:19 by zqonline      
看不懂,水平有限啊。。

#5楼  回复 引用 查看   

2010-09-07 09:20 by Adam哥      
这个实时搜索方面如何?

#6楼  回复 引用 查看   

2010-09-07 09:24 by eng308      
哈哈 原来如此
顶老大一个。。。

#7楼[楼主]  回复 引用 查看   

2010-09-07 09:32 by eaglet      
@Adam哥
可以用主动模式做实时搜索。具体看 Hubble.net 技术详解 系列中的文章。

#8楼  回复 引用 查看   

2010-09-07 09:36 by try      
引用阿不:看到这些公式,就相当的崇拜了。。。

同崇拜

#9楼  回复 引用 查看   

2010-09-07 09:42 by SeaSunK      
引用try:
引用阿不:看到这些公式,就相当的崇拜了。。。

同崇拜

帶來感染力~~

#10楼  回复 引用 查看   

2010-09-07 09:43 by overred      
不错

#11楼  回复 引用 查看   

2010-09-07 09:50 by hailibu      
引用SeaSunK:
引用try:
引用阿不:看到这些公式,就相当的崇拜了。。。

同崇拜

帶來感染力~~

都忘了怎么算了

#12楼  回复 引用 查看   

2010-09-07 10:24 by 阿瑞|www.16hi.com      
太高深了 看着公式都头晕了

#13楼  回复 引用 查看   

2010-09-07 11:53 by 在路上狂奔的蜗牛      
水平有限,公式好看,但是看不懂……

#14楼  回复 引用 查看   

2010-09-07 12:37 by 阳光下的柚子      
嘿嘿,这种文章是一定要顶完收藏看的

#15楼  回复 引用 查看   

2010-09-07 13:07 by Hawker      
请教一个问题,可以在虚拟主机中使用HubbleDotNet吗?

#16楼[楼主]  回复 引用 查看   

2010-09-07 13:16 by eaglet      
@Hawker
HubbleDotNet 的核心模块都是以dll 方式存在的,理论上是可以的。不过通过dll 而不是服务方式来使用hubbledotnet不是我推荐的方式,我也不提供这方面的介绍,需要你自己去研究怎么搞。

#17楼  回复 引用 查看   

2010-09-07 14:00 by Jonathan.yang      
相当嘀崇拜

#18楼  回复 引用 查看   

2010-09-07 14:09 by jimmyjiang      
n年没研究数学公式了

#19楼  回复 引用 查看   

2010-09-07 15:03 by csdn_li      
O(∩_∩)O谢谢老大,我得好好看看!O(∩_∩)O~

#20楼  回复 引用 查看   

2010-09-07 17:37 by 马老虎      
好 认真的了解下!!!

#21楼  回复 引用 查看   

2010-09-08 11:04 by zzybbs      
用Guid做主键,居然不行,郁闷。
private void FixQueryResult(Hubble.SQLClient.QueryResult result)
        {
            List<System.Data.DataColumn> invaildColList = new List<System.Data.DataColumn>(); 

            if (result.DataSet != null)
            {
                if (result.DataSet.Tables != null)
                {
                    foreach (System.Data.DataColumn col in result.DataSet.Tables[0].Columns)
                    {
                        try
                        {
                            Hubble.SQLClient.QueryResultSerialization.GetDataType(col.DataType);
                        }
                        catch
                        {
                            invaildColList.Add(col);
                        }
                    }

                    foreach (System.Data.DataColumn col in invaildColList)
                    {
                        Global.Report.WriteErrorLog(string.Format("Invalid data type = {0} in column:{1}",
                            col.DataType.ToString(), col.ColumnName));
                        result.DataSet.Tables[0].Columns.Remove(col);//Guid类型主键出错!
                    }
                }
            }
        }

#22楼[楼主]  回复 引用 查看   

2010-09-08 12:33 by eaglet      
@zzybbs
一个 bug ,已经改了,升级到 0.9.7.3。
另外 GUID 做主键时,你需要在表中插入一个int 类型的唯一性自增长ID字段,才可以。

#23楼  回复 引用 查看   

2010-09-08 16:18 by zzybbs      
Good.

#24楼  回复 引用 查看   

2010-09-09 21:28 by jiang_chao      
楼主用实例没有,不知道怎么使用,前段时间看了一下Lucene.net还是不太明白

#25楼[楼主]  回复 引用 查看   

2010-09-10 05:54 by eaglet      
@jiang_chao
看下面文章
http://www.cnblogs.com/eaglet/archive/2010/05/13/1734273.html
http://www.cnblogs.com/eaglet/archive/2010/08/30/1812650.html
另外代码中也有一个 asp.net 的例子

#26楼  回复 引用 查看   

2010-11-14 20:00 by 江南烟雨人      
guid做主键时,选择Updatable出现此列是主键的一部分错误!我的版本是0.9.6.0

#27楼[楼主]  回复 引用 查看   

2010-11-15 07:17 by eaglet      
@江南烟雨人
guid 是sql server 的非标准数据类型,而且不能自增长,hubbledotnet 无法支持guid 做为ID 字段,你需要在表中插入一个 int 或 int64 的自增长唯一性字段作为ID 字段。

#28楼  回复 引用 查看   

2010-11-24 22:41 by 老冯      
Too many connects on serverStackTrace

#29楼[楼主]  回复 引用 查看   

2010-11-25 06:47 by eaglet      
@老冯
已经在中文社区回答

#30楼  回复 引用 查看   

2010-11-25 15:53 by 老诺      
你好 eaglet
hubble.net在项目里使用后,我在本地测试完全没问题,但部署到远程服务器上就出错 Unknown Filed : XXX
问下这个错误是怎么引起的? 经过检查不是SQL语句引起的 字段没问题
本地完全不出错