🗃️

合集:图片向量相似检索服务

Created
Aug 4, 2022 07:56 AM
Tags
collection
Original URL
图片向量相似检索服务(1)——解决方案

概述

  • 当您听到“以图搜图”时,是否首先想到了百度、Google 、阿里等搜索引擎的以图搜图功能呢?事实上,完全可以搭建一个属于自己的以图搜图系统:自己建立图片库;自己选择一张图片到库中进行搜索,并得到与其相似的若干图片。
  • 为了让尝试相似图片检索的场景,基于内积距离计算和图片特征提取模型 VGG16 设计了一个以图搜图系统。 正文分为系统概览、 VGG 模型、数据准备、系统部署、总结五个部分。

系统构建

  • 基础环境安装:Python版本:3.6.10
git clone https://github.com/thirtyonelee/image-retrieval.git && cd image-retrieval pip install -r requirements.txt
  • 构建基础索引,默认保存索引至:"<ROOT_DIR>/index/train.h5"
python index.py
  • 体验相似检索,默认使用 Numpy 内积计算引擎,默认测试图片:"<ROOT_DIR>/data/test/001_accordion_image_0001.jpg"
python retrieval.py 或 python retrieval.py --engine=numpy --test_data=<ROOT_DIR>/data/test/001_accordion_image_0001.jpg [{'name': b'001_accordion_image_0002.jpg', 'score': 0.902732}, {'name': b'001_accordion_image_0003.jpg', 'score': 0.872308}, {'name': b'002_anchor_image_0004.jpg', 'score': 0.865453}] 'name' 对应索引库 <ROOT_DIR>/data/train 图片名字

系统架构

  • 系统主要由两部分组成,图片特征提取模型VGG和向量检索引擎。 VGG模型负责将图片转换成向量,向量检索引擎负责存储向量并进行相似向量检索。 具体架构如下图所示:
    • notion image

VGG模型

  • VGGNet由牛津大学的视觉几何组( Visual Geometry Group )和 Google DeepMind 公司的研究员共同提出,是 ILSVRC-2014 中定位任务第一名和分类任务第二名。其突出贡献在于证明使用很小的卷积( 3*3 ),增加网络深度可以有效提升模型的效果,而且 VGGNet 对其他数据集具有很好的泛化能力。 VGG模型在多个迁移学习任务中的表现要优于 GoogleNet ,从图像中提取 CNN 特征, VGG 模型是首选算法。因此,在本方案中选择 VGG 作为深度学习模型。
  • VGGNet 探索了 CNN 的深度及其性能之间的关系,通过反复堆叠 33 的小型卷积核和 22的最大池化层, VGGNet 成功地构筑了 16-19 层深的 CNN 。在本方案中使用了 Keras 的应用模块( keras.applications )提供的 VGG16 模型。

数据准备

  • 这个演示使用 PASCAL VOC 图像集,包含11530张图像,包含20个类别。

总结

  • 项目支持多种距离计算引擎,如:Numpy、Faiss、ES、Milvus
  • 可定制其他距离计算函数,如:欧氏距离 (L2)、汉明距离、杰卡德距离、谷本距离等

附录

图片向量相似检索服务(2)——四种基本距离计算原理

余弦距离(Cosine distance)

余弦相似度原理

  • 用向量空间中的两个向量夹角的余弦值作为衡量两个个体间差异大小的度量,值越接近1,就说明夹角角度越接近0°,也就是两个向量越相似,就叫做余弦相似
  • 余弦相似度公式具体如下:
    • notion image

余弦实际应用

    • 现在假设:A用户喜欢a,b,d;B用户喜欢b,c,e;C用户喜欢c,d;D用户喜欢b,c,d;E用户喜欢a,d,建立物品-用户的倒排表,列出每个物品都被哪些用户喜欢,其中“1”表示喜欢,“0”表示不喜欢。注意:这里的喜欢行为可以理解成是用户在产品上触发的交互,例如点击,评论,点赞,收藏等。具体如下:
      notion image
    • 利用前面说到的余弦定理公式计算两个物品间的相似度,例如:将物品a和b分别看作是多维空间中的两个向量,则有:a(1,0,0,0,1);b(1,1,0,1,0),所以物品a和物品b的相似度为:
      notion image

代码实现

import numpy as np vec1 = np.array([1, 3, 4]) vec2 = np.array([4, 2, 4]) d = np.dot(vec1,vec2)/(np.linalg.norm(vec1) * (np.linalg.norm(vec2)))

点积距离(Dot Product)

点积相似度原理

  • 两个向量的长度与它们夹角余弦的积
  • 点积计算公式:a * b = |a| * |b| * cosθ
  • 点乘又叫向量的内积、数量积,是一个向量和它在另一个向量上的投影的长度的乘积;是标量。
  • 点乘反映着两个向量的“相似度”,两个向量越“相似”,它们的点乘越大。

代码实现

import numpy as np vec1 = np.array([1, 3, 4]) vec2 = np.array([4, 2, 4]) d = np.dot(vec1, vec2)

欧氏距离(Euclidean distance)

欧氏相似度原理

  • 欧氏距离(也称欧几里得度量)指在m维空间中两个点之间的真实距离,或者向量的自然长度(即该点到原点的距离)
  • 欧式相似度公式具体如下:
    • notion image

欧氏实际应用

  • 在数据完整(无维度数据缺失)的情况下, 维度间的衡量单位是一致的, 否则需要标准化处理

代码实现

import numpy as np vec1 = np.array([1, 3, 4]) vec2 = np.array([4, 2, 4]) d = np.linalg.norm(vec1-vec2, ord=2) # 或 d = np.sqrt(np.sum(np.square(vec1-vec2)))

曼哈顿距离(Manhattan distance)

曼哈顿相似度原理

  • 在欧几里得空间的固定直角坐标系上两点所形成的线段对轴产生的投影的距离总和
  • 曼哈度相似度公式具体如下:
    • notion image

曼哈顿实际应用

  • 在数据完整(无维度数据缺失)的情况下, 需要将空间划分成网格, 然后以网格为单位来进行度量, 允许4个方向

代码实现

import numpy as np vec1 = np.array([1, 3, 4]) vec2 = np.array([4, 2, 4]) d = np.linalg.norm(vec1-vec2, ord=1) # 或 d = np.sum(np.abs(vec1-vec2))

附录:

图片向量相似检索服务(3)——基于ES实现

概述

  • 为了让尝试“以图搜图”的相似图片检索的场景,基于ES向量索引计算和图片特征提取模型 VGG16 设计了一个以图搜图系统。

检索场景

  • 推理流程:读取图片,算法生成特征向量
  • 特征入库:把特征向量存入ES中
  • 检索流程:线上实时向量检索
  • 具体流程如下图:
    • notion image

ES向量索引

  • Dense Vector:存储稠密向量,存储为单值字段数组,数组的最大长度不能超过2048,每个文档的数组长度可以不同
    • notion image
      Sparse Vector:存储稀疏向量,存储为非嵌套类型的json对象,key是向量的位置,即integer类型的字符串,范围[0,65535],value是向量值。但7.6版本后不在支持稀疏向量,请谨慎使用
      notion image

ES检索实现

  • 提供余弦、曼哈顿、欧式和点积四种距离方式,具体代码如下:
# 余弦距离 script_query = { "script_score": { "query": {"match_all": {}}, "script": { "source": "cosineSimilarity(params.query_vector, doc['image_vector']) + 1.0", "params": {"query_vector": query_vector} } } } # 曼哈顿距离 script_query = { "script_score": { "query": {"match_all": {}}, "script": { "source": "1 / (1 + l1norm(params.queryVector, doc['image_vector']))", "params": { "queryVector": query_vector } } } } # 欧几里德距离 script_query = { "script_score": { "query": {"match_all": {}}, "script": { "source": "1 / (1 + l2norm(params.queryVector, doc['image_vector']))", "params": { "queryVector": query_vector } } } } # DotProduct实现 script_query = { "script_score": { "query": {"match_all": {}}, "script": { "source": """ double value = doc['image_vector'].size() == 0 ? 0 : dotProduct(params.query_vector, doc['image_vector']); return value; """, "params": {"query_vector": query_vector} } } } response = self.client.search( index=self.index_name, body={ "size": search_size, "query": script_query, "_source": {"includes": ["id", "name", "face_vector"]} } )

ES服务端安装

docker run -it -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.5.0

操作简介

  • 操作一:构建基础索引
python index.py --train_data:自定义训练图片文件夹路径,默认为`<ROOT_DIR>/data/train` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5`
  • 操作二:使用相似检索
python retrieval.py --engine=es --test_data:自定义测试图片详细地址,默认为`<ROOT_DIR>/data/test/001_accordion_image_0001.jpg` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5` --db_name:自定义ES或者Milvus索引库名,默认为`image_retrieval` --engine:自定义检索引擎类型,默认为`numpy`,可选包括:numpy、faiss、es、milvus

总结

  • 扩展 ElasticSearch 的能力使其支持向量检索
  • 便于利用ElasticSearch分布式可扩展的能力
  • 利用 ElasticSearch 查询函数和其他插件,便于扩展其他维度的检索
  • ES向量计算都是线性扫描,耗时和文档数量、硬件性能正相关,请验证后使用
That's all!
图片向量相似检索服务(4)——基于faiss实现

概述

  • 为了让尝试“以图搜图”的相似图片检索的场景,基于Faiss向量索引计算和图片特征提取模型 VGG16 设计了一个以图搜图系统。

检索场景

  • 推理流程:读取图片,算法生成特征向量
  • 特征入库:把特征向量存入ES中
  • 检索流程:线上实时向量检索
  • 具体流程如下图:
    • notion image

Faiss简介

  • faiss是为稠密向量提供高效相似度搜索和聚类的框架。由Facebook AI Research研发。 具有以下特性。
    • 提供多种检索方法
    • 速度快
    • 可存在内存和磁盘中
    • C++实现,提供Python封装调用。
    • 大部分算法支持GPU实现

Faiss检索实现

  • 提供欧氏和内积两种种距离方式,具体代码如下:
import faiss # make faiss available index = faiss.IndexFlatL2(d) # build the index index.add(xb) # add vectors to the index D, I = index.search(xq, k) # actual search
import faiss # make faiss available index = faiss.IndexFlatIP(d) # build the index index.add(xb) # add vectors to the index D, I = index.search(xq, k) # actual search

操作简介

  • 操作一:构建基础索引
python index.py --train_data:自定义训练图片文件夹路径,默认为`<ROOT_DIR>/data/train` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5`
  • 操作二:使用相似检索
python retrieval.py --engine=faiss --test_data:自定义测试图片详细地址,默认为`<ROOT_DIR>/data/test/001_accordion_image_0001.jpg` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5` --db_name:自定义ES或者Milvus索引库名,默认为`image_retrieval` --engine:自定义检索引擎类型,默认为`numpy`,可选包括:numpy、faiss、es、milvus

扩展阅读

That's all!
图片向量相似检索服务(5)——基于milvus实现

概述

  • 为了让尝试“以图搜图”的相似图片检索的场景,基于ES向量索引计算和图片特征提取模型 VGG16 设计了一个以图搜图系统。

检索场景

  • 推理流程:读取图片,算法生成特征向量
  • 特征入库:把特征向量存入Milvus中
  • 检索流程:线上实时向量检索
  • 具体流程如下图:
    • notion image

Milvus服务端安装

  • 下载配置
mkdir -p milvus/conf && cd milvus/conf wget https://raw.githubusercontent.com/milvus-io/milvus/0.10.6/core/conf/demo/server_config.yaml
  • 服务启动
docker run -d --name milvus_cpu_0.11.0 \ -p 19530:19530 \ -p 19121:19121 \ -v <ROOT_DIR>/milvus/db:/var/lib/milvus/db \ -v <ROOT_DIR>/milvus/conf:/var/lib/milvus/conf \ -v <ROOT_DIR>/milvus/logs:/var/lib/milvus/logs \ -v <ROOT_DIR>/milvus/wal:/var/lib/milvus/wal \ milvusdb/milvus:0.10.6-cpu-d022221-64ddc2

Milvus向量索引建库

  • 此处选用已落盘的h5py向量库进行建库
  • 检索类型为内积:MetricType.IP
# 1. 读取索引 h5f = h5py.File(index_dir, 'r') self.retrieval_db = h5f['dataset_1'][:] self.retrieval_name = h5f['dataset_2'][:] h5f.close() # 2. 入库Milvus if self.index_name in self.client.list_collections()[1]: self.client.drop_collection(collection_name=self.index_name) self.client.create_collection({'collection_name': self.index_name, 'dimension': 512, 'index_file_size': 1024, 'metric_type': MetricType.IP}) self.id_dict = {} status, ids = self.client.insert(collection_name=self.index_name, records=[i.tolist() for i in self.retrieval_db]) for i, val in enumerate(self.retrieval_name): self.id_dict[ids[i]] = str(val) self.client.create_index(self.index_name, IndexType.FLAT, {'nlist': 16384}) # pprint(self.client.get_collection_info(self.index_name)) print("************* Done milvus indexing, Indexed {} documents *************".format(len(self.retrieval_db)))

Milvus检索实现

  • 根据加载索引时的定义,此处检索采用点积距离计算方式,具体代码如下:
_, vectors = self.client.search(collection_name=self.index_name, query_records=[query_vector], top_k=search_size, params={'nprobe': 16})
  • 可切换至欧氏:MetricType.L2

操作简介

  • 操作一:构建基础索引
python index.py --train_data:自定义训练图片文件夹路径,默认为`<ROOT_DIR>/data/train` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5`
  • 操作二:使用相似检索
python retrieval.py --engine=milvus --test_data:自定义测试图片详细地址,默认为`<ROOT_DIR>/data/test/001_accordion_image_0001.jpg` --index_file:自定义索引文件存储路径,默认为`<ROOT_DIR>/index/train.h5` --db_name:自定义ES或者Milvus索引库名,默认为`image_retrieval` --engine:自定义检索引擎类型,默认为`numpy`,可选包括:numpy、faiss、es、milvus

总结

  • 基于库的管理方式便捷易懂
  • 使用姿势类似ES,但性能优于ES
  • 由于当前Milvus只支持向量检索,不支持标量相关,如果涉及标量过滤需要自建业务库
  • Milvus社区后续会支持分布式,可以更加方便的应对大索引场景
That's all!