今天在用Solr5.1测试检索时,发现一个奇怪的问题,便于大家对比,先介绍下散仙的环境:
JDK1.7
Lucene5.1
Solr5.1
分词器用的ik(改的ik源码)
先看下测试的5条数据:
id,name,count
1503486364953346048,北京奇虎测腾科技有限公司,1
1503486365060300800,北京奇虎网力科技有限公司,2
1503486365065543680,北京奇虎科技有限公司,3
1503486826976903168,中国,7
1503486827011506176,中国人,8
使用IK(细粒度切词如下:)
北京 奇虎 测 腾 科技 有限公司 有限 有 限 公司
======================
北京 奇虎 网 力 科技 有限公司 有限 有 限 公司
======================
北京 奇虎 科技 有限公司 有限 有 限 公司
======================
中国人 中国 国人
======================
中国
OK,然后,大家想一个场景,假如我在搜索框里面搜索 北京奇虎 时,第几个搜索结果应该被排在第一位?大部分情况下我们都希望第三个结果,排在第一位,但结果会是这样吗?
不好意思,经实际验证,在Lucene5.1作为底层的Solr中,却并不是这样,截图如下:
等等,刚才里面不是还有一个中国人和中国的例子吗,我们搜个中国测试下,事实是这样吗?
奇怪了,因为在Lucene评分中,字段的长度也是影响评分的因素,默认情况下,是文本越短的会排在前面,但是上面两个例子中,竟然有一个出了异常,这难道是一个bug么?
在lucene的评分因素中,主要有如下几个因子:
1,tf
2,idf
3,coord
4,queryNorm
5,boost
6,norm(t,d)
7,payload
8,自定义评分
具体的介绍,请参考我以前的文章:
http://qindongliang.iteye.com/blog/2008396
针对本文这个评分问题,我们主要关注norm这个评分因子,因为它里面包含了长度因子的评分:
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
索引的时候,把 norm 值压缩(encode)成一个 byte 保存在索引中。搜索的时候再把索引中 norm 值解压(decode)成一个 float 值,这个 encode/decode 由 Similarity 提供。官方说:这个过程由于精度问题,以至不是可逆的,如:decode(encode(0.89)) = 0.75。
接下来,查看Lucene的DefaultSimilarity类源码,看下核心的几个方法代码
/** Cache of decoded bytes. */
private static final float[] NORM_TABLE = new float[256];
static {
for (int i = 0; i < 256; i++) {
NORM_TABLE[i] = SmallFloat.byte315ToFloat((byte)i);
}
}
//索引期间执行,将norm编码成一个8位字节
public final long encodeNormValue(float f) {
return SmallFloat.floatToByte315(f);
}
//搜索期间执行,将norm,还原成具体的分数,参与评分
public final float decodeNormValue(long norm) {
return NORM_TABLE[(int) (norm & 0xFF)]; // & 0xFF maps negative bytes to positive above 127
}
仔细看decodeNormValue方法,这个代码,发现里面竟然有将float强制转换为int一个强转,这意味着,精度损失。
什么意思?请看如下代码:
float a=1.524f;
float b=1.589f;
System.out.println((int)a);
System.out.println((int)b);
//结果都是1
知道这个东东后,就发现,文头题的那个问题,没错,就是因为两者的norm编码相差太少,所以导致他们的解码分数一样,从而出现了,排名问题,这也不能算Lucene的bug,可能Lucene的设计者认为,两个norm,相差的值只有大于0.1的情况下,才真正管用,否则一视同仁。
这就是答案了,所以如果想解决上面的问题,可以重新定义一个自己的评分类,并加入自己的逻辑,不建议直接修改源码实现。
最后,给出,本文测试的全部代码,以供参考:
package com.lucene.opera;
import java.io.StringReader;
import java.nio.file.Paths;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.TextField;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.Explanation;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.search.similarities.DefaultSimilarity;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class LuceneTest {
static final String indexpath="D:\\tmp\\luceneindex";
public static void index()throws Exception{
Directory dir=FSDirectory.open(Paths.get(indexpath));
IKAnalyzer ik=new IKAnalyzer(false);
IndexWriterConfig iwc=new IndexWriterConfig(ik);
iwc.setOpenMode(OpenMode.CREATE);
IndexWriter iw=new IndexWriter(dir, iwc);
// FieldType type=new FieldType();
// type.setTokenized(true);
// type.setStored(true);
// type.setIndexOptions(IndexOptions.DOCS);
// type.setOmitNorms(true);
// type.freeze();
//
Document doc=null;
// Document doc=new Document();
// doc.add(new TextField("name", "北京奇虎测腾科技有限公司", Store.YES));
//// doc.add(new Field("pname", "北京奇虎测腾科技有限公司", type));
// iw.addDocument(doc);
//
// doc=new Document();
// doc.add(new TextField("name", "北京奇虎网力科技有限公司", Store.YES));
//// doc.add(new Field("pname", "北京奇虎网力科技有限公司", type));
// iw.addDocument(doc);
//
////
// doc=new Document();
// doc.add(new TextField("name", "北京奇虎科技有限公司", Store.YES));
//// doc.add(new Field("pname", "北京奇虎科技有限公司", type));
// iw.addDocument(doc);
////
doc=new Document();
doc.add(new TextField("name", "北京奇虎测腾科技有限公司", Store.YES));
// doc.add(new Field("pname", "中国", type));
iw.addDocument(doc);
doc=new Document();
doc.add(new TextField("name", "北京奇虎科技有限公司", Store.YES));
// doc.add(new Field("pname", "中国人", type));
iw.addDocument(doc);
iw.commit();
iw.close();
System.out.println("索引成功!");
}
public static void search(String kw)throws Exception{
Directory dir=FSDirectory.open(Paths.get(indexpath));
IKAnalyzer ik=new IKAnalyzer(false);
IndexReader ir=DirectoryReader.open(dir);
IndexSearcher search=new IndexSearcher(ir);
// search.setSimilarity(new DefaultSimilarity());
QueryParser qr=new QueryParser("name", ik);
Query parse = qr.parse(kw);
System.out.println(parse.toString());
// Explanation explain = search.explain(parse , 100);
// System.out.println(explain.toString());
TopDocs td=search.search(parse, 100);
for(ScoreDoc sd:td.scoreDocs){
int i=sd.doc;
Document doc=search.doc(i);
// Explanation explain = search.explain(parse , i);
// System.out.println(explain.toString());
System.out.println(" 名称: "+doc.get("name")+" 评分: "+sd.score);
}
ir.close();
dir.close();
}
public static void main(String[] args)throws Exception {
// float []s=DefaultSimilarity.NORM_TABLE;
// for(int i=0;i<s.length;i++){
// System.out.println(i+" "+s[i]);
// }
// index();
// search("奇虎");
//
float a=1.524f;
float b=1.589f;
System.out.println((int)a);
System.out.println((int)b);
//结果都是1
// test("北京奇虎测腾科技有限公司");
// System.out.println("======================");
// test("北京奇虎网力科技有限公司");
// System.out.println("======================");
// test("北京奇虎科技有限公司");
// System.out.println("======================");
// test("中国人");
// System.out.println("======================");
// test("中国");
}
// "北京奇虎测腾科技有限公司"
// "北京奇虎网力科技有限公司"
// "北京奇虎科技有限公司"
// "中国"
// "中国人"
/****
* 测试分词token
* @param kw
* @throws Exception
*/
public static void test(String kw)throws Exception{
IKAnalyzer ik=new IKAnalyzer(false);
TokenStream token=ik.tokenStream("", new StringReader(kw));
CharTermAttribute term=token.addAttribute(CharTermAttribute.class);
token.reset();
while(token.incrementToken()){
System.out.print(term.toString()+" ");
}
System.out.println();
token.end();
token.close();
}
}
最后欢迎大家扫码关注微信公众号:我是攻城师(woshigcs),我们一起学习,进步和交流!(woshigcs)
本公众号的内容是有关搜索和大数据技术和互联网等方面内容的分享,也是一个温馨的技术互动交流的小家园,有什么问题随时都可以留言,欢迎大家来访!
- 大小: 406.4 KB
- 大小: 281.3 KB
分享到:
相关推荐
基于Lucene的搜索引擎的研究与应用基于Lucene的搜索引擎的研究与应用
基于Lucene的搜索策略研究
lucene3.6 搜索例子
lucene 高级搜索项目 附件搜索 附件内容搜索 全文搜索
基于Lucene的全文搜索引擎研究与应用.pdf 详实的介绍Lucene的架构设计分析
Lucene实现全文搜索,支持英文、模糊和智能查询
基于Lucene小型搜索引擎的研究与实现
lucene 近实时搜索 很清楚的解释了关于lucene近实时搜索的代码。很值得学习
基于Lucene的搜索引擎应用与研究
08.Lucene搜索实战2 共5页 09.Lucene搜索深入实战1 共5页 10.Lucene搜索深入实战2 共11页 11.Lucene搜索深入实战进阶1 共4页 12.Lucene搜索深入实战进阶2 共9页 13.Lucene搜索深入实战进阶3 共5页 14.Lucene搜索深入...
08.Lucene搜索实战2 共5页 09.Lucene搜索深入实战1 共5页 10.Lucene搜索深入实战2 共11页 11.Lucene搜索深入实战进阶1 共4页 12.Lucene搜索深入实战进阶2 共9页 13.Lucene搜索深入实战进阶3 共5页 14.Lucene搜索深入...
Lucene 实时搜索,视频详解,带课程文档,Lucene 实时搜索
全文检索介绍 索引 分词 Lucene介绍 Lucene应用详解 索引器 检索器 条件查询 实用工具及高亮器 Lucene综合应用——仿搜索引擎
08.Lucene搜索实战2 共5页 09.Lucene搜索深入实战1 共5页 10.Lucene搜索深入实战2 共11页 11.Lucene搜索深入实战进阶1 共4页 12.Lucene搜索深入实战进阶2 共9页 13.Lucene搜索深入实战进阶3 共5页 14.Lucene搜索深入...
垂直搜索引擎完全开源版 c#开发基于Lucene.net 1.前台结合Lucene的搜索引擎功能,使得数据搜索更快; 2.新增加采集功能,采集时图片下载,flash下载功能,默认配置的是南海网分类信息的采集规则; 3.该代码简洁,...
主要是关于lucene站内搜索的技术代码,可以使用;按照需要进行代码修改。
08.Lucene搜索实战2 共5页 09.Lucene搜索深入实战1 共5页 10.Lucene搜索深入实战2 共11页 11.Lucene搜索深入实战进阶1 共4页 12.Lucene搜索深入实战进阶2 共9页 13.Lucene搜索深入实战进阶3 共5页 14.Lucene搜索深入...
lucene3.0.3搜索的使用示例lucene3.0.3搜索的使用示例lucene3.0.3搜索的使用示例
基于Lucene的一个财经类网页搜索引擎,对于Lucene爱好者与新手来说,是个很不错的东东哦,呵呵。
利用lucene进行搜索,IndexSearcher是整个Lucene搜索查询相关信息的驱动引擎,在使IndexSearcher之前,需要构建IndexSearcher对象,Lucene提供了两种构建IndexSearcher对象的方式: 1、基于Directory对象构建; 2...