ElasticSearch 向量檢索插件開發(fā)

0X00举户、前言

Elasticsearch是一個(gè)基于Lucene庫(kù)的搜索引擎尤仍,它提供了一個(gè)分布式、支持多租戶的全文搜索引擎蔼紧。隨著業(yè)務(wù)的飛速發(fā)展婆硬,對(duì)于搜索的需求也會(huì)增加,比如:搜索圖片奸例、相似向量等彬犯。我們可以利用 ElasticSearch 良好的插件規(guī)范、豐富的查詢函數(shù)哩至、分布式可擴(kuò)展的能力開發(fā)一個(gè)腳本插件使其支持向量檢索躏嚎。本教程主要參考StaySense的開源項(xiàng)目(見參考1)蜜自。

本教程演示環(huán)境配置:

  • Python: 3.6.4
  • Java: 1.8
  • Maven
  • Docker&Elaseticsearch: 6.7.0

通過(guò)Docker部署Elasticsearch:6.7.0參考:Elasticsearch安裝使用

0X01菩貌、插件開發(fā)

項(xiàng)目地址:https://github.com/DebugWorld-1024/ImageSimilarityPlugin

項(xiàng)目整體目錄:

圖片

1、pom.xml

主要配置一些項(xiàng)目環(huán)境重荠、添加依賴箭阶、打包方式等,完整配置查看項(xiàng)目文件戈鲁。

2仇参、plugin.xml

由于 Elasticsearch 要求自定義插件需要打包成 zip 文件,我們可以配置 Maven Assembly 插件使其自動(dòng)生成婆殿,完整配置查看項(xiàng)目文件诈乒。

3、plugin-descriptor.properties

根據(jù) Elasticsearch 要求婆芦,所有的插件必須包含一個(gè)名為 plugin-descriptor.properties 的插件描述文件怕磨,對(duì)其內(nèi)容有要求且必須放置在 elasticsearch 目錄下。我們?cè)?src/main/resources 目錄下創(chuàng)建 plugin-descriptor.properties 并添加內(nèi)容如下:

name=${elasticsearch.plugin.name}
description=${elasticsearch.plugin.description}
version=${project.version}
classname=${elasticsearch.plugin.classname}
java.version=${maven.compiler.target}
elasticsearch.version=${elasticsearch.version
# extended.plugins=${extendedPlugins}
# has.native.controller=${hasNativeController}

除了 extended.plugins和has.native.controller 都是必須參數(shù)消约,其中 classname 一定是插件運(yùn)行的入口文件肠鲫。具體含義參考官方說(shuō)明:Help for plugin authors

4、代碼

通過(guò)查看官網(wǎng)文檔或粮,腳本插件必須繼承Plugin類导饲,通過(guò)"ScriptEngine"來(lái)實(shí)現(xiàn)的,為了開發(fā)一個(gè)自定義的插件氯材,我們需要實(shí)現(xiàn)"ScriptEngine"接口渣锦,并通過(guò)getScriptEngine()這個(gè)方法來(lái)加載我們的插件,該插件以base64類型讀取ES數(shù)據(jù)氢哮,特征向量的相似算法采用歐式距離泡挺,具體代碼查看項(xiàng)目文件。

5命浴、打包

運(yùn)行打包命令:

mvn clean package

在target目錄下會(huì)生成zip


圖片

0X02娄猫、插件安裝

通過(guò)dcoker cp 命令把zip文件復(fù)制到docker 容器中 /usr/share/elasticsearch

進(jìn)入Elasticsearch:6.7.0的docker容器中

docker exec -it es /bin/bash

安裝自定義插件

elasticsearch-plugin install file:///usr/share/elasticsearch/ImageSimilarity-plugin.zip

安裝成功后贱除,會(huì)在plugin目錄下發(fā)現(xiàn)安裝文件,每次安裝更新插件都需要重啟ES

docker restart es

docker 啟動(dòng) elasticsearch的話媳溺,日志默認(rèn)沒有輸出到文檔月幌,默認(rèn)被終端接收,可以使用 docker logs -f es 查看悬蔽。

0X03扯躺、插件使用

通過(guò)Python程序向ES集群寫入100w條數(shù)據(jù),要注意索引的mappings設(shè)置蝎困,feature是ES存儲(chǔ)特性向量數(shù)據(jù)的字段录语,以base64形式存儲(chǔ)。不同編程語(yǔ)言List & base64轉(zhuǎn)換程序見參考2禾乘。

import random
import base64
import numpy as np
from elasticsearch import Elasticsearch, helpers

dbig = np.dtype('>f8')

es = Elasticsearch()

body = {
    "mappings": {
        "image_search": {
            "properties": {
                "id": {
                    "type": "keyword"
                },
                "feature": {
                    "type": "binary",
                    "doc_values": True
                }
            }
        }
    }
}

index = 'test'
es.indices.delete(index=index, ignore=404)
es.indices.create(index=index, ignore=400, body=body)


def decode_float_list(base64_string):
    """
    base64 轉(zhuǎn) list
    :param base64_string:
    :return:
    """
    bytes_ = base64.b64decode(base64_string)
    return np.frombuffer(bytes_, dtype=dbig).tolist()


def encode_array(arr):
    """
    List 轉(zhuǎn) base64
    :param arr:
    :return:
    """
    base64_str = base64.b64encode(np.array(arr).astype(dbig)).decode("utf-8")
    return base64_str


def generator():
    i = 0
    while True:
        yield {

                'id': i,
                'feature': encode_array([random.random(), random.random()])
            }
        i += 1
        if i >= 1000000:
            break

# 批量插入100w數(shù)據(jù)到es
helpers.bulk(es, generator(), index=index, doc_type='image_search')

查詢程序澎埠,注意source和lang要和插件里一致。

import time
import json
import base64
import numpy as np
from elasticsearch import Elasticsearch, helpers

dbig = np.dtype('>f8')

es = Elasticsearch()

body = {
    "from": 0,
    "size": 5,
    "_source": {
        "excludes": ""
    },
    "sort": {
        "_score": {
            "order": "asc"
        }
    },
    "query": {
        "function_score": {
            "query": {
                "match_all": {}
            },
            "functions": [
                {
                    "script_score": {
                        "script": {
                            "source": "DebugWorld",
                            "lang": "ImageSimilarity",
                            "params": {
                                "field": "feature",
                                "feature": [0.01, 0.03]
                            }
                        }
                    }
                }
            ]
        }
    }
}


def decode_float_list(base64_string):
    """
    base64 轉(zhuǎn) list
    :param base64_string:
    :return:
    """
    bytes_ = base64.b64decode(base64_string)
    return np.frombuffer(bytes_, dtype=dbig).tolist()


time_list = list()
for i in range(1):
    start_time = time.time()
    result = es.search(index='test', doc_type='image_search', body=body)
    for hit in result['hits']['hits']:
        hit['_source']['feature'] = decode_float_list(hit['_source']['feature'])
    time_list.append(time.time() - start_time)
    print(json.dumps(result, indent=4))

print(sum(time_list)/len(time_list), max(time_list), min(time_list))

0X04始藕、插件性能

測(cè)試服務(wù)器配置:2核8G蒲稳,20G磁盤

數(shù)據(jù): 100w數(shù)據(jù)量,160維的特征向量

1000次請(qǐng)求響應(yīng)情況:

平均時(shí)間:0.348s

最慢時(shí)間:1.085s

最快時(shí)間:0.215s

呃伍派。江耀。。百萬(wàn)數(shù)據(jù)量之內(nèi)還是坑得住的诉植,但是阿里云開發(fā)了一個(gè)有點(diǎn)吊的插件aliyun-knn祥国,但是未開源,有興趣的可以看看文檔晾腔。

0X05舌稀、注意事項(xiàng)

  • Elasticsearch的score不能為負(fù)數(shù)
  • 瀏覽器插件Elasticsearch Head 列表頁(yè)不支持展示數(shù)組類型數(shù)據(jù)
  • Elasticsearch版本更新快,不兼容情況嚴(yán)重建车,實(shí)踐請(qǐng)注意ES版本號(hào)
  • 從7.2版本開始扩借,Elasticsearch提供了實(shí)驗(yàn)性的向量檢索功能

0X06、參考

  1. Elasticsearch: Elasticsearch Plugins and Integrations [6.7]
  2. Github:StaySense/fast-cosine-similarity
  3. Github: elastic/elasticsearch
  4. 公眾號(hào):螞蟻金服 ZSearch 在向量檢索上的探索
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缤至,一起剝皮案震驚了整個(gè)濱河市潮罪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌领斥,老刑警劉巖嫉到,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異月洛,居然都是意外死亡何恶,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門嚼黔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)细层,“玉大人惜辑,你說(shuō)我怎么就攤上這事∫呤辏” “怎么了盛撑?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)捧搞。 經(jīng)常有香客問(wèn)我抵卫,道長(zhǎng),這世上最難降的妖魔是什么胎撇? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任介粘,我火速辦了婚禮,結(jié)果婚禮上晚树,老公的妹妹穿的比我還像新娘姻采。我一直安慰自己,他們只是感情好题涨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布偎谁。 她就那樣靜靜地躺著总滩,像睡著了一般纲堵。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上闰渔,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天席函,我揣著相機(jī)與錄音,去河邊找鬼冈涧。 笑死茂附,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的督弓。 我是一名探鬼主播营曼,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼愚隧!你這毒婦竟也來(lái)了蒂阱?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤狂塘,失蹤者是張志新(化名)和其女友劉穎录煤,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荞胡,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡妈踊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泪漂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片廊营。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歪泳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出露筒,到底是詐尸還是另有隱情夹囚,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布邀窃,位于F島的核電站荸哟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瞬捕。R本人自食惡果不足惜鞍历,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肪虎。 院中可真熱鬧劣砍,春花似錦、人聲如沸扇救。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)迅腔。三九已至装畅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沧烈,已是汗流浹背掠兄。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留锌雀,地道東北人蚂夕。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像腋逆,于是被迫代替她去往敵國(guó)和親婿牍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容