如何使用Lucene.NET来帮助在Stack Overflow这样的网站上实现search?
我已经在Meta Stack Overflow上提出了一个类似的问题 ,但是具体涉及到Lucene.NET是否在Stack Overflow上使用。
这里的问题的目的更多的是一个假设,至于如果他们使用Lucene.NET作为站点内search的基础和像 Stack Overflow 这样的站点中的其他因素,会采取什么样的方法。
根据堆栈溢出博客标题为“ SQL 2008全文search问题 ”的条目,有一个强烈的迹象表明,Lucene.NET正在考虑在某些时候,但似乎并非如此,根据评论2010年2月19日Geoff Dalgas :
Lucene.NET没有被用于堆栈溢出 – 我们正在使用SQL Server全文索引。 search是我们继续做小调整的一个领域。
所以我的问题是,如何将Lucene.NET用于具有Stack Overflow相同语义的站点?
这里有一些背景,以及我到目前为止所做的/想过的事情(是的,我一直在实施这个大部分,search是我必须完成的最后一个方面):
技术:
- ASP.NET MVC
- SQL Server 2008
- .NET 3.5
- C#3.0
当然,Lucene.NET也是这个节目的明星。
目的也是转移到.NET / C#4.0 ASAP。 虽然我不认为这是改变游戏规则,但应该指出。
在进入Lucene.NET的各个方面之前,指出它的SQL Server 2008方面以及所涉及的模型是很重要的。
楷模
与堆栈溢出相比,该系统具有多个主模型types。 这些模型的一些例子是:
- 问题:这是人们可以问的问题。 人们可以回答问题,就像堆栈溢出一样。
- 注:这些是单向的预测,所以相对于一个问题,你正在对内容做出声明。 人们不能回复这个。
- 事件:这是关于实时事件的数据。 它有位置信息,date/时间信息。
关于这些模型的重要事项要注意:
- 它们都具有名称/标题(文本)属性和正文(HTML)属性(这些格式是不相关的,因为内容将被适当分析以进行分析)。
- 每个模型的实例在网站上都有一个唯一的URL
然后有堆栈溢出提供哪些IMO,是装饰模型的东西。 这些装饰器可以具有不同的基数,可以是一对一或一对多的:
- 票数:键入用户
- 回复:可选,作为一个例子,请参阅上面的Notes案例
- 已collections:是否将该模型列为用户的最爱?
- 评论:(可选)
- 标签关联:标签位于单独的表格中,以免为每个模型复制标签。 模型和标签关联表之间有一个链接,然后从标签关联表到标签表。
并且有支持方法,它们本身就是一对一的装饰器,它们以相同的方式(通常由模型IDtypes和模型ID)键入到模型中:
- 投票总数:正面总数,负面投票数, 威尔逊得分间隔 (这是重要的,它将基于投票的投票决定信心水平,大部分是假定威尔逊间隔的下限)。
答案(答案)是大多数模型都拥有大多数装饰器的模型,它们只是没有标题或url,模型是否具有答复是可选的。 如果答复是允许的,那当然是一对多的关系。
SQL Server 2008
表格非常符合上面模型的布局,装饰器有单独的表格,还有一些支持的表格和视图,存储过程等等。
应该指出的是,不使用全文search的决定主要基于这样一个事实,即它不像Lucene.NET那样规范化分数。 我对如何利用基于文本的search提出了build议,但是我将不得不在多个模型types中执行search,所以请记住,我将需要以某种方式来标准化分数。
Lucene.NET
这是大问号的地方。 以下是Stack Overflowfunction以及我已经完成的工作。
索引
问题/型号
我相信每个模型应该有一个自己的索引,包含一个唯一的ID来快速查找基于该实例的id(索引,不分析)。
在这个领域,我已经考虑让Lucene.NET分析每个问题/模型,并且每个答案都是单独的。 所以如果有一个问题和五个答案,那么问题和每个答案将被单独索引为一个单元。
这里的想法是,Lucene.NET返回的相关性分数比较容易比较以不同方式投影的模型(比如没有回复的东西)。
作为一个例子,一个问题提出了这个问题,然后答案阐述了这个问题。
对于一个没有答复的笔记,它处理提出这个问题然后再详细说明的问题。
我相信这将有助于使相关性得分更加相关。
标签
起初,我认为这些应该保存在一个单独的索引中,其中多个字段具有相应模型索引中文档的ID。 或者,如果它太大,就会有一个只有标签的索引和另一个索引来维护标签索引和它们所应用的问题之间的关系。 这样,当你点击一个标签(或使用URL结构)时,很容易以渐进的方式看到,如果你成功,你只需要“买进”
- 如果标签存在
- 标签与哪个问题相关联
- 问题本身
但是,实际上,使用SQL Server 2008对基于标签的所有项目进行查询(如点击Stack Overflow中的标签) 非常容易 。基于上述模型,只需要查询如:
select m.Name, m.Body from Models as m left outer join TagAssociations as ta on ta.ModelTypeId = <fixed model type id> and ta.ModelId = m.Id left outer join Tags as t on t.Id = ta.TagId where t.Name = <tag>
由于某些属性在所有模型中共享,因此在不同的模型types/表之间进行一个UNION
足够简单,并产生一组一致的结果。
这与Lucene.NET中的TermQuery
类似(我引用的是Java文档,因为它是全面的,Lucene.NET是为了逐行转换Lucene ,因此所有的文档都是一样的)。
在这里使用Lucene.NET的问题是sorting顺序。 TermQuery涉及到标签的相关性分数是无关紧要的。 它是1或0(它有或没有)。
在这一点上,信心评分(威尔逊评分间隔)用于sorting结果。
这个分数可以存储在Lucene.NET中,但为了对这个字段的结果进行sorting,它将依赖于存储在字段caching中的值,这是我真正想要避免的。 对于大量的文档,字段高速caching可能会变得非常大(威尔逊分数是双倍,而且每个文档需要一个双倍的大小)。
考虑到我可以根据Wilson评分间隔来更改SQL语句,如下所示:
select m.Name, m.Body from Models as m left outer join TagAssociations as ta on ta.ModelTypeId = <fixed model type id> and ta.ModelId = m.Id left outer join Tags as t on t.Id = ta.TagId left outer join VoteTallyStatistics as s on s.ModelTypeId = ta.ModelTypeId and s.ModelId = ta.ModelId where t.Name = <tag> order by --- Use Id to break ties. s.WilsonIntervalLowerBound desc, m.Id
这似乎是一个简单的select来使用这个来处理堆栈溢出function块“获取所有标签的标签”。
回复
原来,我认为这是在它自己的一个单独的索引,关键的问题索引。
我认为每个模型和每个答复(如果有的话)应该有一个组合,以便不同模型的相关性分数在相互比较时更加“相等”。
这当然会膨胀指数。 我现在对此感到很舒服。
或者,有没有办法在Lucene.NET中存储说,作为单个文档的模型和答复,然后将两者都可以得到一个查询处理两个文档的相关性分数为一个 ? 如果是这样,那么这将是理想的 。
当然存在哪些字段将被存储,索引,分析的问题(所有的操作可以是单独的操作,或者混合和匹配)? 一个索引多less钱?
如何使用特殊词干/搬运工用于拼写错误(使用Metaphone)以及同义词(在社区中有专业术语,我将服务于具有多个表示的特定事物的俚语/术语)?
促进
这当然与索引有关,但我认为它是有用的部分。
你在提高领域和/或文件? 如果是这样,你如何提高他们呢? 某些领域的增长是否持续? 还是重新计算投票/查看/collections/外部数据适用的字段。
例如,在文档中,标题是否对身体起了推动作用? 如果是这样,你认为什么推动因素工作良好? 怎么样标签?
这里的思路和Stack Overflow的思路是一样的。 文档中的术语具有相关性,但是如果文档标注了术语,或者标题中包含了该术语,则应该增加该术语。
Shashikant Korebuild议像这样的文档结构:
- 标题
- 题
- 接受的答案(或者如果没有被接受的答案,则是高度投票的答案)
- 所有的答案合并
然后使用提升,但不是基于原始投票价值。 我相信我已经覆盖了威尔士分数区间。
问题是,如果提升应用于整个文档? 我对此没有任何意见,因为这意味着每次用户投票select模型时,都必须重新编制文档索引。
search已标记的项目
我原本以为在查询标签时(通过特别点击标签或使用URL结构查找标签内容),这是一个简单的TermQuery,用于标签的标签索引,然后在关联索引(如有必要),然后返回对于问题,Lucene.NET处理这个真的很快。
但是,考虑到上述有关在SQL Server中执行此操作非常简单的注意事项,在select标记项目时,我select了这种路由。
一般search
所以,现在最突出的问题是,在对内容进行一般性短语或术语search时,为了确定结果的顺序是什么以及如何整合其他信息(例如投票)? 例如,当在Stack Overflow上对ASP.NET MVC进行search时,这些是前五个结果(当使用相关性选项卡时)的计数:
q votes answers accepted answer votes asp.net highlights mvc highlights ------- ------- --------------------- ------------------ -------------- 21 26 51 2 2 58 23 70 2 5 29 24 40 3 4 37 15 25 1 2 59 23 47 2 2
请注意,突出显示仅位于结果页面的标题和摘要中,仅仅是对文档,标题,标签,回复(不pipe它们是否被应用,这是另一个好问题)中的真实词语频率的次要指标。
这一切如何汇集在一起?
在这一点上,我知道Lucene.NET将返回一个标准化的相关性分数,而投票数据会给我一个Wilson分数间隔,我可以用它来确定置信度分数。
我应该如何结合tese两个分数来表示基于相关性和置信度的结果集的sorting顺序?
在我看来,这两者之间应该有一些关系,但是在这一点上,这种关系应该避开我。 我知道随着时间的推移,我必须改进它,但是我真的迷失了这部分。
我最初的想法是,如果相关性分数介于0和1之间,置信度分数介于0和1之间,那么我可以这样做:
1 / ((e ^ cs) * (e ^ rs))
通过这种方式,可以得到一个接近于0的标准化值,结果是更相关和更有把握的,并且可以对其进行sorting。
主要的问题是,如果在标签和/或标题字段上执行提升,那么相关性分数超出0到1的范围(上限变得无界,那么我不知道该如何处理)。
此外,我相信我将不得不调整信心得分来解释完全否定的投票logging。 由于投票计数完全否定,得分低于0的威尔逊得分区间,-500票的投票与-1票或0票的得分相同。
幸运的是,由于负面投票率上升,上限从1下降到0。 我可以将信心分数从-1改为1,如下所示:
confidence score = votetally < 0 ? -(1 - wilson score interval upper bound) : wilson score interval lower bound
这个问题是,在等式中插入0会将所有的项目排在低于投票数低于投票的项目之下。
为了达到这个目的,我想如果信心得分会被用在像上面这样的互惠方程中(我明显担心溢出),那么就需要对其进行修改,使其始终保持积极的态度。 达到这个目标的一个方法是:
confidence score = 0.5 + (votetally < 0 ? -(1 - wilson score interval upper bound) : wilson score interval lower bound) / 2
我的其他问题是如何实际执行Lucene.NET和SQL Server的计算。 我很犹豫要把信心得分放在Lucene索引中,因为它需要使用可能对内存消耗有巨大影响的字段caching(如前所述)。
我有一个想法是从Lucene.NET获得相关性分数,然后使用表值参数将分数传递给SQL Server(以及要select的项目的id),在这一点上我将执行计算与信心得分,然后正确地返回数据ordred。
如前所述,我还有很多其他的问题,而答案已经开始构思,并且随着问题和答案的不断扩展。
你正在寻找的答案真的不能单独使用lucenefind。 您需要sorting和分组algorithm来筛选和理解数据及其相关性。 Lucene可以帮助你获得标准化的数据,但是你需要正确的algorithm。
我build议你查看下面的一本或者全部下面的书,它们会帮助你math,让你指出正确的方向:
智能networking的algorithm
集体智慧在行动
编程集体智能
lucene索引将有以下领域:
- 标题
- 题
- 接受的答案(或者如果没有被接受的答案,则是高度投票的答案)
- 所有的答案合并
所有这些都是分析领域。 长度标准化被禁用,以更好地控制评分。
上述的领域顺序也以降序来反映它们的重要性。 这就是说,如果标题中的查询匹配比接受的答案更重要,那么其他所有内容都保持相同。
提升的数量就是问题的答案,最好的答案可以通过提升这些领域来获得。 但是,原始的upvote计数不能用作提升值,因为它可能会大大扭曲结果。 (有4个upvotes的问题会得到两个upvotes的得分的两倍)。这些价值需要被积极地抑制,然后才能被用作推动因子。 使用自然对数(upvotes> 3)看起来不错。
标题可以被提升一个比问题更高的值。
尽pipe问题之间的相互联系并不是很常见,但对于一个问题来说,如果有一个基本的类似网页的权重可能会引发一些有趣的结果。
我不认为这个问题的标签是非常有价值的search信息。 标签是很好的,当你只是想浏览的问题。 大多数情况下,标签是文本的一部分,因此search标签会导致匹配问题。 不过,这是开放的讨论。
一个典型的search查询将在所有四个字段上执行。
+(title:query question:query accepted_answer:query all_combined:query)
这是一个宽泛的草图,如果需要的话,需要大量的调整才能达到正确的提升值和正确的查询权重。 实验将显示质量的两个维度 – 相关性和重要性的权重。 通过引入新近度作为参数,可以使事情变得复杂。 这里的想法是,如果在特定版本的产品中出现问题,并在稍后的修订版本中修复,则新问题可能对用户更有用。
可以添加一些有趣的曲折search。 如果仅find“less数”匹配结果,则基本同义词search的某种forms可能会有所帮助。 例如,“descrease java heap size”与“reduce java heap size”相同。 但是,那么这也意味着“地图缩小”将开始匹配“地图缩小”。 (拼写检查是显而易见的,但我想,程序员会拼写正确的查询。)
你可能已经对这个问题做了更多的思考,而不是大多数人会试图回答你(部分原因是为期一天,而我是你的第一个回应,我会想象)。 我只是试着解决你最后的三个问题,那里有很多我没有时间去的,我认为这三个问题是最有趣的(物理实现问题可能会当你学到更多东西的时候,最终会“挑选一些东西,然后调整它”)。
投票数据不知道投票使search更相关的东西,坦率地说,只是让他们更受欢迎。 如果这是有道理的,我想说的是,一个给定的职位是否与你的问题相关,大多是与其他人是否相关的独立性。 也就是说,有趣的问题和人们希望find的问题之间至less有一个微弱的相关性。 投票数据可能在纯粹基于数据进行search时最为有用,例如“最受欢迎”types的search。 在通用的基于文本的search中,我可能不会首先提供任何投票权重,但会考虑使用可能为sorting提供轻微权重的algorithm(因此,不会返回结果,而是对次序进行小幅提升)。
回复我同意在这里你的方法,经过一些testing; 请记住,这将是一个基于用户反馈的迭代过程(因此您需要收集有关search是否返回search者成功结果的指标)
其他别忘了用户的分数也。 因此,用户也得到了积分,这也影响了他们在回答每个问题的答案时的默认排名(看起来这主要是针对具有相同数量的颠簸的答复进行tiebreaking)
确定相关性总是很棘手。 你需要弄清楚你要完成什么。 您的search是否试图提供某人可能遇到的问题的完全匹配,或者是否试图提供关于某个主题的最近项目的列表?
一旦你已经知道你想要返回什么,你可以看看你索引每个function的相对效果。 这将得到一个粗略的search。 从那里你根据用户的反馈调整(我build议使用隐式反馈,而不是显式的,否则你会惹恼用户)。
至于索引,你应该尝试把数据,使每个项目有所有必要的信息排名。 这意味着您需要从多个位置获取数据以进行构build。 一些索引系统有能力为现有项目添加值,这样在随后的答案进入时就可以很容易地将问题分数添加到问题中。简单性就是让您每隔一段时间重新构build一个问题。
我觉得Lucene对这份工作不好。 你需要的东西真的很快,可用性高…像SQL但你想要开源吗?
我build议你使用狮身人面像 – http://www.sphinxsearch.com/这是好多了,我说的是经验,我用它们两个。;
狮身人面像是惊人的。 真的是。