בדף הזה מוסבר איך להשתמש ב-Cloud Firestore כדי לבצע חיפושי וקטורים של K-nearest neighbor (KNN) באמצעות הטכניקות הבאות:
- ערכי וקטורים של חנויות
- יצירה וניהול של אינדקסים של וקטורים בשיטת KNN
- שליחת שאילתת K-nearest-neighbor (KNN) באמצעות אחד ממדדי המרחק הנתמכים בין וקטורים
לפני שמתחילים
לפני שמאחסנים הטמעות ב-Cloud Firestore, צריך ליצור הטמעות וקטוריות. Cloud Firestore לא יוצר את ההטמעות. אתם יכולים להשתמש בשירות כמו Vertex AI כדי ליצור ערכים וקטוריים, למשל הטמעות טקסט מנתוני Cloud Firestore. אחר כך תוכלו לאחסן את ההטמעות האלה בחזרה במסמכי Cloud Firestore.
מידע נוסף על הטמעות זמין במאמר מהן הטמעות?
כדי ללמוד איך לקבל הטמעות טקסט באמצעות Vertex AI, אפשר לעיין במאמר קבלת הטמעות טקסט.
אחסון הטמעות וקטוריות
בדוגמאות הבאות מוסבר איך לאחסן הטמעות וקטוריות ב-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 Function
כדי לחשב ולאחסן הטבעות וקטוריות בכל פעם שמסמך מתעדכן או נוצר, אפשר להגדיר Cloud Function:
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
כדי ליצור אינדקס חדש באופן ידני במסוף Google Cloud:
- נכנסים לדף Databases במסוף Google Cloud.
- בוחרים את מסד הנתונים הרצוי מרשימת מסדי הנתונים.
- בתפריט הניווט, לוחצים על Indexes (אינדקסים) ואז על הכרטיסייה Manual (ידני).
- לוחצים על יצירת אינדקס.
כדי ליצור אינדקס לשדה וקטורי לחיפושים וקטוריים, בוחרים באפשרות יצירת אינדקס וקטורי.
-
מזינים מזהה אוסף. מזינים את הנתיב של שדה הווקטור ואת מספר הממדים של הטמעת הווקטור. מוסיפים את השמות של כל השדות הנוספים שרוצים ליצור להם אינדקס, ואת מצב האינדקס לכל שדה.
לוחצים על שמירת האינדקס.
האינדקס החדש יופיע ברשימה של האינדקסים הידניים, ו-Cloud Firestore יתחיל ליצור את האינדקס. אחרי שהאינדקס ייווצר, יופיע סימן וי ירוק ליד האינדקס.
gcloud
לפני שיוצרים אינדקס וקטורי, צריך לשדרג לגרסה העדכנית של Google Cloud CLI:
gcloud components update
כדי ליצור אינדקס וקטורי, משתמשים בפקודה gcloud firestore indexes composite create:
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
where:
- collection-group הוא המזהה של קבוצת האוספים.
- vector-field הוא שם השדה שמכיל את הטמעת הווקטור.
- database-id הוא המזהה של מסד הנתונים.
- vector-configuration כולל את הווקטור
dimensionואת סוג האינדקס. dimensionהוא מספר שלם עד 2048. סוג האינדקס חייב להיותflat. מגדירים את התצורה של האינדקס באופן הבא:{"dimension":"DIMENSION", "flat": "{}"}.
בדוגמה הבאה נוצר אינדקס מורכב, כולל אינדקס וקטורי לשדה vector-field ואינדקס עולה לשדה color. אפשר להשתמש בסוג הזה של אינדקס כדי לסנן מראש נתונים לפני חיפוש של השכן הקרוב ביותר.
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
הצגת רשימה של כל אינדקסים של וקטורים
מסוף Google Cloud
- נכנסים לדף Databases במסוף Google Cloud.
- בוחרים את מסד הנתונים הרצוי מרשימת מסדי הנתונים.
-
בתפריט הניווט, לוחצים על Indexes (אינדקסים) ואז על הכרטיסייה Manual (ידני).
בטבלת האינדקסים מפורטים כל האינדקסים של מסד הנתונים. אינדקסים וקטוריים כוללים שדה וקטורי עם סמל .
gcloud
כדי להציג רשימה של כל האינדקסים ולאחזר את מזהי האינדקסים:
gcloud firestore indexes composite list --database=database-id
מחליפים את database-id במזהה של מסד הנתונים.
אפשר להשתמש במזהה האינדקס כדי לראות פרטים נוספים על אינדקס:
gcloud firestore indexes composite describe index-id --database=database-id
where:
- index-id הוא המזהה של האינדקס שרוצים לתאר.
- database-id הוא המזהה של מסד הנתונים.
מחיקת אינדקס וקטורי
מסוף Google Cloud
- נכנסים לדף Databases במסוף Google Cloud.
- בוחרים את מסד הנתונים הרצוי מרשימת מסדי הנתונים.
-
בתפריט הניווט, לוחצים על Indexes (אינדקסים) ואז על הכרטיסייה Manual (ידני).
- ברשימת האינדקסים הידניים, לוחצים על לחצן האפשרויות הנוספות לצד האינדקס שרוצים למחוק. לוחצים על מחיקה.
- כדי לאשר את מחיקת האינדקס, לוחצים על מחיקת האינדקס בהתראה.
gcloud
gcloud firestore indexes composite delete index-id --database=database-id
where:
- index-id הוא המזהה של האינדקס שרוצים למחוק.
כדי לאחזר את מזהה האינדקס, משתמשים ב-
indexes composite list. - database-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: מדידתEUCLIDEANהמרחק בין הווקטורים. מידע נוסף זמין במאמר בנושא מרחק אוקלידי. -
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
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();
אחזור המרחק הווקטורי המחושב
כדי לאחזר את המרחק הווקטורי המחושב, צריך להקצות שם מאפיין פלט distance_result_field בשאילתה FindNearest, כמו בדוגמה הבאה:
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_PRODUCTdistance מגביל את ערך הסף למסמכים שבהם המרחק גדול מערך הסף שצוין או שווה לו. המרחקים של המכפלה הסקלרית גדלים ככל שהווקטורים דומים יותר.
בדוגמה הבאה מוצג אופן השימוש במדד המרחק EUCLIDEAN כדי לציין סף מרחק להחזרת עד 10 מסמכים קרובים ביותר שנמצאים במרחק של עד 4.5 יחידות:
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()); }
מגבלות
כשעובדים עם הטמעות וקטוריות, חשוב לשים לב להגבלות הבאות:
- המימד המקסימלי של הטמעת נתונים שנתמך הוא 2048. כדי לאחסן אינדקסים גדולים יותר, אפשר להשתמש בצמצום ממדים.
- המספר המקסימלי של מסמכים שיוחזרו משאילת השכן הקרוב ביותר הוא 1,000.
- חיפוש וקטורי לא תומך במאזינים לצילום מצב בזמן אמת.
- רק ספריות הלקוח של Python, Node.js, Go ו-Java תומכות בחיפוש וקטורי.
המאמרים הבאים
- שיטות מומלצות לשימוש ב-Cloud Firestore
- הבנת קריאות וכתיבות בקנה מידה גדול.