스파스 벡터
스파스 벡터는 정보 검색 및 자연어 처리에서 표면 수준의 용어 일치를 포착하는 중요한 방법입니다. 고밀도 벡터는 의미 이해에 탁월하지만, 희소 벡터는 특히 특수 용어나 텍스트 식별자를 검색할 때 더 예측 가능한 일치 결과를 제공하는 경우가 많습니다.
개요
스파스 벡터는 대부분의 요소가 0이고 일부 차원만 0이 아닌 값을 갖는 특수한 고차원 벡터입니다. 아래 다이어그램에서 볼 수 있듯이 고밀도 벡터는 일반적으로 각 위치에 값이 있는 연속 배열로 표현됩니다(예: [0.3, 0.8, 0.2, 0.3, 0.1]). 이와 대조적으로 희소 벡터는 0이 아닌 요소와 해당 차원의 인덱스만 저장하며, 종종 { index: value} 의 키-값 쌍으로 표현됩니다(예: [{2: 0.2}, ..., {9997: 0.5}, {9999: 0.7}]).
스파스 벡터 표현
토큰화 및 점수화를 통해 문서는 각 차원이 어휘의 특정 단어에 해당하는 단어 가방 벡터로 표현될 수 있습니다. 문서에 존재하는 단어만 0이 아닌 값을 가지므로 희소 벡터 표현이 생성됩니다. 스파스 벡터는 두 가지 접근 방식을 사용하여 생성할 수 있습니다:
TF-IDF (용어 빈도 역 문서 빈도) 및 BM25 (베스트 매칭 25)와 같은전통적인 통계 기법은 말뭉치 전체에서 단어의 빈도와 중요도에 따라 가중치를 할당합니다. 이러한 방법은 토큰을 나타내는 각 차원에 대한 점수로 간단한 통계를 계산합니다. Milvus는 텍스트를 스파스 벡터로 자동 변환하여 수동 전처리가 필요 없는 BM25 방식의 내장형 전체 텍스트 검색을 제공합니다. 이 방식은 정밀도와 정확한 일치가 중요한 키워드 기반 검색에 이상적입니다. 자세한 내용은 전체 텍스트 검색을 참조하세요.
신경 스파스 임베딩 모델은 대규모 데이터 세트에 대한 학습을 통해 스파스 표현을 생성하는 학습된 방법입니다. 일반적으로 트랜스포머 아키텍처를 사용하는 딥 러닝 모델로, 의미론적 컨텍스트에 따라 용어를 확장하고 가중치를 부여할 수 있습니다. Milvus는 SPLADE와 같은 모델에서 외부에서 생성된 스파스 임베딩도 지원합니다. 자세한 내용은 임베딩을 참조하세요.
효율적인 검색을 위해 스파스 벡터와 원본 텍스트를 Milvus에 저장할 수 있습니다. 아래 다이어그램은 전체 프로세스를 간략하게 설명합니다.
스파스 벡터 워크플로
밀버스는 스파스 벡터 외에도 고밀도 벡터와 바이너리 벡터도 지원합니다. 고밀도 벡터는 심층적인 의미 관계를 캡처하는 데 이상적이며, 이진 벡터는 빠른 유사도 비교 및 콘텐츠 중복 제거와 같은 시나리오에 탁월합니다. 자세한 내용은 고밀도 벡터 및 바이너리 벡터를 참조하세요.
데이터 형식
다음 섹션에서는 SPLADE와 같은 학습된 스파스 임베딩 모델에서 벡터를 저장하는 방법을 설명합니다. 고밀도 벡터 기반 시맨틱 검색을 보완할 무언가를 찾고 계신다면, 간소화를 위해 SPLADE보다 BM25를 사용한 전체 텍스트 검색을 권장합니다. 품질 평가를 실행하고 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((vals, ([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)] ]
컬렉션 스키마 정의
컬렉션을 만들기 전에 필드를 정의하는 컬렉션 스키마와 선택적으로 텍스트 필드를 해당 스파스 벡터 표현으로 변환하는 함수를 지정해야 합니다.
필드 추가
Milvus에서 스파스 벡터를 사용하려면 다음 필드를 포함하는 스키마로 컬렉션을 만들어야 합니다:
VARCHAR필드에서 자동 생성되거나 입력 데이터에서 직접 제공된 희소 벡터를 저장하기 위해 예약된SPARSE_FLOAT_VECTOR필드.일반적으로 스파스 벡터가 나타내는 원시 텍스트도 컬렉션에 저장됩니다.
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: 이 필드는 최대 100바이트 길이로 자동 생성되는VARCHAR데이터 유형을 사용하여 기본 키를 저장합니다.sparse_vector: 이 필드는SPARSE_FLOAT_VECTOR데이터 유형을 사용하여 스파스 벡터를 저장합니다.text: 이 필드는VARCHAR데이터 유형을 사용하여 텍스트 문자열을 저장하며 최대 길이는 65535바이트입니다.
데이터 삽입 중에 Milvus를 활성화하거나 지정된 텍스트 필드에서 스파스 벡터 임베딩을 생성하려면 함수와 관련된 추가 단계를 수행해야 합니다. 자세한 내용은 전체 텍스트 검색을 참조하세요.
인덱스 매개변수 설정
희소 벡터에 대한 인덱스를 생성하는 과정은 고밀도 벡터의 경우와 유사하지만 지정된 인덱스 유형(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 을 메트릭으로 사용합니다. 자세한 내용은 다음 리소스를 참조하세요:
SPARSE_INVERTED_INDEX: 인덱스 및 해당 매개변수 설명
메트릭 유형: 다양한 필드 유형에 대해 지원되는 메트릭 유형
전체 텍스트 검색: 전체 텍스트 검색에 대한 자세한 자습서
컬렉션 만들기
스파스 벡터 및 인덱스 설정이 완료되면 스파스 벡터를 포함하는 컬렉션을 만들 수 있습니다. 아래 예에서는 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" \
--header "Request-Timeout: 10" \
-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" \
--header "Request-Timeout: 10" \
-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"
}'
유사도 검색 수행
스파스 벡터를 사용하여 유사도 검색을 수행하려면 쿼리 데이터와 검색 매개변수를 모두 준비합니다.
# Prepare search parameters
search_params = {
"params": {"drop_ratio_search": 0.2}, # A tunable drop ratio parameter with a valid range between 0 and 1
}
# 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;
// Prepare search parameters
Map<String,Object> searchParams = new HashMap<>();
searchParams.put("drop_ratio_search", 0.2);
// 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);
// Prepare search parameters
annSearchParams := index.NewCustomAnnParam()
annSearchParams.WithExtraParam("drop_ratio_search", 0.2)
// Query with the sparse vector
queryData, _ := entity.NewSliceSparseEmbedding([]uint32{1, 50, 1000}, []float32{0.2, 0.4, 0.7})
// Prepare search parameters
const searchParams = {drop_ratio_search: 0.2}
// 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"],
search_params=search_params,
consistency_level="Strong"
)
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")
.searchParams(searchParams)
.consistencyLevel(ConsistencyLevel.STRONG)
.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'],
params: searchParams,
consistency_level: "Strong"
});
resultSets, err := client.Search(ctx, milvusclient.NewSearchOption(
"my_collection",
3, // limit
[]entity.Vector{queryData},
).WithANNSField("sparse_vector").
WithOutputFields("pk").
WithAnnParam(annSearchParams))
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"}
export params='{
"consistencyLevel": "Strong"
}'
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
--header "Request-Timeout: 10" \
-d '{
"collectionName": "my_collection",
"data": $queryData,
"annsField": "sparse_vector",
"limit": 3,
"searchParams": $searchParams,
"outputFields": ["pk"],
"params": $params
}'
## {"code":0,"cost":0,"data":[{"distance":0.63,"id":"453577185629572535","pk":"453577185629572535"},{"distance":0.1,"id":"453577185629572534","pk":"453577185629572534"}]}
유사도 검색 매개변수에 대한 자세한 내용은 기본 벡터 검색을 참조하세요.