lucene、solr中的日期衰减方法-------function query --尚未测试在solr4.8

来源:互联网 发布:图片数据库 编辑:程序博客网 时间:2024/06/10 02:40
经常有一种情景是这样的:我们索引了N年的文章,而查询时候无论直接用相关度、或者用时间排序,都是比较鲁莽的;我们想要一种既要相关度比较高,又要时间上比较新的文章。
这时候的解决办法就是,自定义日期衰减的ValueSourceQuery,然后在正常normalQuery的基础上后遭CustomScoreQuery即可。

下面给出2种在solr中使用日期衰减的方法
比如我们的索引中的时间字段是time,正常查询是title:哈哈 keyword:哈哈,
1、使用已有的各种functionQuery的组合
solr中日期衰减的查询方式则是:{!boost b=recip(ms(NOW/HOUR,time),3.16e-11,1,1)}title:哈哈 keyword:哈哈
前面这个式子的含义可以去查询solr wiki:http://wiki.apache.org/solr/FunctionQuery#What_is_a_Function.3F

这个方式,时间的衰减比较平缓,比如昨天的权重是0.999,前天是0.998,一年前的今天是0.5.。。。。。
如果我们需要一个时间衰减比较剧烈的方式,则需要自定义了。
2、自定义ValueSource实现FieldCacheSource
这里我们以lucene4.1为例(各个版本的代码有所偏差,需要根据情况实现),大致原理是:给每个时间设置一个时间衰减因子,然后把文档的相关度乘上时间因子就是最后得分。
2.1和2.3中的实现方式,在得到相关度以后,每次搜索,都会获取所有文档的时间字段,并计算时间权重值。这在效率上是比较慢的,数据在千万级别的时候还可接受,更多的数据则会比较慢。
所以第3部分提供了这个思路的另一个实现方式,它只会计算搜索结果中的文档的时间权重,大大降低了时间。
2.1 先实现是 一个ValueSource。

import java.io.IOException;import java.util.Map;import org.apache.lucene.index.AtomicReaderContext;import org.apache.lucene.queries.function.FunctionValues;import org.apache.lucene.queries.function.valuesource.FieldCacheSource;public class DateFunction extends FieldCacheSource {private static final long serialVersionUID = 6752223682280098130L;private static long now;public DateFunction(String field) {super(field);now = System.currentTimeMillis();}@Overridepublic FunctionValues getValues(Map context,AtomicReaderContext readerContext) throws IOException {long[] times = cache.getLongs(readerContext.reader(), field, false);//获取各个记录中的时间字段毫秒数final float[] weights = new float[times.length];for (int i = 0; i < times.length; i++) {weights[i] = ScoreUtils.getNewsScoreFactor(now, times[i]);//获取每个记录的时间衰减因子}return new FunctionValues() {//返回@Overridepublic float floatVal(int doc) {return weights[doc];}@Overridepublic int intVal(int doc) {return (int) weights[doc];}@Overridepublic String toString(int doc) {return description() + '=' + intVal(doc);}};}}

其中用到的scoreutils定义如下:

public class ScoreUtils {private static float[] daysDampingFactor = new float[32];private static float demoteboost = 0.5f;static {daysDampingFactor[0] = 1;for (int i = 1; i < 7; i++) {daysDampingFactor[i] = daysDampingFactor[i - 1] * demoteboost;}for (int i = 7; i < 31; i++) {daysDampingFactor[i] = daysDampingFactor[i / 7 * 7 - 1]* demoteboost;}for (int i = 31; i < daysDampingFactor.length; i++) {daysDampingFactor[i] = daysDampingFactor[i / 31 * 31 - 1]* demoteboost;}}private static float dayDamping(int delta) {return delta < daysDampingFactor.length ? daysDampingFactor[delta]: daysDampingFactor[daysDampingFactor.length - 1];}public static float getNewsScoreFactor(long now, long time) {float factor = 1;int day = (int) (time / MiscConstants.DAY_MILLIS);int nowDay = (int) (now / MiscConstants.DAY_MILLIS);if (day < nowDay) {factor = dayDamping(nowDay - day);} else if (day > nowDay) {factor = Float.MIN_VALUE;} else if (now - time <= MiscConstants.HALF_HOUR_MILLIS && now >= time) {factor = 2;}return factor;}public static float getNewsScoreFactor(long time) {long now = System.currentTimeMillis();return getNewsScoreFactor(now, time);}}class MiscConstants {/** 24x60x60x1000 */public static final long DAY_MILLIS = 86400000;/** 24x60x60x1000 */public static final long DAY_SECONDS = 86400;/** 60x1000 */public static final int MINUTE_MILLIS = 60000;/** 60x1000 */public static final int HALF_HOUR_MILLIS = 1800000;/** 60x1000 */public static final int MINUTE_SECONDS = 60;}

2.2 如果是在lucene中使用,则在正常的normalQuery基础上,包装一下即可,如下:
ValueSourceQuery dateBooster = new ValueSourceQuery(new DateFieldSource("ptime")); 
CustomScoreQuery dateScoreQuery = new CustomScoreQuery(normalQuery, dateBooster);
2.3 如果是在solr中使用个,还需要实现valuesourcepaser

import org.apache.lucene.queries.function.ValueSource;import org.apache.solr.common.util.NamedList;import org.apache.solr.search.FunctionQParser;import org.apache.solr.search.SyntaxError;import org.apache.solr.search.ValueSourceParser;public class DateSourceParser extends ValueSourceParser {@Overridepublic void init(NamedList namedList) {}@Overridepublic ValueSource parse(FunctionQParser fp) throws SyntaxError {return new DateFunction("ptime");// 被自定义排序的字段}}

并且要在solrconfig.xml的config标签中定义这个parser
<valueSourceParser name="dateDeboost" class="org.netease.solr.custom.DateSourceParser" />
这样在搜索的时候就可使用了{!boost b=dateDeboost()}title:哈哈 keyword:哈哈
ps:这里还支持参数;不用参数的时候dateDeboost(),这样调用就可以了。使用参数的时候dateDeboost(param),fqp.parseArg()可以获取参数。这样就可更自由的控制一下逻辑。

3、自定义ValueSource重用ValueSource
阅读solr的代码后,发现solr中的function query的实现更优雅。
这里记录了solr自定义的各种函数的定义org.apache.solr.search.ValueSourceParser。
其实思路就是不再逐个记录的遍历,主要区别是getValues方法中的实现。具体实现如下:
3.1 实现一个valuesource

import java.io.IOException;import java.util.Map;import org.apache.lucene.index.AtomicReaderContext;import org.apache.lucene.queries.function.FunctionValues;import org.apache.lucene.queries.function.ValueSource;import org.apache.lucene.queries.function.docvalues.FloatDocValues;import org.apache.lucene.search.IndexSearcher;public class DateFunction extends ValueSource {protected final ValueSource source;public DateFunction(ValueSource source) {this.source = source;}@Overridepublic FunctionValues getValues(Map context, AtomicReaderContext readerContext) throws IOException {final FunctionValues vals = source.getValues(context, readerContext);return new FloatDocValues(this) {@Overridepublic float floatVal(int doc) {long ptime = vals.longVal(doc);return ScoreUtils.getNewsScoreFactor(ptime);}};}@Overridepublic void createWeight(Map context, IndexSearcher searcher) throws IOException {source.createWeight(context, searcher);}@Overridepublic String description() {return "This is org.sling.DateFunction.";}@Overridepublic int hashCode() {return source.hashCode();}@Overridepublic boolean equals(Object o) {if (!(o instanceof DateFunction))return false;DateFunction other = (DateFunction) o;return source.equals(other.source);}}

其中scoreutils的定义还是和上面一样。
3.2 在solr中使用
import org.apache.lucene.queries.function.ValueSource;import org.apache.solr.common.util.NamedList;import org.apache.solr.search.FunctionQParser;import org.apache.solr.search.SyntaxError;import org.apache.solr.search.ValueSourceParser;public class DateSourceParser extends ValueSourceParser {    @Override    public void init(NamedList namedList) {    }    @Override    public ValueSource parse(FunctionQParser fp) throws SyntaxError {        //ValueSource不能获取两次。所以fp.parseValueSourceList()和fp.parseValueSource()只能用一个        ValueSource source = fp.parseValueSource();//获取这个ValueSource,并在一个sercher中重用它        return new DateFunction(source);    }}
3.3在lucene中使用
读一下fp.parseValueSource()这部分代码,可以发现,其实这也是用了lucene中的一些类。下面直接给出实现吧

ValueSource valueSource = new LongFieldSource(timeField);FunctionQuery scoreField = new FunctionQuery(new DateFunction(valueSource));CustomScoreQuery dateScoreQuery = new CustomScoreQuery(query, scoreField);//TopDocs top = indexSearcher.search(query, 5);//普通查询TopDocs top = indexSearcher.search(dateScoreQuery, 5);//日期衰减查询ScoreDoc[] scoreDocs = top.scoreDocs;

可以发现,在lucene中普通查询和日期衰减查询的区别就是:构造的查询条件不一样而已。。。
0 0