重新编译ElasticSearch以应对图像搜索和文本语义匹配

写在前面

ElasticSearch7开始增加了Dense Vector和Sparse Vector这两个数据类型,算是为图像搜索以及文本语义匹配提供支持的。

但发现文档(elasticsearch7.5版本)中他的维度真的很低,只能支持到1024维。虽然说这个维度应对一般的文本语义匹配是没什么问题的,但是对于图像搜索来说是远远不够的。

看看目前神经网络训练的图像特征的维度,这里我找到一个人脸识别的案例:keras-vggface

他的源码中提供了三个模型,VGG16,RESNET50,以及SENET50。

可以看到VGG16模型输出的特征向量维度就是2622维度,远超过1024。

而RESNET50和SENET50模型输出的维度是8631,更是远超过1024维度。

这篇文章的目标是增加dense vector的维度,我google了一下解决方案,发现已经有老哥问过这个问题了:Increase Elasticsearch maximum dimensions for sparse vectors

方案就是改源码然后重新编译,不过回答的这个老哥说ES的向量搜索非常慢。虽然如此,但我觉得ES其实还是非常的方便的,生态也不错,以后可能就改善了,所以还是可以来尝试一下的。

修改源代码

查看配置文件可以看到之前ES就增加过一次最大的维度,从500增加到1024。

这里从github上下载es7.5.1的源码后,通过grep命令找到DenseVectorFieldMapper这个类。

这里我最开始下载的是master分支的源码发现这个最大维度其实是2048。

搜索ES的PR发现确实是又加了一次。

看来总是不够用啊。

打开issue看看发现这个老哥说1024不够用,嗯,2048其实也不够用,这次我给你改成4096得了。

源码这里就改一个数字就好了,没啥可说的。

编译源码

接下来编译源码,linux或者mac用户进入到elasticsearch目录运行下面命令即可。

./gradlew assemble

如果是windows的话就是用这个gradlew.bat了。

gradlew.bat assemble

运行了一会儿发现报错了,发现必须要JDK11以上。

但是后面经过我的编译发现11这个版本是不行的,必须是12,JDK13也是不行的。而oracle的官网上是没有jdk12的,那就去下一个openjdk-12好了。

安装好了之后,重新编译,编译的时候发现这里还编译了docker版本,这个其实不需要,而且就算装了docker,版本也不对,还要安装指定版本。这里在setting.gradle中给gradle相关的东西加上注释就可以不编译docker了。

最后,算是编译好了,文档里面说编译后的文件在build/distribution下,我发现根本没有这个东西,找了半天,编译完了的输出是在distribution\archives目录下

主要就是mac、linux以及windows的安装包了。这里我把编译的结果就上传到百度云了。

我这里是在linux下编译的,mac应该也没啥问题,win10上我发现有个文件在管理员上无法解压而没法编译成功,这里我也就不折腾了,因为在一个系统上就可以编译出所有平台的结果,嫌麻烦可以直接用我的编译结果。

链接:https://pan.baidu.com/s/1KCTSuCL5hXtvHGSSN3hMxQ
提取码:4l0i

测试

接下来测试一下dense vector是否达到4096维。这里我用python3来测试。

启动ES服务

这里首先启动ES服务,根据不同版本运行bin目录下的elasticsearch文件即可。

我这里是windows,直接双击elasticsearch.bat即可。

可以看到es已经运行在了9200端口上。

安装库

接下来安装一下python库,这里通过pip安装即可。

pip3 install elasticsearch==7.1.0

创建索引

接下来创建dense索引,这里我们使用的向量维度为4000维,然后随机生成1万个向量并建立索引。

from elasticsearch import Elasticsearch
import random
def get_num():
    return random.randrange(0, 1000) / 100
es = Elasticsearch()
index_mappings = {
  "mappings": {
      "properties": {
        "my_vector":    {
            "type": "dense_vector",
            "dims": 4000,
        },
      }
    },
}
if es.indices.exists(index='test_index') is not True:
    print("create test_index")
    es.indices.create(index='test_index', body=index_mappings)
for i in range(10000):
    data = {
        "id": i,
        "my_vector": [get_num() for k in range(4000)],
    }
    print(data)
    res = es.index(index="test_index", id=i, body=data)
    print(res)

运行后发现速度还不错,很快。

搜索测试

建立完索引后,就可以测试搜索了,这里搜索排序按照余弦相似度,通过以下代码可以完成搜索。

from elasticsearch import Elasticsearch
import random
query_vector = [random.randrange(0, 1000) / 100 for i in range(4000)]
script_query = {
    "script_score": {
        "query": {"match_all": {}},
        "script": {
            "source": "cosineSimilarity(params.query_vector, doc['my_vector']) + 1.0",
            "params": {"query_vector": query_vector}
        }
    }
}
es = Elasticsearch()
searched = es.search("test_index", body={
    "size": 100,
    "query": script_query,
}, timeout=None)
for hit in searched["hits"]["hits"]:
    print(hit["_id"], hit["_score"])

运行后发现速度很快,基本上也是秒出。

ANN搜索部分源码分析

上面一直说到的dense索引其实也就是Approximate nearest neighbours的索引,简称ANN,也就是近似最近邻搜索。

通过运行测试,我发现这个比我之前用的SPTAG,不管从建索引还是搜索方面都要快非常多,于是让我对它的实现产生了很大兴趣。

因为之前从来没看过ElasticSearch的源代码,费了半天劲儿,找了各种PR和issue,找到了提交这个新功能的一个commit:https://github.com/elastic/elasticsearch/commit/b5d532f9e3d184d4bb895835a4d4fef2fb4ee0e8

这里我看了半天,有一条非常关键的注释如下:

这块说,每个dense vector被编码成二值的文档值,然后占用大小是维度的4倍。然后再看代码:

测试一下这段逻辑

其实这里就是把一个值编码成四个值,然后再倒排索引。

不过这里的倒排索引肯定是不太稀疏的,所以用了一个BinaryDocValuesField来存储,这里我也没有细致的研究过,大概查了一下,查到了以下的内容。

总之这个算法,嗯,吐槽,真的是有点儿太启发式了吧。

然后issue翻到了作者说有计划进行加入一些ann搜索方面的算法以应对不同的场景,总之,官方现在是没有这些功能了。

不过google搜索一下,还是有别人在ES上实现了一些ANN搜索功能的插件的。

图像搜索尝试

启发式的方法其实也不一定不好,因为上面这个算法也确实可以算是一个近似,而且速度很快。这里我来尝试一下之前的美眉搜索的应用:https://github.com/nladuo/MMFinder。

这里我重新checkout出一个分支为elasticsearch:

修改代码后创建索引并测试搜索,发现和之前的SPTAG的效果前面完全一样。

再来测试一下Web端,也和上一篇文章一模一样。

这里通过实践一下发现:虽然我现在还是严重的怀疑这个算法的数学合理性,但启发式的方法在实际效果中还是不错的。

对于小型的图像搜索应用,用ES没太大问题,毕竟神经网络有的时候学的也不是很好,对于一般的场景,检索只要把一部分能检索出来就好了,这点感觉ES完全可以胜任。

关于文本语义匹配

对于文本语义的匹配,这里就不太多介绍了,和图像搜索差不多,都是生成Dense Vector,区别就是一个是图像一个是文本。有很多自然语言处理方面的方案,比如说传统的LDA、SVD,以及运用神经网络的Word2Vec,Doc2Vec,还有最新的Bert。

关于Bert,我这里看到了一个开源项目:https://github.com/Hironsan/bertsearch,可以算目前最前沿的方法了,有兴趣可以看一下。

https://juejin.im/post/5e1fe429e51d452fb9734f38

「点点赞赏,手留余香」

    还没有人赞赏,快来当第一个赞赏的人吧!
0 条回复 A 作者 M 管理员
    所有的伟大,都源于一个勇敢的开始!
欢迎您,新朋友,感谢参与互动!欢迎您 {{author}},您在本站有{{commentsCount}}条评论