スパース・ベクトル
スパース・ベクトルは、情報検索や自然言語処理におけるデータ表現の重要な手法である。密なベクトルはその優れた意味理解能力で人気がありますが、キーワードやフレーズの正確なマッチングを必要とするアプリケーションに関しては、疎なベクトルの方がより正確な結果を提供することがよくあります。
概要
スパース・ベクトルは高次元ベクトルの特殊な表現で、ほとんどの要素が0であり、0でない値を持つ次元はわずかです。この特性により、スパースベクトルは大規模で高次元の疎なデータを扱うのに特に効果的です。一般的なアプリケーションは以下の通り。
テキスト分析:各次元が単語に対応し、文書に登場する単語だけがゼロ以外の値を持つ。
推薦システム:ユーザーとアイテムの相互作用行列。各次元は、特定のアイテムに対するユーザーの評価を表し、ほとんどのユーザーは少数のアイテムとしか相互作用しない。
画像処理:局所特徴表現:画像の重要なポイントのみに焦点を当て、高次元の疎なベクトルを生成する。
下図に示すように、密なベクトルは通常、各位置が値を持つ連続的な配列として表現される(例:[0.3, 0.8, 0.2, 0.3, 0.1]
)。対照的に、スパース・ベクトルは非ゼロ要素とそのインデックスのみを格納し、しばしばキーと値のペアとして表現されます(例:[{2: 0.2}, ..., {9997: 0.5}, {9999: 0.7}]
)。この表現は、特に非常に高次元のデータ(例えば10,000次元)を扱う場合に、記憶領域を大幅に削減し、計算効率を向上させます。
疎ベクトル表現
スパースベクトルは、テキスト処理におけるTF-IDF(項頻度-逆文書頻度)やBM25など、様々な手法を用いて生成することができます。さらに、Milvusはスパースベクトルの生成と処理を支援する便利なメソッドを提供しています。詳細は埋め込みをご参照ください。
Milvusはテキストデータの全文検索機能も提供しており、スパースベクトルを生成するために外部の埋め込みモデルを使用することなく、生のテキストデータに対して直接ベクトル検索を行うことができます。詳細は全文検索をご参照ください。
ベクトル化されたデータはMilvusに保存され、管理やベクトル検索に利用することができます。下図は基本的なプロセスを示しています。
Milvusでスパースベクトルを使用する。
Milvusはスパースベクトル以外にも、デンスベクトルやバイナリベクトルにも対応しています。密なベクトルは深い意味的関係を把握するのに理想的であり、バイナリベクトルは迅速な類似性比較やコンテンツの重複排除などのシナリオに優れています。詳細については、密なベクトルと バイナリベクトルを参照してください。
Milvusでスパースベクトルを使う
Milvusでは、以下のいずれかの形式でスパースベクトルを表現することができます。
疎行列 (
scipy.sparse
クラスを使用)from scipy.sparse import csr_matrix # Create a sparse matrix row = [0, 0, 1, 2, 2, 2] col = [0, 2, 2, 0, 1, 2] data = [1, 2, 3, 4, 5, 6] sparse_matrix = csr_matrix((data, (row, col)), shape=(3, 3)) # Represent sparse vector using the sparse matrix sparse_vector = sparse_matrix.getrow(0)
辞書のリスト (
{dimension_index: value, ...}
としてフォーマット)# Represent sparse vector using a dictionary sparse_vector = [{1: 0.5, 100: 0.3, 500: 0.8, 1024: 0.2, 5000: 0.6}]
SortedMap<Long, Float> sparseVector = new TreeMap<>(); sparseVector.put(1L, 0.5f); sparseVector.put(100L, 0.3f); sparseVector.put(500L, 0.8f); sparseVector.put(1024L, 0.2f); sparseVector.put(5000L, 0.6f);
タプルイテレータのリスト(
[(dimension_index, value)]
としてフォーマットされる)# Represent sparse vector using a list of tuples sparse_vector = [[(1, 0.5), (100, 0.3), (500, 0.8), (1024, 0.2), (5000, 0.6)]]
ベクトル・フィールドの追加
Milvusでスパースベクトルを使用するには、コレクション作成時にスパースベクトルを格納するフィールドを定義します。このプロセスには以下が含まれます。
datatype
をサポートされているスパースベクトルデータ型、SPARSE_FLOAT_VECTOR
に設定します。次元を指定する必要はない。
from pymilvus import MilvusClient, DataType
client = MilvusClient(uri="http://localhost:19530")
client.drop_collection(collection_name="my_sparse_collection")
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)
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());
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,
}
];
export primaryField='{
"fieldName": "pk",
"dataType": "VarChar",
"isPrimary": true,
"elementTypeParams": {
"max_length": 100
}
}'
export vectorField='{
"fieldName": "sparse_vector",
"dataType": "SparseFloatVector"
}'
export schema="{
\"autoID\": true,
\"fields\": [
$primaryField,
$vectorField
]
}"
この例では、スパース・ベクトルを格納するために、sparse_vector
というベクトル・ フィールドが追加されている。このフィールドのデータ型はSPARSE_FLOAT_VECTOR
です。
ベクトル・フィールドにインデックス・パラメータを設定する
スパース・ベクトルのインデックスを作成するプロセスは、密なベクトルの場合と似ていますが、指定するインデックス・タイプ (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={"drop_ratio_build": 0.2},
)
import io.milvus.v2.common.IndexParam;
import java.util.*;
List<IndexParam> indexes = new ArrayList<>();
Map<String,Object> extraParams = new HashMap<>();
extraParams.put("drop_ratio_build", 0.2);
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({
index_name: 'sparse_inverted_index',
field_name: 'sparse_vector',
metric_type: MetricType.IP,
index_type: IndexType.SPARSE_WAND,
params: {
drop_ratio_build: 0.2,
},
});
export indexParams='[
{
"fieldName": "sparse_vector",
"metricType": "IP",
"indexName": "sparse_inverted_index",
"indexType": "SPARSE_INVERTED_INDEX",
"params":{"drop_ratio_build": 0.2}
}
]'
上の例では
SPARSE_INVERTED_INDEX
型のインデックスがスパース・ベクトルに作成されます。スパース・ベクトルの場合、SPARSE_INVERTED_INDEX
またはSPARSE_WAND
を指定できます。詳細については、スパース・ベクター・インデックスを参照してください。スパース・ベクトルの場合、
metric_type
はIP
(内積)のみをサポートしています。これは、2 つのスパース・ベクトルの類似度を測定するために使用されます。類似度の詳細については、メトリック型を参照してください。drop_ratio_build
は、スパースベクトル専用のオプションのインデックスパラメータです。これは、インデックス構築時に除外される小さなベクトル値の割合を制御します。例えば、 を指定すると、インデックス作成時にベクトル値の最小20%が除外され、検索時の計算量が削減されます。{"drop_ratio_build": 0.2}
コレクションの作成
スパース・ベクタとインデックスの設定が完了すると、スパース・ベクタを含むコレクションを 作成できます。以下の例では create_collection
メソッドを使用して、my_sparse_collection
という名前のコレクションを作成します。
client.create_collection(
collection_name="my_sparse_collection",
schema=schema,
index_params=index_params
)
import io.milvus.v2.client.ConnectConfig;
import io.milvus.v2.client.MilvusClientV2;
MilvusClientV2 client = new MilvusClientV2(ConnectConfig.builder()
.uri("http://localhost:19530")
.build());
CreateCollectionReq requestCreate = CreateCollectionReq.builder()
.collectionName("my_sparse_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_sparse_collection',
schema: schema,
index_params: indexParams
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/collections/create" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d "{
\"collectionName\": \"my_sparse_collection\",
\"schema\": $schema,
\"indexParams\": $indexParams
}"
データの挿入
コレクションを作成したら、スパース・ベクトルを含むデータを挿入します。
sparse_vectors = [
{"sparse_vector": {1: 0.5, 100: 0.3, 500: 0.8}},
{"sparse_vector": {10: 0.1, 200: 0.7, 1000: 0.9}},
]
client.insert(
collection_name="my_sparse_collection",
data=sparse_vectors
)
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;
List<JsonObject> rows = new ArrayList<>();
Gson gson = new Gson();
{
JsonObject row = new JsonObject();
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();
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 insertR = client.insert(InsertReq.builder()
.collectionName("my_sparse_collection")
.data(rows)
.build());
const data = [
{ sparse_vector: { "1": 0.5, "100": 0.3, "500": 0.8 } },
{ sparse_vector: { "10": 0.1, "200": 0.7, "1000": 0.9 } },
];
client.insert({
collection_name: "my_sparse_collection",
data: data,
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/insert" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"data": [
{"sparse_vector": {"1": 0.5, "100": 0.3, "500": 0.8}},
{"sparse_vector": {"10": 0.1, "200": 0.7, "1000": 0.9}}
],
"collectionName": "my_sparse_collection"
}'
## {"code":0,"cost":0,"data":{"insertCount":2,"insertIds":["453577185629572534","453577185629572535"]}}
類似検索の実行
スパースベクトルを使って類似検索を行うには、クエリベクトルと検索パラメータを用意します。
# Prepare search parameters
search_params = {
"params": {"drop_ratio_search": 0.2}, # Additional optional search parameters
}
# Prepare the query vector
query_vector = [{1: 0.2, 50: 0.4, 1000: 0.7}]
この例では、drop_ratio_search
はスパース・ベクトル専用のオプション・パラメータで、検索中にクエリ・ベクトル内の小さな値を微調整できるようにします。例えば、{"drop_ratio_search": 0.2}
を指定すると、クエリベクトル内の最小20%の値は検索時に無視されます。
次に、search
メソッドを使って類似検索を実行する。
res = client.search(
collection_name="my_sparse_collection",
data=query_vector,
limit=3,
output_fields=["pk"],
search_params=search_params,
)
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.request.data.SparseFloatVec;
import io.milvus.v2.service.vector.response.SearchResp;
Map<String,Object> searchParams = new HashMap<>();
searchParams.put("drop_ratio_search", 0.2);
SortedMap<Long, Float> sparse = new TreeMap<>();
sparse.put(10L, 0.1f);
sparse.put(200L, 0.7f);
sparse.put(1000L, 0.9f);
SparseFloatVec queryVector = new SparseFloatVec(sparse);
SearchResp searchR = client.search(SearchReq.builder()
.collectionName("my_sparse_collection")
.data(Collections.singletonList(queryVector))
.annsField("sparse_vector")
.searchParams(searchParams)
.topK(3)
.outputFields(Collections.singletonList("pk"))
.build());
System.out.println(searchR.getSearchResults());
// Output
//
// [[SearchResp.SearchResult(entity={pk=453444327741536759}, score=1.31, id=453444327741536759), SearchResp.SearchResult(entity={pk=453444327741536756}, score=1.31, id=453444327741536756), SearchResp.SearchResult(entity={pk=453444327741536753}, score=1.31, id=453444327741536753)]]
client.search({
collection_name: 'my_sparse_collection',
data: {1: 0.2, 50: 0.4, 1000: 0.7},
limit: 3,
output_fields: ['pk'],
params: {
drop_ratio_search: 0.2
}
});
curl --request POST \
--url "${CLUSTER_ENDPOINT}/v2/vectordb/entities/search" \
--header "Authorization: Bearer ${TOKEN}" \
--header "Content-Type: application/json" \
-d '{
"collectionName": "my_sparse_collection",
"data": [
{"1": 0.2, "50": 0.4, "1000": 0.7}
],
"annsField": "sparse_vector",
"limit": 3,
"searchParams":{
"params":{"drop_ratio_search": 0.2}
},
"outputFields": ["pk"]
}'
## {"code":0,"cost":0,"data":[{"distance":0.63,"id":"453577185629572535","pk":"453577185629572535"},{"distance":0.1,"id":"453577185629572534","pk":"453577185629572534"}]}
類似検索パラメーターの詳細については、「基本的なANN検索」を参照のこと。
限界
Milvusでスパースベクトルを使用する場合、以下の制限を考慮してください:
現在、スパースベクトルではIP距離メトリックのみがサポートされています。スパースベクトルは次元が高いため、L2距離や余弦距離は実用的ではありません。
疎なベクトル・フィールドでは、SPARSE_INVERTED_INDEXとSPARSE_WANDインデックス型のみがサポートされています。
スパース・ベクトルでサポートされるデータ型:
- 次元部は符号なし32ビット整数でなければならない;
- 値部は非負32ビット浮動小数点数。
スパース・ベクトルは、挿入と検索に関して以下の要件を満たす必要があります:
- ベクトル内の少なくとも1つの値が非ゼロである;
- ベクトルの添字が非負であること。
よくある質問
SPARSE_INVERTED_INDEX と SPARSE_WAND の違いと、その選択方法を教えてください。
SPARSE_INVERTED_INDEXは伝統的な転置インデックスで、SPARSE_WANDは Weak-ANDアルゴリズムを使用して検索中のフルIP距離評価数を減らします。SPARSE_WANDは一般的に高速ですが、ベクトル密度が高くなるにつれて性能が低下する可能性があります。どちらかを選択するには、特定のデータセットとユースケースに基づいた実験とベンチマークを実施してください。
drop_ratio_buildとdrop_ratio_searchパラメータはどのように選択すればよいですか?
drop_ratio_buildと drop_ratio_searchの選択は、データの特性や、検索レイテンシー/スループット、精度に対する要件に依存します。
スパース埋込みの次元は、uint32空間内の任意の離散値にすることができますか?
はい,ただし1つの例外があります.スパース埋込みの次元は,
[0, maximum of uint32)
の範囲内の任意の値にすることができます. つまり,uint32の最大値を使うことはできません.成長しているセグメントの検索は、インデックスを使って行うのですか?
成長中のセグメントを検索する際には、セグメントインデックスと同じ型のインデックスを使用します。インデックスが作成される前の新しい成長中のセグメントについては、 総当たり検索を使用します。
1つのコレクションに、疎なベクトルと密なベクトルの両方を持つことは可能ですか?
はい、複数のベクトル型をサポートしているため、疎なベクトル列と密なベクトル列の両方を持つコレクションを作成し、それに対してハイブリッド検索を実行することができます。