المتجهات المتفرقة

تُعد المتجهات المتفرقة طريقة مهمة لالتقاط مطابقة المصطلحات على مستوى السطح في استرجاع المعلومات ومعالجة اللغة الطبيعية. في حين أن المتجهات الكثيفة تتفوق في الفهم الدلالي، غالبًا ما توفر المتجهات المتفرقة نتائج مطابقة أكثر قابلية للتنبؤ، خاصة عند البحث عن مصطلحات خاصة أو معرّفات نصية.

نظرة عامة

المتجه المتفرّق هو متجه خاص عالي الأبعاد حيث تكون معظم عناصره صفرية، وبعض أبعاده فقط ذات قيم غير صفرية. كما هو موضح في الرسم البياني أدناه، عادةً ما يتم تمثيل المتجهات الكثيفة كمصفوفات متصلة حيث يكون لكل موضع قيمة (على سبيل المثال، [0.3, 0.8, 0.2, 0.3, 0.1]). في المقابل، تخزن المتجهات المتفرقة العناصر غير الصفرية فقط ومؤشراتها للبعد، وغالبًا ما يتم تمثيلها كأزواج قيمة مفتاح { index: value} (على سبيل المثال، [{2: 0.2}, ..., {9997: 0.5}, {9999: 0.7}]).

Sparse Vector Representation تمثيل المتجهات المتفرقة

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

  • التقنيات الإحصائية التقليدية، مثل TF-IDF (تردد المصطلح-تردد المستند العكسي) و BM25 (أفضل 25 مطابقة)، حيث تقوم بتعيين أوزان للكلمات بناءً على تكرارها وأهميتها عبر مجموعة من المستندات. تقوم هذه الطرق بحساب إحصائيات بسيطة كدرجات لكل بُعد، والتي تمثل رمزًا مميزًا. يوفر Milvus بحثًا مدمجًا في النص الكامل باستخدام طريقة BM25، والتي تقوم تلقائيًا بتحويل النص تلقائيًا إلى متجهات متناثرة، مما يلغي الحاجة إلى المعالجة اليدوية المسبقة. هذا الأسلوب مثالي للبحث القائم على الكلمات الرئيسية، حيث تكون الدقة والمطابقة التامة مهمة. راجع البحث عن النص الكامل لمزيد من المعلومات.

  • نماذج التضمين العصبي المتناثر هي طرق مكتسبة لتوليد تمثيلات متناثرة من خلال التدريب على مجموعات بيانات كبيرة. وهي عادةً ما تكون نماذج تعلُّم عميقة ذات بنية تحويلية، قادرة على توسيع المصطلحات وتقييمها بناءً على السياق الدلالي. يدعم Milvus أيضًا التضمينات المتفرقة التي يتم إنشاؤها خارجيًا من نماذج مثل SPLADE. راجع التضمينات للحصول على التفاصيل.

يمكن تخزين المتجهات المتفرقة والنص الأصلي في ميلفوس لاسترجاعها بكفاءة. يوضّح الرسم البياني أدناه العملية الشاملة.

Sparse Vector Workflow سير عمل المتجهات المتفرقة

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

تنسيقات البيانات

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

يدعم ميلفوس مدخلات المتجهات المتفرقة بالتنسيقات التالية:

  • قائمة القواميس (بتنسيق {dimension_index: value, ...})

    # Represent each sparse vector using a dictionary
    sparse_vectors = [{27: 0.5, 100: 0.3, 5369: 0.6} , {100: 0.1, 3: 0.8}]
    
  • مصفوفة متفرقة (باستخدام فئة scipy.sparse )

    from scipy.sparse import csr_matrix
    
    # First vector: indices [27, 100, 5369] with values [0.5, 0.3, 0.6]
    # Second vector: indices [3, 100] with values [0.8, 0.1]
    indices = [[27, 100, 5369], [3, 100]]
    values = [[0.5, 0.3, 0.6], [0.8, 0.1]]
    sparse_vectors = [csr_matrix((values, ([0]*len(idx), idx)), shape=(1, 5369+1)) for idx, vals in zip(indices, values)]
    
  • قائمة المتجهات المتفرقة (على سبيل المثال [(dimension_index, value)])

    # Represent each sparse vector using a list of iterables (e.g. tuples)
    sparse_vector = [
        [(27, 0.5), (100, 0.3), (5369, 0.6)],
        [(100, 0.1), (3, 0.8)]
        ]
    

تحديد مخطط المجموعة

قبل إنشاء مجموعة، تحتاج إلى تحديد مخطط المجموعة، الذي يحدد الحقول واختياريًا دالة لتحويل حقل نصي إلى تمثيل متجه متناثر مطابق.

إضافة حقول

لاستخدام المتجهات المتفرقة في ميلفوس، تحتاج إلى إنشاء مجموعة بمخطط يتضمن الحقول التالية:

  • حقل SPARSE_FLOAT_VECTOR مخصص لتخزين المتجهات المتناثرة، إما أن يتم إنشاؤه تلقائيًا من حقل VARCHAR أو يتم توفيره مباشرة في بيانات الإدخال.

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

from pymilvus import MilvusClient, DataType

client = MilvusClient(uri="http://localhost:19530")

schema = client.create_schema(
    auto_id=True,
    enable_dynamic_fields=True,
)

schema.add_field(field_name="pk", datatype=DataType.VARCHAR, is_primary=True, max_length=100)
schema.add_field(field_name="sparse_vector", datatype=DataType.SPARSE_FLOAT_VECTOR)
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=65535, enable_analyzer=True)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;

import io.milvus.v2.common.DataType;
import io.milvus.v2.service.collection.request.AddFieldReq;
import io.milvus.v2.service.collection.request.CreateCollectionReq;

MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
        .uri("http://localhost:19530")
        .build());
        
CreateCollectionReq.CollectionSchema schema = client.createSchema();
schema.setEnableDynamicField(true);
schema.addField(AddFieldReq.builder()
        .fieldName("pk")
        .dataType(DataType.VarChar)
        .isPrimaryKey(true)
        .autoID(true)
        .maxLength(100)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("sparse_vector")
        .dataType(DataType.SparseFloatVector)
        .build());
schema.addField(AddFieldReq.builder()
        .fieldName("text")
        .dataType(DataType.VarChar)
        .maxLength(65535)
        .enableAnalyzer(true)
        .build());
import { DataType } from "@zilliz/milvus2-sdk-node";

const schema = [
  {
    name: "metadata",
    data_type: DataType.JSON,
  },
  {
    name: "pk",
    data_type: DataType.Int64,
    is_primary_key: true,
  },
  {
    name: "sparse_vector",
    data_type: DataType.SparseFloatVector,
  },
  {
    name: "text",
    data_type: "VarChar",
    enable_analyzer: true,
    enable_match: true,
    max_length: 65535,
  },
];

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("pk").
    WithDataType(entity.FieldTypeVarChar).
    WithIsAutoID(true).
    WithIsPrimaryKey(true).
    WithMaxLength(100),
).WithField(entity.NewField().
    WithName("sparse_vector").
    WithDataType(entity.FieldTypeSparseVector),
).WithField(entity.NewField().
    WithName("text").
    WithDataType(entity.FieldTypeVarChar).
    WithEnableAnalyzer(true).
    WithMaxLength(65535),
)
export primaryField='{
    "fieldName": "pk",
    "dataType": "VarChar",
    "isPrimary": true,
    "elementTypeParams": {
        "max_length": 100
    }
}'

export vectorField='{
    "fieldName": "sparse_vector",
    "dataType": "SparseFloatVector"
}'

export textField='{
    "fieldName": "text",
    "dataType": "VarChar",
    "elementTypeParams": {
        "max_length": 65535,
        "enable_analyzer": true
    }
}'

export schema="{
    \"autoID\": true,
    \"fields\": [
        $primaryField,
        $vectorField,
        $textField
    ]
}"

في هذا المثال، تتم إضافة ثلاثة حقول:

  • pk: يقوم هذا الحقل بتخزين المفاتيح الأساسية باستخدام نوع البيانات VARCHAR ، والذي يتم إنشاؤه تلقائيًا بطول أقصى 100 بايت.

  • sparse_vector: يخزن هذا الحقل المتجهات المتفرقة باستخدام نوع البيانات SPARSE_FLOAT_VECTOR.

  • text: يقوم هذا الحقل بتخزين السلاسل النصية باستخدام نوع البيانات VARCHAR ، بطول أقصاه 65535 بايت.

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

تعيين معلمات الفهرس

تشبه عملية إنشاء فهرس للمتجهات المتفرقة عملية إنشاء فهرس للمتجهات الكثيفة، ولكن مع وجود اختلافات في نوع الفهرس المحدد (index_type)، ومقياس المسافة (metric_type)، ومعلمات الفهرس (params).

index_params = client.prepare_index_params()

index_params.add_index(
    field_name="sparse_vector",
    index_name="sparse_inverted_index",
    index_type="SPARSE_INVERTED_INDEX",
    metric_type="IP",
    params={"inverted_index_algo": "DAAT_MAXSCORE"}, # or "DAAT_WAND" or "TAAT_NAIVE"
)

import io.milvus.v2.common.IndexParam;
import java.util.*;

List<IndexParam> indexes = new ArrayList<>();

Map<String,Object> extraParams = new HashMap<>();
extraParams.put("inverted_index_algo": "DAAT_MAXSCORE"); // Algorithm used for building and querying the index

indexes.add(IndexParam.builder()
        .fieldName("sparse_vector")
        .indexName("sparse_inverted_index")
        .indexType(IndexParam.IndexType.SPARSE_INVERTED_INDEX)
        .metricType(IndexParam.MetricType.IP)
        .extraParams(extraParams)
        .build());


const indexParams = await client.createIndex({
    field_name: 'sparse_vector',
    metric_type: MetricType.IP,
    index_name: 'sparse_inverted_index',
    index_type: IndexType.SPARSE_INVERTED_INDEX,
    params: {
      inverted_index_algo: 'DAAT_MAXSCORE', 
    },
});

idx := index.NewSparseInvertedIndex(entity.IP, 0.2)
indexOption := milvusclient.NewCreateIndexOption("my_collection", "sparse_vector", idx)

export indexParams='[
        {
            "fieldName": "sparse_vector",
            "metricType": "IP",
            "indexName": "sparse_inverted_index",
            "indexType": "SPARSE_INVERTED_INDEX",
            "params":{"inverted_index_algo": "DAAT_MAXSCORE"}
        }
    ]'

يستخدم هذا المثال نوع الفهرس SPARSE_INVERTED_INDEX مع IP كمقياس. لمزيد من التفاصيل، راجع المصادر التالية:

إنشاء مجموعة

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

client.create_collection(
    collection_name="my_collection",
    schema=schema,
    index_params=index_params
)
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
        .collectionName("my_collection")
        .collectionSchema(schema)
        .indexParams(indexes)
        .build();
client.createCollection(requestCreate);
import { MilvusClient } from "@zilliz/milvus2-sdk-node";

const client = new MilvusClient({
    address: 'http://localhost:19530'
});

await client.createCollection({
    collection_name: 'my_collection',
    schema: schema,
    index_params: indexParams
});
err = client.CreateCollection(ctx,
    milvusclient.NewCreateCollectionOption("my_collection", schema).
        WithIndexOptions(indexOption))
if err != nil {
    fmt.Println(err.Error())
    // handle error
}
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
}"

إدراج البيانات

يجب عليك توفير بيانات لجميع الحقول المحددة أثناء إنشاء المجموعة، باستثناء الحقول التي يتم إنشاؤها تلقائيًا (مثل المفتاح الأساسي مع تمكين auto_id ). إذا كنت تستخدم دالة BM25 المدمجة لإنشاء متجهات متناثرة تلقائيًا، فيجب عليك أيضًا حذف حقل المتجه المتناثر عند إدراج البيانات.

data = [
    {
        "text": "information retrieval is a field of study.",
        "sparse_vector": {1: 0.5, 100: 0.3, 500: 0.8}
    },
    {
        "text": "information retrieval focuses on finding relevant information in large datasets.",
        "sparse_vector": {10: 0.1, 200: 0.7, 1000: 0.9}
]

client.insert(
    collection_name="my_collection",
    data=data
)
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import io.milvus.v2.service.vector.request.InsertReq;
import io.milvus.v2.service.vector.response.InsertResp;

import java.util.ArrayList;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;

Gson gson = new Gson();
List<JsonObject> rows = new ArrayList<>();

{
    JsonObject row = new JsonObject();
    row.addProperty("text", "information retrieval is a field of study.");
    
    SortedMap<Long, Float> sparse = new TreeMap<>();
    sparse.put(1L, 0.5f);
    sparse.put(100L, 0.3f);
    sparse.put(500L, 0.8f);
    row.add("sparse_vector", gson.toJsonTree(sparse));
    rows.add(row);
}
{
    JsonObject row = new JsonObject();
    row.addProperty("text", "information retrieval focuses on finding relevant information in large datasets.");
    
    SortedMap<Long, Float> sparse = new TreeMap<>();
    sparse.put(10L, 0.1f);
    sparse.put(200L, 0.7f);
    sparse.put(1000L, 0.9f);
    row.add("sparse_vector", gson.toJsonTree(sparse));
    rows.add(row);
}

InsertResp insertResp = client.insert(InsertReq.builder()
        .collectionName("my_collection")
        .data(rows)
        .build());
const data = [
    {
        text: 'information retrieval is a field of study.',
        sparse_vector: {1: 0.5, 100: 0.3, 500: 0.8}
    {
        text: 'information retrieval focuses on finding relevant information in large datasets.',
        sparse_vector: {10: 0.1, 200: 0.7, 1000: 0.9}
    },
];

client.insert({
    collection_name: "my_collection",
    data: data
});
texts := []string{
    "information retrieval is a field of study.",
    "information retrieval focuses on finding relevant information in large datasets.",
}
textColumn := entity.NewColumnVarChar("text", texts)

// Prepare sparse vectors
sparseVectors := make([]entity.SparseEmbedding, 0, 2)
sparseVector1, _ := entity.NewSliceSparseEmbedding([]uint32{1, 100, 500}, []float32{0.5, 0.3, 0.8})
sparseVectors = append(sparseVectors, sparseVector1)
sparseVector2, _ := entity.NewSliceSparseEmbedding([]uint32{10, 200, 1000}, []float32{0.1, 0.7, 0.9})
sparseVectors = append(sparseVectors, sparseVector2)
sparseVectorColumn := entity.NewColumnSparseVectors("sparse_vector", sparseVectors)

_, err = client.Insert(ctx, milvusclient.NewColumnBasedInsertOption("my_collection").
    WithColumns(
        sparseVectorColumn,
        textColumn
        
    ))
if err != nil {
    fmt.Println(err.Error())
    // handle err
}
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.",
            "sparse_vector": {"1": 0.5, "100": 0.3, "500": 0.8}
        },
        {
            "text": "information retrieval focuses on finding relevant information in large datasets.",
            "sparse_vector": {"10": 0.1, "200": 0.7, "1000": 0.9}
        }     
    ],
    "collectionName": "my_collection"
}'

لإجراء بحث تشابه باستخدام متجهات متناثرة، قم بإعداد بيانات الاستعلام ومعلمات البحث.

# Query with sparse vector
query_data = [{1: 0.2, 50: 0.4, 1000: 0.7}]
import io.milvus.v2.service.vector.request.data.EmbeddedText;
import io.milvus.v2.service.vector.request.data.SparseFloatVec;

// Query with the sparse vector
SortedMap<Long, Float> sparse = new TreeMap<>();
sparse.put(1L, 0.2f);
sparse.put(50L, 0.4f);
sparse.put(1000L, 0.7f);
SparseFloatVec queryData = new SparseFloatVec(sparse);
// Query with the sparse vector
queryData, _ := entity.NewSliceSparseEmbedding([]uint32{1, 50, 1000}, []float32{0.2, 0.4, 0.7})
// Query with the sparse vector
const queryData = [{1: 0.2, 50: 0.4, 1000: 0.7}]
# Prepare search parameters
export queryData='["What is information retrieval?"]'

# Query with the sparse vector
export queryData='[{1: 0.2, 50: 0.4, 1000: 0.7}]'

ثم، قم بتنفيذ بحث التشابه باستخدام الطريقة search:

res = client.search(
    collection_name="my_collection",
    data=query_data,
    limit=3,
    output_fields=["pk"],
)

print(res)

# Output
# data: ["[{'id': '453718927992172266', 'distance': 0.6299999952316284, 'entity': {'pk': '453718927992172266'}}, {'id': '453718927992172265', 'distance': 0.10000000149011612, 'entity': {'pk': '453718927992172265'}}]"]
import io.milvus.v2.service.vector.request.SearchReq;
import io.milvus.v2.service.vector.response.SearchResp;

SparseFloatVec queryVector = new SparseFloatVec(sparse);

SearchResp searchR = client.search(SearchReq.builder()
        .collectionName("my_collection")
        .data(Collections.singletonList(queryData))
        .annsField("sparse_vector")
        .topK(3)
        .outputFields(Collections.singletonList("pk"))
        .build());
        
System.out.println(searchR.getSearchResults());

// Output
//
// [[SearchResp.SearchResult(entity={pk=457270974427187729}, score=0.63, id=457270974427187729), SearchResp.SearchResult(entity={pk=457270974427187728}, score=0.1, id=457270974427187728)]]
await client.search({
    collection_name: 'my_collection',
    data: queryData,
    limit: 3,
    output_fields: ['pk'],
});
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
    "my_collection",
    3, // limit
    []entity.Vector{queryData},
).WithANNSField("sparse_vector").
    WithOutputFields("pk").
if err != nil {
    fmt.Println(err.Error())
    // handle err
}

for _, resultSet := range resultSets {
    fmt.Println("IDs: ", resultSet.IDs.FieldData().GetScalars())
    fmt.Println("Scores: ", resultSet.Scores)
    fmt.Println("Pks: ", resultSet.GetColumn("pk").FieldData().GetScalars())
}

// Results:
//   IDs:  string_data:{data:"457270974427187705"  data:"457270974427187704"}
//   Scores:  [0.63 0.1]
//   Pks:  string_data:{data:"457270974427187705"  data:"457270974427187704"}

curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
    "collectionName": "my_collection",
    "data": $queryData,
    "annsField": "sparse_vector",
    "limit": 3,
    "outputFields": ["pk"]
}'

## {"code":0,"cost":0,"data":[{"distance":0.63,"id":"453577185629572535","pk":"453577185629572535"},{"distance":0.1,"id":"453577185629572534","pk":"453577185629572534"}]}

لمزيد من المعلومات حول معلمات بحث التشابه، راجع بحث المتجهات الأساسية.

جرب Managed Milvus مجاناً

Zilliz Cloud خالي من المتاعب، ويعمل بواسطة Milvus ويعمل بسرعة 10 أضعاف.

ابدأ
التعليقات

هل كانت هذه الصفحة مفيدة؟