重新编译ElasticSearch以应对图像搜索和文本语义匹配
2020/1/23 6:26:34
本文主要是介绍重新编译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
目录下
我这里是在linux下编译的,mac应该也没啥问题,win10上我发现有个文件在管理员上无法解压而没法编译成功,这里我也就不折腾了,因为在一个系统上就可以编译出所有平台的结果,嫌麻烦可以直接用我的编译结果。
链接:pan.baidu.com/s/1KCTSuCL5… 提取码: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:github.com/elastic/ela…
这里我看了半天,有一条非常关键的注释如下:
这块说,每个dense vector被编码成二值的文档值,然后占用大小是维度的4倍。然后再看代码:
测试一下这段逻辑 其实这里就是把一个值编码成四个值,然后再倒排索引。不过这里的倒排索引肯定是不太稀疏的,所以用了一个BinaryDocValuesField来存储,这里我也没有细致的研究过,大概查了一下,查到了以下的内容。
总之这个算法,嗯,吐槽,真的是有点儿太启发式了吧。
然后issue翻到了作者说有计划进行加入一些ann搜索方面的算法以应对不同的场景,总之,官方现在是没有这些功能了。
不过google搜索一下,还是有别人在ES上实现了一些ANN搜索功能的插件的。
图像搜索尝试
启发式的方法其实也不一定不好,因为上面这个算法也确实可以算是一个近似,而且速度很快。这里我来尝试一下之前的美眉搜索的应用:github.com/nladuo/MMFi…
这里我重新创建了一个分支为elasticsearch(下面的代码都在里面):
修改代码后创建索引并测试搜索,发现和之前的SPTAG的效果前面完全一样。
再来测试一下Web端,也和上一篇文章一模一样。
这里通过实践一下发现:虽然我现在还是严重的怀疑这个算法的数学合理性,但启发式的方法在实际效果中还是不错的。
对于小型的图像搜索应用,用ES没太大问题,毕竟神经网络有的时候学的也不是很好,对于一般的场景,检索只要把一部分能检索出来就好了,这点感觉ES完全可以胜任。
关于文本语义匹配
对于文本语义的匹配,这里就不太多介绍了,和图像搜索差不多,都是生成Dense Vector,区别就是一个是图像一个是文本。有很多自然语言处理方面的方案,比如说传统的LDA、SVD,以及运用神经网络的Word2Vec,Doc2Vec,还有最新的Bert。
关于Bert,我这里看到了一个开源项目:github.com/Hironsan/be…,可以算目前最前沿的方法了,有兴趣可以看一下。
这篇关于重新编译ElasticSearch以应对图像搜索和文本语义匹配的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享