如何在 Elasticsearch 中实施图像相似度搜索

blog-thumb-website-search.png

了解如何通过短短几步便成功在 Elastic 中实施图像相似度搜索。开始时要创建应用程序环境,然后导入 NLP 模型,最后针对您的图像集完成嵌入的生成工作。

整体了解 Elastic 图像相似度搜索 >> 

如何创建环境

第一步是为您的应用程序创建环境。一般要求包括:

  • Git
  • Python 3.9
  • Docker
  • 数百张图像

为了确保实现最好效果,需要使用数百张图像,这一点十分重要。

前往工作文件夹并查看所创建的存储库代码。然后导航至存储库文件夹。

$ git clone https://github.com/radoondas/flask-elastic-image-search.git
$ cd flask-elastic-image-search

因为要使用 Python 来运行代码,您需要确保所有条件均已满足且环境已准备就绪。现在创建虚拟环境并安装所有依赖项。

$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt

Elasticsearch 集群和嵌入模型

登录您的账号并快速创建一个 Elasticsearch 集群。使用下列参数创建一个小型集群:

  • 一个 2GB 内存的热节点
  • 一个 4GB 内存的 ML (Machine Learning) 节点(该节点的大小很重要,因为您将要导入到 Elasticsearch 中的 NLP 模型就要占用 ~1.5GB 内存。)

您的部署准备完毕之后,前往 Kibana 并检查您的 Machine Learning 节点的容量。在此视图中,您将会看到有一个 Machine Learning 节点。此时尚未加载模型。

使用 Eland 库上传来自 OpenAI 的 CLIP 嵌入模型。Eland 是一个 Python Elasticsearch 客户端,可用来在 Elasticsearch 中探索和分析数据,并且能够同时处理文本和图像。您将会使用此模型来基于文本输入生成嵌入并查询匹配的图像。更多详情请参见 Eland 库的文档

对于下一步,您将需要 Elasticsearch 终端。您可以从部署详情部分内的 Elasticsearch 云控制台获取此终端。

使用终端 URL,在存储库的根目录中执行下列命令。Eland 客户端将会连接至 Elasticsearch 集群并将模型上传到 Machine Learning 节点中。您可以使用 –url 参数来引用自己的实际集群 URL,例如下方代码中“image-search.es.europe-west1.gcp.cloud.es.io”便是集群 URL。

--url https://elastic:<password>@image-search.es.europe-west1.gcp.cloud.es.io:443

输入 Eland 导入命令。

$ eland_import_hub_model --url https://elastic:<password>@<URL>:443 \
  --hub-model-id sentence-transformers/clip-ViT-B-32-multilingual-v1 \
  --task-type text_embedding --ca-certs app/conf/ess-cloud.cer \
  --start

输出将类似于以下内容:

2022-12-12 13:40:52,308 INFO : Establishing connection to Elasticsearch
2022-12-12 13:40:52,327 INFO : Connected to cluster named 'image-search-8.6.1' (version: 8.5.3)
2022-12-12 13:40:52,328 INFO : Loading HuggingFace transformer tokenizer and model 'sentence-transformers/clip-ViT-B-32-multilingual-v1'
2022-12-12 13:41:03,032 INFO : Creating model with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'
2022-12-12 13:41:03,050 INFO : Uploading model definition
100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 129/129 [00:42<00:00,  3.01 parts/s]
2022-12-12 13:41:45,902 INFO : Uploading model vocabulary
2022-12-12 13:41:46,120 INFO : Starting model deployment
2022-12-12 13:41:52,825 INFO : Model successfully imported with id 'sentence-transformers__clip-vit-b-32-multilingual-v1'

上传过程可能会耗时几分钟,具体取决于您的网络连接。完成后,在 Machine Learning Kibana 页面上查看所训练模型的列表:Menu(菜单)-> Analytics(分析)-> Machine Learning -> Model management(模型管理)-> Trained models(所训练模型)。确认 NLP CLIP 模型处于 ‘started’(已开始)状态。

如果您在屏幕上收到一条消息——需要同步 ML 作业和所训练模型——则点击链接以对模型完成同步。

如何创建图像嵌入

创建 Elasticsearch 集群并导入嵌入模型后,您需要对图像数据进行矢量化并为您数据集中的每一张图像创建图像嵌入。

要创建图像嵌入,可使用简单的 Python 脚本。您可以在此处找到脚本:create-image-embeddings.py。此脚本会遍历您图像所在的目录并生成单独的图像嵌入。它将会创建带名称和相对路径的文档,并使用所提供的映射将其存到 Elasticsearch 索引 ‘my-image-embeddings’ 中。

将您的所有图像(照片)放到文件夹 ‘app/static/images’ 中。使用带子文件夹的目录结构来确保图像井然有序。所有图像都准备就绪后,使用几个参数执行脚本。

至为关键的一点是至少要有数百张图像,才能实现比较合理的效果。图像数量太少会导致结果达不到您的期望,因为您将要搜索的空间会特别狭小,而且到搜索向量的距离会特别接近。

在文件夹 image_embeddings 中,运行脚本并针对变量使用您的值。

$ cd image_embeddings
$ python3 create-image-embeddings.py \
  --es_host='https://image-search.es.europe-west1.gcp.cloud.es.io:443' \
  --es_user='elastic' --es_password=<password> \
  --ca_certs='../app/conf/ess-cloud.cer'

这一任务会需要一定时间才能完成,具体取决于图像数量、图像大小、您的 CPU 以及您的网络连接。先用较少的图像进行实验,然后再尝试处理完整的数据集。
脚本运行完毕之后,您可以使用 Kibana 开发工具验证索引 my-image-embeddings 是否存在并拥有相对应的文档。

GET _cat/indices/my-image-embeddings?v

health status index               uuid                   pri rep docs.count docs.deleted store.size pri.store.size
green  open   my-image-embeddings vfA3wOheT1C79R-PceDyXg   1   1       1222            0     24.4mb         12.2mb

看一下这些文档,您会发现特别相似的 JSON 对象(就像这个示例)。您会看到图像名称、图像 id,以及在 images 文件夹内的相对路径。搜索时,前端应用程序会使用这一路径来正确显示图像。
JSON 文档中最重要的部分是 ‘image_embedding’,因为其中包含 CLIP 模型所生成的密集矢量。当应用程序搜索图像或相似图像时,会用到这一矢量。

{
   "_index": "my-image-embeddings",
   "_id": "_g9ACIUBMEjlQge4tztV",
   "_score": 6.703597,
   "_source": {
     "image_id": "IMG_4032",
     "image_name": "IMG_4032.jpeg",
     "image_embedding": [
       -0.3415695130825043,
       0.1906963288784027,
       .....
       -0.10289803147315979,
       -0.15871885418891907
       ],
     "relative_path": "phone/IMG_4032.jpeg"
   }
}

使用 Flask 应用程序搜索图像

现在您的环境已经完全创建完毕,您可以进行下一步了,亦即使用自然语言实际搜索图像并寻找相似图像,不妨使用我们所提供的 Flask 应用程序作为概念验证。该网络应用程序具有简单的 UI,可简化图像搜索。您可以在此 GitHub 存储库中获取原型 Flask 应用程序。

该应用程序会在后台执行两项任务。您将搜索字符串输入到搜索框中之后,应用程序将会使用 Machine Learning _infer 终端对文本进行矢量化。然后,会使用矢量来针对索引 my-image-embeddings 执行带有您的密集矢量的查询。

您可以在这个示例中看到那两个查询。第一个 API 调用使用了 _infer 终端,结果是一个密集矢量。

POST _ml/trained_models/sentence-transformers__clip-vit-b-32-multilingual-v1/deployment/_infer
{
  "docs" : [
    {"text_field": "endless route to the top"}
    ]
}

在第二个任务(即搜索查询)中,我们将会使用密集矢量并按照分数将图像排序。

GET my-image-embeddings/_search
{
  "knn": {
    "field": "image_embedding",
    "k": 5,
    "num_candidates": 10,
    "query_vector": [
    -0.19898493587970734,
    0.1074572503566742,
    -0.05087625980377197,
    ...
    0.08200495690107346,
    -0.07852292060852051
  ]
  },
  "fields": [
    "image_id", "image_name", "relative_path"

  ],
  "_source": false
}

为了配置 Flask 应用程序并让它正常运行,导航至存储库的根文件夹及并配置 .env 文件。会使用配置文件中的值来连接至 Elasticsearch 集群。您需要为下列变量插入值。这些是在图像嵌入生成过程中用到的同一批值。

  • ES_HOST='URL:PORT'
  • ES_USER='elastic'
  • ES_PWD='password'

就绪后,在主文件夹中运行 Flask 应用程序并等待直至它启动。

# In the main directory 
$ flask run --port=5001

如果应用程序启动,您将会看到类似下面的输出,在末尾显示您需要前往哪个 URL 以访问该应用程序。

flask run --port=5001
 * Serving Flask app 'flask-elastic-image-search.py' (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5001
Press CTRL+C to quit

恭喜!您的应用程序现在应该已经配置完毕并运行了,可通过互联网浏览器在 http://127.0.0.1:5001 上访问。

导航至图像搜索选项卡并输入能够最确切地描述您的图像的文本。尝试使用非关键字或描述性文本。

在下面的示例中,输入的文本是“endless route to the top”(到达顶部的无尽头路径)。 显示的是来自我们数据集的结果。如果用户喜欢结果集中的某张特定图像,可以简单地点击旁边的按钮,相似的图像便会显示出来。用户可以无限次地进行这一操作,并通过图像数据集构建他们自己的路径。

也可以通过简单地上传图像来进行搜索。应用程序会将图像转换为矢量并在数据集中搜索相似的图像。如要搜索图像,请导航至第三个选项卡相似图像,从磁盘中上传图片,并点击搜索

由于我们在 Elasticsearch 中使用的 NLP (sentence-transformers/clip-ViT-B-32-multilingual-v1) 模型是多语言模型并支持以多种语言进行推理,所以请尝试用您自己的语言搜索图片。然后还可以使用英语文本对结果进行验证。

有很重要的一点需要注意:所使用的模型是通用模型,虽然相当准确,但您所得到的的结果可能会不同,具体取决于用例和其他因素。如果需要实现更高的准确度,您则必须对通用模型进行调整或开发您自己的模型——CLIP 模型仅旨在作为您的一个起始点。

代码摘要

您可以在 GitHub 存储库中找到完整代码。您还可以检查 routes.py 中的代码,此处的代码用来实施应用程序的主逻辑。除了显而易见的路径定义,您应该专注于用来定义 _infer 和 _search 终端(infer_trained_modelknn_search_images)的方法。负责生成图像嵌入的代码位于 create-image-embeddings.py 文件中。

总结

现在 Flask 应用已经设置完毕,您可以轻松搜索自己的图像集啦!Elastic 在平台内提供矢量搜索的原生集成,所以无需与外部进程进行通信。您能够灵活地开发和部署您使用 PyTorch 开发的定制嵌入模型。

相较于图像搜索的其他传统方式,语义图像搜索具有下列优点:

  • 更高的准确度:无须依赖图像的文本元描述,矢量相似度便能捕获上下文和关联。
  • 更好的用户体验:描述您正在查找什么,或者提供一张示例图片,而不再需要猜测哪个关键词可能相关。
  • 对图像数据库进行分类:无需担心如何为您的图像编制目录——相似度搜索无须整理图像就能从一堆图像中找到相关的那些。

如果您的用例更多地依靠文本数据,您可以查阅之前的博文详细了解如何实施语义搜索并将自然语言处理应用到文本。对于文本数据,将矢量相似度与传统关键字计分相结合能够让您同时收获这两种方法的优点。
准备好开始体验了吗?请访问我们的虚拟活动中心注册参加实操式矢量搜索研讨会,还可通过我们的在线论坛与社区进行互动。