干貨 | Elasticsearch 向量搜索的工程化實戰(zhàn)
1、背景
作為一家搜索引擎公司,我們會很倚賴 ES 幫忙處理包括文章召回,數(shù)據(jù)源劃分,實體、標簽管理等任務,而且都收到了不錯的結果。
最近我們需要對行業(yè)知識庫進行建模,其中可能會涉及到實體匹配、模糊搜索、向量搜索等多種召回和算分方式,最終我們選擇了通過 ES 7.X (最終選擇 7.10)里的新功能,Dense vector 幫忙一起完成這部分的需求。
2、技術選型
2.1 解決方案需求
支持向量搜索 支持多維度篩選、過濾 吞吐速率 學習、使用成本 運維成本
2.2 使用場景設計
離線數(shù)據(jù)準備 在離線數(shù)據(jù)構建完成后,存入該引擎 引擎對數(shù)據(jù)中各字段進行索引 在線數(shù)據(jù)召回 根據(jù) query理解結果構建的query語句進行數(shù)據(jù)召回對結果進行一定的篩選 對結果進行一定的打分排序
2.3 數(shù)據(jù)結構設計
在確定了數(shù)據(jù)的使用場景我們確定了數(shù)據(jù)結構中,大致會包含以下一些字段
唯一 id:用以做知識的去重和快速獲取 實體、屬性、取值:用來描述知識的具體內(nèi)容 置信度:用來描述知識的可信度 分類 flag:知識主要分類及推薦 category 等 向量表示:作為知識相似性、相關性召回、打分的依據(jù) ref 信息:用來回溯解析/獲取該知識的源信息 其他屬性:包括生效、刪除、修改時間等支持性的通用屬性 
2.4 解決方案對比
為了能支持上述的使用需求,我們對比了包括 ES、Faiss 等多種解決方案。其中,Faiss 和 SPTAG 只是核心算法庫,需要進行二次開發(fā)包裝成服務;Milvus 的 1.x 版本中只能存儲 id 和 向量,不能完整的滿足我們的使用需求;基于集群穩(wěn)定性和可維護性等考慮,相對于后置插件的部署,我們更傾向于使用 ES 的原生功能,所以選擇 ES 的原生向量搜索功能作為我們的最終選擇。
對比參考:
| 種類 | 實現(xiàn)語言 | 客戶端支持 | 多條件召回 | 學習成本 | 引入成本 | 運維成本 | 分布式 | 性能 | 社區(qū) | 備注 |
|---|---|---|---|---|---|---|---|---|---|---|
| Elasticsearch | Java | Java/Python | yes | 低 | 低 | 中 | yes | 中 | 活躍 | 原生功能 |
| Faiss | Python | Python | no | 中 | 高 | 高 | no | 高 | 一般 | 需要二次開發(fā) |
| Milvus | Python + GoLang | Python/Java/GoLang | no | 中 | 中 | 中 | no | 高 | 一般 | 1.x 功能不全 |
| OpenDistro Elasticsearch KNN | Java + C++ | Java/Python | yes | 中 | 中 | 中 | yes | 中 | 一般 | 內(nèi)置插件 |
| SPTAG | C++ | Python + C# | no | 高 | 中 | 中 | no | 高 | 一般 | 需要二次開發(fā) |
3、數(shù)據(jù)流轉(zhuǎn)流程
3.1 離線數(shù)據(jù)處理部分
從多數(shù)據(jù)源采集數(shù)據(jù) 數(shù)據(jù)清洗及預處理 通過算法引擎提取知識 通過算法引擎將知識轉(zhuǎn)換為向量 將知識的基礎信息連同向量數(shù)據(jù)存入 ES
3.2 在線數(shù)據(jù)召回部分
從前端獲取搜索條件 通過 query理解模塊進行檢索條件解析從 ES中進行搜索對結果進行分數(shù)調(diào)整 返回前端
4、ES 向量搜索的使用示例
4.1 索引設計
Settings:
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2,
"index": {
"routing": {
"allocation": {
"require": {
"node_group": "hot" // 1)
}
}
},
"store": {
"preload": [ // 2)
"knowledge",
"category",
"available",
"confidence",
"del",
"kid"
]
},
"search": {
"slowlog": {
"threshold": {
"query": {
"warn": "1s" // 3)
},
"fetch": {
"warn": "1s" // 3)
}
}
}
},
"translog": {
"flush_threshold_size": "512mb", // 4)
"sync_interval": "5m", // 4)
"durability": "async" // 4)
},
"sort": {
"field": [ // 5)
"kid",
"confidence"
],
"order": [ // 5)
"asc",
"desc"
]
}
}
}
}
說明:
由于向量數(shù)據(jù)較大,所以傾向于將整個索引都放置在硬件性能更好的節(jié)點 為了支持高性能過濾,將常用的字段預先加載在內(nèi)存中 對慢查詢開啟日志方便后續(xù)性能問題的調(diào)查 知識庫的重建是離線的,會在更新時進行大量寫入,所以對 translog的提交間隔拉長,加快寫入速度在實際使用中kid是自增id,同時可能會對知識的置信度做排序等,所以會使用 sort field存儲這兩個字段
Mapping:
{
"mappings": {
"properties": {
"kid": {
"type": "keyword"
},
"knowledge": {
"type": "keyword"
},
"knowledge_phrase": { // 1)
"type": "text",
"analyzer": "faraday"
},
"attribue": { // 1)
"type": "keyword",
"fields": {
"phrase": {
"type": "text",
"analyzer": "faraday"
}
}
},
"value": { // 1)
"type": "keyword",
"fields": {
"phrase": {
"type": "text",
"analyzer": "faraday"
}
}
},
"confidence": { // 2)
"type": "double"
},
"category": {
"type": "keyword"
},
"vector": { // 3)
"type": "dense_vector",
"dims": 512
},
"ref": {
"type": "text",
"index": false
},
"available": {
"type": "keyword"
},
"del": {
"type": "keyword"
},
"create_timestamp": {
"type": "date",
"format": [
"strict_date_hour_minute_second",
"yyyy-MM-dd HH:mm:ss"
]
},
"update_timestamp": {
"type": "date",
"format": [
"strict_date_hour_minute_second",
"yyyy-MM-dd HH:mm:ss"
]
}
}
}
}
說明:
除了對知識條目的完整搜索之外,還會需要進行模糊檢索,我們使用了自研的 farady分詞器對知識條目的各部分進行了分詞處理知識庫中的知識條目會有一部分進行專家/人工審核和維護,所以會對不同的條目設置不同的置信度 數(shù)據(jù)預處理之后會轉(zhuǎn)成 512 位的向量存在這個字段中
4.2 數(shù)據(jù)流轉(zhuǎn)
離線部分:
數(shù)據(jù)采集及清洗 通過 模型A從文章中找到知識條目通過 模型B將知識條目轉(zhuǎn)化成向量此處 模型A模型B為自研模型,運用了包括知識密度計算等算法以及berttersonflow等框架將原文、知識條目等核心內(nèi)容插入數(shù)據(jù)庫 將核心知識內(nèi)容、向量等組裝成檢索單元插入 ES專家團隊會針對數(shù)據(jù)庫中的知識條目進行審核、修改和迭代 算法團隊會根據(jù)知識條目的更新以及其他的標注對數(shù)據(jù)鏈路中的模型進行迭代,對在線知識庫進行更新
在線部分:
前端收到請求之后調(diào)用 query 理解組件進行分析剔除無效內(nèi)容之后,找出 query里的分類信息等意圖之后,構建用來召回的向量和相關的篩選條件通過組合出來的 ES的query條件對知識庫進行篩選,并配合置信度等對結果進行調(diào)整對召回結果進行不同策略的分數(shù)調(diào)整和排序,最后輸出給前端
4.3 示例 query
POST knowledge_current_reader/_search
{
"query": {
"script_score": {
"query": {
"bool": {
"filter": [
{
"term": {
"del": 0
}
},
{
"term": {
"available": 1
}
}
],
"must": {
"bool": {
"should": [
{
"term": {
"category": "type_1",
"boost": 10
}
},
{
"term": {
"category": "type_2",
"boost": 5
}
}
]
}
},
"should": [
{
"match_phrase": {
"knowledge_phrase": {
"query": "some_query",
"boost": 10
}
}
},
{
"match": {
"attribute": {
"query": "some_query",
"boost": 5
}
}
},
{
"match": {
"value": {
"query": "some_query",
"boost": 5
}
}
},
{
"term": {
"knowledge": {
"value": "some_query",
"boost": 30
}
}
},
{
"term": {
"attribute": {
"value": "some_query",
"boost": 15
}
}
},
{
"term": {
"value": {
"value": "some_query",
"boost": 10
}
}
}
]
}
},
"script": {
"source": "cosineSimilarity(params.query_vector, 'vector') + sigmoid(1, Math.E, _score) + (1 / Math.log(doc['confidence'].value))",
"params": {
"query_vector": [ ... ]
}
}
}
}
}
說明:
上述 query的條件、參數(shù)僅做示意,屬于實際線上使用的脫敏、簡化版計算公式為迭代中某一版,后續(xù)調(diào)整和升級并未體現(xiàn) 邊界條件及空值在輔助服務和 pipeline中進行處理,簡化了其中邊界條件處理和判斷部分邏輯
5、遇到的問題
5.1 響應時間長
由于需要進行向量計算,ES 需要耗費大量時間、資源做距離計算,為此我們進行了以下一些優(yōu)化:
特征值截取小數(shù)位數(shù): 為了保證特征的表征,我們并沒有調(diào)整由 bert框架輸出的向量位數(shù)在權衡了存取效率、數(shù)據(jù)精度和計算速度之后,我們將每一個 label的精度由16位截取為5位小數(shù)這樣雖然損失了部分精度(約 X%),但是大大降低了存取和計算時間(約Y%)在進行 query之前預先對意圖、可能分類進行分析為了減少納入計算排序的數(shù)據(jù),我們會在 query組裝之前對原始query內(nèi)容進行分析配合用戶行為埋點和專家的先驗知識,將知識進行大致分類,并對 query和分類進行不同權重的匹配這樣雖然降低了召回率(約 X%),但增加了準確性(約Y%),同時也提高了部分計算效率(約Z%)精簡計算公式 將一部分分數(shù)計算的邏輯外置,盡可能精簡 ES需要處理的運算邏輯在召回之后增加多種打分策略,通過配置進行應用、權重調(diào)整等操作 這樣降低了 ES的響應時間(約X%),同時通過外置的打分公式調(diào)整,間接的提高了準確性(約Y%)
5.2 知識質(zhì)量參差不齊
由于知識條目是通過算法進行抽取的,而且知識還會存在一定的時效性,可能造成知識的不準確等問題,為此我們進行了以下一些優(yōu)化:
持續(xù)的算法迭代: 根據(jù)用戶埋點信息和標注信息對模型進行持續(xù)迭代 選取更加優(yōu)質(zhì)的知識抽取結果對線上數(shù)據(jù)進行全量/增量更新 經(jīng)過 X批次的迭代,將知識的正確性從Y%提高到了Z%對模型輸出的知識進行后置處理 將僅存在部分助詞(如 的)差異的知識條目進行過濾、合并給部分熱門的知識條目設置過期時間,并通過部分人工審核的方式干預知識條目的生產(chǎn) 維護專家知識庫的方式對可信知識進行標記及提權 維護了 X類目的Y條專家知識,同時經(jīng)過人工干預了大概Z%的知識條目,將知識的正確性從W%提高到了K%
結論與展望
本文依托我們公司的使用場景,對圍繞 ES 向量字段(Dense vector)構建的一個系統(tǒng)進行了大致描述,同時對一些常見問題及解決方案進行了闡述。
目前該方案支持了我們對于知識庫的相關搜索功能,相較于之前的純基于實體識別和 ngram 匹配的方案整體準確率和召回率都有將近兩位數(shù)百分比的提升。
未來我們會對整個系統(tǒng)的響應速度、穩(wěn)定性進行提升,并對知識庫的構建效率以及知識的準確性持續(xù)進行迭代。
作者介紹
死敵wen,Elastic 認證工程師,搜索架構師,10年+工作經(jīng)驗,畢業(yè)于復旦大學。
博客:https://blog.csdn.net/weixin_40601534
Github:https://github.com/godlockin
說明

上個月,死磕 Elasticsearch 知識星球搞了:“群智涌現(xiàn)”杯輸出倒逼輸入——Elastic干貨輸出活動。
后續(xù)會不定期逐步推出系列文章,目的:以文會友,“輸出倒逼輸入”。
推薦
更短時間更快習得更多干貨!
已帶領77位球友通過 Elastic 官方認證!

