RankLib评分列表有着严格的标准格式。第一列包括一个文档的评分(0到4);接下来的一个列是查询ID,比如“qid:1”;随后的列包含了与文档关联的特征值对,其中左边是1开始的特征索引,右边的数字是该特征的值。RankLib的README文件中有示例如下:
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注意其中的注释(# 1A等),这些注释是评价该文档的标识。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”)上,从而可以使用关键字来生成特征值。我们在头信息中提供了这个映射,示例代码会展示:
# Add your keyword strings below, the feature script will # Use them to populate your query templates # # 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“查询”,区别于Elasticsearch Query DSL“查询”,后者是符合Elasticsearch规范的,用于生成特征值。
上面并不是一个文章的RangLib评价列表,仅仅是一个给定文档对于一个给定关键字搜索的迷你示例。要成为一个完备的训练集,需要包含上述特征值,并在每行后面的第一个评分列表后显示1:0 2:1 。。。等。
为了生成这些特征值,还需要提出可能跟电影相关性对应的特征。正如前文所述,这些就是Elasticsearch查询。这些Elasticsearch查询的得分将会填充上述评分列表。在上述例子中,我们通过一个对应到每个特征数字的jinja模板来实现。比如文件1.json.jinja就是下列查询:
{ "query": { "match": { "title": "" } } }换句换说,我们已经决定特征1对于我们的电影搜索系统来说,应该就是用户关键字与所匹配的标题属性的TF*IDF相关度。2.jinja.json展示了一个多文本字段的复杂检索:
{ "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 } } }LTR的一个有趣之处是猜测哪些特征与相关度相关联。比如,你可以改变特征1和2为任意Elasticsearch查询,也可以多次试验增加额外的特征3。多个特征的问题是,你想获得足够典型的训练样本来覆盖所有候选特征值。后面的文章我们会探讨更多关于训练和测试LTR模型的话题。
基于这两点,最小评分列表和建议的Query DSL查询/特征集合,我们需要为RankLib生成一个全量的评价列表,并加载RankLib生成的模型到Elasticsearch中待用。这表示:
获取特征的每一个关键字/文档对的相关度得分。即发布查询到Elasticsearch来记录相关度得分
输出一个完整的评分文件,同时包含等级和关键字查询id,以及第一步中的特征值
运行Ranklib来训练模型
加载模型到Elasticsearch待搜索时调用
完成这一切的代码都在train.py中了,建议分步执行:
然后只要运行:
python train.py这个单一脚本执行了上面提到的所有步骤。现在来过一遍代码:
首先加载最小评判列表,仅包含文档、关键词查询ID、等级元组,以及文件头部的特定搜索关键词:
judgements = judgmentsByQid(judgmentsFromFile(filename='sample_judgements.txt'))然后我们发起批量的Elasticsearch查询并记录每个评价的特征(在评价中扩大通过率)。
kwDocFeatures(es, index='tmdb', searchType='movie', judgements=judgements)函数kwDocFeatures遍历1.json.jinja到N.json.jinja(特征/查询对),策略上使用Elasticsearch的批量搜索API(_search)来批量执行Elasticsearch查询,以便为每一个关键词/文档元组获取一个相关度得分。代码有点长,可以在看到。