期望搜索效果
Q: 我想首先按相关度排序,然后再根据时间进行倒序排列
A: 同一个查询问题,每个结果的匹配度是不用的,例如,搜索“俄罗斯五金机械”,得到如下三个结果:
-
2017俄罗斯莫斯科机械,设备技术与产品展览会
-
2017俄罗斯机械设备展
-
2016俄罗斯五金机械展
以上三个例子中,相关度最好的是”俄罗斯五金机械展“, 原因是同时包含所有关键词,并且长度最短。 由于这3个结果的匹配度各不相同,不可能”再根据时间进行倒序排列“。 只有匹配度相同的多个展会,才能按照时间顺序二次排列。
Q: 可是竞争对手(例如展酷)就能做出这样的效果
A: 搜一两个关键词效果好不难,难的是各种类型的搜索效果都不太差。 用下面列出来的关键词进行搜索,看看他们的搜索结果有多不靠谱。
Q: 可是百度,Google的搜索结果就很靠谱,这种技术已经相当普及了,为什么我们不能实现?
A: 国内用户被盗版惯坏了,以为世界上并不存在”软件研发成本“这种事,真的该醒醒了。 软件产品的研发成本是巨大的,免费的软件要么是人家太有钱不跟你计较,顺便整死竞争对手, 要么是靠植入广告流氓插件挣钱,要么是开源,然而Google、百度、展酷、牛展网……没有一个愿意开源。
现在展会搜索用户体验不够好,希望大家多提意见和建议,帮助我们向高水平看齐,持续改进。
搜索效果检查关键词
下面列出的查询语句用于测试搜索效果的好坏:
-
2016五金
-
2016科隆五金
-
五金2016
-
五金机械2016
相关度计算方法
指标定义和公式
相关度指搜索问题(query, q)与搜索目标域(field, f)之间的匹配程度。 例如搜索中文名称中有“五金机械”的展会,这里“中文名称”对应的数据库字段nameZHCN 就是目标域,“五金机械”就是问题。问题可以被拆分成多个关键词(term, t),例如五金机械 包含五金和机械两个关键词。
根据以下指标计算问题和目标域之间的相关度:
-
tf (term frequency): 表示关键词出现的频率的高低,关键词出现的频率越高, 匹配程度越高,例如查询“五金机械”,“2016俄罗斯五金机械展”比"2017俄罗斯机械设备展"匹配度高。 计算公式:
tf(q in f) = sqrt(frequency)
, 其中frequency
指field中包含的term数量,例如“2016俄罗斯五金机械展”的frequency是2(五金,机械), 所以tf("五金机械" in "2016俄罗斯五金机械展") = sqrt(2) = 1.414
,tf("五金机械" in "2017俄罗斯机械设备展") = sqrt(1) = 1
. -
idf (inverse document frequency): 一个关键词在所有文档中出现的频率越高, 则其匹配权重越低,例如搜索“五金机械展”,“2017俄罗斯五金机械博览会”(A) 和 “2016俄罗斯五金机械展”(B)哪个匹配度高? 我们认为A高于B,因为“展”这个词太常见了,匹配到“展”的价值显然小于匹配“五金”的价值。 怎么把这个特点用算法表达出来?就要用到idf。计算公式:
idf(t) = 1 + ln(maxDoc / (matchedDoc + 1))
. 其中maxDoc
是所有文档数量,matchedDoc
是包含关键词t
的文档的数量。 例如整个type包含3422个文档,其中40个包含关键词“五金”,则idf("五金") = 1 + ln(3422 / (40 + 1)) = 5.4244
,idf("展") = 1 + ln(3422 / (2410 + 1)) = 1.35018
。 另外,A和B的tf是一样的:分别包含3个term(五金,机械,展)中的两个。 -
fn (field-length norm): 域越长,匹配度越低,实际上反映了”关键词密度“的高低, 域越长,关键词密度越低。例如搜索“2017俄罗斯五金机械”,”2017俄罗斯五金机械展“ 的匹配度高于“2017俄罗斯莫斯科机械,设备技术与五金产品展览会”。 计算公式:
fn(f) = 1 / sqrt(numTerm)
. 其中numTerm
是域f
包含的关键词数量。 例如fn("2017年土耳其国际门及门锁五金机械博览会") = 1 / sqrt(10) = 0.3162
,numTerm = 10
是域被中文分词后词的数量。 -
qn (query norm): 这个指标没什么用,可以忽略,计算公式:
qn(f) = 1 / sqrt(idf1^2 + idf2^2 + ...)
, 其中idf1
,idf2
... 是f
中包含的每个关键词的idf值。 例如qn("2017年土耳其国际门及门锁五金机械博览会") = 1 / sqrt(5.4244^2 + 4.2476^2) = 0.1451
实例分析
搜索"五金机械",我们来看看它的相关度2.1818347是怎么算出来的:
api='api.newfairs.com'
idx=production
type=Fair
http -b POST "https://$api/$type/_search?explain=1&pretty=1&search_type=dfs_query_then_fetch" query:='{"bool":{"must":[{ "match": { "recurrence.nameZHCN": "五金机械" }}]}}' sort:='[{ "_score": { "order": "desc" }}, {"recurrence.timeStart": {"order" : "desc", "mode": "max"}}]' size=1 | jq -r '.hits.hits[] | "\(._score): \(._source.recurrence[0].nameZHCN), \(._source.recurrence[0].timeStart), \(._explanation)"'
2.1818347: 2017年土耳其国际门及门锁五金机械博览会, 2017-01-04T16:00:00.000Z,
{
"value": 2.1529999,
"description": "sum of:",
"details": [
{
"value": 2.1529999,
"description": "sum of:",
"details": [
{
"value": 1.3346298,
"description": "weight(recurrence.nameZHCN:五金 in 3005) [PerFieldSimilarity], result of:",
"details": [
{
"value": 1.3346298,
"description": "score(doc=3005,freq=1.0), product of:",
"details": [
{
"value": 0.7873329,
"description": "queryWeight, product of:",
"details": [
{
"value": 5.4244084,
"description": "idf(docFreq=40, maxDocs=3422)",
"details": []
},
{
"value": 0.14514631,
"description": "queryNorm",
"details": []
}
]
},
{
"value": 1.6951276,
"description": "fieldWeight in 3005, product of:",
"details": [
{
"value": 1,
"description": "tf(freq=1.0), with freq of:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
}
]
},
{
"value": 5.4244084,
"description": "idf(docFreq=40, maxDocs=3422)",
"details": []
},
{
"value": 0.3125,
"description": "fieldNorm(doc=3005)",
"details": []
}
]
}
]
}
]
},
{
"value": 0.81837,
"description": "weight(recurrence.nameZHCN:机械 in 3005) [PerFieldSimilarity], result of:",
"details": [
{
"value": 0.81837,
"description": "score(doc=3005,freq=1.0), product of:",
"details": [
{
"value": 0.61652803,
"description": "queryWeight, product of:",
"details": [
{
"value": 4.2476315,
"description": "idf(docFreq=132, maxDocs=3422)",
"details": []
},
{
"value": 0.14514631,
"description": "queryNorm",
"details": []
}
]
},
{
"value": 1.3273848,
"description": "fieldWeight in 3005, product of:",
"details": [
{
"value": 1,
"description": "tf(freq=1.0), with freq of:",
"details": [
{
"value": 1,
"description": "termFreq=1.0",
"details": []
}
]
},
{
"value": 4.2476315,
"description": "idf(docFreq=132, maxDocs=3422)",
"details": []
},
{
"value": 0.3125,
"description": "fieldNorm(doc=3005)",
"details": []
}
]
}
]
}
]
}
]
},
{
"value": 0,
"description": "match on required clause, product of:",
"details": [
{
"value": 0,
"description": "# clause",
"details": []
},
{
"value": 0.14514631,
"description": "_type:Fair, product of:",
"details": [
{
"value": 1,
"description": "boost",
"details": []
},
{
"value": 0.14514631,
"description": "queryNorm",
"details": []
}
]
}
]
}
]
}
分析上面的_explanation
的输出:
计算结果图示
上面的搜索命令中,搜索URL后面的?explain=1&pretty=1&search_type=dfs_query_then_fetch
是什么意思?
主要为了解决ES的sharding效果带来的计算结果不一致问题,
详见参考文献"How scoring works in Elasticsearch"的"The Sharding Effect"一节。
参考文献
搜索结果的排序方法
观察上面的搜索命令,其中排序部分是:
sort:='[{ "_score": { "order": "desc" }}, {"recurrence.timeStart": {"order" : "desc", "mode": "max"}}]'
其中包含两个排序指令:
-
按照相关度逆序排列;
-
对于匹配度相同的展会,按照展会开始时间逆序排列
参考文献:Sorting