日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 综合教程 >内容正文

综合教程

利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题

發(fā)布時間:2023/10/11 综合教程 94 老码农
生活随笔 收集整理的這篇文章主要介紹了 利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在自然語言處理領域,有一個常見且重要的任務就是文本相似度搜索。文本相似度搜索是指根據用戶輸入的一段文本,從數據庫中找出與之最相似或最相關的一段或多段文本。它可以應用在很多場景中,例如問答系統(tǒng)、推薦系統(tǒng)、搜索引擎等。

比如,當用戶在知乎上提出一個問題時,系統(tǒng)就可以從知乎上已有的回答中找出與該問題最匹配或最有價值的回答,并展示給用戶。

要實現類似高效的搜索,我們需要使用一些特殊的數據結構和算法。其中,向量相似度搜索是一種在大規(guī)模數據搜索中表現優(yōu)秀的算法。而Redis作為一種高性能的鍵值數據庫,也可以幫助我們實現向量相似度搜索。今天我們就來體驗一把,還請幫忙點個關注

猿惑豁

在開始學習如何使用Redis實現向量相似度搜索之前,需要了解向量及向量相似度搜索的基本知識和原理,以便更好地理解后面的內容。

什么是向量?

向量是數學、物理學和工程科學等多個自然科學中的基本概念,它是一個具有方向和長度的量,用于描述問題,如空間幾何、力學、信號處理等。在計算機科學中,向量被用于表示數據,如文本、圖像或音頻。此外,向量還代表AI模型對文本、圖像、音頻、視頻等非結構化數據的印象。

向量相似度搜索的基本原理

向量相似度搜索的基本原理是通過將數據集中的每個元素映射為向量,并使用特定相似度計算算法,如基于余弦相似度的、基于歐氏相似度或基于Jaccard相似度等算法,找到與查詢向量最相似的向量。

Redis實現向量相似度搜索

了解原理后,我們開始來實現如何使用Redis實現向量相似度搜索。Redis允許我們在FT.SEARCH命令中使用向量相似度查詢。使我們可以加載、索引和查詢作為Redis哈希或JSON文檔中字段存儲的向量。

//相關文檔地址

https://redis.io/docs/interact/search-and-query/search/vectors

1、Redis Search安裝

關于Redis Search的安裝和使用,此處不再贅述,如果您對此不熟悉,可以參考上一篇文章:

C#+Redis Search:如何用Redis實現高性能全文搜索

2、創(chuàng)建向量索引庫

這里我們使用NRedisStack和StackExchange.Redis兩個庫來與Redis進行交互操作。

//創(chuàng)建一個Redis連接
static ConnectionMultiplexer mux = ConnectionMultiplexer.Connect("localhost");
//獲取一個Redis數據庫
static IDatabase db = mux.GetDatabase();
//創(chuàng)建一個RediSearch客戶端
static SearchCommands ft = new SearchCommands(db, null);

在進行向量搜索之前,首先需要定義并創(chuàng)建索引,并指定相似性算法。

public static async Task CreateIndexAsync()
{
await ft.CreateAsync(indexName,
new FTCreateParams()
.On(IndexDataType.HASH)
.Prefix(prefix),
new Schema()
.AddTagField("tag")
.AddTextField("content")
.AddVectorField("vector",
VectorField.VectorAlgo.HNSW,
new Dictionary<string, object>()
{
["TYPE"] = "FLOAT32",
["DIM"] = 2,
["DISTANCE_METRIC"] = "COSINE"
}));
}

這段代碼的意思是:

  • 使用了一個異步方法 ft.CreateAsync 來創(chuàng)建索引。它接受三個參數:索引名稱 indexName,一個 FTCreateParams 對象和一個 Schema 對象;
  • FTCreateParams 類提供了一些參數選項,用于指定索引的參數。這里使用 .On(IndexDataType.HASH)  方法來指定索引數據類型為哈希,并使用 .Prefix(prefix)  方法來指定索引數據的前綴;
  • Schema 類用于定義索引中的字段和字段類型。這里定義了一個標簽字段(tag field)用于區(qū)分過慮數據。定義了一個文本字段(text field)用于存儲原始數據,以及一個向量字段(vector field)用于存儲經原始數據轉化后的向量數據;
  • 使用了 VectorField.VectorAlgo.HNSW 來指定向量算法為 HNSW(Hierarchical Navigable Small World)。還傳遞了一個字典對象,用于設置向量字段的參數。其中,鍵為字符串類型,值為對象類型。

目前Redis支持兩種相似度算法:

HNSW分層導航小世界算法,使用小世界網絡構建索引,具有快速查詢速度和小內存占用,時間復雜度為O(logn),適用于大規(guī)模索引。

FLAT暴力算法,它對所有的鍵值對進行掃描,然后根據鍵值對的距離計算出最短路徑,時間復雜度為O(n),其中n是鍵值對的數量。這種算法時間復雜度非常高,只適用于小規(guī)模的索引。

3、添加向量到索引庫

索引創(chuàng)建后,我們將數據添加到索引中。

public async Task SetAsync(string docId, string prefix, string tag, string content, float[] vector)
{
await db.HashSetAsync($"{prefix}{docId}", new HashEntry[] {
new HashEntry ("tag", tag),
new HashEntry ("content", content),
new HashEntry ("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
});
}

SetAsync方法用于將一個具有指定文檔ID、前綴、標簽、內容及內容的向量存儲到索引庫中。并使用SelectMany()方法和BitConverter.GetBytes()方法將向量轉換為一個字節(jié)數組。

4、向量搜索

Redis 支持兩種類型的向量查詢:KNN查詢和Range查詢,也可以將兩種查詢混合使用。

KNN 查詢

KNN 查詢用于在給定查詢向量的情況下查找前 N 個最相似的向量。

public async IAsyncEnumerable<(string Content, double Score)> SearchAsync(float[] vector, int limit)
{
var query = new Query($"*=>[KNN {limit} @vector $vector AS score]")
.AddParam("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("score")
.ReturnFields("content", "score")
.Limit(0, limit)
.Dialect(2); var result = await ft.SearchAsync(indexName, query).ConfigureAwait(false);
foreach (var document in result.Documents)
{
yield return (document["content"],Convert.ToDouble(document["score"]));
}
}

這段代碼的意思是:

  • 創(chuàng)建一個查詢對象 query,并設置查詢條件。查詢條件包括:
    1. "*=>[KNN {limit} @vector $vector AS score]":使用KNN算法進行向量相似度搜索,限制結果數量為limit,使用給定的向量vector作為查詢向量,將查詢結果按照相似度得分進行排序;
    2. AddParam("vector", vector.SelectMany(BitConverter.GetBytes).ToArray()):將浮點數數組轉換為字節(jié)數組,并將其作為查詢參數傳遞給查詢;
    3. SetSortBy("score"):按照相似度得分對結果進行排序;
    4. ReturnFields("content", "score"):將content和score兩個字段從結果集中返回;
    5. Limit(0, limit):限制結果集的起始位置為0,結果數量為limit;
    6. Dialect(2):設置查詢方言為2,即Redis默認的查詢語言Redis Protocol;
  • 調用異步搜索方法 ft.SearchAsync(indexName, query),并等待搜索結果;
  • 遍歷搜索結果集 result.Documents,將每個文檔轉換為 (string Content, double Score) 元組,并通過 yield 語句進行迭代返回。

Range 查詢:

Range查詢提供了一種根據 Redis 中的向量字段與基于某些預定義閾值(半徑)的查詢向量之間的距離來過濾結果的方法。類似于 NUMERIC 和 GEO 子句,可以在查詢中多次出現,特別是可以和 KNN 進行混合搜索。

public static async IAsyncEnumerable<(string Tag, string Content, double Score)> SearchAsync(string tag, float[] vector, int limit)
{
var query = new Query($"(@tag:{tag})=>[KNN {limit} @vector $vector AS score]")
.AddParam("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("score")
.ReturnFields("tag", "content", "score")
.Limit(0, limit)
.Dialect(2); var result = await ft.SearchAsync(indexName, query).ConfigureAwait(false);
foreach (var document in result.Documents)
{
yield return (document["tag"], document["content"], Convert.ToDouble(document["score"]));
}
}

這段代碼使用了KNN和Range混合查詢,與上一段代碼相比,新增了@tag參數,將限制結果僅包含給定標簽的內容。這樣做可以增加查詢的準確性,提高查詢效率。

5、從索引庫中刪除向量

public async Task DeleteAsync(string docId, string prefix)
{
await db.KeyDeleteAsync($"{prefix}{docId}");
}

這個方法通過刪除與指定向量相關聯的哈希緩存鍵,來實現從索引庫中刪除指定向量數據。

6、刪除向量索引庫

public async Task DropIndexAsync()
{
await ft.DropIndexAsync(indexName, true);
}

這個方法 await ft.DropIndexAsync接受兩個參數: indexName 和 true 。indexName 表示索引庫的名稱, true 表示在刪除索引時是否刪除索引文件。

7、查詢索引庫信息

public async Task<InfoResult> InfoAsync()
{
return await ft.InfoAsync(indexName);
}

通過 await ft.InfoAsync(indexName) 方法,我們可以獲取到指定索引庫的大小,文檔數量等相關索引庫信息。

完整 Demo 如下:

using NRedisStack;
using NRedisStack.Search;
using NRedisStack.Search.DataTypes;
using NRedisStack.Search.Literals.Enums;
using StackExchange.Redis;
using static NRedisStack.Search.Schema; namespace RedisVectorExample
{
class Program
{
//創(chuàng)建一個Redis連接
static ConnectionMultiplexer mux = ConnectionMultiplexer.Connect("localhost");
//獲取一個Redis數據庫
static IDatabase db = mux.GetDatabase();
//創(chuàng)建一個RediSearch客戶端
static SearchCommands ft = new SearchCommands(db, null);
//索引名稱
static string indexName = "test:index";
//索引前綴
static string prefix = "test:data";
static async Task Main(string[] args)
{
//創(chuàng)建一個向量的索引
await CreateIndexAsync(); //添加一些向量到索引中
await SetAsync("1", "A", "測試數據A1", new float[] { 0.1f, 0.2f });
await SetAsync("2", "A", "測試數據A2", new float[] { 0.3f, 0.4f });
await SetAsync("3", "B", "測試數據B1", new float[] { 0.5f, 0.6f });
await SetAsync("4", "C", "測試數據C1", new float[] { 0.7f, 0.8f }); //刪除一個向量
await DeleteAsync("4"); //KUN搜索
await foreach (var (Content, Score) in SearchAsync(new float[] { 0.1f, 0.2f }, 2))
{
Console.WriteLine($"內容:{Content},相似度得分:{Score}");
} //混合
await foreach (var (Tag, Content, Score) in SearchAsync("A", new float[] { 0.1f, 0.2f }, 2))
{
Console.WriteLine($"標簽:{Tag},內容:{Content},相似度得分:{Score}");
} //檢查索引是否存在
var info = await InfoAsync();
if (info != null)
await DropIndexAsync(); //存在則刪除索引
} public static async Task CreateIndexAsync()
{
await ft.CreateAsync(indexName,
new FTCreateParams()
.On(IndexDataType.HASH)
.Prefix(prefix),
new Schema()
.AddTagField("tag")
.AddTextField("content")
.AddVectorField("vector",
VectorField.VectorAlgo.HNSW,
new Dictionary<string, object>()
{
["TYPE"] = "FLOAT32",
["DIM"] = 2,
["DISTANCE_METRIC"] = "COSINE"
}));
} public static async Task SetAsync(string docId, string tag, string content, float[] vector)
{
await db.HashSetAsync($"{prefix}{docId}", new HashEntry[] {
new HashEntry ("tag", tag),
new HashEntry ("content", content),
new HashEntry ("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
});
} public static async Task DeleteAsync(string docId)
{
await db.KeyDeleteAsync($"{prefix}{docId}");
} public static async Task DropIndexAsync()
{
await ft.DropIndexAsync(indexName, true);
} public static async Task<InfoResult> InfoAsync()
{
return await ft.InfoAsync(indexName);
} public static async IAsyncEnumerable<(string Content, double Score)> SearchAsync(float[] vector, int limit)
{
var query = new Query($"*=>[KNN {limit} @vector $vector AS score]")
.AddParam("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("score")
.ReturnFields("content", "score")
.Limit(0, limit)
.Dialect(2); var result = await ft.SearchAsync(indexName, query).ConfigureAwait(false);
foreach (var document in result.Documents)
{
yield return (document["content"], Convert.ToDouble(document["score"]));
}
} public static async IAsyncEnumerable<(string Tag, string Content, double Score)> SearchAsync(string tag, float[] vector, int limit)
{
var query = new Query($"(@tag:{tag})=>[KNN {limit} @vector $vector AS score]")
.AddParam("vector", vector.SelectMany(BitConverter.GetBytes).ToArray())
.SetSortBy("score")
.ReturnFields("tag", "content", "score")
.Limit(0, limit)
.Dialect(2); var result = await ft.SearchAsync(indexName, query).ConfigureAwait(false);
foreach (var document in result.Documents)
{
yield return (document["tag"], document["content"], Convert.ToDouble(document["score"]));
}
}
}
}

篇幅原因先到這里,下一篇我們接著探討如何利用ChatGPT Embeddings技術提取文本向量,并基于Redis實現文本相似度匹配。相比傳統(tǒng)方法,這種方式能夠更好地保留文本的語義和情感信息,從而更準確地反映文本的實質性內容。

感謝閱讀,點贊+分享+收藏+關注
文章出自猿惑豁微信公眾號

總結

以上是生活随笔為你收集整理的利用Redis实现向量相似度搜索:解决文本、图像和音频之间的相似度匹配问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。