文章转自:http://blog.csdn.net/duck_genuine/article/details/6257540

首先说一下lucene对文档的评分规则:

score(q,d)   =   coord(q,d) ·  queryNorm(q) · ( tf(t in d) ·  idf(t)2 ·  t.getBoost() ·  norm(t,d) )

具体可以查看相关文章:http://blog.chenlb.com/2009/08/lucene-scoring-architecture.html

这里先考虑三个因素coord(q,d)与tf(t in d),当查询串中,命中的词越多,coord计算的值则越大,某个词在文档中出现的次数越多则tf的值越大。还有就是norm(t,d),这个主要是文档boost与字段boost的影响。值越大,对整体评分的影响越重。

首先说tf对搜索结果的影响:

这里是在于本站使用的搜索评分开始是默认的评分器的情况下,但发现有些不足之处。因为站内搜索主要是视频的标题与标签。对于一个视频文档来说,标题或者与标签重复的词本身就是无意义的,比如标题为"美女美女美女美女",标签为“美女”,如果让tf 的作用变大,明显示会使得它的评分更大,而其实并不是视频网站想要的效果。因为我们更想让它更加发散,这样,用户的点击率才会高。所以我们应该让所有命中词的文档的tf 不受频率的影响,使其tf=1.0f;如下自定义的评分器

  1. /**
  2. * @author yuzhy
  3. * 实现自已的评分器
  4. * 文档中重复多少个词不影响分数
  5. *
  6. */
  7. public class MySolrSimilarity extends DefaultSimilarity {
  8. @Override
  9. public float tf(float freq) {
  10. return 1.0f;
  11. }
  12. @Override
  13. public float tf(int freq) {
  14. return 1.0f;
  15. }
  16. }

别小看这段代码 ,因为使用这种评分,对于一个文档来说,一个term在文档出现的频率并不影响,即是不用担心作弊的情况,因为在这方面上他们的分数都是一样的。之前还考虑了对标题与标签的重复字符串的处理,采用后缀树结构来处理公共子串,后来发现这种方法来得更简洁。

因为使用的是solr来作搜索服务来架构,所以首先修改solr默认的Similarity类。在solr 的配置文件schemal.xml,最后中修改或增加:

<similarity class="com.wole.solr.search.MySimilarity"/>

设置为自定义的评分器,重启solr服务后,自定义的评分器就生效了。搜索" 美女"后,不再出现“美女美女美女美女”文档靠前排的效果了。

接着说一下coord的影响:

搜索“htc Incredible S” 三个词,由于没有这完全命中,则使用了宽松规则,即命中一个词也返回进行排序,之前的评分,前几条的结果为:

  1. <doc>
  2. <str name="Subject">S.H.E爱而为一的魔力 幕后全纪录</str>
  3. <str name="tag">she selina hebe ella 爱而为一</str>
  4. <int name="public_time">1103150000</int>
  5. <int name="times">370</int>
  6. <int name="hd">1</int>
  7. </doc>
  8. <doc>
  9. <str name="Subject">1000种死法-S04-01.1024X576.x264</str>
  10. <str name="tag">1000种死法    </str>
  11. <int name="public_time">1103140000</int>
  12. <int name="times">692</int>
  13. <int name="hd">1</int>
  14. </doc>
  15. <doc>
  16. <str name="Subject">p-s-1</str>
  17. <str name="tag">    </str>
  18. <int name="public_time">1103150000</int>
  19. <int name="times">58</int>
  20. <int name="hd">1</int>
  21. </doc>

可以看到,命中的词S 的文档给排到较前,本应该让命中越来的词的文档分数更高,但因为这三个文档在其它方面影响到评分,使得它的最后分数高于命中多个词的文档,而排到最前,所以这样的搜索体验不够好,好的体验应该是让命中的词越多排得越高,所以我首先降低计算norm(t,d)的值。测试调了其权重值,让coord占更大的比例值,效果马上出来更好的,其前三条记录为:

  1. <doc>
  2. <str name="Subject">不可思议htc Incredible 对比 apple iphone4</str>
  3. <str name="tag">Incredible htc apple iphone4 苹果</str>
  4. <int name="public_time">1009250000</int>
  5. <int name="times">29758</int>
  6. <int name="hd">0</int>
  7. </doc>
  8. <doc>
  9. <str name="Subject">不可思议 htc Incredible 比拼 苹果 iphone 3gs</str>
  10. <str name="tag">不可思议 Incredible htc 苹果 apple</str>
  11. <int name="public_time">1009250000</int>
  12. <int name="times">20231</int>
  13. <int name="hd">0</int>
  14. </doc>
  15. <doc>
  16. <str name="Subject">HTC incredible拆解全过程</str>
  17. <str name="tag">手机 HTC incredible DROID系列 </str>
  18. <int name="public_time">1005030000</int>
  19. <int name="times">3649</int>
  20. <int name="hd">0</int>
  21. </doc>

这里命中两个词htc Incredible的文档给排到最前面来,显然这才更符合用户需要的。即使没有完全命中,它的相关性会更逼近。

最后讲一下norm(t,d):

没有norms 意味着
索引阶段禁用了文档boost 和域的boost 及长度标准化。好处在于节省内存,不用在搜索阶
段为索引中的每篇文档的每个域都占用一个字节来保存norms 信息了。但是对norms 信息
的禁用是必须全部域都禁用的,一旦有一个域不禁用,则其他禁用的域也会存放默认的
norms 值。因为为了加快norms 的搜索速度,Lucene 是根据文档号乘以每篇文档的norms
信息所占用的大小来计算偏移量的,中间少一篇文档,偏移量将无法计算。也即norms 信
息要么都保存,要么都不保存。

norm(t,d) 压缩几个索引期间的加权和长度因子:

  • Document boost - 文档加权,在索引之前使用 doc.setBoost()
  • Field boost - 字段加权,也在索引之前调用 field.setBoost()
  • lengthNorm(field) - 由字段内的 Token 的个数来计算此值,字段越短,评分越高,在做索引的时候由 Similarity.lengthNorm 计算。

以上所有因子相乘得出 norm 值,如果文档中有相同的字段,它们的加权也会相乘:

norm(t,d)   =   doc.getBoost() ·  lengthNorm(field) · f.getBoost()
  field f in d named as t  

搜索组件为dismax,其中文档bf的计算是由三个字段

public_time (视频发布时间)^15,times(视频播放数)^15,hd(视频高清)^4

字段的bf值为

qf=Subject^1+tag^0.3

如果想让coord的值靠前,计算文档 boost 与字段boost  的值应该降低一个级别。

改为:

public_time (视频发布时间)^1.5,times(视频播放数)^1.5,hd(视频高清)^0.4

这样 norm计算的值就远远小于 coord ,使命中越多词分数越高的效果

norm(t,d)   =   doc.getBoost() ·  lengthNorm(field) · f.getBoost()
  field f in d named as t

自定义评分器Similarity,提高搜索体验(转)的更多相关文章

  1. lucene正向索引(续)——域(Field)的元数据信息在.fnm里,在倒排表里,利用跳跃表,有利于大大提高搜索速度。

    4.1.2. 域(Field)的元数据信息(.fnm) 一个段(Segment)包含多个域,每个域都有一些元数据信息,保存在.fnm文件中,.fnm文件的格式如下: FNMVersion 是fnm文件 ...

  2. Solutions:如何运用Elastic App Search快速建立出色的React搜索体验

    建立搜索体验是一项艰苦的工作. 乍一看似乎很容易:建立一个搜索栏,将数据放入数据库,然后让用户输入对该数据库的查询. 但是,在数据建模,底层逻辑以及(当然)总体设计和用户体验方面,有很多事情要考虑. ...

  3. HTML5+CSS3+JQuery打造自定义视频播放器

    来源:http://www.html5china.com/HTML5features/video/201109206_1994.html 简介HTML5的<video>标签已经被目前大多数 ...

  4. Struts2透过自定义拦截器实现登录之后跳转到原页面

    Struts2通过自定义拦截器实现登录之后跳转到原页面 这个功能对用户体验来说是非常重要的.实现起来其实很简单. 拦截器的代码如下: package go.derek.advice; import g ...

  5. lucene全文搜索之四:创建索引搜索器、6种文档搜索器实现以及搜索结果分析(结合IKAnalyzer分词器的搜索器)基于lucene5.5.3

    前言: 前面几章已经很详细的讲解了如何创建索引器对索引进行增删查(没有更新操作).如何管理索引目录以及如何使用分词器,上一章讲解了如何生成索引字段和创建索引文档,并把创建的索引文档保存到索引目录,到这 ...

  6. Iterator、for..of,for...in和自定义遍历器**

    Iterator.for..of,for...in和自定义遍历器 Iterator: var arr = [1,2,3,4,5]; var ite = arr[Symbol.iterator](); ...

  7. struts2--实现自定义拦截器

    前言: 本篇文章,我想说清实现拦截器的办法,还有为什么要这样做: 目录: 1.需求目的 2.实现步骤及原理(文字怕描述不清,画图描述) 3.代码 4.总结 一.需求目的 规范或限制越级访问(例如:一个 ...

  8. Java内存管理-掌握自定义类加载器的实现(七)

    勿在流沙筑高台,出来混迟早要还的. 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 上一篇分析了ClassLoader的类加载相关的核心源码,也简单介绍了ClassLoa ...

  9. cena评测系统:自定义校验器(自定义评测插件编写)

    Cena评测系统,最受欢迎的信息学竞赛离线评测系统. 它是开放源程序的信息学竞赛评测系统,能满足大多数程序设计竞赛的测评需求. 特色功能: 通过局域网自动收取选手程序. 高效率的数据文件配置工具. 自 ...

随机推荐

  1. NProgress.js template

    NProgress.js:加载进度条:http://ricostacruz.com/nprogress/ 基础的这几个方法 这个网站上都有 我在一个地方看到这个代码 NProgress.configu ...

  2. jQ函数after、append、appendTo的区别

    1.after函数定义和用法:after() 方法在被选元素后插入指定的内容.参考:http://keleyi.com/a/bjac/cfyxd60g.htm 语法:$(selector).after ...

  3. JS判断是否是数字

    function isNumber(value) { var patrn = /^[0-9]*$/; if (patrn.exec(value) == null || value == "& ...

  4. js异步方式

    node.js的例子 1.回调 创建 main.js 文件 var fs = require("fs"); fs.readFile('input.txt','utf8', func ...

  5. LINQ / LINQ to SQL / LINQ to XXX 它们到底有什么区别

    LINQ是新生事物,不过从不少文章和讨论上看来,这方面的概念也已经有点混沌不清了.因此我们经常可以看到这样的话: LINQ只能将数据表与实体属性一一对应…… LINQ开发指南:在LINQ中进行数据库字 ...

  6. Junit单元测试笔记

    什么是单元测试? 单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的.很明确的功能是否正确. 单元测试目的? 执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致. 白盒测试和单 ...

  7. gulp-babel 取消严格模式方法

    最近项目决定用ES6语法重构,于是引入了gulp-babel去编译ES6. 问题来了,babel编译ES6会自动添加"use strict"在js文件的最前面,这就导致之前的项目文 ...

  8. 你真的了解UINavigationController吗?

    一:首先查看一下关于UINavigationController的定义 NS_CLASS_AVAILABLE_IOS(2_0) @interface UINavigationController : ...

  9. 自定义button

    改变button内部label和imageView的frame - (CGRect)titleRectForContentRect:(CGRect)contentRect - (CGRect)imag ...

  10. 浅析Dagger2的使用

    什么是Dagger2 Dagger是为Android和Java平台提供的一个完全静态的,在编译时进行依赖注入的框架,原来是由Square公司维护,现在由Google维护. 我们知道Dagger是一个依 ...