Trang này cho bạn biết cách sử dụng Cloud Firestore để thực hiện các lượt tìm kiếm vectơ K-nearest neighbor (KNN) bằng các kỹ thuật sau:
- Lưu trữ các giá trị vectơ
- Tạo và quản lý chỉ mục vectơ KNN
- Tạo truy vấn K-nearest-neighbor (KNN) bằng một trong các phương pháp đo lường khoảng cách vectơ được hỗ trợ
Lưu trữ các phần nhúng vectơ
Bạn có thể tạo các giá trị vectơ như nội dung văn bản nhúng từ dữ liệu Cloud Firestore và lưu trữ các giá trị đó trong tài liệu Cloud Firestore.
Thao tác ghi với vectơ nhúng
Ví dụ sau đây cho thấy cách lưu trữ một vectơ nhúng trong tài liệu 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();
Tính toán vectơ nhúng bằng một Hàm trên đám mây
Để tính toán và lưu trữ các vectơ nhúng bất cứ khi nào một tài liệu được cập nhật hoặc tạo, bạn có thể thiết lập một Hàm trên đám mây:
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
Tạo và quản lý chỉ mục vectơ
Trước khi có thể thực hiện tìm kiếm bằng thuật toán kề gần nhất bằng các vectơ nhúng, bạn phải tạo một chỉ mục tương ứng. Các ví dụ sau đây minh hoạ cách tạo và quản lý chỉ mục vectơ bằng Google Cloud CLI. Bạn cũng có thể quản lý chỉ mục vectơ bằng Firebase CLI và Terraform.
Tạo chỉ mục vectơ
Trước khi tạo chỉ mục vectơ, hãy nâng cấp lên phiên bản mới nhất của Google Cloud CLI:
gcloud components update
Để tạo chỉ mục vectơ, hãy sử dụng 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
trong đó:
- collection-group là mã của nhóm bộ sưu tập.
- vector-field là tên của trường chứa vectơ nhúng.
- database-id là mã nhận dạng của cơ sở dữ liệu.
- vector-configuration bao gồm vectơ
dimension
và loại chỉ mục.dimension
là một số nguyên lên đến 2048. Loại chỉ mục phải làflat
. Định dạng cấu hình chỉ mục như sau:{"dimension":"DIMENSION", "flat": "{}"}
.
Ví dụ sau đây sẽ tạo một chỉ mục tổng hợp, bao gồm chỉ mục vectơ cho trường vector-field
và chỉ mục tăng dần cho trường color
. Bạn có thể sử dụng loại chỉ mục này để lọc trước dữ liệu trước khi tìm kiếm hàng xóm gần nhất.
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
Liệt kê tất cả chỉ mục vectơ
gcloud
gcloud firestore indexes composite list --database=database-id
Thay thế database-id bằng mã nhận dạng của cơ sở dữ liệu.
Xoá chỉ mục vectơ
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
trong đó:
- index-id là mã của chỉ mục cần xoá.
Sử dụng
indexes composite list
để truy xuất mã chỉ mục. - database-id là mã nhận dạng của cơ sở dữ liệu.
Mô tả chỉ mục vectơ
gcloud
gcloud firestore indexes composite describe index-id --database=database-id
trong đó:
- index-id là mã của chỉ mục cần mô tả. Sử dụng hoặc
indexes composite list
để truy xuất mã chỉ mục. - database-id là mã nhận dạng của cơ sở dữ liệu.
Tạo truy vấn kề gần nhất
Bạn có thể thực hiện tìm kiếm theo mức độ tương đồng để tìm các vectơ nhúng gần nhất. Tính năng tìm kiếm theo mức độ tương đồng yêu cầu chỉ mục vectơ. Nếu không có chỉ mục, Cloud Firestore sẽ đề xuất một chỉ mục để tạo bằng gcloud CLI.
Ví dụ sau đây tìm 10 vectơ hàng xóm gần nhất của vectơ truy vấn.
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();
Khoảng cách vectơ
Truy vấn kề gần nhất hỗ trợ các tuỳ chọn sau đây cho khoảng cách vectơ:
EUCLIDEAN
: Đo khoảng cách EUCLIDEAN giữa các vectơ. Để tìm hiểu thêm, hãy xem phần Euclidean.COSINE
: So sánh các vectơ dựa trên góc giữa các vectơ đó, cho phép bạn đo lường mức độ tương đồng không dựa trên độ lớn của vectơ. Bạn nên sử dụngDOT_PRODUCT
với vectơ chuẩn hoá đơn vị thay vì khoảng cách COSINE. Khoảng cách này tương đương về mặt toán học với hiệu suất tốt hơn. Để tìm hiểu thêm, hãy xem phần Tương đồng cosin để tìm hiểu thêm.DOT_PRODUCT
: Tương tự nhưCOSINE
nhưng chịu ảnh hưởng của độ lớn của vectơ. Để tìm hiểu thêm, hãy xem phần Tích vô hướng.
Chọn phương pháp đo khoảng cách
Tuỳ thuộc vào việc tất cả các vectơ nhúng của bạn có được chuẩn hoá hay không, bạn có thể xác định phương pháp đo khoảng cách nào sẽ được sử dụng để tìm phương pháp đo khoảng cách. Một vectơ nhúng được chuẩn hoá có độ lớn (độ dài) chính xác là 1.0.
Ngoài ra, nếu bạn biết mô hình của mình được huấn luyện bằng phương pháp đo lường khoảng cách nào, hãy sử dụng phương pháp đo lường khoảng cách đó để tính toán khoảng cách giữa các vectơ nhúng.
Dữ liệu đã chuẩn hoá
Nếu bạn có một tập dữ liệu mà tất cả các vectơ nhúng đều được chuẩn hoá, thì cả ba phương pháp đo lường khoảng cách đều cung cấp cùng một kết quả tìm kiếm ngữ nghĩa. Về cơ bản, mặc dù mỗi phép đo khoảng cách trả về một giá trị khác nhau, nhưng các giá trị đó được sắp xếp theo cùng một cách. Khi các phần nhúng được chuẩn hoá, DOT_PRODUCT
thường là phương thức tính toán hiệu quả nhất, nhưng sự khác biệt là không đáng kể trong hầu hết các trường hợp. Tuy nhiên, nếu ứng dụng của bạn rất nhạy cảm với hiệu suất, thì DOT_PRODUCT
có thể giúp điều chỉnh hiệu suất.
Dữ liệu chưa chuẩn hoá
Nếu bạn có một tập dữ liệu mà các vectơ nhúng không được chuẩn hoá, thì việc sử dụng DOT_PRODUCT
làm phép đo khoảng cách là không chính xác về mặt toán học vì tích vô hướng không đo khoảng cách. Tuỳ thuộc vào cách tạo nội dung nhúng và loại nội dung tìm kiếm được ưu tiên, phương pháp đo khoảng cách COSINE
hoặc EUCLIDEAN
sẽ tạo ra kết quả tìm kiếm tốt hơn so với các phương pháp đo khoảng cách khác.
Bạn có thể cần thử nghiệm với COSINE
hoặc EUCLIDEAN
để xác định phương thức nào phù hợp nhất với trường hợp sử dụng của mình.
Không chắc liệu dữ liệu có được chuẩn hoá hay không
Nếu không chắc dữ liệu của mình có được chuẩn hoá hay không và muốn sử dụng DOT_PRODUCT
, bạn nên sử dụng COSINE
.
COSINE
giống như DOT_PRODUCT
có tích hợp tính năng chuẩn hoá.
Khoảng cách được đo bằng COSINE
dao động từ 0
đến 2
. Kết quả gần với 0
cho biết các vectơ rất giống nhau.
Lọc trước giấy tờ
Để lọc trước tài liệu trước khi tìm các tài liệu gần nhất, bạn có thể kết hợp tính năng tìm kiếm theo mức độ tương đồng với các toán tử truy vấn khác. Bộ lọc tổng hợp and
và or
được hỗ trợ. Để biết thêm thông tin về các bộ lọc trường được hỗ trợ, hãy xem phần Toán tử truy vấn.
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();
Truy xuất khoảng cách vectơ đã tính
Bạn có thể truy xuất khoảng cách vectơ đã tính bằng cách chỉ định tên thuộc tính đầu ra distance_result_field
trên truy vấn FindNearest
, như minh hoạ trong ví dụ sau:
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")); }
Nếu muốn sử dụng mặt nạ trường để trả về một tập hợp con các trường tài liệu cùng với distanceResultField
, thì bạn cũng phải thêm giá trị của distanceResultField
vào mặt nạ trường, như trong ví dụ sau:
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")); }
Chỉ định ngưỡng khoảng cách
Bạn có thể chỉ định ngưỡng tương đồng chỉ trả về các tài liệu nằm trong ngưỡng đó. Hành vi của trường ngưỡng phụ thuộc vào phương pháp đo lường khoảng cách mà bạn chọn:
- Khoảng cách
EUCLIDEAN
vàCOSINE
giới hạn ngưỡng ở những tài liệu có khoảng cách nhỏ hơn hoặc bằng ngưỡng đã chỉ định. Các phép đo khoảng cách này giảm khi các vectơ trở nên giống nhau hơn. - Khoảng cách
DOT_PRODUCT
giới hạn ngưỡng đối với các tài liệu có khoảng cách lớn hơn hoặc bằng ngưỡng đã chỉ định. Khoảng cách tích vô hướng tăng lên khi các vectơ trở nên giống nhau hơn.
Ví dụ sau đây cho thấy cách chỉ định ngưỡng khoảng cách để trả về tối đa 10 tài liệu gần nhất, cách xa tối đa 4, 5 đơn vị bằng cách sử dụng chỉ số khoảng cách EUCLIDEAN
:
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()); }
Các điểm hạn chế
Khi bạn làm việc với các vectơ nhúng, hãy lưu ý các giới hạn sau:
- Phương diện nhúng tối đa được hỗ trợ là 2048. Để lưu trữ các chỉ mục lớn hơn, hãy sử dụng tính năng giảm chiều.
- Số lượng tài liệu tối đa được trả về từ truy vấn đối tượng gần nhất là 1000.
- Tìm kiếm vectơ không hỗ trợ trình nghe ảnh chụp nhanh theo thời gian thực.
- Chỉ có thư viện ứng dụng Python, Node.js, Go và Java mới hỗ trợ tính năng tìm kiếm vectơ.
Bước tiếp theo
- Đọc về các phương pháp hay nhất cho Cloud Firestore.
- Tìm hiểu về hoạt động đọc và ghi trên quy mô lớn.