《筆記》LLM Zoomcamp 2025 第二堂課(上)

第二堂課的影片和 Jupyter Notebook(含上下兩部分)都出來了。

  • Course module:
  • Notebook:
  • LLM course:

▌2-1 Qdrant 向量搜尋介紹

關鍵字搜尋:文字相似性
向量搜尋:語意相似性(除文字外,還可搜尋圖片、影片、聲音),以及更多(推薦文章連結?)
如果有任何問題,可以到 Qdrant 開源社群 Discord 中詢問。

實作:

準備
docker pull
Web UI

step 0. Qdrant client & Fastembed

Qdrant 客戶端:以 Python 客戶端為例
Fastembed 套件:數據向量化的套件,使其能夠進行向量搜尋(方便數據向量化並上傳到 Qdrant)

python -m install -q “qdrant-client[fastembed]>=1.14.2”

▌2-2 語意搜尋實作與嵌入模型選擇

step 1

client = QdrantClient(“http://localhost:6333”)

如何在專門的向量搜尋解決方案中組織這些數據:
什麼進入嵌入(針對什麼進行語意搜尋),什麼進入元數據(用於更嚴格的過濾條件)

關鍵步驟:選擇正確的嵌入模型

我們以課程的 QA 為例
這個數據集已經過整理。清理過和分塊(所以分成小塊,這些小塊對向量化數據的嵌入模型來說更容易消化)。

在語意搜尋中使用什麼是有意義的?
在過濾條件中使用什麼作為元數據保存是有意義的?

像課程或部分這樣的東西,在過濾設置中更有意義,例如我們想詢問關於特定部分或特定課程的具體問題,我們可以更多地保存為元數據。

step 2

https://github.com/alexeygrigorev/llg-rag-workshop/raw/main/notebooks/documents.json

只有幾個問題和答案時,比較向量是很容易的,
像示範的課程 QA 中,大約有一千個,但當它擴展到數十億時,這種比較變得更加繁重
量大的時候,要有專門的解決方案,這就是它們通常如何處理存儲和處理數據的方式。

步驟一:將所有答案嵌入神經網絡模型,將它們轉換為向量。(離線完成)

選擇一個模型,然後嵌入和索引我們的數據(課程問題的答案以及它們的元數據,例如課程名稱,稍後可以用於過濾)。

步驟二:將問題嵌入在相同模型的向量空間,轉換為向量。然後在向量索引中和所有答案進行比較,回覆就是與問題語意最相似的答案。

對問題進行語意搜尋,我們將找到語意相似的答案。

在我們的案例中,是一般問答性質的簡短英文文本,找到合適的嵌入模型比較簡單。
但對於特定領域,最好找到合適的嵌入模型。

我們以 FastEmbed 做示範,因為它與 Qdrant 庫整合得很好,不必煩惱如何將數據集轉換為所需的格式。
而且 FastEmbed 相當輕量,比 PyTorch sentence transformers 執行得更快。

FastEmbed 也支援不同類型的向量,不同類型的嵌入,例如混合搜尋、多向量、圖像嵌入和其他類型的模型。

FastEmbed 支援密集文本嵌入,我們可以在這裡看到支援的模型列表以及它們的描述。

在這裡你可以看到模型名稱、來源(它們都在 Hugging Face 上)、它們的大小,對於向量搜尋解決方案的選擇和配置非常重要的是它們輸出的嵌入維度、輸出向量的維度。

》FastEmbed 支援模型

%load_ext autoreload
%autoreload 2
The autoreload extension is already loaded. To reload it, use:
  %reload_ext autoreload
import pandas as pd

from fastembed import (
    SparseTextEmbedding,
    TextEmbedding,
    LateInteractionTextEmbedding,
    ImageEmbedding,
)
from fastembed.rerank.cross_encoder import TextCrossEncoder

》1. Supported Text Embedding Models

supported_models = (
    pd.DataFrame(TextEmbedding.list_supported_models())
    .sort_values("size_in_GB")
    .drop(columns=["sources", "model_file", "additional_files"])
    .reset_index(drop=True)
)
supported_models
model dim description license size_in_GB
0 BAAI/bge-small-en-v1.5 384 Text embeddings, Unimodal (text), English, 512… mit 0.067
1 BAAI/bge-small-zh-v1.5 512 Text embeddings, Unimodal (text), Chinese, 512… mit 0.090
2 snowflake/snowflake-arctic-embed-xs 384 Text embeddings, Unimodal (text), English, 512… apache-2.0 0.090
3 sentence-transformers/all-MiniLM-L6-v2 384 Text embeddings, Unimodal (text), English, 256… apache-2.0 0.090
4 jinaai/jina-embeddings-v2-small-en 512 Text embeddings, Unimodal (text), English, 819… apache-2.0 0.120
5 BAAI/bge-small-en 384 Text embeddings, Unimodal (text), English, 512… mit 0.130
6 snowflake/snowflake-arctic-embed-s 384 Text embeddings, Unimodal (text), English, 512… apache-2.0 0.130
7 nomic-ai/nomic-embed-text-v1.5-Q 768 Text embeddings, Multimodal (text, image), Eng… apache-2.0 0.130
8 BAAI/bge-base-en-v1.5 768 Text embeddings, Unimodal (text), English, 512… mit 0.210
9 sentence-transformers/paraphrase-multilingual-… 384 Text embeddings, Unimodal (text), Multilingual… apache-2.0 0.220
10 Qdrant/clip-ViT-B-32-text 512 Text embeddings, Multimodal (text&image), Engl… mit 0.250
11 jinaai/jina-embeddings-v2-base-de 768 Text embeddings, Unimodal (text), Multilingual… apache-2.0 0.320
12 BAAI/bge-base-en 768 Text embeddings, Unimodal (text), English, 512… mit 0.420
13 snowflake/snowflake-arctic-embed-m 768 Text embeddings, Unimodal (text), English, 512… apache-2.0 0.430
14 nomic-ai/nomic-embed-text-v1.5 768 Text embeddings, Multimodal (text, image), Eng… apache-2.0 0.520
15 jinaai/jina-embeddings-v2-base-en 768 Text embeddings, Unimodal (text), English, 819… apache-2.0 0.520
16 nomic-ai/nomic-embed-text-v1 768 Text embeddings, Multimodal (text, image), Eng… apache-2.0 0.520
17 snowflake/snowflake-arctic-embed-m-long 768 Text embeddings, Unimodal (text), English, 204… apache-2.0 0.540
18 mixedbread-ai/mxbai-embed-large-v1 1024 Text embeddings, Unimodal (text), English, 512… apache-2.0 0.640
19 jinaai/jina-embeddings-v2-base-code 768 Text embeddings, Unimodal (text), Multilingual… apache-2.0 0.640
20 sentence-transformers/paraphrase-multilingual-… 768 Text embeddings, Unimodal (text), Multilingual… apache-2.0 1.000
21 snowflake/snowflake-arctic-embed-l 1024 Text embeddings, Unimodal (text), English, 512… apache-2.0 1.020
22 thenlper/gte-large 1024 Text embeddings, Unimodal (text), English, 512… mit 1.200
23 BAAI/bge-large-en-v1.5 1024 Text embeddings, Unimodal (text), English, 512… mit 1.200
24 intfloat/multilingual-e5-large 1024 Text embeddings, Unimodal (text), Multilingual… mit 2.240
點擊觀看其他四種支援模型

》2. Supported Sparse Text Embedding Models

(
    pd.DataFrame(SparseTextEmbedding.list_supported_models())
    .sort_values("size_in_GB")
    .drop(columns=["sources", "model_file", "additional_files"])
    .reset_index(drop=True)
)
model vocab_size description license size_in_GB requires_idf
0 Qdrant/bm25 NaN BM25 as sparse embeddings meant to be used wit… apache-2.0 0.010 True
1 Qdrant/bm42-all-minilm-l6-v2-attentions 30522.0 Light sparse embedding model, which assigns an… apache-2.0 0.090 True
2 prithivida/Splade_PP_en_v1 30522.0 Independent Implementation of SPLADE++ Model f… apache-2.0 0.532 NaN
3 prithvida/Splade_PP_en_v1 30522.0 Independent Implementation of SPLADE++ Model f… apache-2.0 0.532 NaN

》3. Supported Late Interaction Text Embedding Models

(
    pd.DataFrame(LateInteractionTextEmbedding.list_supported_models())
    .sort_values("size_in_GB")
    .drop(columns=["sources", "model_file"])
    .reset_index(drop=True)
)
model dim description license size_in_GB additional_files
0 answerdotai/answerai-colbert-small-v1 96 Text embeddings, Unimodal (text), Multilingual… apache-2.0 0.13 NaN
1 colbert-ir/colbertv2.0 128 Late interaction model mit 0.44 NaN
2 jinaai/jina-colbert-v2 128 New model that expands capabilities of colbert… cc-by-nc-4.0 2.24 [onnx/model.onnx_data]

》4. Supported Image Embedding Models

(
    pd.DataFrame(ImageEmbedding.list_supported_models())
    .sort_values("size_in_GB")
    .drop(columns=["sources", "model_file"])
    .reset_index(drop=True)
)
model dim description license size_in_GB
0 Qdrant/resnet50-onnx 2048 Image embeddings, Unimodal (image), 2016 year apache-2.0 0.10
1 Qdrant/clip-ViT-B-32-vision 512 Image embeddings, Multimodal (text&image), 202… mit 0.34
2 Qdrant/Unicom-ViT-B-32 512 Image embeddings, Multimodal (text&image), 202… apache-2.0 0.48
3 Qdrant/Unicom-ViT-B-16 768 Image embeddings (more detailed than Unicom-Vi… apache-2.0 0.82

》5. Supported Rerank Cross Encoder Models

(
    pd.DataFrame(TextCrossEncoder.list_supported_models())
    .sort_values("size_in_GB")
    .drop(columns=["sources", "model_file"])
    .reset_index(drop=True)
)
model size_in_GB description license
0 Xenova/ms-marco-MiniLM-L-6-v2 0.08 MiniLM-L-6-v2 model optimized for re-ranking t… apache-2.0
1 Xenova/ms-marco-MiniLM-L-12-v2 0.12 MiniLM-L-12-v2 model optimized for re-ranking … apache-2.0
2 jinaai/jina-reranker-v1-tiny-en 0.13 Designed for blazing-fast re-ranking with 8K c… apache-2.0
3 jinaai/jina-reranker-v1-turbo-en 0.15 Designed for blazing-fast re-ranking with 8K c… apache-2.0
4 BAAI/bge-reranker-base 1.04 BGE reranker base model for cross-encoder re-r… mit
5 jinaai/jina-reranker-v2-base-multilingual 1.11 A multi-lingual reranker model for cross-encod… cc-by-nc-4.0

向量搜尋解決方案的選擇和配置,非常重要的是它們輸出的嵌入維度、輸出向量的維度。

資源也非常重要。維度越多,你需要的資源就越多,當然表達的意義也會越好。

我們將根據幾個簡單的參數做出選擇:

  1. 模型類型:文本嵌入 Text Embedding Models

  2. 嵌入維度:像範例這樣簡單的例子,使用 512 維的小型或中等大小的嵌入比較合適。

512 維的模型有三個:BAAI/bge-small-zh-v1.5 ║ jinaai/jina-embeddings-v2-small-en ║ Qdrant/clip-ViT-B-32-text

Qdrant/clip-ViT-B-32-text 是多模態的,它與圖像一起工作,因為我們根本不處理圖像,選擇它沒有意義。

BAAI/bge-small-zh-v1.5 這個模型與中文一起工作,但我們處理的是英語。

所以我們選擇 jinaai/jina-embeddings-v2-small-en

你可以查看到模型使用哪些指標,大多數嵌入模型使用餘弦相似性作為相似性指標和嵌入之間的語意距離。

簡單的說,向量差距角度愈小,語意愈接近。

▌2-3 建立 Qdrant 集合與資料索引

接著是建立 Qdrant 集合與資料索引

首先是創建一個 Qdrant 集合

什麼是 Qdrant 集合?

我們在 Qdrant 中操作兩個主要實體:

  1. 點(point):點基本上是一個數據點,

    在我們的情況下,它將是一個答案及其元數據。
    點有一個 ID,它有一個嵌入向量或幾個嵌入向量,在我們的情況下,它將是由 BGE 提供的我們答案的嵌入。

  2. 有效載荷(payload):元數據(可選,非必要)

    在我們的情況下,有效載荷(payload)是課程和部分。

所有數據點形成一個集合,我們可以把集合想像成所有數據點的容器,解決某個特定問題。

我們先設定集合名稱:zoomcamp-rag

  • 嵌入維度:如同前面介紹,我們選擇 512 維。

  • 語意相似性:如同前面介紹,我們使用餘弦的角度來計算語意距離。

創建集合後,將我們的數據點、答案及其元數據插入到集合中。

這裡我們只是創建一個點結構,然後上傳到 Qdrant。

上傳時,會同時發生兩件事:

  1. FastEmbed 將下載模型,並將其保存在本地的臨時文件中(你也可以自行更改路徑)。

  2. 然後它將嵌入我們課程中的每個答案,將它們轉換為向量,一個接一個上傳到 Qdrant 集合中。

Python 客戶端為批量上傳提供了工具,可以平行、重試或惰性批量處理(兩個特定函數:upload_collection、upload_points)。

當所有點都已上傳到數據庫,我們就可以視覺化研究它們,了解語意向量搜尋的含義。

我們有 948 個點,948 個我們嵌入到數據庫並上傳的答案。

例如,一些關於時間段問題的答案,它們會聚集在一起。

你可以看到來自不同課程的不同點如何在語意上相似,這幫助你研究和理解你的非結構化數據中的模式是什麼。

講師鼓勵同學們大量使用儀表板,非常有助於理解我們的數據中發生了什麼。

Step 3

介紹 fastembed
TextEmbedding.list_supported_models()

for model in TextEmbedding.list_supported_models()

jina-embeddings-v2-small-en: 33 million parameters.
jina-embeddings-v2-base-en: 137 million parameters (you are here).
jina-embeddings-v2-base-zh: Chinese-English Bilingual embeddings.
jina-embeddings-v2-base-de: German-English Bilingual embeddings.
jina-embeddings-v2-base-es: Spanish-English Bilingual embeddings.

▌2-4 執行語意相似性搜尋

現在我們要進行相似性語意搜尋,找到對傳入問題最相關的答案。

搜尋函數接收一個查詢(我們的問題),它用相同的模型嵌入它,借助 FastEmbed,所以是 BGE。

返回值是與問題餘弦相似性最接近的答案。

payload=true 用於顯示元數據,讓我們以人類可讀的方式看到答案。

在使用 Qdrant 進行混合搜尋(下一個講師的影片)之前,我們來進行帶過濾器的相似性搜尋。

使用我們與嵌入一起上傳的元數據課程和部分,展示如何基於不同課程進行過濾,因為我們想問關於特定課程的問題,而不是所有課程。

一般情況下,我們非常關注過濾,所以 Qdrant 有很多不同類型的元數據可以存儲,以及很多不同的複雜過濾條件。

向量搜尋一些新的技術,從 agents 和 RAG 和 MCP 到超越相似性搜尋的東西,例如非結構化數據分析,都可以用向量搜尋專門解決方案來完成。


▌參考資料