このページでは、Cloud Firestore を使用して、次の手法で K 最近傍(KNN)ベクトル検索を行う方法について説明します。
- ベクター値を保存する
- KNN ベクトル インデックスを作成して管理する
- サポートされているベクトル距離測定のいずれかを使用して K 最近傍(KNN)クエリを実行する
ベクトル エンベディングを保存する
Cloud Firestore データからテキスト エンベディングなどのベクター値を作成し、Cloud Firestore ドキュメントに保存できます。
ベクター エンベディングを使用した書き込みオペレーション
次の例は、ベクター エンベディングを Cloud Firestore ドキュメントに保存する方法を示しています。
Python
Node.js
import { Firestore, FieldValue, } from "@google-cloud/firestore"; const db = new Firestore(); const coll = db.collection('coffee-beans'); await coll.add({ name: "Kahawa coffee beans", description: "Information about the Kahawa coffee beans.", embedding_field: FieldValue.vector([1.0 , 2.0, 3.0]) });
Go
Java
import com.google.cloud.firestore.CollectionReference; import com.google.cloud.firestore.DocumentReference; import com.google.cloud.firestore.FieldValue; import com.google.cloud.firestore.VectorQuery; CollectionReference coll = firestore.collection("coffee-beans"); Map<String, Object> docData = new HashMap<>(); docData.put("name", "Kahawa coffee beans"); docData.put("description", "Information about the Kahawa coffee beans."); docData.put("embedding_field", FieldValue.vector(new double[] {1.0, 2.0, 3.0})); ApiFuture<DocumentReference> future = coll.add(docData); DocumentReference documentReference = future.get();
Cloud Functions の関数を使用してベクター エンベディングを計算する
ドキュメントが更新または作成されるたびにベクター エンベディングを計算して保存するには、Cloud Functions の関数を設定します。
Python
@functions_framework.cloud_event def store_embedding(cloud_event) -> None: """Triggers by a change to a Firestore document. """ firestore_payload = firestore.DocumentEventData() payload = firestore_payload._pb.ParseFromString(cloud_event.data) collection_id, doc_id = from_payload(payload) # Call a function to calculate the embedding embedding = calculate_embedding(payload) # Update the document doc = firestore_client.collection(collection_id).document(doc_id) doc.set({"embedding_field": embedding}, merge=True)
Node.js
/** * A vector embedding will be computed from the * value of the `content` field. The vector value * will be stored in the `embedding` field. The * field names `content` and `embedding` are arbitrary * field names chosen for this example. */ async function storeEmbedding(event: FirestoreEvent<any>): Promise<void> { // Get the previous value of the document's `content` field. const previousDocumentSnapshot = event.data.before as QueryDocumentSnapshot; const previousContent = previousDocumentSnapshot.get("content"); // Get the current value of the document's `content` field. const currentDocumentSnapshot = event.data.after as QueryDocumentSnapshot; const currentContent = currentDocumentSnapshot.get("content"); // Don't update the embedding if the content field did not change if (previousContent === currentContent) { return; } // Call a function to calculate the embedding for the value // of the `content` field. const embeddingVector = calculateEmbedding(currentContent); // Update the `embedding` field on the document. await currentDocumentSnapshot.ref.update({ embedding: embeddingVector, }); }
Go
// Not yet supported in the Go client library
Java
// Not yet supported in the Java client library
ベクトル インデックスを作成して管理する
ベクトル エンベディングで最近傍検索を実行するには、対応するインデックスを作成する必要があります。次の例では、Google Cloud CLI を使用してベクトル インデックスを作成して管理する方法を示します。ベクトル インデックスは、Firebase CLI と Terraform で管理することもできます。
ベクトル インデックスを作成する
ベクトル インデックスを作成する前に、Google Cloud CLI を最新バージョンにアップグレードします。
gcloud components update
ベクトル インデックスを作成するには、gcloud firestore indexes composite create
を使用します。
gcloud
gcloud firestore indexes composite create \ --collection-group=collection-group \ --query-scope=COLLECTION \ --field-config field-path=vector-field,vector-config='vector-configuration' \ --database=database-id
ここで
- collection-group は、コレクション グループの ID です。
- vector-field は、ベクター エンベディングを含むフィールドの名前です。
- database-id は、データベースの ID です。
- vector-configuration には、ベクトル
dimension
とインデックス タイプが含まれます。dimension
は、2,048 までの整数です。インデックスのタイプはflat
にする必要があります。 インデックス構成を{"dimension":"DIMENSION", "flat": "{}"}
形式にします。
次の例では、フィールド vector-field
のベクトル インデックスとフィールド color
の昇順インデックスを含む複合インデックスを作成します。このタイプのインデックスを使用して、最近傍探索の前にデータを事前にフィルタできます。
gcloud
gcloud firestore indexes composite create \ --collection-group=collection-group \ --query-scope=COLLECTION \ --field-config=order=ASCENDING,field-path="color" \ --field-config field-path=vector-field,vector-config='{"dimension":"1024", "flat": "{}"}' \ --database=database-id
すべてのベクター インデックスを一覧表示する
gcloud
gcloud firestore indexes composite list --database=database-id
database-id は、データベースの ID に置き換えます。
ベクトル インデックスを削除する
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
ここで
- index-id は、削除するインデックスの ID です。
インデックス ID は
indexes composite list
を使用して取得します。 - database-id は、データベースの ID です。
ベクター インデックスについて説明する
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
ここで
- index-id は、説明するインデックスの ID です。
インデックス ID は
indexes composite list
を使用して取得します。 - database-id は、データベースの ID です。
最近傍クエリを実行する
類似度検索を実行して、ベクター エンベディングの最近傍を見つけることができます。類似性検索にはベクター インデックスが必要です。インデックスが存在しない場合、Cloud Firestore では gcloud CLI を使用して作成するインデックスが提案されます。
次の例では、クエリベクトルの 10 個の最近傍を検索します。
Python
Node.js
import { Firestore, FieldValue, VectorQuery, VectorQuerySnapshot, } from "@google-cloud/firestore"; // Requires a single-field vector index const vectorQuery: VectorQuery = coll.findNearest({ vectorField: 'embedding_field', queryVector: [3.0, 1.0, 2.0], limit: 10, distanceMeasure: 'EUCLIDEAN' }); const vectorQuerySnapshot: VectorQuerySnapshot = await vectorQuery.get();
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get();
ベクター距離
最近傍クエリでは、ベクトル距離に関する次のオプションがサポートされています。
EUCLIDEAN
: ベクター間のユークリッド距離を測定します。詳細については、ユークリッドをご覧ください。COSINE
: ベクター間の角度に基づいてベクターを比較します。これにより、ベクターの大きさに基づかない類似性を測定できます。コサイン距離ではなく、単位正規化ベクターを使用してDOT_PRODUCT
を使用することをおすすめします。数学的には、パフォーマンスが向上します。詳しくは、学習するコサイン類似度をご覧ください。DOT_PRODUCT
:COSINE
に似ていますが、ベクターの大きさの影響を受けます。詳細については、ドット積をご覧ください。
距離測定を選択する
すべてのベクトル エンベディングが正規化されているかどうかによって、距離測定を見つけるために使用する距離測定を決定できます。正規化されたベクトル エンベディングの強度(長さ)は正確に 1.0 です。
また、モデルのトレーニングに使用された距離測定がわかっている場合は、その距離測定を使用してベクトル エンベディング間の距離を計算します。
正規化されたデータ
すべてのベクトル エンベディングが正規化されたデータセットがある場合、3 つの距離測定はすべて同じセマンティック検索の結果を提供します。基本的に、各距離測定から異なる値が返されますが、値の並べ替え方法は同じです。エンべディングが正規化されている場合、通常は DOT_PRODUCT
が最も計算効率が優れていますが、ほとんどの場合、その差はごくわずかです。ただし、アプリケーションのパフォーマンスが非常に重要である場合は、DOT_PRODUCT
がパフォーマンス チューニングに役立つ場合があります。
正規化されていないデータ
ベクトル エンベディングが正規化されていないデータセットがある場合、ドット積は距離を測定しないため、DOT_PRODUCT
を距離測定として使用することは数学的に正しいものではありません。エンベディングの生成方法と優先する検索タイプに応じて、COSINE
または EUCLIDEAN
距離測定のいずれかを使用すると、もう一方よりも主観的に優れた検索結果が得られます。COSINE
または EUCLIDEAN
を試して、ユースケースにどちらが最適かを判断しなければならない場合があります。
データが正規化されているかどうか不明
データが正規化されているかどうかが不明な場合に DOT_PRODUCT
を使用する場合は、代わりに COSINE
を使用することをおすすめします。COSINE
は、正規化が組み込まれた DOT_PRODUCT
に似ています。COSINE
を使用して測定された距離は 0
~2
の範囲です。結果が 0
に近い場合は、ベクトルが非常に類似していることを示しています。
ドキュメントを事前にフィルタする
最近傍を検索する前にドキュメントを事前フィルタするには、類似度検索を他のクエリ演算子と組み合わせます。and
複合フィルタと or
複合フィルタがサポートされています。サポートされているフィールド フィルタの詳細については、クエリ演算子をご覧ください。
Python
Node.js
// Similarity search with pre-filter // Requires composite vector index const preFilteredVectorQuery: VectorQuery = coll .where("color", "==", "red") .findNearest({ vectorField: "embedding_field", queryVector: [3.0, 1.0, 2.0], limit: 5, distanceMeasure: "EUCLIDEAN", }); const vectorQueryResults = await preFilteredVectorQuery.get();
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery preFilteredVectorQuery = coll .whereEqualTo("color", "red") .findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN); ApiFuture<VectorQuerySnapshot> future = preFilteredVectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get();
計算されたベクトル距離を取得する
計算されたベクトル距離を取得するには、次の例に示すように、FindNearest
クエリに distance_result_field
出力プロパティ名を割り当てます。
Python
Node.js
const vectorQuery: VectorQuery = coll.findNearest( { vectorField: 'embedding_field', queryVector: [3.0, 1.0, 2.0], limit: 10, distanceMeasure: 'EUCLIDEAN', distanceResultField: 'vector_distance' }); const snapshot: VectorQuerySnapshot = await vectorQuery.get(); snapshot.forEach((doc) => { console.log(doc.id, ' Distance: ', doc.get('vector_distance')); });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder().setDistanceResultField("vector_distance").build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId() + " Distance: " + document.get("vector_distance")); }
フィールド マスクを使用して、distanceResultField
とともにドキュメント フィールドのサブセットを返す場合は、次の例に示すように、フィールドマスクに distanceResultField
の値も含める必要があります。
Python
Node.js
const vectorQuery: VectorQuery = coll .select('name', 'description', 'vector_distance') .findNearest({ vectorField: 'embedding_field', queryVector: [3.0, 1.0, 2.0], limit: 10, distanceMeasure: 'EUCLIDEAN', distanceResultField: 'vector_distance' });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll .select("name", "description", "vector_distance") .findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder() .setDistanceResultField("vector_distance") .build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId() + " Distance: " + document.get("vector_distance")); }
距離のしきい値を指定する
しきい値内のドキュメントのみを返す類似しきい値を指定できます。しきい値フィールドの動作は、選択した距離測定によって異なります。
EUCLIDEAN
距離とCOSINE
距離は、距離が指定されたしきい値以下であるドキュメントにしきい値を制限します。ベクトルが類似するほど、これら距離測定は小さくなります。DOT_PRODUCT
距離は、距離が指定されたしきい値以上であるドキュメントにのみしきい値を適用します。ベクトルが類似するほど、ドット積距離は大きくなります。
次の例では、EUCLIDEAN
距離指標を使用して、最大 4.5 単位の距離にある最寄りのドキュメントを最大 10 件返す距離しきい値を指定する方法を示します。
Python
Node.js
const vectorQuery: VectorQuery = coll.findNearest({ vectorField: 'embedding_field', queryVector: [3.0, 1.0, 2.0], limit: 10, distanceMeasure: 'EUCLIDEAN', distanceThreshold: 4.5 }); const snapshot: VectorQuerySnapshot = await vectorQuery.get(); snapshot.forEach((doc) => { console.log(doc.id); });
Go
Java
import com.google.cloud.firestore.VectorQuery; import com.google.cloud.firestore.VectorQueryOptions; import com.google.cloud.firestore.VectorQuerySnapshot; VectorQuery vectorQuery = coll.findNearest( "embedding_field", new double[] {3.0, 1.0, 2.0}, /* limit */ 10, VectorQuery.DistanceMeasure.EUCLIDEAN, VectorQueryOptions.newBuilder() .setDistanceThreshold(4.5) .build()); ApiFuture<VectorQuerySnapshot> future = vectorQuery.get(); VectorQuerySnapshot vectorQuerySnapshot = future.get(); for (DocumentSnapshot document : vectorQuerySnapshot.getDocuments()) { System.out.println(document.getId()); }
制限事項
ベクター エンベディングを使用する場合は、次の制限事項に注意してください。
- サポートされているエンべディング ディメンションの最大値は 2,048 です。サイズの大きいインデックスを保存するには、次元削減を使用します。
- 最近傍クエリから返すドキュメントの最大数は 1,000 です。
- ベクター検索は、リアルタイム スナップショット リスナーをサポートしていません。
- Python、Node.js、Go、Java のクライアント ライブラリだけがベクトル検索をサポートしています。
次のステップ
- Cloud Firestore のベスト プラクティスを確認する。
- 大規模な読み取りと書き込みについて確認する。