使用Qdrant向量資料庫實作語意相似度比對

什麼是向量資料庫?

在許多的AI實作當中,都有向量資料庫的使用需求。例如RAG(檢索增強生成)、或是資料的相關性比對、相似性搜尋…等,這些應用情境中,我們都會用到向量資料庫。

向量資料庫主要的任務,當然就是存儲和查詢向量數據。被儲存的向量數據通常是高維度的資料,以陣列(集合)的形式呈現。

例如,OpenAI就有提供一組Embedding API,讓我們可以把文字給向量化。你可以透過該 API,把一句話(一段文字)轉換成向量數據,類似底下這樣:
enter image description here

OpenAI 的這組 Embedding API,在預設狀況下,會把資料轉成 1536 維度的向量數據,其背後的行為是把文字送給一個訓練好的模型,透過該模型跑出這組向量數據,再回傳給用戶。

然而,把一段文字給向量化的目的是什麼呢?

當我們把文字做了這種轉換後,在 1536 維度的座標空間(向量數據)中,愈相似的文字,透過Embedding API會得到距離愈接近的座標點,藉此,我們得以迅速的判斷兩段文字在語意上的相似性。

而向量資料庫中所謂的查詢功能,主要就是向量的檢索,像是『近鄰搜索(Nearest Neighbor Search)』,當用戶或應用程式提交一個向量查詢時,向量資料庫會幫我們找出資料庫中,與之最相似(接近)的向量座標點。

因此,開發人員只需要透過 Embedding API,把文字轉成向量,再把轉換好的向量座標值,儲存到向量資料庫之中,未來就可以透過向量資料庫來查詢相似(接近)的文字。

關於這個部分,如果讀者有興趣,前陣子有段網路上的影片把多維度向量資料庫的概念介紹的蠻清楚的:
https://www.youtube.com/watch?v=W_ZUUDJsUtA
https://www.youtube.com/watch?v=ct20Kv8yn0U

為何需要?

向量資料庫最實務上的應用,就是找到類似的特徵值。

例如,當我們把人臉的特徵值以向量資料的形式儲存到向量資料庫中之後,我們就可以透過資料庫本身提供的搜尋功能,快速地尋找出相似的人臉,這也是許多AI應用實現的基礎。

我們底下的程式碼範例,則是找出最接近的問題。(呃…什麼意思?🤔 請往下繼續看)

我們在建立對談機器人時候,常常需要讓機器人回答用戶的問題。這時,我們會讓用戶輸入問句,然後透過 Embedding API 取得該問句的向量值,接著對比既有的向量資料庫中,來自KB知識庫已經向量化好的問句,找出最接近的問題,以便於取得並顯示最適當的答案。

例如,前陣子疫情期間,很多醫療院所被諮詢電話打爆,因此有醫院製作了對談機器人,來協助回答各種常見的問題。要實現這個,只需要把整理好的問題Q&A,先透過Embedding API做向量化,然後把數據存入向量資料庫:
圖片

當用戶透過對談機器人問問題時,即可快速找到最接近(相關)的問題,直接以該問題預設的答案回覆用戶。

這對於已經有QA知識庫的企業來說,可以把既有的知識庫瞬間轉變為客服機器人的QA數據來源,快速地完成客服機器人地建立。

過去,我都是透過 Azure AI 裡面的 Question Answering現有服務來實現這個功能。底下使用 向量資料庫 和 OpenAI 的 Embedding API 來試做 ,發現其實效果也不差耶~

整個流程如下:
圖片

如何運行

Qdrant向量資料庫具有容器化的版本,這意味著你可以直接透過底下的docker指令(前提當然是你的電腦上安裝有 docker engine),就可以從 docker hub 上下載 image 並且運行:

docker run -p 6333:6333 -p 6334:6334 -v ${PWD}/qdrant_storage:/qdrant/storage:z qdrant/qdrant

上面的指令從docker hub下載 qdrant image,同時設定儲存位置參數,並且開啟 6333, 6334 兩個port。

執行後的畫面如下:
enter image description here

這表示qdrant向量資料庫已經在你的環境運行起來了,這時候如果你用 http://localhost:6333/dashboard 這個網址,會看到資料庫後台:
enter image description here
你可以從後台看每一個Collections(類似Database,在非關聯式資料庫上,大多稱為Collection),並且看到裡面建立出的Collection:
圖片

當然,若你一開始整個資料庫是空的,那顯然沒有上面的內容。這時,我們可以透過程式碼來建立資料庫與向量資料紀錄。

撰寫程式碼

當Qdrant向量資料庫被建立起來之後,我們可以透過底下這樣的指令從C#程式碼連接到該資料庫:

//連線到 Qdrant DB
var  c=  "CDC_Caccine_QA";
var  qdrantClient  =  new  QdrantClient("localhost", 6334);
var  isExist  =  await  qdrantClient.CollectionExistsAsync(Qdrant_collection_name)

你看到上面程式碼會發現,我們可以透過 CollectionExistsAsync(…) 方法來判斷該 collection(database) 是否存在,如果不存在,可以透過底下程式碼來建立:

// 建立一個新的集合
await qdrantClient.CreateCollectionAsync(
    collectionName: Qdrant_collection_name,
    vectorsConfig: new VectorParams { Size = 1536, Distance = Distance.Cosine}
);

請特別注意上面的參數,為了配合OpenAI的Embedding API,我們的 Size 會用 1536,距離判斷方式則採用 Cosine (餘弦相似度) 。

建立好 Collection 之後,我們就可以透過OpenAI的Embedding API 把資料庫中的每個問題轉成向量值(底下 GetEmbedding() 函式就是在做這個部分):

// 取得嵌入向量
static List<float> GetEmbeddings(OpenAIClient client, string utterance)
{
    List<float> embeddingVector = new List<float>();
    // 調用API並獲取嵌入向量
    EmbeddingsOptions embeddingOptions = new EmbeddingsOptions()
    {
        DeploymentName = "text-embedding-3-small",
        Input = { utterance },
    };
    // 調用API並獲取嵌入向量
    var returnValue = client.GetEmbeddingsAsync(embeddingOptions);
    // 創建一個新的列表來存儲嵌入向量
    foreach (var item in returnValue.Result.Value.Data[0].Embedding.ToArray())
    {
        embeddingVector.Add(item);
    }
    return embeddingVector;
}

取得向量值之後,經過整理,就可以透過底下程式碼將其 儲存/更新 到資料庫中:

foreach (var item in Questions)
{
    // 將每一個問題取得向量值後,insert到points集合
    points.Add(
         new()
         {
             Id = (ulong)i++, // 選擇一個唯一ID
             Payload = { ["type"] = "CDC", ["utterance"] = item }, // 其他metadata
             // 取得問題的嵌入向量
             Vectors = GetEmbeddings(openAIClient, item).ToArray()  // 確保向量是以 List<float> 的形式
         }
        );
}
// 將points中的問題集 Insert 到 Qdrant DB
await qdrantClient.UpsertAsync(collectionName: Qdrant_collection_name, points);

經過上面這樣,我們把每一個已知的問題向量化儲存到資料庫後,當用戶透過 Chat Bot 輸入任何問題,系統的邏輯就是,將用戶輸入的問題,轉成向量資料,送給 Qdrant 資料庫來查詢,取得最接近的問題:

//輸入問題   
Console.Write("\n\n請輸入與施打疫苗有關的問題('q' 離開):");
question = Console.ReadLine();
if (string.IsNullOrEmpty(question) || question == "q" || question == "") return;
// 搜索最相關的問題
var searchResult = await qdrantClient.SearchAsync(
    collectionName: Qdrant_collection_name,
    vector: GetEmbeddings(openAIClient, question).ToArray(),
    limit: 3,
    payloadSelector: true
);

Console.WriteLine("\n\n 列出最相關的問題:");
foreach (var item in searchResult)
{
    Console.WriteLine($"Score:{item.Score} utterance:{item.Payload["utterance"].StringValue}");
}

再進一步找到該問題相關的答案,呈現給用戶即可。

完整的範例請參考 github:
https://github.com/isdaviddong/test_Qdrant.git

透過上面這樣的範例,我們就可以讓用戶以自然語言隨意輸入問題,Chat Bot透過向量資料庫,找到知識庫中最相似(接近)的問題,然後把該問題準備好的答案回覆給用戶,如此一來,我們就可以快速地完成一個QA或客服機器人囉…
圖片

範例位於:
https://github.com/isdaviddong/test_Qdrant.git

範例中的資料位於:
接種期程與注意事項. - 衛生福利部疾病管制署 (cdc.gov.tw)

留言

這個網誌中的熱門文章

在POC或迷你專案中使用 LiteDB

專業的價值...

讓 LINE Bot 對談機器人顯示 "Loading..." 動畫

周末讀書會 - 一如既往