{
"query": { /*a simple base query goes here*/ },
"rescore": {
"window_size": 100,
"query": {
"rescore_query": {
"ltr": {
"model": {
"stored": "dummy"
},
"features": [{
"match": {
"title": < users keyword search >
}
}...
3 qid:1 1:1 2:1 3:0 4:0.2 5:0 # 1A 2 qid:1 1:0 2:0 3:1 4:0.1 5:1 # 1B 1 qid:1 1:0 2:1 3:0 4:0.4 5:0 # 1C 1 qid:1 1:0 2:0 3:1 4:0.3 5:0 # 1D 1 qid:2 1:0 2:0 3:1 4:0.2 5:0 # 2A
注意一下注释(用井号开头的),是评分的文档标识。这个标识对于 Ranklib 没用,不过对应我们阅读很有用。后面我们会看到通过 Elasticsearch 检索也会用到这类标识符。
我们用一个最小版本的文件举例 (看 这里)。我们需要从文件的削减版开始,这个文件仅仅含有一级,查询 id 和文档 id 元组. 像这样:
4 qid:1 # 7555 3 qid:1 # 1370 3 qid:1 # 1369 3 qid:1 # 1368 0 qid:1 # 136278 ...
如上所述,我们为分级的文件提供 Elasticsearch _id,作为每行的注释。
我们必须将每个查询 id (qid:1) 与实际的关键字查询 ("Rambo") 相匹配,以便用关键字生成特征值。我们在例子代码的开头提供匹配关系:
# 在下面加入你的关键字, 特征脚本将会它们安置于查询模板
# 在下面加入你的关键字, 特征脚本将会它们安置于查询模板
# # qid:1: rambo # qid:2: rocky # qid:3: bullwinkle # # https://sourceforge.net/p/lemur/wiki/RankLib%20File%20Format/ # # 4 qid:1 # 7555 3 qid:1 # 1370 3 qid:1 # 1369 3 qid:1 # 1368 0 qid:1 # 136278 ...
为使大家更好地了解,我将要把 ranklib"查询" (qid:1 等等) 作为关键字来区分 Elasticsearch Query DSL 查询 ,Elasticsearch Query DSL 查询是 Elasticsearch 用于生成特征值的明确的结构。
上述不是完整的 Ranklib 判断列表。当给定关键字查询文档时,它仅仅是相关性等级的极简案例。要成为完整的训练集,它需要包含上述值的特征,在每行第一个判断列表显示后包含 1:0 2:1 ... 等等。
为生成那些特征值,我们需要提出与电影相关的特征属性。正如我们刚才所述,有 Elasticsearch 查询。Elasticsearch 查询的分数将填充上述的判断列表。
上述例子中,我们用 jinja 模板与每个特征数字相对应。例如, 文件 1.json.jinja 是下面的 Query DSL 查询:
{ "query": { "match": { "title": "" } } }
{ "query": { "multi_match": { "query": "", "type": "cross_fields", "fields": ["overview", "genres.name", "title", "tagline", "belongs_to_collection.name", "cast.name", "directors.name"], "tie_breaker": 1.0 } } }
学习排名的乐趣之一是假设特征的相关性。举例来说,在任意 Elasticsearch 查询中,你可以改变特征 1 和 2,你也可以通过增加额外的特征来实验。
特征太多就会出现问题,你需要充分的训练样本来覆盖所有合理的特征值。在后续文章中,我们将讨论更多关于训练与测试学习来排名模型。
通过精简版判断列表和 Query DSL 查询/特征 集这两个因素,我们要为 Ranklib 生成更多的判断列表,将 Ranklib 生成模型加载进 Elasticsearch。这意味着:
执行下面命令:
python train.py
这个脚本贯穿了上面提到的所有步骤,遍览代码:
首先,我们用文档加载精简的判断列表,关键字查询 id,级别元组,在文件开头指定的查询关键字:
judgements = judgmentsByQid(judgmentsFromFile(filename='sample_judgements.txt'))
kwDocFeatures(es, index='tmdb', searchType='movie', judgements=judgements)
kwDocFeatures 功能遍历 1.json.jinja 到 N.json.jinja(特征/查询),批量 Elasticsearch 查询获取相关分数,为每一个关键字/文档元组使用 Elasticsearch 批量查询 (_msearch) API。代码在这里可以看。
一旦我们有全量的特征,我们就在新文件输出全量的训练集(判断附加特征)(sample_judgements_wfeatures.txt):
buildFeaturesJudgmentsFile(judgements, filename='sample_judgements_wfeatures.txt')
3 qid:1 1:9.476478 2:25.821222 # 1370 3 qid:1 1:6.822593 2:23.463709 # 1369
特征 1 是 "Rambo" 的 TF*IDF 分数查询(1.json.jinja),特征 2 是更复杂的 TF*IDF 分数查询(2.json.jinja)
接下来,我们训练!以下行使用保存的文件作为判断数据,通过命令行来执行 Ranklib.jar。
trainModel(judgmentsWithFeaturesFile='sample_judgements_wfeatures.txt', modelOutput='model.txt')
正如你所看到的,这里只是简单地执行 java -jar Ranklib.jar 来训练 LambdaMART 模型:
def trainModel(judgmentsWithFeaturesFile, modelOutput): # java -jar RankLib-2.6.jar -ranker 6 -train sample_judgements_wfeatures.txt -save model.txt cmd = "java -jar RankLib-2.6.jar -ranker 6 -train %s -save %s" % (judgmentsWithFeaturesFile, modelOutput) print("Running %s" % cmd) os.system(cmd)
saveModel(es, scriptName='test', modelFname='model.txt')
这里 saveModel 仅仅是读取文件内容,并将其作为 ranklib 脚本以 POST 方式存储到 Elasticsearch 中。
3.使用学习排序模型进行搜索
一旦你完成了训练,你就可以进行搜索了!在 search.py 中有一个简单的查询例子。 你可以运行 python search.py rambo,它将使用训练好的模型搜索“rambo”,执行重新评分查询:
{ "query": { "match": { "_all": "rambo" } }, "rescore": { "window_size": 20, "query": { "rescore_query": { "ltr": { "model": { "stored": "test" }, "features": [{ "match": { "title": "rambo" } }, { "multi_match": { "query": "rambo", "type": "cross_fields", "tie_breaker": 1.0, "fields": ["overview", "genres.name", "title", "tagline", "belongs_to_collection.name", "cast.name", "directors.name"] } }] } } } } }
注意,我们只在这里排序前20个结果。 我们直接使用学习排序查询。 事实上,直接运行模型就工作得很好。
虽然它需要几百毫秒来搜索整个集合。 对于更大的集合,它就不能工作了。 一般来说,由于学习排序模型的性能成本,最好重新排序前 N 个结果。
这个例子只是为了抛砖引玉。 你需要根据实际问题的不同选择功能,如特征,记录特征的方式,训练模型的方式以及实施基准排名功能等。 这些在我们写的相关搜索中仍然适用。
欢迎光临 168大数据 (http://www.bi168.cn/) | Powered by Discuz! X3.2 |