이 페이지에서는 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
자바
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 함수로 벡터 임베딩 계산
문서가 업데이트 또는 생성될 때마다 벡터 임베딩을 계산하고 저장하기 위해 Cloud 함수를 설정할 수 있습니다.
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
자바
// 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
은 최대 2048까지의 정수입니다. 색인 유형은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입니다.
indexes composite list
를 사용하여 색인 ID를 검색합니다. - database-id는 데이터베이스의 ID입니다.
벡터 색인 설명
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
각 항목의 의미는 다음과 같습니다.
- index-id는 설명할 색인의 ID입니다.
indexes composite list
를 사용하여 색인 ID를 검색합니다. - 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
자바
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
: 벡터 사이의 EUCLIDEAN 거리를 측정합니다. 자세한 내용은 유클리드를 참조하세요.COSINE
: 벡터 크기를 기반으로 하지 않는 유사성을 측정할 수 있도록 벡터 사이의 각도를 기준으로 벡터를 비교합니다. COSINE 거리 대신DOT_PRODUCT
를 단위 정규화된 벡터와 함께 사용하는 것이 좋습니다. 이는 수학적으로 동일하며 성능이 더 우수합니다. 자세한 내용은 코사인 유사성을 참조하세요.DOT_PRODUCT
:COSINE
과 유사하지만 벡터의 크기에 영향을 받습니다. 자세한 내용은 내적을 참조하세요.
거리 측정 선택
모든 벡터 임베딩이 정규화되었는지 여부에 따라 거리 측정을 찾는 데 사용할 거리 측정을 결정할 수 있습니다. 정규화된 벡터 임베딩의 크기(길이)는 정확히 1.0입니다.
또한 모델이 어떤 거리 측정으로 학습되었는지 알고 있다면 해당 거리 측정을 사용하여 벡터 임베딩 간의 거리를 계산할 수 있습니다.
정규화된 데이터
모든 벡터 임베딩이 정규화된 데이터 세트가 있다면, 세 거리 측정 모두 동일한 시맨틱 검색 결과를 제공합니다. 기본적으로 각 거리 측정은 서로 다른 값을 반환하지만 해당 값은 동일한 방식으로 정렬됩니다. 임베딩이 정규화될 때는 일반적으로 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
자바
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
자바
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
자바
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
자바
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()); }
제한사항
벡터 임베딩으로 작업할 때 다음 제한사항에 유의하세요.
- 지원되는 최대 임베딩 차원은 2048입니다. 더 큰 색인을 저장하려면 차원 축소를 사용합니다.
- 최근접 이웃 쿼리에서 반환할 최대 문서 수는 1,000개입니다.
- 벡터 검색은 실시간 스냅샷 리스너를 지원하지 않습니다.
- Python, Node.js, Go, 자바 클라이언트 라이브러리만 벡터 검색을 지원합니다.
다음 단계
- Cloud Firestore의 권장사항 알아보기
- 대규모 읽기 및 쓰기 이해