学习

如何在 NodeJS 中使用 Redis 执行向量相似度搜索

Will Johnston
作者
Will Johnston, Redis 开发者增长经理
Prasan Kumar
作者
Prasan Kumar, Redis 技术解决方案开发者

本教程您将学习什么#

本教程是关于如何在 NodeJS 环境中利用 Redis 进行向量相似度搜索的全面指南。本教程面向具备 NodeJS/JavaScript 生态系统专业知识的软件开发者,将为您提供进行高级向量操作所需的知识和技术。以下是本教程涵盖的内容

基础概念:

  • 关于向量:深入探讨机器学习中向量的基础概念。
  • 向量数据库:了解专门用于高效处理向量数据的数据库。
  • 向量相似度:掌握向量比较的概念和重要性。了解向量相似度在推荐系统、内容检索等领域发挥关键作用的一些用例。

向量生成:

  • 文本内容:学习从文本数据生成向量的技术。
  • 图像内容:了解如何将图像表示为向量以及如何处理它们。

Redis 数据库设置:

  • 数据填充:动手将向量数据填充到 Redis 数据库中。
  • 索引创建:了解在 Redis 中索引向量字段的过程,优化准确性和性能。

Redis 中的高级向量查询

  • KNN (k-近邻) 查询:深入了解 KNN 的概念及其在 Redis 中的实现,以检索与查询向量最相似的向量。
  • 范围查询:了解如何检索与目标向量在一定距离或范围内的向量。

向量相似度计算:(可选)如果您想了解向量相似度搜索背后的数学原理

  • 欧几里得距离:了解用于计算相似度的 L2 范数方法。
  • 余弦相似度:深入研究角差及其在向量空间中的重要性。
  • 内积:了解理解向量相似度的另一个重要指标。

更多资源:通过与 Redis 中向量相关的其他资源进一步学习。

向量介绍#

机器学习中的向量是什么?#

在机器学习的背景下,向量是数据的数学表示。它是一个有序的数字列表,编码了数据的特征或属性。

向量可以被认为是多维空间中的点,其中每个维度对应一个特征。 例如,考虑一个简单的电商 产品 数据集。每个产品可能包含诸如 价格、 质量 和 受欢迎程度 等特征。

ID

产品

价格 ($)

质量 (1 - 10)

受欢迎程度 (1 - 10)

1

Puma 男士 Race 黑色手表

150

5

8

2

Puma 男士 Top Fluctuation 红黑手表

180

7

6

3

Inkfruit 女士 Behind 米色 T恤

5

9

7

现在,产品 1 Puma 男士 Race 黑色手表 可以表示为向量 [150, 5, 8]

在更复杂的场景中,例如自然语言处理 (NLP),单词或整个句子可以被转换为密集向量(通常称为嵌入),这些向量捕获文本的语义含义。向量在许多机器学习算法中扮演着基础角色,特别是那些涉及距离测量的算法,如聚类和分类算法。

什么是向量数据库?#

向量数据库是专门为存储和搜索向量而优化的系统。这些数据库专为效率而设计,在支持向量搜索应用(包括推荐系统、图像搜索和文本内容检索)方面发挥着关键作用。这些数据库通常被称为向量存储、向量索引或向量搜索引擎,它们采用向量相似度算法来识别与给定查询向量密切匹配的向量。

提示

Redis Cloud 是向量数据库的流行选择,因为它提供了丰富的适合向量存储和搜索的数据结构和命令。Redis Cloud 允许您通过本教程中进一步概述的几种不同方式对向量进行索引和执行向量相似度搜索。它还保持了高水平的性能和可伸缩性。

什么是向量相似度?#

向量相似度是一种衡量两个向量相似程度的度量,通常通过评估它们在多维空间中的距离角度来量化。当向量代表数据点(如文本或图像)时,相似度得分可以表明底层数据点在特征或内容方面的相似程度。

向量相似度的用例

  • 推荐系统:如果您有代表用户偏好或商品配置文件的向量,您可以快速找到与用户偏好向量最相似的商品。
  • 图像搜索:存储代表图像特征的向量,然后检索与给定图像向量最相似的图像。
  • 文本内容检索:存储代表文本内容(例如,文章、产品描述)的向量,并找到与给定查询向量最相关的文本。
计算向量相似度

如果您有兴趣了解更多关于向量相似度背后的数学知识,请向下滚动到 如何计算向量相似度? 部分。

生成向量#

在我们的场景中,重点围绕生成句子(产品描述)和图像(产品图像)嵌入或向量。有大量 AI 模型库,例如 GitHub,其中 AI 模型经过预训练、维护和共享。

对于句子嵌入,我们将使用来自 Hugging Face 模型中心 的模型,而对于图像嵌入,将利用来自 TensorFlow Hub 的模型以增加多样性。

GITHUB 代码

下面是克隆本教程中使用的源代码的命令

git clone https://github.com/redis-developer/redis-vector-nodejs-solutions.git

句子/文本向量#

为了生成句子嵌入,我们将使用 Hugging Face 的一个模型,标题为 Xenova/all-distilroberta-v1。它是适用于 transformer.js 的 sentence-transformers/all-distilroberta-v1 兼容版本,带有 ONNX 权重。

信息

Hugging Face Transformers 是用于自然语言处理 (NLP) 任务的知名开源工具。它简化了尖端 NLP 模型的使用。

transformers.js 库本质上是 Hugging Face 流行 Python 库的 JavaScript 版本。

信息

ONNX (Open Neural Network eXchange) 是一个开放标准,它定义了一组通用的运算符和通用的文件格式,用于表示各种框架(包括 PyTorch 和 TensorFlow)中的深度学习模型

下面是一个 Node.js 代码片段,展示了如何为任何提供的 句子 创建向量嵌入

npm install @xenova/transformers
src/text-vector-gen.ts
import * as transformers from '@xenova/transformers';

async function generateSentenceEmbeddings(_sentence): Promise<number[]> {
  let modelName = 'Xenova/all-distilroberta-v1';
  let pipe = await transformers.pipeline('feature-extraction', modelName);

  let vectorOutput = await pipe(_sentence, {
    pooling: 'mean',
    normalize: true,
  });

  const embeddings: number[] = Object.values(vectorOutput?.data);
  return embeddings;
}

export { generateSentenceEmbeddings };

以下是示例文本的向量输出预览

示例输出
const embeddings = await generateSentenceEmbeddings('I Love Redis !');
console.log(embeddings);
/*
 768 dim vector output
 embeddings = [
    -0.005076113156974316,  -0.006047232076525688,   -0.03189406543970108,
    -0.019677048549056053,    0.05152582749724388,  -0.035989608615636826,
    -0.009754283353686333,   0.002385444939136505,   -0.04979122802615166,
    ....]
*/

图像向量#

为了获取图像嵌入,我们将利用 TensorFlow 的 mobilenet 模型。

下面是一个 Node.js 代码片段,展示了如何为任何提供的 图像 创建向量嵌入

npm i @tensorflow/tfjs @tensorflow/tfjs-node @tensorflow-models/mobilenet jpeg-js
src/image-vector-gen.ts
import * as tf from '@tensorflow/tfjs-node';
import * as mobilenet from '@tensorflow-models/mobilenet';

import * as jpeg from 'jpeg-js';

import * as path from 'path';
import { fileURLToPath } from 'url';
import * as fs from 'fs/promises';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

async function decodeImage(imagePath) {
  imagePath = path.join(__dirname, imagePath);

  const buffer = await fs.readFile(imagePath);
  const rawImageData = jpeg.decode(buffer);
  const imageTensor = tf.browser.fromPixels(rawImageData);
  return imageTensor;
}

async function generateImageEmbeddings(imagePath: string) {
  const imageTensor = await decodeImage(imagePath);

  // Load MobileNet model
  const model = await mobilenet.load();

  // Classify and predict what the image is
  const prediction = await model.classify(imageTensor);
  console.log(`${imagePath} prediction`, prediction);

  // Preprocess the image and get the intermediate activation.
  const activation = model.infer(imageTensor, true);

  // Convert the tensor to a regular array.
  const vectorOutput = await activation.data();

  imageTensor.dispose(); // Clean up tensor

  return vectorOutput; //DIM 1024
}

以下是示例手表图像的向量输出图示:

示例输出
//watch image
const imageEmbeddings = await generateImageEmbeddings('images/11001.jpg');
console.log(imageEmbeddings);
/*
 1024 dim vector output
 imageEmbeddings = [
    0.013823275454342365,    0.33256298303604126,                     0,
    2.2764432430267334,    0.14010703563690186,     0.972867488861084,
    1.2307443618774414,      2.254523992538452,   0.44696325063705444,
    ....]

  images/11001.jpg (mobilenet model) prediction [
  { className: 'digital watch', probability: 0.28117117285728455 },
  { className: 'spotlight, spot', probability: 0.15369531512260437 },
  { className: 'digital clock', probability: 0.15267866849899292 }
]
*/

数据库设置#

GITHUB 代码

下面是克隆本教程中使用的源代码的命令

git clone https://github.com/redis-developer/redis-vector-nodejs-solutions.git

示例数据填充#

为了本教程的目的,让我们考虑一个简化的电商场景。提供的 products JSON 文件展示了我们将讨论的向量搜索功能。

src/data.ts
const products = [
  {
    _id: '1',
    price: 4950,
    productDisplayName: 'Puma Men Race Black Watch',
    brandName: 'Puma',
    ageGroup: 'Adults-Men',
    gender: 'Men',
    masterCategory: 'Accessories',
    subCategory: 'Watches',
    imageURL: 'images/11002.jpg',
    productDescription:
      '<p>This watch from puma comes in a heavy duty design. The asymmetric dial and chunky casing gives this watch a tough appearance perfect for navigating the urban jungle.<br /><strong><br />Dial shape</strong>: Round<br /><strong>Case diameter</strong>: 32 cm<br /><strong>Warranty</strong>: 2 Years<br /><br />Stainless steel case with a fixed bezel for added durability, style and comfort<br />Leather straps with a tang clasp for comfort and style<br />Black dial with cat logo on the 12 hour mark<br />Date aperture at the 3 hour mark<br />Analog time display<br />Solid case back made of stainless steel for enhanced durability<br />Water resistant upto 100 metres</p>',
  },
  {
    _id: '2',
    price: 5450,
    productDisplayName: 'Puma Men Top Fluctuation Red Black Watches',
    brandName: 'Puma',
    ageGroup: 'Adults-Men',
    gender: 'Men',
    masterCategory: 'Accessories',
    subCategory: 'Watches',
    imageURL: 'images/11001.jpg',
    productDescription:
      '<p style="text-align: justify;">This watch from puma comes in a clean sleek design. This active watch is perfect for urban wear and can serve you well in the gym or a night of clubbing.<br /><strong><br />Case diameter</strong>: 40 mm&lt;</p>',
  },

  {
    _id: '3',
    price: 499,
    productDisplayName: 'Inkfruit Women Behind Cream Tshirts',
    brandName: 'Inkfruit',
    ageGroup: 'Adults-Women',
    gender: 'Women',
    masterCategory: 'Apparel',
    subCategory: 'Topwear',
    imageURL: 'images/11008.jpg',
    productDescription:
      '<p><strong>Composition</strong><br />Yellow round neck t-shirt made of 100% cotton, has short sleeves and graphic print on the front<br /><br /><strong>Fitting</strong><br />Comfort<br /><br /><strong>Wash care</strong><br />Hand wash separately in cool water at 30 degrees <br />Do not scrub <br />Do not bleach <br />Turn inside out and dry flat in shade <br />Warm iron on reverse<br />Do not iron on print<br /><br />Flaunt your pretty, long legs in style with this inkfruit t-shirt. The graphic print oozes sensuality, while the cotton fabric keeps you fresh and comfortable all day. Team this with a short denim skirt and high-heeled sandals and get behind the wheel in style.<br /><br /><em>Model statistics</em><br />The model wears size M in t-shirts <br />Height: 5\'7", Chest: 33"</p>',
  },
];

以下是将 产品 数据以 JSON 格式填充到 Redis 的示例代码。这些数据还包含产品描述和图像的向量。

src/index.ts
async function addProductWithEmbeddings(_products) {
  const nodeRedisClient = getNodeRedisClient();

  if (_products && _products.length) {
    for (let product of _products) {
      console.log(
        `generating description embeddings for product ${product._id}`,
      );
      const sentenceEmbedding = await generateSentenceEmbeddings(
        product.productDescription,
      );
      product['productDescriptionEmbeddings'] = sentenceEmbedding;

      console.log(`generating image embeddings for product ${product._id}`);
      const imageEmbedding = await generateImageEmbeddings(product.imageURL);
      product['productImageEmbeddings'] = imageEmbedding;

      await nodeRedisClient.json.set(`products:${product._id}`, '$', {
        ...product,
      });
      console.log(`product ${product._id} added to redis`);
    }
  }
}

您可以在 RedisInsight 中查看产品 JSON 数据

提示

下载 RedisInsight 以便直观地探索您的 Redis 数据或在工作台中使用原始 Redis 命令。

创建向量索引#

为了在 Redis 中对 JSON 字段进行搜索,必须对它们进行索引。以下方法突出了对不同类型字段进行索引的过程。这包括向量字段,例如 productDescriptionEmbeddingsproductImageEmbeddings

src/redis-index.ts
import {
  createClient,
  SchemaFieldTypes,
  VectorAlgorithms,
  RediSearchSchema,
} from 'redis';

const PRODUCTS_KEY_PREFIX = 'products';
const PRODUCTS_INDEX_KEY = 'idx:products';
const REDIS_URI = 'redis://localhost:6379';
let nodeRedisClient = null;

const getNodeRedisClient = async () => {
  if (!nodeRedisClient) {
    nodeRedisClient = createClient({ url: REDIS_URI });
    await nodeRedisClient.connect();
  }
  return nodeRedisClient;
};

const createRedisIndex = async () => {
  /*    (RAW COMMAND)
          FT.CREATE idx:products
          ON JSON
              PREFIX 1 "products:"
          SCHEMA
          "$.productDisplayName" as productDisplayName TEXT NOSTEM SORTABLE
          "$.brandName" as brandName TEXT NOSTEM SORTABLE
          "$.price" as price NUMERIC SORTABLE
          "$.masterCategory" as "masterCategory" TAG
          "$.subCategory" as subCategory TAG
          "$.productDescriptionEmbeddings" as productDescriptionEmbeddings VECTOR "FLAT" 10
                  "TYPE" FLOAT32
                  "DIM" 768
                  "DISTANCE_METRIC" "L2"
                  "INITIAL_CAP" 111
                  "BLOCK_SIZE"  111
          "$.productDescription" as productDescription TEXT NOSTEM SORTABLE
          "$.imageURL" as imageURL TEXT NOSTEM
          "$.productImageEmbeddings" as productImageEmbeddings VECTOR "HNSW" 8
                          "TYPE" FLOAT32
                          "DIM" 1024
                          "DISTANCE_METRIC" "COSINE"
                          "INITIAL_CAP" 111

      */
  const nodeRedisClient = await getNodeRedisClient();

  const schema: RediSearchSchema = {
    '$.productDisplayName': {
      type: SchemaFieldTypes.TEXT,
      NOSTEM: true,
      SORTABLE: true,
      AS: 'productDisplayName',
    },
    '$.brandName': {
      type: SchemaFieldTypes.TEXT,
      NOSTEM: true,
      SORTABLE: true,
      AS: 'brandName',
    },
    '$.price': {
      type: SchemaFieldTypes.NUMERIC,
      SORTABLE: true,
      AS: 'price',
    },
    '$.masterCategory': {
      type: SchemaFieldTypes.TAG,
      AS: 'masterCategory',
    },
    '$.subCategory': {
      type: SchemaFieldTypes.TAG,
      AS: 'subCategory',
    },
    '$.productDescriptionEmbeddings': {
      type: SchemaFieldTypes.VECTOR,
      TYPE: 'FLOAT32',
      ALGORITHM: VectorAlgorithms.FLAT,
      DIM: 768,
      DISTANCE_METRIC: 'L2',
      INITIAL_CAP: 111,
      BLOCK_SIZE: 111,
      AS: 'productDescriptionEmbeddings',
    },
    '$.productDescription': {
      type: SchemaFieldTypes.TEXT,
      NOSTEM: true,
      SORTABLE: true,
      AS: 'productDescription',
    },
    '$.imageURL': {
      type: SchemaFieldTypes.TEXT,
      NOSTEM: true,
      AS: 'imageURL',
    },
    '$.productImageEmbeddings': {
      type: SchemaFieldTypes.VECTOR,
      TYPE: 'FLOAT32',
      ALGORITHM: VectorAlgorithms.HNSW, //Hierarchical Navigable Small World graphs
      DIM: 1024,
      DISTANCE_METRIC: 'COSINE',
      INITIAL_CAP: 111,
      AS: 'productImageEmbeddings',
    },
  };
  console.log(`index ${PRODUCTS_INDEX_KEY} created`);

  try {
    await nodeRedisClient.ft.dropIndex(PRODUCTS_INDEX_KEY);
  } catch (indexErr) {
    console.error(indexErr);
  }
  await nodeRedisClient.ft.create(PRODUCTS_INDEX_KEY, schema, {
    ON: 'JSON',
    PREFIX: PRODUCTS_KEY_PREFIX,
  });
};
FLAT 与 HNSW 索引

FLAT:当向量以“FLAT”结构索引时,它们以原始形式存储,不添加任何层次结构。针对 FLAT 索引的搜索需要算法对每个向量进行线性扫描,以找到最相似的匹配项。虽然这很准确,但计算量大且速度较慢,因此适用于较小的数据集。

HNSW (Hierarchical Navigable Small World):HNSW 是一种以图为中心的方法,专为高维数据索引而量身定制。对于较大的数据集,对索引中的每个向量进行线性比较会非常耗时。HNSW 采用概率方法,确保更快的搜索结果,但会牺牲一点准确性。

INITIAL_CAP 和 BLOCK_SIZE 参数

INITIAL_CAP 定义了向量索引的初始容量。它有助于预分配索引空间。

BLOCK_SIZE 定义了向量索引每个块的大小。随着更多向量的添加,Redis 将按块分配内存,每个块的大小等于 BLOCK_SIZE。这有助于优化索引增长期间的内存分配。

什么是向量 KNN 查询?#

KNN,即 k-近邻算法,用于分类和回归任务,但当我们提到“KNN 搜索”时,通常是指在数据集中找到与给定查询点最近(最相似)的“k”个点。在向量搜索的上下文中,这意味着识别数据库中与给定查询向量最相似的“k”个向量,通常基于余弦相似度或欧几里得距离等距离度量。

使用 Redis 进行向量 KNN 查询#

Redis 允许您对向量进行索引,然后 使用 KNN 方法 进行搜索。

下面是一个 Node.js 代码片段,展示了如何为任何提供的 搜索文本 执行 KNN 查询

src/knn-query.ts

请查找 Redis 中的 KNN 查询输出 (输出中较低的分数或距离表示更高的相似度。)
const float32Buffer = (arr) => {
  const floatArray = new Float32Array(arr);
  const float32Buffer = Buffer.from(floatArray.buffer);
  return float32Buffer;
};
const queryProductDescriptionEmbeddingsByKNN = async (
  _searchTxt,
  _resultCount,
) => {
  //A KNN query will give us the top n documents that best match the query vector.

  /*  sample raw query

        FT.SEARCH idx:products
        "*=>[KNN 5 @productDescriptionEmbeddings $searchBlob AS score]"
        RETURN 4 score brandName productDisplayName imageURL
        SORTBY score
        PARAMS 2 searchBlob "6\xf7\..."
        DIALECT 2

    */
  //https://redis.ac.cn/docs/interact/search-and-query/query/

  console.log(`queryProductDescriptionEmbeddingsByKNN started`);
  let results = {};
  if (_searchTxt) {
    _resultCount = _resultCount ?? 5;

    const nodeRedisClient = getNodeRedisClient();
    const searchTxtVectorArr = await generateSentenceEmbeddings(_searchTxt);

    const searchQuery = `*=>[KNN ${_resultCount} @productDescriptionEmbeddings $searchBlob AS score]`;

    results = await nodeRedisClient.ft.search(PRODUCTS_INDEX_KEY, searchQuery, {
      PARAMS: {
        searchBlob: float32Buffer(searchTxtVectorArr),
      },
      RETURN: ['score', 'brandName', 'productDisplayName', 'imageURL'],
      SORTBY: {
        BY: 'score',
        // DIRECTION: "DESC"
      },
      DIALECT: 2,
    });
  } else {
    throw 'Search text cannot be empty';
  }

  return results;
};

注意

示例输出
const result = await queryProductDescriptionEmbeddingsByKNN(
  'Puma watch with cat', //search text
  3, //max number of results expected
);
console.log(JSON.stringify(result, null, 4));

/*
{
    "total": 3,
    "documents": [
        {
            "id": "products:1",
            "value": {
                "score": "0.762174725533",
                "brandName": "Puma",
                "productDisplayName": "Puma Men Race Black Watch",
                "imageURL": "images/11002.jpg"
            }
        },
        {
            "id": "products:2",
            "value": {
                "score": "0.825711071491",
                "brandName": "Puma",
                "productDisplayName": "Puma Men Top Fluctuation Red Black Watches",
                "imageURL": "images/11001.jpg"
            }
        },
        {
            "id": "products:3",
            "value": {
                "score": "1.79949247837",
                "brandName": "Inkfruit",
                "productDisplayName": "Inkfruit Women Behind Cream Tshirts",
                "imageURL": "images/11008.jpg"
            }
        }
    ]
}
*/
KNN 查询可以与标准 Redis 搜索功能结合使用,通过 混合查询 实现

什么是向量范围查询?#

范围查询检索落在指定值范围内的数据。对于向量而言,“范围查询”通常指检索与目标向量在一定距离内的所有向量。此处的“范围”是向量空间中的半径。

使用 Redis 进行向量范围查询#

下面是一个 Node.js 代码片段,展示了如何执行向量 范围查询,针对任何提供的范围(半径/距离)

src/range-query.ts

请查找 Redis 中的范围查询输出
const queryProductDescriptionEmbeddingsByRange = async (_searchTxt, _range) => {
  /*  sample raw query

       FT.SEARCH idx:products
       "@productDescriptionEmbeddings:[VECTOR_RANGE $searchRange $searchBlob]=>{$YIELD_DISTANCE_AS: score}"
       RETURN 4 score brandName productDisplayName imageURL
       SORTBY score
       PARAMS 4 searchRange 0.685 searchBlob "A=\xe1\xbb\x8a\xad\x...."
       DIALECT 2
       */

  console.log(`queryProductDescriptionEmbeddingsByRange started`);
  let results = {};
  if (_searchTxt) {
    _range = _range ?? 1.0;

    const nodeRedisClient = getNodeRedisClient();

    const searchTxtVectorArr = await generateSentenceEmbeddings(_searchTxt);

    const searchQuery =
      '@productDescriptionEmbeddings:[VECTOR_RANGE $searchRange $searchBlob]=>{$YIELD_DISTANCE_AS: score}';

    results = await nodeRedisClient.ft.search(PRODUCTS_INDEX_KEY, searchQuery, {
      PARAMS: {
        searchBlob: float32Buffer(searchTxtVectorArr),
        searchRange: _range,
      },
      RETURN: ['score', 'brandName', 'productDisplayName', 'imageURL'],
      SORTBY: {
        BY: 'score',
        // DIRECTION: "DESC"
      },
      DIALECT: 2,
    });
  } else {
    throw 'Search text cannot be empty';
  }

  return results;
};

图像与文本向量查询#

示例输出
const result2 = await queryProductDescriptionEmbeddingsByRange(
  'Puma watch with cat', //search text
  1.0, //with in range or distance
);
console.log(JSON.stringify(result2, null, 4));
/*
{
    "total": 2,
    "documents": [
        {
            "id": "products:1",
            "value": {
                "score": "0.762174725533",
                "brandName": "Puma",
                "productDisplayName": "Puma Men Race Black Watch",
                "imageURL": "images/11002.jpg"
            }
        },
        {
            "id": "products:2",
            "value": {
                "score": "0.825711071491",
                "brandName": "Puma",
                "productDisplayName": "Puma Men Top Fluctuation Red Black Watches",
                "imageURL": "images/11001.jpg"
            }
        }
    ]
}
*/

图像向量 vs 文本向量查询#

图像向量 vs 文本向量查询

向量 KNN/范围查询的语法是一致的,无论您是处理图像向量还是文本向量。就像有一个用于文本向量查询的方法名为 queryProductDescriptionEmbeddingsByKNN 一样,代码库中也有一个相应的用于图像的方法名为 queryProductImageEmbeddingsByKNN

GITHUB 代码

下面是用于克隆本教程中使用到的源代码的命令

git clone https://github.com/redis-developer/redis-vector-nodejs-solutions.git

希望本教程能帮助您了解如何使用 Redis 进行向量相似性搜索。

(可选)如果您还想了解向量相似性搜索背后的数学原理,请阅读以下内容

如何计算向量相似性?#

有几种技术可用于评估向量相似性,其中一些最常见的是

欧几里得距离 (L2 范数)#

欧几里得距离 (L2 范数) 计算多维空间中两点之间的线性距离。值越低表示距离越近,相似性越高。

为了说明目的,让我们评估来自早期电商数据集的 product 1product 2,并考虑所有特征来确定它们之间的 Euclidean Distance

举例来说,我们将使用通过 chart.js 制作的二维图表,比较我们产品的 Price vs. Quality 特征,仅关注这两个属性来计算 Euclidean Distance

余弦相似性#

余弦相似性 衡量两个向量之间夹角的余弦值。余弦相似性值介于 -1 和 1 之间。值越接近 1 意味着夹角越小,相似性越高;值越接近 -1 意味着夹角越大,相似性越低。余弦相似性在处理文本向量时尤其流行于自然语言处理(NLP)领域。

KNN 查询可以与标准 Redis 搜索功能结合使用,通过 混合查询 实现

如果两个向量指向同一方向,它们之间夹角的余弦值为 1。如果它们正交,余弦值为 0;如果它们指向相反方向,余弦值为 -1。

同样,考虑来自之前数据集的 product 1product 2,并计算所有特征的 Cosine Distance

我们使用 chart.js 制作了一个二维图表,展示了 Price vs. Quality 特征。它仅基于这些属性可视化了 Cosine Similarity

内积#

内积 (点积) 内积(或点积)在传统意义上并非距离度量,但可用于计算相似性,特别是在向量归一化(模长为 1)时。它是两个数列对应项乘积的总和。

KNN 查询可以与标准 Redis 搜索功能结合使用,通过 混合查询 实现

内积可以看作是衡量两个向量在给定向量空间中“对齐”程度的度量。值越高表示相似性越高。然而,对于长向量而言,原始值可能较大;因此,建议进行归一化以获得更好的解释。如果向量已归一化,它们的点积在两者相同时将为 1 if they are identical,在两者正交(不相关)时将为 0 if they are orthogonal

考虑我们的 product 1product 2,让我们计算所有特征的 Inner Product

提示

向量也可以以 二进制格式 存储在数据库中以节省空间。在实际应用中,在向量的维度(这会影响存储和计算成本)与它们捕获的信息的质量或粒度之间取得平衡至关重要。

延伸阅读#