• 關於 Milvus
  • 開始使用
  • 概念
  • 使用者指南
    • 收藏集
    • 模式與資料欄位
    • 插入與刪除
    • 索引
    • 搜尋
    • 功能與模型推論
    • 儲存優化
    • 快照
  • 資料匯入
  • AI 工具
  • 管理指南
  • 工具
  • 整合
  • 教學
  • 常見問題
  • API Reference

BM25 功能

BM25 函式可將原始文字轉換為稀疏向量,並根據詞彙相關性對文件進行評分,從而實現全文本搜尋。它應用了基於詞彙的匹配和頻率感知權重,以支援與查詢詞彙密切匹配的文本文檔的高效檢索。

作為一個本機文字函式,BM25 函式在 Milvus 內執行,不需要模型推論或外部整合。它為基於文字的搜尋情況提供了確定且透明的檢索機制。

BM25 如何運作

BM25演算法是一種基於詞彙的相關性評分演算法,廣泛應用於全文檢索。在 Milvus 中,BM25 實作為稀疏檢索管道,可將文字轉換為詞彙權重表示,並使用分散式稀疏索引檢索前K 個文件。

整體工作流程包含兩個對稱的路徑:文件擷取查詢文字處理,它們共用相同的文字分析邏輯。

文件擷取:從文字到稀疏表示

插入文件時,其原始文字會先由分析器處理,分析器會將文字標記化為個別術語。

例如,文件:

"We are loving Milvus!"

可分析為下列詞彙:

["we", "love", "milvus"]

然後,每個文件會以詞彙頻率 (TF) 表示,記錄每個詞彙在文件中出現的次數。舉例來說

{
  "we": 1,
  "love": 1,
  "milvus": 1
}

同時,Milvus 會更新語料庫層級的統計資料,包括

  • 每個詞彙的文檔頻率 (DF)

  • 平均文件長度

  • 將每個詞彙映射到包含該詞彙的文檔的發佈清單

將文件的 TF 表示法插入稀疏嵌入(sparse embeddings),其中的詞彙發佈會在節點間進行分割,以便進行可擴展性檢索。

查詢文字處理:套用 IDF 加權

當發出基於文字的查詢時,會由在文件擷取過程中使用的相同分析器來處理,以確保術語分割的一致性。

例如,查詢

"who loves Milvus?"

可以被分析成

["who", "love", "milvus"]

對於每個查詢詞彙,Milvus 會從語料庫統計中查詢其反向文檔頻率(IDF)。IDF 反映了一個詞彙在整個資料集中的資訊量:較罕見的詞彙會得到較高的權重,而常見的詞彙會得到較低的權重。

在概念上,這會產生一組 IDF 加權的查詢詞,例如:

{
  "who": 0.1,
  "love": 0.5,
  "milvus": 1.2
}

BM25 評分與前 K 檢索

BM25 根據匹配的查詢字詞計算相關性分數,從而對文件進行排序。計分在詞彙層級執行,並在文件層級彙總。

詞彙層級計分

對於出現在文件中的每個查詢詞彙,BM25 會計算詞彙層級的得分:

term_score =
  IDF(term) ×
  TF_boost(term, document, k1) ×
  length_normalization(document, b)

其中:

  • IDF(term)反映該詞彙在集合中的罕見程度

  • TF_boost(...,k1)隨著詞彙頻率的增加而增加,但隨著頻率的增加而飽和

  • length_normalization(..., b)根據文件長度調整得分

文件層級計分和 Top-K 檢索

最終的文件分數是所有匹配查詢字詞的字詞層級分數的總和:

document_score =
  sum of term_score over all matched query terms

文檔會依最終得分排序,並傳送得分最高的 Top-K 文檔。

開始之前

使用 BM25 功能之前,請規劃您的資料庫模式,以確保它支援詞彙全文檢索:

  • 原始內容的文字欄位

    您的資料集中必須包含一個VARCHAR 欄位來儲存原始文字。這個欄位是將被處理以進行全文檢索的文字來源。

  • 文字欄位的分析器

    文字欄位必須啟用分析器。分析器定義了在 BM25 函式計算詞彙相關性之前,如何將文字標記化和規範化。

    預設情況下,Milvus 提供內建分析器,根據空白和標點符號化文字。如果您的應用程式需要自訂標記化或規範化行為,您可以定義一個自訂分析器。詳情請參閱為您的使用個案選擇正確的分析器

  • BM25 輸出的稀疏向量

    您的集合必須包含SPARSE_FLOAT_VECTOR 欄位,以儲存 BM25 函式所產生的稀疏表示。這個欄位用於全文檢索時的索引和檢索。

想好這些模式層級的注意事項後,請繼續建立資料集並使用 BM25 函式。

步驟 1:使用 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 := "http://localhost:19530"
token := "root:Milvus"

client, err := milvusclient.New(ctx, &milvusclient.ClientConfig{
    Address: milvusAddr,
    APIKey: token
})
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"
            }
        ]
    }'

定義 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": {}
            }
        ]
    }'

設定索引

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

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
            }
        }
    ]'

建立集合

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

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" \
--header "Request-Timeout: 10" \
-d "{
    \"collectionName\": \"my_collection\",
    \"schema\": $schema,
    \"indexParams\": $indexParams
}"

一旦建立了具有 BM25 功能的集合,您就可以插入文字,並根據文字查詢執行詞彙搜尋。

步驟 2:將文字資料插入資料集中

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

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" \
--header "Request-Timeout: 10" \
-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"
}'

步驟 3:使用文字查詢進行搜尋

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

search_params = {

}

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,
    search_params=search_params
)

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" \
--header "Request-Timeout: 10" \
--data-raw '{
    "collectionName": "my_collection",
    "data": [
        "whats the focus of information retrieval?"
    ],
    "annsField": "sparse",
    "limit": 3,
    "outputFields": [
        "text"
    ],
    "searchParams":{
        "params":{}
    }
}'