Redis也能玩向量:基于Tair Vector实现图文多模态检索

本文中提到的支持向量检索的数据库Tair,目前在阿里云可免费使用3个月,领取方式请点击:

点击领取Tair 3个月免费使用权

本文介绍基于Tair Vector和CLIP实现实时高性能图文多模态检索的解决方案。

背景信息

在互联网中,大量信息(例如图片、文本等)通常以非结构化的形式存在。达摩院的CLIP开源模型内置了Text transformer、ResNet等模型,支持对图片、文本等非结构化数据进行特征提取,并解析成结构化数据。

您可以先通过CLIP模型将图片、文档等数据预处理,然后将CLIP的预处理结果存入Tair中,根据Vector提供的近邻检索功能,实现高效的图文多模态检索。更多关于Tair Vector的信息,请参见Vector

方案概述

  1. 下载图片数据。

    本示例使用的测试数据:

    • 图片:为开源的宠物图片数据集,包含7000多张各种形态的宠物图片。

    • 文本:“狗”、“白色的狗”和“奔跑的白色的狗”。

  2. 连接Tair实例,具体实现可参见示例代码中的get_tair函数。

  3. 在Tair中分别为图片(Images)和文字(Texts)创建向量索引,具体实现可参见示例代码中的create_index函数。

  4. 写入图片和文本数据。

    通过CLIP模型对图片、文本数据进行预处理,并通过Vector的TVS.HSET命令,将其名称和其特征信息存入Tair中。具体实现可参见示例代码中的insert_images函数(写入图片)和upsert_text函数(写入文本)。

  5. 进行多模态查询。

    • 以文搜图

      通过CLIP模型将待查询的文本内容进行预处理,然后通过Vector的TVS.KNNSEARCH命令,查询Tair数据库与该文本描述最为相似的图片。具体实现可参见示例代码中的query_images_by_text函数。

    • 以图搜文

      通过CLIP模型将待查询的图片内容进行预处理,然后通过Vector的TVS.KNNSEARCH命令,查询Tair数据库中与该图片最为吻合的文本。具体实现可参见示例代码中的query_texts_by_image函数。

    说明
    • 查询的文本或图片无需存入TairVector中。

    • TVS.KNNSEARCH支持指定返回的结果数量(topK),返回的相似距离(distance)越小,表示相似度越高。

示例代码

本示例的Python版本为3.8,且需安装Tair-py、torch、Image、pylab、plt、CLIP等依赖,其中Tair-py的快捷安装命令为:pip3 install tair

# -*- coding: utf-8 -*-
# !/usr/bin/env python
from tair import Tair
from tair.tairvector import DistanceMetric
from tair import ResponseError

from typing import List
import torch
from PIL import Image
import pylab
from matplotlib import pyplot as plt
import os
import cn_clip.clip as clip
from cn_clip.clip import available_models


def get_tair() -> Tair:
    """
    该方法用于连接Tair实例。
    * host:Tair实例连接地址。
    * port:Tair实例的端口号,默认为6379。
    * password:Tair实例的密码(默认账号)。若通过自定义账号连接,则密码格式为“username:password”。
    """
    tair: Tair = Tair(
        host="r-8vbehg90y9rlk9****pd.redis.rds.aliyuncs.com",
        port=6379,
        db=0,
        password="D******3",
        decode_responses=True
    )
    return tair


def create_index():
    """
    创建存储图片、文本向量的Vector索引:
    * 图片Key名称为"index_images"、文本Key名称为"index_texts"。
    * 向量维度为1024。
    * 计算向量距离函数为IP。
    * 索引算法为HNSW。
    """
    ret = tair.tvs_get_index("index_images")
    if ret is None:
        tair.tvs_create_index("index_images", 1024, distance_type="IP",
                              index_type="HNSW")
    ret = tair.tvs_get_index("index_texts")
    if ret is None:
        tair.tvs_create_index("index_texts", 1024, distance_type="IP",
                              index_type="HNSW")


def insert_images(image_dir):
    """
    您需要输入图片的路径,该方法会自动遍历路径下的图片文件。
    同时,该方法会调用extract_image_features方法(通过CLIP模型对图片文件进行预处理,并返回图片的特征信息),并行将返回的特征信息存入TairVector中。
    存入Tair的格式为:
    * 向量索引名称为“index_images”(固定)。
    * Key为图片路径及其文件名,例如“test/images/boxer_18.jpg”。
    * 特征信息为1024维向量。
    """
    file_names = [f for f in os.listdir(image_dir) if (f.endswith('.jpg') or f.endswith('.jpeg'))]
    for file_name in file_names:
        image_feature = extract_image_features(image_dir + "/" + file_name)
        tair.tvs_hset("index_images", image_dir + "/" + file_name, image_feature)


def extract_image_features(img_name):
    """
    该方法将通过CLIP模型对图片文件进行预处理,并返回图片的特征信息(1024维向量)。
    """
    image_data = Image.open(img_name).convert("RGB")
    infer_data = preprocess(image_data)
    infer_data = infer_data.unsqueeze(0).to("cuda")
    with torch.no_grad():
        image_features = model.encode_image(infer_data)
    image_features /= image_features.norm(dim=-1, keepdim=True)
    return image_features.cpu().numpy()[0]  # [1, 1024]


def upsert_text(text):
    """
    您需要输入需存储的文本,该方法会调用extract_text_features方法(通过CLIP模型对文本进行预处理,并返回文本的特征信息),并行将返回的特征信息存入TairVector中。
    存入Tair的格式为:
    * 向量索引名称为“index_texts”(固定)。
    * Key为文本内容,例如“奔跑的狗”。
    * 特征信息为1024维向量。
    """
    text_features = extract_text_features(text)
    tair.tvs_hset("index_texts", text, text_features)


def extract_text_features(text):
    """
    该方法将通过CLIP模型对文本进行预处理,并返回文本的特征信息(1024维向量)。
    """
    text_data = clip.tokenize([text]).to("cuda")
    with torch.no_grad():
        text_features = model.encode_text(text_data)
    text_features /= text_features.norm(dim=-1, keepdim=True)
    return text_features.cpu().numpy()[0]  # [1, 1024]


def query_images_by_text(text, topK):
    """
    该方法将用于以文搜图。
    您需要输入待搜索的文本内容(text)和返回的结果数量(topK)。
    该方法会通过CLIP 模型将待查询的文本内容进行预处理,然后通过Vector的TVS.KNNSEARCH命令,查询Tair数据库中与该文本描述最为相似的图片。
    将返回目标图片的Key名称和相似距离(distance),其中相似距离(distance)越小,表示相似度越高。
    """
    text_feature = extract_text_features(text)
    result = tair.tvs_knnsearch("index_images", topK, text_feature)
    for k, s in result:
        print(f'key : {k}, distance : {s}')
        img = Image.open(k.decode('utf-8'))
        plt.imshow(img)
        pylab.show()


def query_texts_by_image(image_path, topK=3):
    """
    该方法将用于以图搜文。
    您需要输入待查询图片的路径和返回的结果数量(topK)。
    该方法会通过CLIP 模型将待查询的图片内容进行预处理,然后通过Vector的TVS.KNNSEARCH命令,查询Tair数据库中与该图片最为吻合的文本。
    将返回目标文本的Key名称和相似距离(distance),其中相似距离(distance)越小,表示相似度越高。
    """
    image_feature = extract_image_features(image_path)
    result = tair.tvs_knnsearch("index_texts", topK, image_feature)
    for k, s in result:
        print(f'text : {k}, distance : {s}')

if __name__ == "__main__":
    # 连接Tair数据库,并分别创建存储图片和文本的向量索引。
    tair = get_tair()
    create_index()
    # 加载Chinese-CLIP模型。
    model, preprocess = clip.load_from_name("RN50", device="cuda", download_root="./")
    model.eval()
    
    # 例如宠物图片数据集的路径为“/home/CLIP_Demo”,写入图片数据。
    insert_images("/home/CLIP_Demo")
    # 写入文本示例数据("狗"、"白色的狗"、"奔跑的白色的狗")。
    upsert_text("狗")
    upsert_text("白色的狗")
    upsert_text("奔跑的白色的狗")

    # 以文搜图,查询最符合文本"奔跑的狗"的三张图。
    query_images_by_text("奔跑的狗", 3)
    # 以图搜文,指定图片路径,查询比较符合图片描述的文本。
    query_texts_by_image("/home/CLIP_Demo/boxer_18.jpg",3)

结果展示

  • 以文搜图,查询最符合文本"奔跑的狗"的三张图,结果如下。

  • 以图搜文,指定查询图片如下。

    查询结果如下。

    {
      "results":[
        {
          "text":"奔跑的白色的狗”,
          "distance": "0.4052203893661499"
        },
        {
          "text":"白色的狗",
          "distance": "0.44666868448257446"
        },
        {
          "text":"狗",
          "distance": "0.4553511142730713"
        }
      ]
    }

总结

Tair作为纯内存数据库,内置了HNSW等索引算法加快了检索的速度。

采用CLIP与Tair Vector进行多模态检索,既可以进行以文搜图(例如商品推荐场景),也可以进行以图搜文(例如写作场景)。同时,您也可以采用其他的模型取代CLIP模型,实现以文本搜索视频、音频等更多模态的检索功能。

 本文中提到的支持向量检索的数据库Tair,目前在阿里云可免费使用3个月,领取方式请点击:

点击领取Tair 3个月免费使用权

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于Redis实现分布式锁主要分为以下几个步骤: 1. 使用SETNX命令(set if not exists)尝试获取锁,如果返回1则表示获取锁成功,返回0则表示获取锁失败。 2. 设置锁的过期时间,防止获取锁后由于异常情况导致未释放锁而造成资源浪费。可以使用EXPIRE命令给锁设置一个过期时间。 3. 在请求执行完成后,利用DEL命令释放锁,确保锁被正确释放。 在具体实现时,可以使用Redis的单点式或集群式。 在单点式下,使用SETNX命令设置一个指定的键(表示锁)的值为当前时间戳,同时给该键设置一个适当的过期时间。如果SETNX命令的返回值为1,则表示获取锁成功;如果返回值为0,则表示获取锁失败。 在集群式下,可以使用RedLock算法来实现分布式锁。RedLock算法是基于多个Redis节点的互斥性来实现的,主要分为以下几个步骤: 1. 获取当前时间戳,并计算出获取锁的超时时间。 2. 依次在多个Redis节点上请求获取锁,在请求获取锁时,需要指定相同的键名和值,并设置一个适当的过期时间。 3. 统计成功获取锁的节点数,如果成功数量满足半数以上的节点,表示获取锁成功。 4. 获取锁失败的情况下,检查锁的超时时间,如果超时则放弃获取锁。 5. 如果成功获取锁,则执行对应的操作,并在操作完成后释放锁。 使用Redis实现分布式锁需要注意以下几个问题: 1. 锁释放的时机要合理,不要过早释放锁,以防止其他请求获取到未完成的锁。 2. 锁的超时时间要根据业务需求设置,以避免长时间占用锁。 3. 由于Redis是内存数据库,需要注意锁的持久性问题,在锁被释放之前发生Redis节点崩溃等情况时,需要能够保证锁的正确释放和重新获取。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值