• نبذة عن ميلفوس
  • ابدأ الآن
  • المفاهيم
  • دليل المستخدم
  • استيراد البيانات
  • أدوات الذكاء الاصطناعي
  • دليل الإدارة
  • الأدوات
  • عمليات الدمج
  • البرامج التعليمية
  • الأسئلة الشائعة
  • API Reference

دالة BM25

تتيح الدالة BM25 إمكانية البحث في النص الكامل عن طريق تحويل النص الخام إلى متجهات متفرقة وتسجيل المستندات بناءً على الصلة المعجمية. وهي تطبق المطابقة القائمة على المصطلحات والترجيح المدرك للتردد لدعم الاسترجاع الفعال للمستندات النصية التي تتطابق بشكل وثيق مع مصطلحات الاستعلام.

وباعتبارها دالة نصية محلية، تعمل دالة BM25 داخل Milvus ولا تتطلب استدلالًا نموذجيًا أو عمليات تكامل خارجية. وهي توفر آلية استرجاع حتمية وشفافة لسيناريوهات البحث المستندة إلى النص.

كيف تعمل BM25

خوارزمية BM25 هي خوارزمية لتسجيل الملاءمة القائمة على المصطلحات والمستخدمة على نطاق واسع في استرجاع النص الكامل. في Milvus، يتم تنفيذ BM25 في Milvus كخط أنابيب استرجاع متناثر يحول النص إلى تمثيلات مرجحة للمصطلح ويسترجع أفضل K من المستندات باستخدام فهارس متفرقة موزعة.

يتألف سير العمل الكلي من مسارين متماثلين: استيعاب المستند ومعالجة نص الاستعلام، وهما يشتركان في نفس منطق تحليل النص.

استيعاب المستند: من النص إلى التمثيل المتناثر

عندما يتم إدراج مستند، تتم معالجة نصه الخام أولاً بواسطة محلل، والذي يقوم بترميز النص إلى مصطلحات فردية.

على سبيل المثال، المستند:

"We are loving Milvus!"

يمكن تحليلها إلى المصطلحات التالية:

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

ثم يتم تمثيل كل مستند بعد ذلك كتمثيل لتكرار المصطلح (TF)، والذي يسجل عدد مرات ظهور كل مصطلح في المستند. على سبيل المثال:

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

في الوقت نفسه، يقوم برنامج Milvus بتحديث الإحصائيات على مستوى مجموعة المستندات، بما في ذلك:

  • تكرار المستند (DF) لكل مصطلح

  • متوسط طول المستند

  • قوائم الترحيل التي تربط كل مصطلح بالمستندات التي تحتوي عليه

يتم إدراج تمثيل TF الخاص بالوثيقة في التضمينات المتناثرة، حيث يتم تقسيم نشرات المصطلحات عبر العقد لاسترجاعها بشكل قابل للتطوير.

عملية نص الاستعلام: تطبيق ترجيح IDF

عند إصدار استعلام يستند إلى نص، تتم معالجته بواسطة نفس المحلل المستخدم أثناء استيعاب المستند، مما يضمن تجزئة المصطلحات بشكل متسق.

على سبيل المثال، الاستعلام

"who loves Milvus?"

يمكن تحليله إلى

["who", "love", "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(مصطلح) مدى ندرة المصطلح في المجموعة

  • يزيدTF_boost(...، k1) مع تكرار المصطلح ولكنه يتشبع مع زيادة التكرار

  • الطول_التطبيع(...، ب) يعدل الدرجة بناءً على طول المستند

تسجيل النقاط على مستوى المستند واسترجاع أعلى K

درجة المستند النهائية هي مجموع الدرجات على مستوى المصطلح لجميع مصطلحات الاستعلام المتطابقة:

document_score =
  sum of term_score over all matched query terms

يتم ترتيب المستندات حسب درجاتها النهائية، ويتم إرجاع أعلى K-المستندات التي حصلت على أعلى الدرجات.

قبل البدء

قبل استخدام الدالة BM25، قم بتخطيط مخطط مجموعتك للتأكد من أنه يدعم البحث المعجمي عن النص الكامل:

  • حقل نصي للمحتوى الخام

    يجب أن تتضمن مجموعتك حقلاً VARCHAR لتخزين النص الخام. هذا الحقل هو مصدر النص الذي ستتم معالجته للبحث عن النص الكامل.

  • محلل لحقل النص

    يجب أن يحتوي حقل النص على محلل ممكّن. يحدد المحلل كيفية ترميز النص وتطبيعه قبل أن يتم حساب الصلة المعجمية بواسطة دالة BM25.

    بشكل افتراضي، يوفر ميلفوس محللًا مدمجًا يقوم بترميز النص استنادًا إلى المسافات البيضاء وعلامات الترقيم. إذا كان تطبيقك يتطلب سلوك ترميز أو تطبيع مخصص، يمكنك تحديد محلل مخصص. راجع اختيار المحلل المناسب لحالة الاستخدام الخاصة بك للحصول على التفاصيل.

  • متجه متناثر لإخراج BM25

    يجب أن تتضمن مجموعتك حقلاً SPARSE_FLOAT_VECTOR لتخزين التمثيلات المتفرقة التي تم إنشاؤها بواسطة دالة BM25. يُستخدم هذا الحقل للفهرسة والاسترجاع أثناء البحث عن النص الكامل.

بعد معرفة هذه الاعتبارات على مستوى المخطط، تابع إنشاء المجموعة واستخدام دالة BM25.

الخطوة 1: إنشاء مجموعة مع دالة BM25

لاستخدام دالة BM25، يجب عليك تعريفها عند إنشاء المجموعة. تصبح الدالة جزءًا من مخطط المجموعة ويتم تطبيقها تلقائيًا أثناء إدراج البيانات والبحث عنها.

تحديد حقول المخطط

يجب أن يتضمن مخطط مجموعتك ثلاثة حقول مطلوبة على الأقل:

  • الحقل الأساسي: يحدد بشكل فريد كل كيان في المجموعة.

  • حقل نصي (VARCHAR): يخزن المستندات النصية الخام. يجب تعيين enable_analyzer=True حتى يتمكن ميلفوس من معالجة النص لترتيب صلة 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، ثم يُرجع أعلىK (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":{}
    }
}'