apache solr
本教程描述了如何在Apache Solr中实现现代的学习排名 (LTR,也称为机器学习排名)系统。 它适用于Solr经验为零但对机器学习和信息检索概念感到满意的人。 几个月前,我就是其中的一员,我发现使用网上找到的Solr资料进行安装和运行非常具有挑战性。 这是我尝试编写入门时希望拥有的教程的尝试。
在Linux上启动香草Solr实例 (在我的情况下为Fedora)实际上非常简单。 在Fedora上,首先下载Solr源tarball (即包含“ src”的文件)并将其解压缩到合理的位置。 接下来, cd进入Solr目录:
cd
/ path
/ to
/ solr-
< version
>/ solr
构建Solr需要Apache Ant和Apache Ivy ,因此安装它们:
sudo dnf
install ant ivy
现在构建Solr:
ant server
您可以通过运行以下命令来确认Solr是否正常工作:
bin
/ solr start
并确保您在http:// localhost:8983 / solr /上看到Solr Admin界面。 您可以使用以下命令停止Solr(但现在不要停止它):
bin
/ solr stop
Solr是一个搜索平台,因此您只需要知道如何执行以下两项操作即可:索引数据和定义排名模型。 Solr具有类似REST的API,这意味着将使用curl命令进行更改。 首先,创建一个名为test的核心 :
bin
/ solr create
-c
test
这个看似简单的命令实际上在幕后做了很多事情。 具体来说,它定义了一个模式 ,该模式告诉Solr应该如何处理文档(如思考标记化 , 词干 化等)和搜索(例如,使用tf-idf 向量空间模型 ),并设置了一个配置文件 ,该配置文件指定了哪些库和Solr将使用的处理程序。 可以通过以下方式删除核心:
bin
/ solr delete
-c
test
好的,让我们添加一些文档。 首先下载Solr in Action GitHub上提供的tweets的XML文件 。 看一下XML文件的内部。 请注意,它是如何使用<add>标记来告诉Solr将多个文档(以<doc>标记表示)添加到索引的。 要索引这些推文,请运行:
bin
/ post
-c
test
/ path
/ to
/ tweets.xml
如果现在访问http:// localhost:8983 / solr / (可能需要刷新),然后单击左侧的“ Core Selector”下拉列表,则可以选择测试内核。 如果然后单击“查询”选项卡,将显示查询界面。 如果单击底部的蓝色“执行查询”按钮,将显示一个JSON文档,其中包含有关刚刚被索引的推文的信息。 恭喜,您刚运行了第一个成功的查询! 具体来说,您使用了/ select RequestHandler来执行查询*:*。 *:*是一种特殊的语法,它告诉Solr 返回所有内容 。 我认为Solr查询语法不是很直观,因此您只需要习惯一下即可。
现在,您已经建立并运行了一个基本的Solr实例,为LTR系统定义功能。 像所有机器学习问题一样,有效的特征工程对于成功至关重要。 除了其他文本特征(例如长度)和文档外,现代LTR模型的标准功能还包括使用多个相似性度量(例如tf-idf矢量或BM25的 余弦相似性 )来比较多个文本字段(例如,正文,标题)特征(例如年龄,PageRank)。 一个很好的起点是Microsoft Research的学术数据集功能列表 。 其他一些常用功能的列表可以在马萨诸塞大学阿默斯特分校研究员蒋洁普的讲义的幻灯片32中找到。
首先,修改/ path / to / solr- <version> / solr / server / solr / test / conf / managed-schema ,使其包含模型所需的文本字段。 首先,更改文本字段,使其具有text_general类型(已在managed-schema中定义)。 text_general类型将允许您计算BM25相似度。 由于文本字段已经存在(在为推文建立索引时会自动创建),因此需要使用replace-field命令,如下所示:
curl
-X POST
-H
'Content-type:application/json'
--data-binary
'{
"replace-field" : {
"name":"text",
"type":"text_general",
"indexed":"true",
"stored":"true",
"multiValued":"true"}
}' http:
// localhost:
8983
/ solr
/ test
/ schema
我鼓励您在每次更改后仔细查看托管模式,以便对正在发生的事情有所了解。 接下来,指定text_tfidf类型,这将使您可以计算tf-idf余弦相似度:
curl
-X POST
-H
'Content-type:application/json'
--data-binary
'{
"add-field-type" : {
"name":"text_tfidf",
"class":"solr.TextField",
"positionIncrementGap":"100",
"indexAnalyzer":{
"tokenizer":{
"class":"solr.StandardTokenizerFactory"},
"filter":{
"class":"solr.StopFilterFactory",
"ignoreCase":"true",
"words":"stopwords.txt"},
"filter":{
"class":"solr.LowerCaseFilterFactory"}},
"queryAnalyzer":{
"tokenizer":{
"class":"solr.StandardTokenizerFactory"},
"filter":{
"class":"solr.StopFilterFactory",
"ignoreCase":"true",
"words":"stopwords.txt"},
"filter":{
"class":"solr.SynonymGraphFilterFactory",
"ignoreCase":"true",
"synonyms":"synonyms.txt"},
"filter":{
"class":"solr.LowerCaseFilterFactory"}},
"similarity":{
"class":"solr.ClassicSimilarityFactory"}}
}' http:
// localhost:
8983
/ solr
/ test
/ schema
现在添加一个text_tfidf字段,该字段将是您刚定义的text_tfidf类型:
curl
-X POST
-H
'Content-type:application/json'
--data-binary
'{
"add-field" : {
"name":"text_tfidf",
"type":"text_tfidf",
"indexed":"true",
"stored":"false",
"multiValued":"true"}
}' http:
// localhost:
8983
/ solr
/ test
/ schema
因为text字段和text_tfidf字段的内容相同(只是处理方式不同),所以请告诉Solr将内容从text复制到text_tfidf :
curl
-X POST
-H
'Content-type:application/json'
--data-binary
'{
"add-copy-field" : {
"source":"text",
"dest":"text_tfidf"}
}' http:
// localhost:
8983
/ solr
/ test
/ schema
现在您可以重新索引数据了:
bin
/ post
-c
test
/ home
/ malcorn
/ solr-in-action
/ example-docs
/ ch6
/ tweets.xml
现在您的文档已正确索引,建立一个LTR模型。 如果您是LTR的新手,我建议您参阅Tie-Yan Liu的论文和教科书 。 如果您熟悉机器学习,那么这些想法就应该很容易掌握。 我还建议您查看有关LTR的Solr文档 ,我将在本节中链接到该文档 。 首先在Solr中启用LTR需要对/path/to/solr-<version>/solr/server/solr/test/solrconfig.xml 进行一些更改 。 将以下文本复制并粘贴到<config>和</ config>标记之间的任意位置(分别位于文件的顶部和底部)。
<lib dir = "${solr.install.dir:../../../..}/contrib/ltr/lib/" regex = ".*\.jar" />
<lib dir = "${solr.install.dir:../../../..}/dist/" regex = "solr-ltr-\d.*\.jar" />
<queryParser name = "ltr" class = "org.apache.solr.ltr.search.LTRQParserPlugin" />
<cache name = "QUERY_DOC_FV"
class = "solr.search.LRUCache"
size = "4096"
initialSize = "2048"
autowarmCount = "4096"
regenerator = "solr.search.NoOpRegenerator" />
<transformer name = "features" class = "org.apache.solr.ltr.response.transform.LTRFeatureLoggerTransformerFactory" >
<str name = "fvCacheName" > QUERY_DOC_FV
</str >
</transformer >
现在您可以在启用LTR的情况下运行Solr。 首先,停止Solr:
bin
/ solr stop
然后在启用LTR插件的情况下重新启动它:
bin
/ solr start -Dsolr.ltr.enabled=
true
接下来,将模型特征和模型规范推至Solr。 在Solr中, 使用JSON格式的文件定义 LTR功能。 对于此模型,将以下功能保存在my_efi_features.json中 :
[
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"tfidf_sim_a"
,
"class"
:
"org.apache.solr.ltr.feature.SolrFeature"
,
"params"
:
{
"q"
:
"{!dismax qf=text_tfidf}${text_a}"
}
}
,
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"tfidf_sim_b"
,
"class"
:
"org.apache.solr.ltr.feature.SolrFeature"
,
"params"
:
{
"q"
:
"{!dismax qf=text_tfidf}${text_b}"
}
}
,
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"bm25_sim_a"
,
"class"
:
"org.apache.solr.ltr.feature.SolrFeature"
,
"params"
:
{
"q"
:
"{!dismax qf=text}${text_a}"
}
}
,
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"bm25_sim_b"
,
"class"
:
"org.apache.solr.ltr.feature.SolrFeature"
,
"params"
:
{
"q"
:
"{!dismax qf=text}${text_b}"
}
}
,
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"max_sim"
,
"class"
:
"org.apache.solr.ltr.feature.SolrFeature"
,
"params"
:
{
"q"
:
"{!dismax qf='text text_tfidf'}${text}"
}
}
]
命令存储区告诉Solr在哪里存储特征。 name是功能的名称; class指定哪个Java类将处理该功能 ; and params提供有关其Java类所需的功能的其他信息。 对于SolrFeature ,您需要提供查询。 {!dismax qf = text_tfidf} $ {text_a}告诉Solr使用DisMaxQParser使用text_a的内容搜索text_tfidf字段。 使用DisMax解析器而不是看似更明显的FieldQParser (例如{!field f = text_tfidf} $ {text_a} )的原因是因为FieldQParser会自动将多项查询转换为“短语”(即,它将类似“猫在帽子”到,有效“的戴帽子 的猫 ”,而不是“对”,“猫”,“中”,“中”,“帽子”)。 这种FieldQParser行为(对我来说似乎是一个很奇怪的默认设置)最终使我头疼不已 ,但是我最终找到了DisMaxQParser的解决方案。
{!dismax QF =“文本text_tfidf”} $ {}文字告诉Solr的搜索文本和文本的内容text_tfidf领域两者结合,然后把这些两个分数的最大值。 尽管此功能在这种情况下并没有真正意义,因为已经将两个字段的相似性用作功能,但它演示了如何实现此功能。 例如,假设您的语料库中的文档最多链接到其他五个文本数据源。 在搜索过程中合并该信息可能是有道理的,而在多个相似性评分中取最大值是做到这一点的一种方法。
要将功能推送到Solr,请运行以下命令:
curl
-XPUT
'http://localhost:8983/solr/test/schema/feature-store'
--data-binary
"@/path/to/my_efi_features.json"
-H
'Content-type:application/json'
如果要上传新功能,首先必须使用以下方法删除旧功能:
curl
-XDELETE
'http://localhost:8983/solr/test/schema/feature-store/my_efi_feature_store'
接下来,将以下模型规范保存在my_efi_model.json中 :
{
"store"
:
"my_efi_feature_store"
,
"name"
:
"my_efi_model"
,
"class"
:
"org.apache.solr.ltr.model.LinearModel"
,
"features"
:
[
{
"name"
:
"tfidf_sim_a"
}
,
{
"name"
:
"tfidf_sim_b"
}
,
{
"name"
:
"bm25_sim_a"
}
,
{
"name"
:
"bm25_sim_b"
}
,
{
"name"
:
"max_sim"
}
]
,
"params"
:
{
"weights"
:
{
"tfidf_sim_a"
:
1.0
,
"tfidf_sim_b"
:
1.0
,
"bm25_sim_a"
:
1.0
,
"bm25_sim_b"
:
1.0
,
"max_sim"
:
0.5
}
}
}
在这种情况下, store指定将模型使用的特征 存储在哪里; name是模型的名称; class指定哪个Java类将实现模型; features是模型功能的列表; params提供了模型的Java类所需的其他信息。 首先使用LinearModel ,它简单地获取特征值的加权和以生成分数。 显然,提供的权重是任意的。 为了找到更好的权重,您需要从Solr中提取训练数据。 我将在RankNet部分中更深入地讨论这个主题。
您可以使用以下方法将模型推至Solr:
curl
-XPUT
'http://localhost:8983/solr/test/schema/model-store'
--data-binary
"@/path/to/my_efi_model.json"
-H
'Content-type:application/json'
现在,您可以运行第一个LTR查询了:
您应该看到类似以下内容:
{
"responseHeader"
:
{
"status"
:
0
,
"QTime"
:
101
,
"params"
:
{
"q"
:
"historic north"
,
"fl"
:
"id,score,[features]"
,
"rq"
:
"{!ltr model=my_efi_model efi.text_a=historic efi.text_b=north efi.text='historic north'}"
}
}
,
"response"
:
{
"numFound"
:
1
,
"start"
:
0
,
"maxScore"
:
3.0671878
,
"docs"
:
[
{
"id"
:
"1"
,
"score"
:
3.0671878
,
"[features]"
:
"tfidf_sim_a=0.53751516,tfidf_sim_b=0.0,bm25_sim_a=0.84322417,bm25_sim_b=0.84322417,max_sim=1.6864483"
}
]
}
}
参考请求, q = historic north是用于获取初始结果的查询(在这种情况下使用BM25),然后使用LTR模型对其进行重新排名。 rq是提供所有LTR参数的位置,而efi代表“ 外部功能信息 ”,它使您可以在查询时指定功能。 在这种情况下,你填充text_a参数与长期历史中,text_b参数与术语北部 ,并与多项查询“历史悠久的北”(注意,这是不被视为“词组的正文参数”)。 fl = id,score,[features]告诉Solr在结果中包括id , score和model特征。 您可以通过在Solr Admin UI的“查询”界面中执行关联的搜索来验证特征值正确。 例如,在q文本框中键入text_tfidf:historic ,在fl文本框中键入score ,然后单击“执行查询”按钮,应返回值0.53751516。
对于LTR系统,线性模型通常使用所谓的“ 逐点 ”方法进行训练,这是单独考虑文档的地方(即,模型会询问“此文档是否与查询相关?”); 但是,逐点方法通常不适用于LTR问题。 RankNet是使用“ 成对 ”方法的神经网络,在该方法中, 成对地考虑具有相对相对优先级的文档(即,模型询问“对于查询而言,文档A是否比文档B更相关?”) 。 开箱即用不支持RankNet,但是我已经在Solr和Keras中实现了RankNet 。 值得注意的是, LambdaMART可能更适合您的搜索应用程序。 但是,可以使用我的Keras实现在GPU上对RankNet进行快速培训,这使其成为解决搜索问题的很好的解决方案,其中只有一个文档与任何给定查询相关。 有关RankNet,LambdaRank和LambdaMART的详尽(技术性)概述,请参阅Chris Burges在Microsoft Research期间撰写的论文 。
要在Solr中启用RankNet,您必须将RankNet.java添加到/ path / to / solr- <version> / solr / contrib / ltr / src / java / org / apache / solr / ltr / model ,然后重新构建Solr(提醒:构建在/ path / to / solr- <version> / solr中 ):
ant server
现在,如果您检查/path/to/solr-<version>/solr/dist/solr-ltr-{version}-SNAPSHOT.jar ,则应该在/ org / apache / solr / ltr / model /下看到RankNet.class 。
不幸的是,Solr中建议的特征提取方法 非常慢 (其他Solr用户似乎同意这可能更快 )。 即使是并行发出请求,我也花了近三天的时间才能提取约200,000个查询的功能。 我认为更好的方法可能是对查询建立索引,然后计算“文档”(由真实的文档和查询组成)之间的相似度,但这确实应该纳入Solr。 无论如何,这是一些使用查询从Solr提取特征的Python示例代码:
import numpy
as np
import requests
import simplejson
# Number of documents to be re-ranked.
RERANK
=
50
with
open
(
"RERANK.int"
,
"w"
)
as f:
f.
write
(
str
( RERANK
)
)
# Build query URL.
q_id
= row
[
"id"
]
q_field_a
= row
[
"field_a"
] .
strip
(
) .
lower
(
)
q_field_b
= row
[
"field_b"
] .
strip
(
) .
lower
(
)
q_field_c
= row
[
"field_c"
] .
strip
(
) .
lower
(
)
q_field_d
= row
[
"field_d"
] .
strip
(
) .
lower
(
)
all_text
=
" " .
join
(
[ q_field_a
, q_field_b
, q_field_c
, q_field_d
]
)
url
=
"http://localhost:8983/solr/test/query"
# We only re-rank one document when extracting features because we want to be
# able to compare the LTR model to the BM25 ranking. Setting reRankDocs=1
# ensures the original ranking is maintained.
url +
=
"?q={0}&rq={{!ltr model=my_efi_model reRankDocs=1 " .
format
( all_text
)
url +
=
"efi.field_a='{0}' efi.field_b='{1}' efi.field_c='{2}' efi.field_d='{3}' " .
format
( field_a
, field_b
, field_c
, field_d
)
url +
=
"efi.all_text='{0}'}}&fl=id,score,[features]&rows={1}" .
format
( all_text
, RERANK
)
# Get response and check for errors.
response
= requests.
request
(
"GET"
, url
)
try :
json
= simplejson.
loads
( response.
text
)
except simplejson.
JSONDecodeError :
print
( q_id
)
return
if
"error"
in json:
print
( q_id
)
return
# Extract the features.
results_features
=
[
]
results_targets
=
[
]
results_ranks
=
[
]
add_data
=
False
for
( rank
, document
)
in
enumerate
( json
[
"response"
]
[
"docs"
]
) :
features
= document
[
"[features]"
] .
split
(
","
)
feature_array
=
[
]
for feature
in features:
feature_array.
append
( feature.
split
(
"="
)
[
1
]
)
feature_array
= np.
array
( feature_array
, dtype
=
"float32"
)
results_features.
append
( feature_array
)
doc_id
= document
[
"id"
]
# Check if document is relevant to query.
if q_id
in relevant.
get
( doc_id
,
{
}
) :
results_ranks.
append
( rank +
1
)
results_targets.
append
(
1
)
add_data
=
True
else :
results_targets.
append
(
0
)
if add_data:
np.
save
(
"{0}_X.npy" .
format
( q_id
)
, np.
array
( results_features
)
)
np.
save
(
"{0}_y.npy" .
format
( q_id
)
, np.
array
( results_targets
)
)
np.
save
(
"{0}_rank.npy" .
format
( q_id
)
, np.
array
( results_ranks
)
)
现在您可以训练一些模型了。 首先,提取数据并评估整个数据集上的BM25排名。
import
glob
import numpy
as np
rank_files
=
glob .
glob
(
"*_rank.npy"
)
suffix_len
=
len
(
"_rank.npy"
)
RERANK
=
int
(
open
(
"RERANK.int"
) .
read
(
)
)
ranks
=
[
]
casenumbers
=
[
]
Xs
=
[
]
ys
=
[
]
for rank_file
in rank_files:
X
= np.
load
( rank_file
[ :-suffix_len
] +
"_X.npy"
)
casenumbers.
append
( rank_file
[ :suffix_len
]
)
if X.
shape
[
0
]
!= RERANK:
print
( rank_file
[ :-suffix_len
]
)
continue
rank
= np.
load
( rank_file
)
[
0
]
ranks.
append
( rank
)
y
= np.
load
( rank_file
[ :-suffix_len
] +
"_y.npy"
)
Xs.
append
( X
)
ys.
append
( y
)
ranks
= np.
array
( ranks
)
total_docs
=
len
( ranks
)
print
(
"Total Documents: {0}" .
format
( total_docs
)
)
print
(
"Top 1: {0}" .
format
(
( ranks
==
1
) .
sum
(
) / total_docs
)
)
print
(
"Top 3: {0}" .
format
(
( ranks
<=
3
) .
sum
(
) / total_docs
)
)
print
(
"Top 5: {0}" .
format
(
( ranks
<=
5
) .
sum
(
) / total_docs
)
)
print
(
"Top 10: {0}" .
format
(
( ranks
<=
10
) .
sum
(
) / total_docs
)
)
接下来,构建并评估(逐点)线性支持向量机。
from scipy.
stats
import rankdata
from sklearn.
svm
import LinearSVC
X
= np.
concatenate
( Xs
,
0
)
y
= np.
concatenate
( ys
)
train_per
=
0.8
train_cutoff
=
int
( train_per *
len
( ranks
)
) * RERANK
train_X
= X
[ :train_cutoff
]
train_y
= y
[ :train_cutoff
]
test_X
= X
[ train_cutoff:
]
test_y
= y
[ train_cutoff:
]
model
= LinearSVC
(
)
model.
fit
( train_X
, train_y
)
preds
= model._predict_proba_lr
( test_X
)
n_test
=
int
(
len
( test_y
) / RERANK
)
new_ranks
=
[
]
for i
in
range
( n_test
) :
start
= i * RERANK
end
= start + RERANK
scores
= preds
[ start:end
,
1
]
score_ranks
= rankdata
( -scores
)
old_rank
= np.
argmax
( test_y
[ start:end
]
)
new_rank
= score_ranks
[ old_rank
]
new_ranks.
append
( new_rank
)
new_ranks
= np.
array
( new_ranks
)
print
(
"Total Documents: {0}" .
format
( n_test
)
)
print
(
"Top 1: {0}" .
format
(
( new_ranks
==
1
) .
sum
(
) / n_test
)
)
print
(
"Top 3: {0}" .
format
(
( new_ranks
<=
3
) .
sum
(
) / n_test
)
)
print
(
"Top 5: {0}" .
format
(
( new_ranks
<=
5
) .
sum
(
) / n_test
)
)
print
(
"Top 10: {0}" .
format
(
( new_ranks
<=
10
) .
sum
(
) / n_test
)
)
现在您可以试用RankNet。 首先,组装训练数据,以使每一行都包含一个相关的文档向量和一个不相关的文档向量(对于给定的查询)。 因为在特征提取阶段返回了50行,所以每个查询在数据集中将有49个文档对。
Xs
=
[
]
for rank_file
in rank_files:
X
= np.
load
( rank_file
[ :-suffix_len
] +
"_X.npy"
)
if X.
shape
[
0
]
!= RERANK:
print
( rank_file
[ :-suffix_len
]
)
continue
rank
= np.
load
( rank_file
)
[
0
]
pos_example
= X
[ rank -
1
]
for
( i
, neg_example
)
in
enumerate
( X
) :
if i
== rank -
1 :
continue
Xs.
append
( np.
concatenate
(
( pos_example
, neg_example
)
)
)
X
= np.
stack
( Xs
)
dim
=
int
( X.
shape
[
1
] /
2
)
train_per
=
0.8
train_cutoff
=
int
( train_per *
len
( ranks
)
) *
( RERANK -
1
)
train_X
= X
[ :train_cutoff
]
test_X
= X
[ train_cutoff:
]
在Keras中建立模型:
from keras
import backend
from keras.
callbacks
import ModelCheckpoint
from keras.
layers
import Activation
, Add
, Dense
, Input
, Lambda
from keras.
models
import Model
y
= np.
ones
(
( train_X.
shape
[
0
]
,
1
)
)
INPUT_DIM
= dim
h_1_dim
=
64
h_2_dim
= h_1_dim //
2
h_3_dim
= h_2_dim //
2
# Model.
h_1
= Dense
( h_1_dim
, activation
=
"relu"
)
h_2
= Dense
( h_2_dim
, activation
=
"relu"
)
h_3
= Dense
( h_3_dim
, activation
=
"relu"
)
s
= Dense
(
1
)
# Relevant document score.
rel_doc
= Input
( shape
=
( INPUT_DIM
,
)
, dtype
=
"float32"
)
h_1_rel
= h_1
( rel_doc
)
h_2_rel
= h_2
( h_1_rel
)
h_3_rel
= h_3
( h_2_rel
)
rel_score
= s
( h_3_rel
)
# Irrelevant document score.
irr_doc
= Input
( shape
=
( INPUT_DIM
,
)
, dtype
=
"float32"
)
h_1_irr
= h_1
( irr_doc
)
h_2_irr
= h_2
( h_1_irr
)
h_3_irr
= h_3
( h_2_irr
)
irr_score
= s
( h_3_irr
)
# Subtract scores.
negated_irr_score
= Lambda
(
lambda x: -
1 * x
, output_shape
=
(
1
,
)
)
( irr_score
)
diff
= Add
(
)
(
[ rel_score
, negated_irr_score
]
)
# Pass difference through sigmoid function.
prob
= Activation
(
"sigmoid"
)
( diff
)
# Build model.
model
= Model
( inputs
=
[ rel_doc
, irr_doc
]
, outputs
= prob
)
model.
compile
( optimizer
=
"adagrad"
, loss
=
"binary_crossentropy"
)
现在训练并测试模型:
NUM_EPOCHS
=
30
BATCH_SIZE
=
32
checkpointer
= ModelCheckpoint
( filepath
=
"valid_params.h5"
, verbose
=
1
, save_best_only
=
True
)
history
= model.
fit
(
[ train_X
[ :
, :dim
]
, train_X
[ :
, dim:
]
]
, y
,
epochs
= NUM_EPOCHS
, batch_size
= BATCH_SIZE
, validation_split
=
0.05
,
callbacks
=
[ checkpointer
]
, verbose
=
2
)
model.
load_weights
(
"valid_params.h5"
)
get_score
= backend.
function
(
[ rel_doc
]
,
[ rel_score
]
)
n_test
=
int
( test_X.
shape
[
0
] /
( RERANK -
1
)
)
new_ranks
=
[
]
for i
in
range
( n_test
) :
start
= i *
( RERANK -
1
)
end
= start +
( RERANK -
1
)
pos_score
= get_score
(
[ test_X
[ start
, :dim
] .
reshape
(
1
, dim
)
]
)
[
0
]
neg_scores
= get_score
(
[ test_X
[ start:end
, dim:
]
]
)
[
0
]
scores
= np.
concatenate
(
( pos_score
, neg_scores
)
)
score_ranks
= rankdata
( -scores
)
new_rank
= score_ranks
[
0
]
new_ranks.
append
( new_rank
)
new_ranks
= np.
array
( new_ranks
)
print
(
"Total Documents: {0}" .
format
( n_test
)
)
print
(
"Top 1: {0}" .
format
(
( new_ranks
==
1
) .
sum
(
) / n_test
)
)
print
(
"Top 3: {0}" .
format
(
( new_ranks
<=
3
) .
sum
(
) / n_test
)
)
print
(
"Top 5: {0}" .
format
(
( new_ranks
<=
5
) .
sum
(
) / n_test
)
)
print
(
"Top 10: {0}" .
format
(
( new_ranks
<=
10
) .
sum
(
) / n_test
)
)
# Compare to BM25.
old_ranks
= ranks
[ -n_test:
]
print
(
"Total Documents: {0}" .
format
( n_test
)
)
print
(
"Top 1: {0}" .
format
(
( old_ranks
==
1
) .
sum
(
) / n_test
)
)
print
(
"Top 3: {0}" .
format
(
( old_ranks
<=
3
) .
sum
(
) / n_test
)
)
print
(
"Top 5: {0}" .
format
(
( old_ranks
<=
5
) .
sum
(
) / n_test
)
)
print
(
"Top 10: {0}" .
format
(
( old_ranks
<=
10
) .
sum
(
) / n_test
)
)
如果模型的结果令人满意,请将参数保存到JSON文件中,以推送到Solr:
import json
weights
= model.
get_weights
(
)
solr_model
= json.
load
(
open
(
"my_efi_model.json"
)
)
solr_model
[
"class"
]
=
"org.apache.solr.ltr.model.RankNet"
solr_model
[
"params"
]
[
"weights"
]
=
[
]
for i
in
range
(
len
( weights
) //
2
) :
matrix
= weights
[
2 * i
] .
T
bias
= weights
[
2 * i +
1
]
bias
= bias.
reshape
( bias.
shape
[
0
]
,
1
)
out_matrix
= np.
hstack
(
( matrix
, bias
)
)
np.
savetxt
(
"layer_{0}.csv" .
format
( i
)
, out_matrix
, delimiter
=
","
)
matrix_str
=
open
(
"layer_{0}.csv" .
format
( i
)
) .
read
(
) .
strip
(
)
solr_model
[
"params"
]
[
"weights"
] .
append
( matrix_str
)
solr_model
[
"params"
]
[
"nonlinearity"
]
=
"relu"
with
open
(
"my_efi_model.json"
,
"w"
)
as out:
json.
dump
( solr_model
, out
, indent
=
4
)
并像以前一样将其推送(删除之后):
curl
-XDELETE
'http://localhost:8983/solr/test/schema/model-store/my_efi_model'
curl
-XPUT
'http://localhost:8983/solr/test/schema/model-store'
--data-binary
"@/path/to/my_efi_model.json"
-H
'Content-type:application/json'
一切就在这里-Apache Solr中的现代学习排名系统。
翻译自: https://opensource.com/article/17/11/learning-rank-apache-solr
apache solr