学习

使用 NodeJS 在 Redis 中执行向量相似性搜索

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

您将在本教程中学到的内容#

本教程是关于利用 Redis 在 NodeJS 环境中进行向量相似性搜索的综合指南。本教程面向具有 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 允许您对向量进行索引并在本教程中进一步介绍的不同方式执行向量相似性搜索。它还保持着高性能和可扩展性。

什么是向量相似性?#

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

向量相似度的用例

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

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

生成向量#

在我们的场景中,我们的重点是生成句子(产品描述)和图像(产品图像)嵌入或向量。像 GitHub 这样的 AI 模型仓库有很多,在那里预先训练、维护和共享 AI 模型。

对于句子嵌入,我们将使用来自 Hugging Face 模型中心的模型,而对于图像嵌入,我们将使用来自 TensorFlow 集中库 的模型,以提供多样性。

GITHUB 代码

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

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

句子/文本向量#

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

信息

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

transformers.j 库本质上是 Hugging Face 广受欢迎的 Python 库的 JavaScript 版本。

信息

ONNX(开放神经网络交换) 是一种开放标准,定义了表示各种框架(包括 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>',
  },
];

以下是将 products 数据作为 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 中观察 products 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 VS HNSW 索引

FLAT:当向量以“FLAT”结构进行索引时,它们会以原始形式存储,没有任何附加的层次结构。对 FLAT 索引的搜索将要求算法线性扫描每个向量,以找到最相似的匹配项。虽然这很准确,但它计算量大且速度较慢,使其成为较小数据集的理想选择。

HNSW(分层可导航小世界):HNSW 是一种以图形为中心的算法,专为索引高维数据而设计。对于较大的数据集,对索引中每个向量进行线性比较会很耗时。HNSW 采用概率方法,确保更快的搜索结果,但准确性略有下降。

INITIAL_CAP 和 BLOCK_SIZE 参数

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
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;
};

请在 Redis 中找到 KNN 查询的输出 (输出中较低的得分或距离表示更高的相似度。)

示例输出
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
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;
};

请在 Redis 中找到范围查询的输出

示例输出
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"
            }
        }
    ]
}
*/

图像与文本向量查询#

图像与文本向量查询

示例正文向量 KNN/范围查询的语法一致,无论您是使用图像向量还是文本向量。就像存在名为 queryProductDescriptionEmbeddingsByKNN 的文本向量查询方法一样,在代码库中也存在对应于图像的 queryProductImageEmbeddingsByKNN 方法。

GITHUB 代码

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

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

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

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

如何计算向量相似度?#

有多种技术可用于评估向量相似度,其中一些最流行的技术包括

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

欧几里德距离(L2 范数) 计算多维空间中两点之间的线性距离。较低的值表示更接近,因此相似度更高。

为了说明,让我们评估来自早期电子商务数据集的 产品 1 和 产品 2 并确定 欧几里德距离 ,同时考虑所有特征。

例如,我们将使用 chart.js 制作的二维图表来比较我们产品的 价格与质量 特征,只关注这两个属性来计算 欧几里德距离

余弦相似度#

余弦相似度 测量两个向量之间角度的余弦。余弦相似度值介于 -1 和 1 之间。更接近 1 的值表示更小的角度和更高的相似度,而更接近 -1 的值表示更大的角度和更低的相似度。余弦相似度在处理文本向量时,在 NLP 中尤其受欢迎。

注意

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

再次考虑之前数据集中 产品 1产品 2,并计算所有特征的 余弦距离

使用 chart.js,我们制作了一个 价格与质量 特征的二维图表。它仅根据这些属性可视化 余弦相似度

内积#

内积(点积) 内积(或点积)不是传统意义上的距离度量,但可用于计算相似度,尤其是在向量被归一化(具有 1 的大小)时。它是两个数字序列的对应项的乘积的总和。

注意

内积可以被认为是在给定向量空间中两个向量“对齐”程度的度量。较高的值表示较高的相似度。然而,对于较长的向量,原始值可能会很大;因此,建议进行归一化以更好地解释。如果向量被归一化,它们的点积将是 1 如果它们相同0 如果它们是正交的(不相关)。

考虑到我们的 产品 1产品 2,让我们计算所有特征的 内积

提示

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

进一步阅读#