搜索Suggest需要優(yōu)化問題:
- 怎么優(yōu)化Suggest詞庫,提升Suggest詞準(zhǔn)確率
- 怎么提高響應(yīng)速度
suggest詞庫獲取
- 冷啟動可以從內(nèi)容中提取熱詞數(shù)據(jù)來解決,或者人工設(shè)置
- 挖掘搜索日志:
- 挖掘近1個月搜索日志,按照每天獨(dú)立IP進(jìn)行統(tǒng)計(jì)頻次,即每個IP用戶天搜索同一關(guān)鍵詞多次只記一次,用IP過濾也有其局限性,偽IP,動態(tài)IP,局域網(wǎng)共享同一公網(wǎng)IP,都會影響到基于IP來判斷用戶的準(zhǔn)確性,你也可以使用sessionId或者userId來判斷
- 統(tǒng)計(jì)后搜索詞頻次之后,抽取搜索頻次>100(自定閾值)的詞,同時對日志數(shù)據(jù)進(jìn)行清洗,過濾去除大于10個字(去除太長的長尾詞),單字和符號內(nèi)容
- 定時更新suggest詞庫中。
- 搜索日志里面包含大量 誤輸入詞:
- 需要在suggest詞庫里面去掉誤輸入詞,對于搜索頻次高的詞,可以挖掘其對應(yīng)的正確詞,通過同義詞進(jìn)行查詢改寫。
- 誤輸入詞同義詞挖掘可以通過挖掘搜索session序列,使用word2vec訓(xùn)練來獲取誤輸入詞的同義詞,通過分詞器同義詞設(shè)置,對誤輸入詞進(jìn)行查詢改寫。
搜索Suggest需實(shí)現(xiàn)以下幾個功能:
- 匹配:能夠通過用戶的輸入進(jìn)行前綴匹配;
- 排序:根據(jù)建議詞的優(yōu)先級或者搜索熱度進(jìn)行排序;
- 糾錯:能夠?qū)τ脩舻妮斎脒M(jìn)行拼寫糾錯(suggest建議優(yōu)先prefix匹配,不宜過多提示,因此只需提供前綴匹配,中文拼音匹配即可)
搜索時:如上圖所示,可引導(dǎo)用戶選擇category,提升Suggest準(zhǔn)確度
匹配
- 通過分詞器對Suggest進(jìn)行單字,全拼,拼音首字母進(jìn)行索引
Elasticsearch 對于的字段mapping settings及分詞器設(shè)置參考
suggest 字段
- "preserve_separators": false, 這個設(shè)置為false,將忽略空格之類的分隔符
- "preserve_position_increments": true,如果建議詞第一個詞是停用詞,我們使用了過濾停用詞的分析器,需要將此設(shè)置為false
提升響應(yīng)速度
關(guān)于completion FST編碼原理
如:“天上人間” 分析為:“天上人間”、“天上”、“上人”、“人間” 四個詞條。 要注意這4個詞條還有順序,也就是position分別為0, 1, 2, 3。FST實(shí)際上是前綴編碼,這些詞被順序串聯(lián)在一起進(jìn)行編碼,并記錄了每個詞條的相對位置,編碼后形如:天上人間|天上|上人|人間
0 1 2 3
特別注意,這時候所有的查找都只能從0位置的“天”開始。做completion suggest的時候, 輸入的詞條經(jīng)過分析后, 必須有相同的前綴和相對位址。 因?yàn)槟愕乃阉饔玫膕imple analyzer,當(dāng)輸入"天"的時候, 分析出來的是"天" (0), 在FST里是從起始位置開始可以匹配到。其他輸入“天上” “天上人” 都是從位置0開始的前綴,也都可以匹配。
但是如果你輸入“上”, simple analyzer分析出來的是"上" (0), 去FST里查,第一個就不匹配,所以沒結(jié)果。
為了幫助理解,針對你的例子,可以試一下如下的搜索:
POST test_suggestion/_search
{
"suggest": {
"term-suggestion": {
"prefix": "天上人間 天上 上",
"completion": {
"field": "keyword_suggestion"
}
}
}
}
你會發(fā)現(xiàn),上面用空格分隔的3個詞,也可以match。 原因在于搜索用的simple analyzer是用空格一類的分隔符分詞的,分詞結(jié)果是: 天上人間|天上|上 0 1 2,順著FST走下去,可以做到前綴匹配。
總結(jié)來說,當(dāng)使用completion suggester的時候, 不是用于完成 類似于 "關(guān)鍵詞"這樣的模糊匹配場景,而是用于完成關(guān)鍵詞前綴匹配的。 對于漢字的處理,無需使用ik/ HanLP一類的分詞器,直接使用keyword analyzer,配合去除一些不需要的stop word即可。
舉個例子,做火車站站名的自動提示補(bǔ)全,你可能希望用戶輸入“上海” 或者 “虹橋” 都提示"上海虹橋火車站“ 。 如果想使用completion suggester來做,正確的方法是為"上海虹橋火車站“這個站名準(zhǔn)備2個completion詞條,分別是:
"上海虹橋火車站"
"虹橋火車站"
這樣用戶的輸入不管是從“上海”開始還是“虹橋”開始,都可以得到"上海虹橋火車站"的提示。
- 因此想要實(shí)現(xiàn)completion suggest 中文拼音混合提示,需要提供三個字段,中文字段,采用standard分詞,全拼字段,首字母字段,對漢字都采用standard分詞,分詞后對單字進(jìn)行分詞,確保FST索引的都是單字對應(yīng)的拼音,這樣應(yīng)該就可以完成中英文拼音suggest
- 第一步是先采用漢字前綴匹配的結(jié)果,使用全拼匹配也可以返回結(jié)果,但是存在同音字時,weight高的同音字會覆蓋原來的字,導(dǎo)致suggest不準(zhǔn)確
- 第二部,當(dāng)漢字匹配數(shù)量不夠時,啟用全拼匹配,可以達(dá)到拼音糾錯補(bǔ)充效果,索引時只索引全拼拼音
- 第三步:正常來說首字母拼音一般匹配不到內(nèi)容,此時可以使用拼音首字母匹配,索引時只索引首字母拼音
- 第四步:前面匹配的Suggest詞不夠時,最后也可以采用fuzzy查詢進(jìn)行補(bǔ)全
使用fuzzy模糊查詢
fuzzy模糊查詢是基于編輯距離算法來匹配文檔。編輯距離的計(jì)算基于我們提供的查詢詞條和被搜索文檔。
Complete suggest支持fuzzy查詢,計(jì)算編輯距離對CPU消耗比較大,需要設(shè)置以下參數(shù)來限制對性能的影響:
-
prefix_length
不能被 “模糊化” 的初始字符數(shù)。 大部分的拼寫錯誤發(fā)生在詞的結(jié)尾,而不是詞的開始。 例如通過將 prefix_length 設(shè)置為 3 ,你可能夠顯著降低匹配的詞項(xiàng)數(shù)量。
2.min_length
開始進(jìn)行模糊匹配的最小輸入長度
3.fuzzy查詢只在前綴匹配數(shù)不夠時啟用進(jìn)行補(bǔ)全
排序
從搜索日志挖掘的Suggest詞,可以根據(jù)搜索詞的搜索頻次作為熱度來設(shè)置weight,Suggest會根據(jù)weight來排序。
java API代碼參考
LinkedHashSet<String> returnSet = new LinkedHashSet<>();
Client client = elasticsearchTemplate.getClient();
SuggestRequestBuilder suggestRequestBuilder = client.prepareSuggest(elasticsearchTemplate.getPersistentEntityFor(SuggestEntity.class).getIndexName());
//全拼前綴匹配
CompletionSuggestionBuilder fullPinyinSuggest = new CompletionSuggestionBuilder("full_pinyin_suggest")
.field("full_pinyin").text(input).size(10);
//漢字前綴匹配
CompletionSuggestionBuilder suggestText = new CompletionSuggestionBuilder("suggestText")
.field("suggestText").text(input).size(size);
//拼音搜字母前綴匹配
CompletionSuggestionBuilder prefixPinyinSuggest = new CompletionSuggestionBuilder("prefix_pinyin_text")
.field("prefix_pinyin").text(input).size(size);
suggestRequestBuilder = suggestRequestBuilder.addSuggestion(fullPinyinSuggest).addSuggestion(suggestText).addSuggestion(prefixPinyinSuggest);
SuggestResponse suggestResponse = suggestRequestBuilder.execute().actionGet();
Suggest.Suggestion prefixPinyinSuggestion = suggestResponse.getSuggest().getSuggestion("prefix_pinyin_text");
Suggest.Suggestion fullPinyinSuggestion = suggestResponse.getSuggest().getSuggestion("full_pinyin_suggest");
Suggest.Suggestion suggestTextsuggestion = suggestResponse.getSuggest().getSuggestion("suggestText");
List<Suggest.Suggestion.Entry> entries = suggestTextsuggestion.getEntries();
//漢字前綴匹配
for (Suggest.Suggestion.Entry entry : entries) {
List<Suggest.Suggestion.Entry.Option> options = entry.getOptions();
for (Suggest.Suggestion.Entry.Option option : options) {
returnSet.add(option.getText().toString());
}
}
//全拼suggest補(bǔ)充
if (returnSet.size() < 10) {
List<Suggest.Suggestion.Entry> fullPinyinEntries = fullPinyinSuggestion.getEntries();
for (Suggest.Suggestion.Entry entry : fullPinyinEntries) {
List<Suggest.Suggestion.Entry.Option> options = entry.getOptions();
for (Suggest.Suggestion.Entry.Option option : options) {
if (returnSet.size() < 10) {
returnSet.add(option.getText().toString());
}
}
}
}
//首字母拼音suggest補(bǔ)充
if (returnSet.size() == 0) {
List<Suggest.Suggestion.Entry> prefixPinyinEntries = prefixPinyinSuggestion.getEntries();
for (Suggest.Suggestion.Entry entry : prefixPinyinEntries) {
List<Suggest.Suggestion.Entry.Option> options = entry.getOptions();
for (Suggest.Suggestion.Entry.Option option : options) {
returnSet.add(option.getText().toString());
}
}
}
return new ArrayList<>(returnSet);
ES setting mapping配置
{
"settings": {
"analysis": {
"analyzer": {
"prefix_pinyin_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"prefix_pinyin"
]
},
"full_pinyin_analyzer": {
"tokenizer": "standard",
"filter": [
"lowercase",
"full_pinyin"
]
}
},
"filter": {
"_pattern": {
"type": "pattern_capture",
"preserve_original": 1,
"patterns": [
"([0-9])",
"([a-z])"
]
},
"prefix_pinyin": {
"type": "pinyin",
"keep_first_letter": true,
"keep_full_pinyin": false,
"none_chinese_pinyin_tokenize": false,
"keep_original": false
},
"full_pinyin": {
"type": "pinyin",
"keep_first_letter": false,
"keep_full_pinyin": true,
"keep_original": false,
"keep_none_chinese_in_first_letter": false
}
}
}
},
"mappings": {
"suggest": {
"properties": {
"id": {
"type": "string"
},
"suggestText": {
"type": "completion",
"analyzer": "standard",
"payloads": true,
"preserve_separators": false,
"preserve_position_increments": true,
"max_input_length": 50
},
"prefix_pinyin": {
"type": "completion",
"analyzer": "prefix_pinyin_analyzer",
"search_analyzer": "standard",
"preserve_separators": false,
"payloads": true
},
"full_pinyin": {
"type": "completion",
"analyzer": "full_pinyin_analyzer",
"search_analyzer": "full_pinyin_analyzer",
"preserve_separators": false,
"payloads": true
}
}
}
}
}
DSL查詢語句
POST _suggest
{
"text": "cy",
"prefix_pinyin": {
"completion": {
"field": "prefix_pinyin",
"size": 10
}
},
"full_pinyin": {
"completion": {
"field": "full_pinyin",
"size": 10
}
},
"suggestText": {
"completion": {
"field": "suggestText",
"size": 10
}
}
}