Redis `Vector Search`:向量相似度搜索与 AI 应用

好,让我们开始这场关于 Redis Vector Search 的技术讲座,主题是:向量相似度搜索与 AI 应用。

各位观众,各位朋友,各位未来的人工智能大师们,大家好!今天咱们不整虚的,直接上干货,聊聊 Redis Vector Search 这玩意儿,看看它到底能帮咱们在 AI 的道路上走多远。

第一部分:啥是向量,啥是相似度?

在深入 Redis Vector Search 之前,咱们得先搞明白两个概念:向量和相似度。

  • 向量 (Vector): 别一听这词儿就觉得高大上,其实它就是一堆数字。比如,你可以用一个向量 [0.2, 0.5, 0.1, 0.8] 来表示一篇文章,每个数字代表某个关键词在这篇文章里的重要程度。图像、音频、甚至用户行为,都可以转换成向量。关键在于,向量能把复杂的东西变成计算机能理解的数字。

  • 相似度 (Similarity): 有了向量,我们就能算相似度了。相似度就是衡量两个向量有多像的指标。常用的相似度算法有:

    • 余弦相似度 (Cosine Similarity): 这是最常用的。它计算的是两个向量夹角的余弦值。余弦值越接近 1,向量越相似;越接近 -1,向量越不相似;接近 0,则表示向量正交,也就是毫不相关。

    • 欧几里得距离 (Euclidean Distance): 这就是我们熟悉的距离公式。距离越小,向量越相似。

    • 点积 (Dot Product): 直接计算向量的乘积之和,但通常需要对向量进行归一化处理,使其长度相等,才能保证结果的准确性。

    用表格来总结一下:

    相似度算法 计算公式 优点 缺点
    余弦相似度 cos(θ) = (A · B) / (||A|| * ||B||) 对向量长度不敏感,只关注方向,适合处理文本相似度等问题。 计算复杂度相对较高。
    欧几里得距离 √∑(Ai - Bi)² 简单直观,容易理解。 对向量长度敏感,需要对向量进行归一化处理。
    点积 A · B = ∑(Ai * Bi) 计算简单,速度快。 需要对向量进行归一化处理,否则结果会受到向量长度的影响。

第二部分:Redis Vector Search 是个啥?

现在主角登场了!Redis Vector Search,顾名思义,就是在 Redis 数据库里进行向量搜索。它能让我们快速找到与给定向量最相似的其他向量。

为啥要用 Redis 来做向量搜索呢?因为 Redis 快啊!Redis 是基于内存的数据库,读写速度非常快,这对于需要实时响应的 AI 应用来说至关重要。

Redis Vector Search 的核心在于它的索引结构。目前,Redis 支持两种索引结构:

  • FLAT: 暴力搜索,遍历所有向量,计算相似度,找到最相似的。适合数据量小的情况。

  • HNSW (Hierarchical Navigable Small World): 一种近似最近邻搜索算法。它通过构建多层图结构,加速搜索过程。适合数据量大的情况。

第三部分:动手实践:用 Redis Vector Search 搜索电影

光说不练假把式,咱们来个实战演练。假设我们有一堆电影,每个电影都有一个向量表示,表示了这部电影的剧情、演员、风格等等。现在,我们想找一部和《泰坦尼克号》最相似的电影。

1. 准备数据

首先,我们需要准备一些电影数据,并把它们转换成向量。这里为了简化,我们直接用 Python 模拟一些数据:

import redis
import numpy as np

# 连接 Redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)

# 清空 Redis (可选)
redis_client.flushdb()

# 模拟电影数据
movies = {
    "泰坦尼克号": [0.9, 0.8, 0.7, 0.6, 0.5],
    "阿凡达": [0.2, 0.3, 0.9, 0.1, 0.4],
    "星球大战": [0.1, 0.2, 0.8, 0.9, 0.3],
    "复仇者联盟": [0.3, 0.9, 0.2, 0.8, 0.1],
    "肖申克的救赎": [0.8, 0.7, 0.6, 0.5, 0.9]
}

# 将电影数据存入 Redis
for movie_name, movie_vector in movies.items():
    # 将向量转换为字节数组
    movie_vector_bytes = np.array(movie_vector, dtype=np.float32).tobytes()
    redis_client.hset("movies", movie_name, movie_vector_bytes)

print("电影数据已存入 Redis")

2. 创建索引

接下来,我们需要在 Redis 中创建一个向量索引。这里我们使用 HNSW 索引:

# 创建索引
try:
    redis_client.ft("movie_index").create_index(
        fields=[
            redis.ft.VectorField(
                "value",
                "HNSW",
                {
                    "TYPE": "FLOAT32",
                    "DIM": 5,  # 向量维度
                    "DISTANCE_METRIC": "COSINE"  # 距离度量方式
                }
            )
        ]
    )
    print("索引创建成功")
except Exception as e:
    print(f"索引创建失败: {e}")

这段代码的意思是:

  • redis_client.ft("movie_index"):创建一个名为 movie_index 的索引。
  • create_index(...):定义索引的字段。
  • VectorField("value", "HNSW", ...):创建一个向量字段,使用 HNSW 算法。
  • "DIM": 5:向量维度是 5。
  • "DISTANCE_METRIC": "COSINE":使用余弦相似度作为距离度量方式。

3. 将向量数据加载到索引中

虽然我们已经将向量存入了 Redis,但是还没有把它们加载到索引中。我们需要使用 HSET 命令将向量数据添加到索引中。

# 将电影数据加载到索引中
for movie_name, movie_vector in movies.items():
    movie_vector_bytes = np.array(movie_vector, dtype=np.float32).tobytes()
    redis_client.hset(movie_name, mapping={"value": movie_vector_bytes}) # 这里的key是电影的名字,value是一个hash,hash里面有个字段叫value,存储的是向量
    print(f"电影 {movie_name} 已添加到索引")

4. 进行搜索

现在,我们可以进行搜索了。我们想找到和《泰坦尼克号》最相似的电影:

# 搜索
query_vector = movies["泰坦尼克号"]
query_vector_bytes = np.array(query_vector, dtype=np.float32).tobytes()

# 构建查询语句
query = f"*=>[KNN 3 @value $vector AS score]" # KNN 3 表示返回3个结果

query_params = {"vector": query_vector_bytes}

# 执行查询
results = redis_client.ft("movie_index").search(query, query_params=query_params)

# 输出结果
print("搜索结果:")
for i, doc in enumerate(results.docs):
    print(f"  {i+1}. 电影: {doc.id}, 相似度: {doc.score}")

这段代码的意思是:

  • query = "*=>[KNN 3 @value $vector AS score]":构建查询语句,使用 KNN (K-Nearest Neighbors) 算法,返回 3 个最相似的电影。@value 表示向量字段,$vector 表示查询向量,AS score 表示将相似度分数命名为 score
  • query_params = {"vector": query_vector_bytes}:设置查询参数,将查询向量传递给查询语句。
  • results = redis_client.ft("movie_index").search(query, query_params=query_params):执行查询。
  • results.docs:包含了搜索结果的文档列表,每个文档都有一个 id (电影名称) 和一个 score (相似度分数)。

第五部分:更高级的用法

上面的例子只是 Redis Vector Search 的冰山一角。它还有很多更高级的用法,比如:

  • 使用过滤器 (Filters): 可以在搜索时添加过滤器,例如只搜索特定类型的电影。
  • 动态更新索引: 可以实时添加、删除、更新向量数据。
  • 使用不同的距离度量方式: 除了余弦相似度,还可以使用欧几里得距离、点积等。

第六部分:Redis Vector Search 的应用场景

Redis Vector Search 在 AI 领域有着广泛的应用场景:

  • 推荐系统: 根据用户的历史行为,推荐相似的商品、电影、音乐等。
  • 图像搜索: 根据图像的特征向量,搜索相似的图像。
  • 自然语言处理: 根据文本的语义向量,搜索相似的文本。
  • 欺诈检测: 根据用户的行为向量,检测欺诈行为。

用表格来总结一下:

应用场景 描述 示例
推荐系统 根据用户的历史行为,计算用户的兴趣向量,然后搜索与用户兴趣向量最相似的商品、电影、音乐等。 用户购买了《哈利波特》,推荐《魔戒》、《纳尼亚传奇》等奇幻小说。
图像搜索 将图像转换为特征向量,然后搜索与查询图像的特征向量最相似的图像。 用户上传一张猫的照片,搜索与该照片相似的其他猫的照片。
自然语言处理 将文本转换为语义向量,然后搜索与查询文本的语义向量最相似的文本。 用户输入“如何制作蛋糕”,搜索与该问题相关的菜谱、教程等。
欺诈检测 将用户的行为转换为行为向量,然后搜索与已知欺诈用户的行为向量最相似的用户。 检测信用卡欺诈,识别异常交易模式。

第七部分:总结与展望

今天,我们一起探索了 Redis Vector Search 的世界。它是一种强大的工具,可以帮助我们在 AI 应用中实现高效的向量相似度搜索。

当然,Redis Vector Search 还有很多需要完善的地方。比如,目前只支持 FLOAT32 数据类型,对于一些需要更高精度的数据,可能需要进行转换。另外,HNSW 索引的构建过程比较耗时,需要根据实际情况进行优化。

但是,我相信随着 Redis 的不断发展,Vector Search 会变得越来越强大,成为 AI 领域不可或缺的一部分。

好了,今天的讲座就到这里。希望大家有所收获,也希望大家能在 AI 的道路上越走越远!记住,编程的世界没有尽头,只有不断学习,才能保持进步!

附录:完整代码

为了方便大家学习,这里附上完整的代码:

import redis
import numpy as np

# 连接 Redis
redis_client = redis.Redis(host='localhost', port=6379, db=0)

# 清空 Redis (可选)
redis_client.flushdb()

# 模拟电影数据
movies = {
    "泰坦尼克号": [0.9, 0.8, 0.7, 0.6, 0.5],
    "阿凡达": [0.2, 0.3, 0.9, 0.1, 0.4],
    "星球大战": [0.1, 0.2, 0.8, 0.9, 0.3],
    "复仇者联盟": [0.3, 0.9, 0.2, 0.8, 0.1],
    "肖申克的救赎": [0.8, 0.7, 0.6, 0.5, 0.9]
}

# 将电影数据存入 Redis
for movie_name, movie_vector in movies.items():
    # 将向量转换为字节数组
    movie_vector_bytes = np.array(movie_vector, dtype=np.float32).tobytes()
    redis_client.hset("movies", movie_name, movie_vector_bytes)

print("电影数据已存入 Redis")

# 创建索引
try:
    redis_client.ft("movie_index").create_index(
        fields=[
            redis.ft.VectorField(
                "value",
                "HNSW",
                {
                    "TYPE": "FLOAT32",
                    "DIM": 5,  # 向量维度
                    "DISTANCE_METRIC": "COSINE"  # 距离度量方式
                }
            )
        ]
    )
    print("索引创建成功")
except Exception as e:
    print(f"索引创建失败: {e}")

# 将电影数据加载到索引中
for movie_name, movie_vector in movies.items():
    movie_vector_bytes = np.array(movie_vector, dtype=np.float32).tobytes()
    redis_client.hset(movie_name, mapping={"value": movie_vector_bytes}) # 这里的key是电影的名字,value是一个hash,hash里面有个字段叫value,存储的是向量
    print(f"电影 {movie_name} 已添加到索引")

# 搜索
query_vector = movies["泰坦尼克号"]
query_vector_bytes = np.array(query_vector, dtype=np.float32).tobytes()

# 构建查询语句
query = f"*=>[KNN 3 @value $vector AS score]" # KNN 3 表示返回3个结果

query_params = {"vector": query_vector_bytes}

# 执行查询
results = redis_client.ft("movie_index").search(query, query_params=query_params)

# 输出结果
print("搜索结果:")
for i, doc in enumerate(results.docs):
    print(f"  {i+1}. 电影: {doc.id}, 相似度: {doc.score}")

大家可以把这段代码复制到自己的电脑上,跑起来看看效果。如果遇到问题,欢迎提问!

记住,实践是检验真理的唯一标准。只有亲自尝试,才能真正理解 Redis Vector Search 的强大之处。

最后,祝大家编程愉快!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注