חיפוש דמיון עם Spanner ו-Vertex AI

1. מבוא

חידושים אחרונים בלמידה עמוקה (Deep Learning) אפשרו לייצג טקסט ונתונים אחרים באופן שמשקף משמעות סמנטית. התוצאה היא גישה חדשה לחיפוש, שנקראת 'חיפוש וקטורי', שמשתמש בייצוגים וקטוריים של טקסט (שנקראים הטמעות) כדי למצוא את המסמכים הרלוונטיים ביותר לשאילתה של המשתמש. חיפוש וקטורי עדיפה על פני חיפוש מסורתי אפליקציות כמו חיפוש ביגוד, שבו בדרך כלל המשתמשים מחפשים פריטים לפי התיאור, הסגנון או ההקשר, ולא לפי השם המדויק של המוצר או המותג. אנחנו יכולים לשלב את מסד הנתונים של Cloud Spanner עם חיפוש Vector כדי לבצע התאמת דמיון בין וקטורים. באמצעות שימוש ב-Spanner וב-Vector Search יחד, הלקוחות יכולים ליצור שילוב עוצמתי שמשלב את הזמינות, המהימנות וההיקף של Spanner עם יכולות מתקדמות של חיפוש דמיון – חיפוש מבוסס-Vertex AI Vector. את החיפוש הזה מבצעים השוואה של הטמעות של פריטים באינדקס של חיפוש וקטורי והחזרת ההתאמות הדומות ביותר.

תרחיש לדוגמה

נניח שאתם מדעני נתונים בחנות אופנה שמנסה להתעדכן בנתוני מגמות, חיפושי מוצרים והמלצות שמשתנים במהירות. האתגר הוא, אם יש לכם משאבים מוגבלים וסילואים של נתונים. בפוסט הזה בבלוג אנחנו מדגימים איך ליישם תרחיש לדוגמה של המלצה על פריטי לבוש באמצעות גישת חיפוש דמיון על נתוני פריטי לבוש.אלה השלבים שמתוארים:

  1. הנתונים מגיעים מ-Spanner
  2. וקטורים שנוצרו עבור נתוני פריטי הלבוש באמצעות ML.PREDICT ונשמרו ב-Spanner
  3. נתוני וקטורים של Spanner שמשולבים עם חיפוש וקטורי באמצעות משימות של תהליך עבודה ו-dataflow
  4. בוצע חיפוש וקטורי כדי למצוא התאמת דמיון לקלט שהוזן על ידי משתמש

אנחנו ניצור אפליקציית אינטרנט להדגמה כדי לבצע חיפוש פריטי לבוש על סמך טקסט שהוזן על ידי משתמשים. האפליקציה מאפשרת למשתמשים לחפש פריטי לבוש על ידי הזנת תיאור טקסט.

אינדקס חיפוש וקטורי של Spanner:

הנתונים של חיפוש ביגוד מאוחסנים ב-Spanner. אנחנו נפעיל את Embeddings API של Vertex AI במבנה ML.PREDICT ישירות מנתוני Spanner. לאחר מכן נשתמש במשימות ב-Dataflow וב-Workflow שמבצעות העלאות בכמות גדולה של הנתונים האלה (מלאי והטמעות) אל Vector Search של Vertex AI, ונרענן את האינדקס.

הרצת שאילתות משתמש באינדקס:

כשמשתמש מזין תיאור של פריטי לבוש, האפליקציה יוצרת את ההטמעות (embeddings) בזמן אמת באמצעות ה-API של Text Embeddings. לאחר מכן הנתונים האלה נשלחים כקלט ל-Vector Search API כדי למצוא 10 תיאורי מוצרים רלוונטיים מהאינדקס ולהציג את התמונה המתאימה.

סקירת הארכיטקטורה

הארכיטקטורה של אפליקציית Spanner- Vector Search מוצגת בתרשים הבא שכולל שני חלקים:

אינדקס Spanner לחיפוש וקטורי: a79932a25bee23a4.png

אפליקציית לקוח להרצת שאילתות משתמשים באינדקס:

b2b4d5a5715bd4c4.pngמה תפַתחו

Spanner לאינדקס וקטורי:

  • מסד נתונים של Spanner לאחסון ולניהול של נתוני המקור וההטמעות התואמות
  • משימה של תהליך עבודה שמעלה נתונים בכמות גדולה (מזהה והטמעות) למסד הנתונים של Vertex AI Vector Search.
  • Vector Search API המשמש למציאת תיאורי מוצרים רלוונטיים מהאינדקס.

הרצת שאילתות משתמש באינדקס:

  • אפליקציית אינטרנט שמאפשרת למשתמשים להזין תיאורי טקסט של פריטי לבוש, מבצעת חיפוש דמיון באמצעות נקודת הקצה של האינדקס שנפרסה ומחזירה את פריטי הלבוש הקרובים ביותר לקלט.

איך זה עובד

כשמשתמש מזין תיאור טקסט של פריטי לבוש, אפליקציית האינטרנט שולחת את התיאור אל Vector Search API. לאחר מכן, Vector Search API משתמש בהטמעות של תיאורי פריטי לבוש כדי למצוא את תיאורי המוצרים הרלוונטיים ביותר מהאינדקס. לאחר מכן, תיאורי המוצרים והתמונות הרלוונטיות יוצגו למשתמש. תהליך העבודה הכללי הוא:

  1. ליצור הטמעות לנתונים שמאוחסנים ב-Spanner.
  2. ייצוא והעלאה של הטמעות לאינדקס של חיפוש וקטורי.
  3. שולחים שאילתה לאינדקס של חיפוש וקטורי כדי למצוא פריטים דומים, על ידי ביצוע חיפוש בקרבת מקום קרוב.

2. דרישות

  • דפדפן כמו Chrome או Firefox
  • פרויקט ב-Google Cloud שמופעל בו חיוב

לפני שמתחילים

  1. במסוף Google Cloud, בדף בורר הפרויקטים, בוחרים או יוצרים פרויקט ב-Google Cloud.
  2. הקפידו לוודא שהחיוב מופעל בפרויקט שלכם ב-Cloud. איך בודקים אם החיוב מופעל בפרויקט
  3. לוודא שכל ממשקי ה-API הנדרשים (Cloud Spanner, Vertex AI, Google Cloud Storage) מופעלים.
  4. תשתמשו ב-Cloud Shell, סביבת שורת הפקודה שפועלת ב-Google Cloud שטעונה מראש ב-gcloud. עיינו במאמרי העזרה לפקודות ולשימוש ב-gcloud. אם הפרויקט לא מוגדר, מגדירים אותו באמצעות הפקודה הבאה:
gcloud config set project <YOUR_PROJECT_ID>
  1. כדי להתחיל, עוברים לדף Cloud Spanner עם הפרויקט הפעיל ב-Google Cloud.

3. הקצה העורפי: יצירת מקור נתונים והטמעות של Spanner

בתרחיש לדוגמה הזה, מסד הנתונים Spanner מכיל את מלאי פריטי הלבוש עם התמונות והתיאור המתאימים. מקפידים ליצור הטמעות לתיאור הטקסט ולשמור אותן במסד הנתונים של Spanner בתור ARRAY<float64>.

  1. יצירת הנתונים של Spanner

יוצרים מכונה בשם spanner-vertex ומסד נתונים בשם 'spanner-vertex-embeddings'. יוצרים טבלה באמצעות ה-DDL:

CREATE TABLE
  apparels ( id NUMERIC,
    category STRING(100),
    sub_category STRING(50),
    uri STRING(200),
    content STRING(2000),
    embedding ARRAY<FLOAT64>
    )
PRIMARY KEY
  (id);
  1. איך מוסיפים נתונים לטבלה באמצעות INSERT SQL

האפשרות להוסיף סקריפטים עבור נתונים לדוגמה זמינה כאן.

  1. יצירת מודל הטמעת טקסט

הפעולה הזו נדרשת כדי שנוכל ליצור הטמעות לתוכן בקלט. בהמשך מוצג ה-DDL לאותו ה-DDL:

CREATE MODEL text_embeddings INPUT(content STRING(MAX))
OUTPUT(
  embeddings
    STRUCT<
      statistics STRUCT<truncated BOOL, token_count FLOAT64>,
      values ARRAY<FLOAT64>>
)
REMOTE OPTIONS (
  endpoint = '//aiplatform.googleapis.com/projects/abis-345004/locations/us-central1/publishers/google/models/textembedding-gecko');
  1. ליצור הטמעות טקסט לנתוני המקור

יוצרים טבלה לאחסון ההטמעות ולהוספה של ההטמעות שנוצרו. באפליקציית מסד נתונים בעולם האמיתי, עומס הנתונים ל-Spanner עד לשלב 2 יהיה טרנזקציות. כדי לשמור על השיטות המומלצות לעיצוב, אני רוצה לשמור על נורמליזציה של טבלאות העסקאות. לכן, כדאי ליצור טבלה נפרדת להטמעות.

CREATE TABLE apparels_embeddings (id string(100), embedding ARRAY<FLOAT64>)
PRIMARY KEY (id);

INSERT INTO apparels_embeddings(id, embeddings) 
SELECT CAST(id as string), embeddings.values
FROM ML.PREDICT(
  MODEL text_embeddings,
  (SELECT id, content from apparels)
) ;

עכשיו, לאחר שהתוכן וההטמעות בכמות גדולה מוכנים, כדאי ליצור אינדקס חיפוש וקטורי ונקודת קצה (endpoint) כדי לאחסן את ההטמעות שיעזרו לבצע את החיפוש הווקטורי.

4. משימה בתהליך עבודה: ייצוא נתונים של Spanner לחיפוש וקטורי

  1. יצירת קטגוריה של Cloud Storage

ההרשאה הזו נדרשת כדי לאחסן הטמעות מ-Spanner בקטגוריה של GCS בפורמט JSON, שחיפוש וקטורי מצפה לקבל כקלט. יוצרים קטגוריה באותו אזור שבו נמצאים הנתונים ב-Spanner. אם צריך, יוצרים תיקייה שבתוכה, אבל יוצרים בה בעיקר קובץ ריק שנקרא empty.json.

  1. הגדרה של Cloud Workflow

כדי להגדיר ייצוא באצווה מ-Spanner לאינדקס של Vertex AI Vector Search:

יוצרים אינדקס ריק:

מוודאים שאינדקס החיפוש Vector נמצא באותו אזור כמו הקטגוריה של Cloud Storage והנתונים. פועלים לפי 11 השלבים של ההוראות בכרטיסיית המסוף בקטע יצירת אינדקס לעדכון מספר פריטים בדף 'ניהול אינדקסים'. בתיקייה שמועברת אל contentDeltaUri, יוצרים קובץ ריק בשם empty.json כי אי אפשר ליצור אינדקס בלי הקובץ הזה. הפעולה הזו יוצרת אינדקס ריק.

אם כבר יש לכם אינדקס, אתם יכולים לדלג על השלב הזה. תהליך העבודה יחליף את האינדקס שלך.

הערה: אי אפשר לפרוס אינדקס ריק בנקודת קצה. לכן אנחנו דוחים את השלב של פריסת הנתונים בנקודת קצה לשלב מאוחר יותר, אחרי ייצוא של נתוני הווקטור ל-Cloud Storage.

שכפול מאגר ה-Git הזה: יש כמה דרכים לשכפל מאגר git, דרך אחת היא להריץ את הפקודה הבאה באמצעות GitHub CLI. מריצים את 2 הפקודות הבאות מהטרמינל של Cloud Shell:

gh repo clone cloudspannerecosystem/spanner-ai

cd spanner-ai/vertex-vector-search/workflows

התיקייה מכילה שני קבצים

  • batch-export.yaml: זוהי ההגדרה של תהליך העבודה.
  • sample-batch-input.json: זוהי דוגמה לפרמטרים של קלט תהליך העבודה.

מגדירים קלט.json מהקובץ לדוגמה: בשלב הראשון מעתיקים את קובץ ה-JSON לדוגמה.

cp sample-batch-input.json משתנה.json

לאחר מכן עורכים את input.json עם פרטי הפרויקט. במקרה כזה, ב-json צריך להיראות כך:

{
  "project_id": "<<YOUR_PROJECT>>",
  "location": "<<us-central1>>",
  "dataflow": {
    "temp_location": "gs://<<YOUR_BUCKET>>/<<FOLDER_IF_ANY>>/workflow_temp"
  },
  "gcs": {
    "output_folder": "gs://<<YOUR_BUCKET>>/<<FOLDER_IF_ANY>>/workflow_output"
  },
  "spanner": {
    "instance_id": "spanner-vertex",
    "database_id": "spanner-vertex-embeddings",
    "table_name": "apparels_embeddings",
    "columns_to_export": "embedding,id"
  },
  "vertex": {
    "vector_search_index_id": "<<YOUR_INDEX_ID>>"
  }
}

הרשאות הגדרה

בסביבות ייצור, מומלץ מאוד ליצור חשבון שירות חדש ולהקצות לו תפקיד IAM אחד או יותר עם ההרשאות המינימליות הנדרשות לניהול השירות. התפקידים הבאים נדרשים כדי להגדיר את תהליך העבודה לייצוא נתונים מ-Spanner (הטמעה) אל אינדקס החיפוש הווקטורי:

חשבון שירות ב-Cloud Workflow:

כברירת מחדל הוא משתמש בחשבון השירות שמשמש כברירת המחדל של Compute Engine.

אם אתם משתמשים בחשבון שירות שהוגדר באופן ידני, צריך לכלול את התפקידים הבאים:

כדי להפעיל משימה ב-Dataflow: אדמין של Dataflow, Dataflow Worker.

כדי להתחזות לחשבון שירות של עובד ב-dataflow: משתמש בחשבון שירות.

כדי לכתוב יומנים: Logs Writer

כדי להפעיל מחדש את Vertex AI Vector Search: Vertex AI User.

חשבון שירות של Dataflow Worker:

אם אתם משתמשים בחשבון שירות שהוגדר באופן ידני, צריך לכלול את התפקידים הבאים:

כדי לנהל את תהליך הנתונים: Dataflow Admin, Dataflow Worker. כדי לקרוא נתונים מ-Spanner: קורא מסד הנתונים של Cloud Spanner. גישת כתיבה ב-GCS Container Registry: בעלים של קטגוריית אחסון ב-GCS.

  1. פריסת Cloud Workflow

פורסים את קובץ ה-yaml של תהליך העבודה בפרויקט ב-Google Cloud. אפשר להגדיר את האזור או המיקום שבהם תהליך העבודה יפעל בזמן ביצועו.

gcloud workflows deploy vector-export-workflow --source=batch-export.yaml --location="us-central1" [--service account=<service_account>]

or 

gcloud workflows deploy vector-export-workflow --source=batch-export.yaml --location="us-central1"

תהליך העבודה אמור להופיע עכשיו בדף Workflows במסוף Google Cloud.

הערה: אפשר ליצור את תהליך העבודה ולפרוס אותו גם ממסוף Google Cloud. פועלים לפי ההנחיות במסוף Cloud. להגדרת תהליך העבודה, מעתיקים ומדביקים את התוכן שלbat-export.yaml.

לאחר השלמת התהליך, מפעילים את תהליך העבודה כדי שייצוא הנתונים יתחיל.

  1. הפעלת תהליך העבודה ב-Cloud

מריצים את הפקודה הבאה כדי להפעיל את תהליך העבודה:

gcloud workflows execute vector-export-workflow --data="$(cat input.json)"

הביצוע אמור להופיע בכרטיסייה 'פעולות' בקטע 'תהליכי עבודה'. הפעולה הזו אמורה לטעון את הנתונים למסד הנתונים של חיפוש וקטורי ולהוסיף אותם לאינדקס.

הערה: אפשר להפעיל את הפקודה גם מהמסוף באמצעות הלחצן 'ביצוע'. פועלים לפי ההנחיות, ולאחר מכן מעתיקים ומדביקים את התוכן של הקלט המותאם אישית שהוזן.

5. פריסת אינדקס חיפוש וקטורי

פריסת האינדקס בנקודת קצה

כדי לפרוס את האינדקס, אפשר לבצע את הפעולות הבאות:

  1. בדף אינדקסים של חיפוש וקטורי, אמור להופיע לחצן DEPLOY לצד האינדקס שיצרתם בשלב 2 של הקטע הקודם. לחלופין, אפשר לעבור לדף פרטי האינדקס וללחוץ על הלחצן 'העברה לנקודת קצה'.
  2. מספקים את המידע הדרוש ופורסים את האינדקס בנקודת קצה.

לחלופין, אפשר לעיין ב-notebook הזה כדי לפרוס אותו בנקודת קצה (דלגו לחלק של הפריסה). לאחר הפריסה, חשוב לשים לב למזהה האינדקס שנפרס ולכתובת ה-URL של נקודת הקצה.

6. קצה קדמי: נתוני משתמש לחיפוש וקטורי

בואו נבנה אפליקציית Python פשוטה עם חוויית משתמש מבוססת gRadio כדי לבדוק במהירות את ההטמעה שלנו: אפשר לעיין בהטמעה כאן כדי להטמיע את אפליקציית ההדגמה הזאת ב-notebook של colab.

  1. נשתמש ב-SDK של aiplatform python לקריאה ל-Embeddings API, וגם להפעלת נקודת קצה של אינדקס חיפוש Vector.
# [START aiplatform_sdk_embedding]
!pip install google-cloud-aiplatform==1.35.0 --upgrade --quiet --user


import vertexai
vertexai.init(project=PROJECT_ID, location="us-central1")


from vertexai.language_models import TextEmbeddingModel


import sys
if "google.colab" in sys.modules:
    # Define project information
    PROJECT_ID = " "  # Your project id
    LOCATION = " "  # Your location 


    # Authenticate user to Google Cloud
    from google.colab import auth
    auth.authenticate_user()
  1. נשתמש ב-gRadio כדי להדגים את אפליקציית ה-AI שאנחנו מפתחים בקלות ובמהירות באמצעות ממשק משתמש. צריך להפעיל מחדש את סביבת זמן הריצה לפני הטמעת השלב הזה.
!pip install gradio
import gradio as gr
  1. לאחר הזנת הקלט של המשתמש, מפעילים את ממשק ה-API של הטמעות הטקסט. לשם כך, נשתמש במודל הטמעת הטקסט: textembedding-gecko@previous

השיטה הבאה מפעילה את המודל להטמעת טקסט ומחזירה את ההטמעות הווקטוריות של הטקסט שהוזן על ידי המשתמש:

def text_embedding(content) -> list:
    """Text embedding with a Large Language Model."""
    model = TextEmbeddingModel.from_pretrained("textembedding-gecko@latest")
    embeddings = model.get_embeddings(content)
    for embedding in embeddings:
        vector = embedding.values
        #print(f"Length of Embedding Vector: {len(vector)}")
    return vector

לבדיקה

text_embedding("red shorts for girls")

אתם אמורים לראות פלט דומה לזה (שימו לב שהתמונה נחתכה לגובה כך שלא ניתן לראות את תגובת הווקטור במלואה):

5d8355ec04dac1f9.png

  1. צריך להצהיר על מזהה האינדקס שנפרס והמזהה של נקודת הקצה
from google.cloud import aiplatform
DEPLOYED_INDEX_ID = "spanner_vector1_1702366982123"
#Vector Search Endpoint
index_endpoint = aiplatform.MatchingEngineIndexEndpoint('projects/273845608377/locations/us-central1/indexEndpoints/2021628049526620160')
  1. צריך להגדיר את שיטת החיפוש Vector כדי לקרוא לנקודת הקצה של האינדקס ולהציג את התוצאה עם 10 ההתאמות הקרובות ביותר לתגובת ההטמעה שתואמת לטקסט שהוזן על ידי משתמש.

בהגדרת השיטה הבאה של חיפוש וקטורי, שימו לב שהשיטה find_neighbors מופעלת כדי לזהות את 10 הווקטורים הקרובים ביותר.

def vector_search(content) -> list:
  result = text_embedding(content)
  #call_vector_search_api(content)
  index_endpoint = aiplatform.MatchingEngineIndexEndpoint('projects/273845608377/locations/us-central1/indexEndpoints/2021628049526620160')
  # run query
  response = index_endpoint.find_neighbors(
      deployed_index_id = DEPLOYED_INDEX_ID,
      queries = [result],
      num_neighbors = 10
  )
  out = []
  # show the results
  for idx, neighbor in enumerate(response[0]):
      print(f"{neighbor.distance:.2f} {spanner_read_data(neighbor.id)}")
      out.append(f"{spanner_read_data(neighbor.id)}")
  return out

תבחינו גם בקריאה לשיטה spanner_read_data. בואו נבחן אותה בשלב הבא.

  1. מגדירים את ההטמעה של שיטת 'קריאת נתונים' ב-Spanner שמפעילה את השיטה Activate_sql כדי לחלץ את התמונות שתואמות למזהים של הווקטורים השכנים הקרובים ביותר, שמוחזרים מהשלב האחרון.
!pip install google-cloud-spanner==3.36.0


from google.cloud import spanner


instance_id = "spanner-vertex"
database_id = "spanner-vertex-embeddings"
projectId = PROJECT_ID
client = spanner.Client()
client.project = projectId
instance = client.instance(instance_id)
database = instance.database(database_id)
def spanner_read_data(id):
    query = "SELECT uri FROM apparels where id = " + id
    outputs = []
    with database.snapshot() as snapshot:
        results = snapshot.execute_sql(query)


        for row in results:
            #print(row)
            #output = "ID: {}, CONTENT: {}, URI: {}".format(*row)
            output = "{}".format(*row)
            outputs.append(output)


    return "\n".join(outputs)

היא אמורה להחזיר את כתובות ה-URL של התמונות שתואמות לווקטורים שנבחרו.

  1. לסיום, נאחד את החלקים בממשק משתמש ונפעיל את תהליך החיפוש הווקטורי
from PIL import Image


def call_search(query):
  response = vector_search(query)
  return response


input_text = gr.Textbox(label="Enter your query. Examples: Girls Tops White Casual, Green t-shirt girls, jeans shorts, denim skirt etc.")
output_texts = [gr.Image(label="") for i in range(10)]
demo = gr.Interface(fn=call_search, inputs=input_text, outputs=output_texts, live=True)
resp = demo.launch(share = True)

התוצאה אמורה להופיע באופן הבא:

8093b39fbab1a9cc.png

תמונה: קישור

אפשר לצפות בסרטון התוצאה: כאן.

7. הסרת המשאבים

כדי לא לצבור חיובים לחשבון Google Cloud עבור המשאבים שבהם השתמשתם בפוסט הזה:

  1. במסוף Google Cloud, נכנסים לדף Manage resources:
  2. ברשימת הפרויקטים, בוחרים את הפרויקט שרוצים למחוק ולוחצים על Delete.
  3. כדי למחוק את הפרויקט, כותבים את מזהה הפרויקט בתיבת הדו-שיח ולוחצים על Shut down.
  4. אם לא רוצים למחוק את הפרויקט, מוחקים את המופע של Spanner: עוברים למכונה שיצרתם כרגע בפרויקט ולוחצים על DELETE INSTANCE בפינה הימנית העליונה של דף הסקירה הכללית של המכונה.
  5. אפשר גם לעבור אל האינדקס של Vector Search, לבטל את הפריסה של נקודת הקצה והאינדקס ולמחוק את האינדקס.

8. סיכום

מעולה! השלמת בהצלחה את ההטמעה של Spanner – Vertex Vector Search על ידי

  1. יצירת מקור נתונים של Spanner והטמעות עבור אפליקציות שמקורן ממסד הנתונים של Spanner.
  2. יצירת אינדקס של מסד נתונים Vector Search.
  3. שילוב של נתוני וקטורים מ-Spanner לחיפוש וקטורי באמצעות משימות Dataflow ו-Workflow.
  4. פריסת אינדקס בנקודת קצה (endpoint).
  5. לבסוף, הפעלה של חיפוש וקטורי בקלט של משתמשים בהטמעה מבוססת Python של Vertex AI sdk.

אפשר להרחיב את ההטמעה לתרחיש לדוגמה שלכם או לאלתר את התרחיש לדוגמה הנוכחי עם תכונות חדשות. מידע נוסף על יכולות למידת המכונה של Spanner זמין כאן.