全文檢索

全文檢索是一種在文字資料集中擷取包含特定詞彙或短語的文件,然後根據相關性對結果進行排序的功能。此功能克服了語意搜尋可能會忽略精確詞彙的限制,確保您收到最精確且與上下文最相關的結果。此外,它還可以透過接受原始文字輸入來簡化向量搜尋,自動將您的文字資料轉換為稀疏嵌入,而不需要手動產生向量嵌入。

使用 BM25 演算法進行相關性評分,此功能在檢索擴充生成 (RAG) 的情境中特別有價值,它會優先處理與特定搜尋詞彙密切相符的文件。

透過整合全文檢索與以語意為基礎的密集向量檢索,您可以提高檢索結果的準確性與相關性。如需詳細資訊,請參閱混合搜尋

BM25 實作

Milvus 提供以 BM25 相關性演算法為動力的全文檢索,BM25 是資訊檢索系統中廣泛採用的計分功能,Milvus 將其整合到檢索工作流程中,以提供精確、相關性排序的文字結果。

Milvus 的全文檢索遵循以下工作流程:

  1. 原始文字輸入:您插入文字文件或使用純文字提供查詢,不需要嵌入模型。

  2. 文字分析:Milvus 使用分析器將您的文字處理成有意義的詞彙,以便進行索引和搜尋。

  3. BM25 函式處理:內建的函數可將這些詞彙轉換成 BM25 計分最佳化的稀疏向量表示。

  4. 集合儲存:Milvus 將產生的稀疏嵌入資料儲存在資料集中,以便進行快速檢索和排序。

  5. BM25 相關性評分:在搜尋時,Milvus 應用 BM25 計分功能來計算文件相關性,並傳回最符合查詢字詞的排序結果。

Full Text Search 全文檢索

要使用全文檢索,請遵循以下主要步驟:

  1. 建立一個集合:設定所需欄位,並定義將原始文字轉換為稀疏嵌入的 BM25 函式。

  2. 插入資料:將原始文字文件輸入到資料集中。

  3. 執行搜尋:使用自然語言查詢文字來擷取根據 BM25 相關性排序的結果。

若要啟用 BM25 驅動的全文檢索,您必須準備一個包含所需欄位的資料集、定義 BM25 函式以產生稀疏向量、配置索引,然後再建立資料集。

定義模式欄位

您的資料集模式必須包含至少三個必填欄位:

  • 主要欄位:唯一識別集合中的每個實體。

  • 文字欄位(VARCHAR):儲存原始文字文件。必須設定enable_analyzer=True ,以便 Milvus 能夠為 BM25 相關性排序處理文字。預設情況下,Milvus 使用 standard 分析器進行文字分析。若要設定不同的分析器,請參閱分析器總覽

  • 稀疏向量場(SPARSE_FLOAT_VECTOR):儲存由 BM25 函式自動產生的稀疏嵌入。

from pymilvus import MilvusClient, DataType, Function, FunctionType

client = MilvusClient(
    uri="http://localhost:19530",
    token="root:Milvus"
)

schema = client.create_schema()

schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True) # Primary field
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=1000, enable_analyzer=True) # Text field
schema.add_field(field_name="sparse", datatype=DataType.SPARSE_FLOAT_VECTOR) # Sparse vector field; no dim required for sparse vectors
import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq.CollectionSchema schema = CreateCollectionReq.CollectionSchema.builder()
        .build();
schema.addField(AddFieldReq.builder()
        .fieldName("id")
        .dataType(DataType.Int64)
        .isPrimaryKey(true)
        .autoID(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(1000)
        .enableAnalyzer(true)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("sparse")
        .dataType(DataType.SparseFloatVector)
        .build());
import (
    "context"
    "fmt"

    "github.com/milvus-io/milvus/client/v2/column"
    "github.com/milvus-io/milvus/client/v2/entity"
    "github.com/milvus-io/milvus/client/v2/index"
    "github.com/milvus-io/milvus/client/v2/milvusclient"
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

milvusAddr := "localhost:19530"
client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
})
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
defer client.Close(ctx)

schema := entity.NewSchema()
schema.WithField(entity.NewField().
    WithName("id").
    WithDataType(entity.FieldTypeInt64).
    WithIsPrimaryKey(true).
    WithIsAutoID(true),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithMaxLength(1000),
).WithField(entity.NewField().
    WithName("sparse").
    WithDataType(entity.FieldTypeSparseVector),
)
import { MilvusClient, DataType } from "@zilliz/milvus2-sdk-node";

const address = "http://localhost:19530";
const token = "root:Milvus";
const client = new MilvusClient({address, token});
const schema = [
  {
    name: "id",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "text",
    data_type: "VarChar",
    enable_analyzer: true,
    enable_match: true,
    max_length: 1000,
  },
  {
    name: "sparse",
    data_type: DataType.SparseFloatVector,
  },
];

console.log(res.results)
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true
                }
            },
            {
                "fieldName": "sparse",
                "dataType": "SparseFloatVector"
            }
        ]
    }'

在前面的配置中、

  • id:作為主索引鍵,由auto_id=True 自動產生。

  • text: 儲存原始文字資料,用於全文檢索作業。資料類型必須是VARCHAR ,因為VARCHAR 是 Milvus 用來儲存文字的字串資料類型。

  • sparse:一個向量欄位,保留用來儲存內部產生的稀疏嵌入,以進行全文本搜尋作業。資料類型必須是SPARSE_FLOAT_VECTOR

定義 BM25 函式

BM25 函數會將標記化的文字轉換成支援 BM25 計分的稀疏向量。

定義函數並將其加入您的模式:

bm25_function = Function(
    name="text_bm25_emb", # Function name
    input_field_names=["text"], # Name of the VARCHAR field containing raw text data
    output_field_names=["sparse"], # Name of the SPARSE_FLOAT_VECTOR field reserved to store generated embeddings
    function_type=FunctionType.BM25, # Set to `BM25`
)

schema.add_function(bm25_function)
import io.milvus.common.clientenum.FunctionType;
import io.milvus.v2.service.collection.request.CreateCollectionReq.Function;

import java.util.*;

schema.addFunction(Function.builder()
        .functionType(FunctionType.BM25)
        .name("text_bm25_emb")
        .inputFieldNames(Collections.singletonList("text"))
        .outputFieldNames(Collections.singletonList("sparse"))
        .build());
function := entity.NewFunction().
    WithName("text_bm25_emb").
    WithInputFields("text").
    WithOutputFields("sparse").
    WithType(entity.FunctionTypeBM25)
schema.WithFunction(function)
const functions = [
    {
      name: 'text_bm25_emb',
      description: 'bm25 function',
      type: FunctionType.BM25,
      input_field_names: ['text'],
      output_field_names: ['sparse'],
      params: {},
    },
];
export schema='{
        "autoId": true,
        "enabledDynamicField": false,
        "fields": [
            {
                "fieldName": "id",
                "dataType": "Int64",
                "isPrimary": true
            },
            {
                "fieldName": "text",
                "dataType": "VarChar",
                "elementTypeParams": {
                    "max_length": 1000,
                    "enable_analyzer": true
                }
            },
            {
                "fieldName": "sparse",
                "dataType": "SparseFloatVector"
            }
        ],
        "functions": [
            {
                "name": "text_bm25_emb",
                "type": "BM25",
                "inputFieldNames": ["text"],
                "outputFieldNames": ["sparse"],
                "params": {}
            }
        ]
    }'

參數

說明

name

函數名稱。這個函式會將text 欄位的原始文字轉換成 BM25 相容的稀疏向量,並儲存在sparse 欄位。

input_field_names

需要將文字轉換為稀疏向量的VARCHAR 欄位名稱。對於FunctionType.BM25 ,此參數只接受一個欄位名稱。

output_field_names

儲存內部產生的稀疏向量的欄位名稱。對於FunctionType.BM25 ,此參數只接受一個欄位名稱。

function_type

要使用的函數類型。必須是FunctionType.BM25

如果多個VARCHAR 欄位需要 BM25 處理,請為每個欄位定義一個 BM25 函式,每個函式都有唯一的名稱和輸出欄位。

設定索引

定義包含必要欄位和內建函式的模式後,為您的集合設定索引。

index_params = client.prepare_index_params()

index_params.add_index(
    field_name="sparse",

    index_type="SPARSE_INVERTED_INDEX",
    metric_type="BM25",
    params={
        "inverted_index_algo": "DAAT_MAXSCORE",
        "bm25_k1": 1.2,
        "bm25_b": 0.75
    }

)
import io.milvus.v2.common.IndexParam;

Map<String,Object> params = new HashMap<>();
params.put("inverted_index_algo", "DAAT_MAXSCORE");
params.put("bm25_k1", 1.2);
params.put("bm25_b", 0.75);

List<IndexParam> indexes = new ArrayList<>();
indexes.add(IndexParam.builder()
        .fieldName("sparse")
        .indexType(IndexParam.IndexType.AUTOINDEX)
        .metricType(IndexParam.MetricType.BM25)
        .extraParams(params)
        .build());    
indexOption := milvusclient.NewCreateIndexOption("my_collection", "sparse",
    index.NewAutoIndex(entity.MetricType(entity.BM25)))
    .WithExtraParam("inverted_index_algo", "DAAT_MAXSCORE")
    .WithExtraParam("bm25_k1", 1.2)
    .WithExtraParam("bm25_b", 0.75)
const index_params = [
  {
    field_name: "sparse",
    metric_type: "BM25",
    index_type: "SPARSE_INVERTED_INDEX",
    params: {
        "inverted_index_algo": "DAAT_MAXSCORE",
        "bm25_k1": 1.2,
        "bm25_b": 0.75
    }
  },
];
export indexParams='[
        {
            "fieldName": "sparse",
            "metricType": "BM25",
            "indexType": "AUTOINDEX",
            "params":{
               "inverted_index_algo": "DAAT_MAXSCORE",
               "bm25_k1": 1.2,
               "bm25_b": 0.75
            }
        }
    ]'

參數

說明

field_name

要建立索引的向量欄位名稱。對於全文檢索,這應該是儲存所產生的稀疏向量的欄位。在本範例中,設定值為sparse

index_type

要建立的索引類型。AUTOINDEX 允許 Milvus 自動最佳化索引設定。如果您需要對索引設定有更多控制,您可以從 Milvus 中各種可用於稀疏向量的索引類型中選擇。如需詳細資訊,請參閱Milvus 支援的索引

metric_type

此參數的值必須設定為BM25 ,專門用於全文檢索功能。

params

特定於索引的附加參數字典。

params.inverted_index_algo

用於建立和查詢索引的演算法。有效值:

  • "DAAT_MAXSCORE" (預設):使用 MaxScore 演算法的最佳化 Document-at-a-Time (DAAT) 查詢處理。MaxScore 透過跳過可能影響最小的詞彙和文件,為高k值或包含許多詞彙的查詢提供更好的效能。為了達到這個目的,MaxScore 會根據最大影響分數,將詞彙分為必要和非必要兩組,並將重點放在對 top-k 結果有貢獻的詞彙上。

  • "DAAT_WAND":使用 WAND 演算法優化 DAAT 查詢處理。WAND 利用最大影響分數跳過非競爭性文件,評估較少的命中文件,但每次命中的開銷較高。這使得 WAND 對於k值較小的查詢或較短的查詢更有效率,在這些情況下跳過是較可行的。

  • "TAAT_NAIVE":Basic Term-at-a-Time (TAAT) 查詢處理。雖然與DAAT_MAXSCOREDAAT_WAND 相比較慢,但TAAT_NAIVE 提供了獨特的優勢。DAAT 演算法使用快取的最大影響分數,不論全域集合參數 (avgdl) 如何改變,這些分數都會保持靜態,而TAAT_NAIVE 則不同,它會動態適應這些變化。

params.bm25_k1

控制詞彙頻率飽和。較高的值會增加詞彙頻率在文件排名中的重要性。值範圍:[1.2, 2.0].

params.bm25_b

控制文件長度規範化的程度。通常使用介於 0 和 1 之間的值,一般預設值約為 0.75。值為 1 表示不進行長度規範化,而值為 0 則表示完全規範化。

建立集合

現在使用已定義的模式和索引參數建立集合。

client.create_collection(
    collection_name='my_collection', 
    schema=schema, 
    index_params=index_params
)
import io.milvus.v2.service.collection.request.CreateCollectionReq;

CreateCollectionReq requestCreate = CreateCollectionReq.builder()
        .collectionName("my_collection")
        .collectionSchema(schema)
        .indexParams(indexes)
        .build();
client.createCollection(requestCreate);
err = client.CreateCollection(ctx,
    milvusclient.NewCreateCollectionOption("my_collection", schema).
        WithIndexOptions(indexOption))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
await client.create_collection(
    collection_name: 'my_collection', 
    schema: schema, 
    index_params: index_params,
    functions: functions
);
export CLUSTER_ENDPOINT="http://localhost:19530"
export TOKEN="root:Milvus"

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
    \"collectionName\": \"my_collection\",
    \"schema\": $schema,
    \"indexParams\": $indexParams
}"

插入文字資料

設定好集合和索引後,您就可以插入文字資料了。在這個過程中,您只需要提供原始文字。我們之前定義的內建函式會自動為每個文字項目產生相對應的稀疏向量。

client.insert('my_collection', [
    {'text': 'information retrieval is a field of study.'},
    {'text': 'information retrieval focuses on finding relevant information in large datasets.'},
    {'text': 'data mining and information retrieval overlap in research.'},
])
import com.google.gson.Gson;
import com.google.gson.JsonObject;

import io.milvus.v2.service.vector.request.InsertReq;

Gson gson = new Gson();
List<JsonObject> rows = Arrays.asList(
        gson.fromJson("{\"text\": \"information retrieval is a field of study.\"}", JsonObject.class),
        gson.fromJson("{\"text\": \"information retrieval focuses on finding relevant information in large datasets.\"}", JsonObject.class),
        gson.fromJson("{\"text\": \"data mining and information retrieval overlap in research.\"}", JsonObject.class)
);

client.insert(InsertReq.builder()
        .collectionName("my_collection")
        .data(rows)
        .build());
// go
await client.insert({
collection_name: 'my_collection', 
data: [
    {'text': 'information retrieval is a field of study.'},
    {'text': 'information retrieval focuses on finding relevant information in large datasets.'},
    {'text': 'data mining and information retrieval overlap in research.'},
]);
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
    "data": [
        {"text": "information retrieval is a field of study."},
        {"text": "information retrieval focuses on finding relevant information in large datasets."},
        {"text": "data mining and information retrieval overlap in research."}       
    ],
    "collectionName": "my_collection"
}'

將資料插入資料庫後,您就可以使用原始文字查詢來執行全文檢索。Milvus 會自動將您的查詢轉換成稀疏向量,並使用 BM25 演算法將配對的搜尋結果排序,然後傳回 topK (limit) 結果。

您可以透過設定文字高亮器來高亮搜尋結果中的匹配詞彙。詳情請參閱文字高亮程式

res = client.search(
    collection_name='my_collection', 
    data=['whats the focus of information retrieval?'],
    anns_field='sparse',
    output_fields=['text'], # Fields to return in search results; sparse field cannot be output
    limit=3,
)

print(res)
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.response.SearchResp;

Map<String,Object> searchParams = new HashMap<>();

SearchResp searchResp = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(new EmbeddedText("whats the focus of information retrieval?")))
        .annsField("sparse")
        .topK(3)
        .searchParams(searchParams)
        .outputFields(Collections.singletonList("text"))
        .build());
annSearchParams := index.NewCustomAnnParam()
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection", // collectionName
    3,               // limit
    []entity.Vector{entity.Text("whats the focus of information retrieval?")},
).WithConsistencyLevel(entity.ClStrong).
    WithANNSField("sparse").
    WithAnnParam(annSearchParams).
    WithOutputFields("text"))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("text: ", resultSet.GetColumn("text").FieldData().GetScalars())
}
await client.search(
    collection_name: 'my_collection', 
    data: ['whats the focus of information retrieval?'],
    anns_field: 'sparse',
    output_fields: ['text'],
    limit: 3,
)
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--data-raw '{
    "collectionName": "my_collection",
    "data": [
        "whats the focus of information retrieval?"
    ],
    "annsField": "sparse",
    "limit": 3,
    "outputFields": [
        "text"
    ],
    "searchParams":{
        "params":{}
    }
}'

參數

說明

search_params

包含搜尋參數的字典。

params.drop_ratio_search

搜尋時要忽略的低重要性字詞比例。詳情請參閱Sparse Vector

data

自然語言的原始查詢文字。Milvus 使用 BM25 函式自動將您的文字查詢轉換成稀疏向量 - 請勿提供預先計算的向量。

anns_field

包含內部產生的稀疏向量的欄位名稱。

output_fields

搜尋結果中要回傳的欄位名稱清單。支援所有欄位,除了包含 BM25 產生的嵌入的稀疏向量欄位。常見的輸出欄位包括 primary key 欄位 (例如id) 和原始文字欄位 (例如text)。如需詳細資訊,請參閱常見問題

limit

返回頂部匹配的最大數目。

常見問題

不可以,BM25 函式產生的稀疏向量無法直接在全文檢索中存取或輸出。以下是詳細資訊:

  • BM25 函式會在內部產生稀疏向量,用於排序和檢索

  • 這些向量會儲存在稀疏字段中,但無法包含在output_fields

  • 您只能輸出原始文字欄位和元資料 (如id,text)

範例:

# ❌ This throws an error - you cannot output the sparse field
client.search(
    collection_name='my_collection', 
    data=['query text'],
    anns_field='sparse',
    output_fields=['text', 'sparse']  # 'sparse' causes an error
    limit=3,
    search_params=search_params
)

# ✅ This works - output text fields only
client.search(
    collection_name='my_collection', 
    data=['query text'],
    anns_field='sparse',
    output_fields=['text']
    limit=3,
    search_params=search_params
)

如果我不能存取稀疏向量欄位,為什麼我需要定義它?

稀疏向量欄位作為內部搜尋索引,類似於使用者不會直接互動的資料庫索引。

設計原理

  • 關注點的分離:您處理文字 (輸入/輸出),Milvus 處理向量 (內部處理)

  • 效能:預先計算的稀疏向量可在查詢時快速進行 BM25 排序

  • 使用者體驗:在簡單的文字介面後,抽離複雜的向量操作

如果您需要向量存取

  • 使用手動稀疏向量操作取代全文檢索

  • 為自訂稀疏向量工作流程建立獨立的集合

如需詳細資訊,請參閱Sparse Vector