משחקים עם Embeddings

03/11/2024

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

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

1. איך מחשבים Embedding

הרעיון הראשון והוא מאוד פשוט זה שבשביל לחשב Embedding רק צריך לשלוח טקסט למודל שפה גדול, במקרה שלנו ל ChatGPT. אני ברובי אז לקחתי ספריה שנקראת ruby-openai והשתמשתי בקוד שנראה ככה:

response = client.embeddings(
  parameters: {
    model: "text-embedding-3-large",
    input: "The food was delicious and the waiter..."
  }
)

puts response.dig("data", 0, "embedding")

בשביל לחשב Embeddings על פוסטים הפעלתי את הקוד הבא על 100 הפוסטים האחרונים שפרסמתי:

sources = blogPost.last(100).map(&:markdown_source)

embeddings2 = sources.map do |s|
  client.embeddings(parameters: {model: "text-embedding-3-large", input: s })
end

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

2. מרחק בין Embeddings

ראיתי ברשת כל מיני דרכים לחשב מרחקים בין Embeddings אבל נשמע שהפופולרית ביותר למצוא דברים שקשורים אחד לשני נקראת cosine similarity. הלכתי ל ChatGPT וביקשתי ממנו שיכתוב לי פונקציה ברובי לחשב את זה בין שני וקטורים:

def cosine_similarity(vec1, vec2)
  dot_product = vec1.zip(vec2).map { |a, b| a * b }.sum
  magnitude1 = Math.sqrt(vec1.map { |v| v**2 }.sum)
  magnitude2 = Math.sqrt(vec2.map { |v| v**2 }.sum)

  dot_product / (magnitude1 * magnitude2)
end

3. התוצאות

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

distances = embeddings2.map {|i| cosine_similarity(embeddings2[-1]["data"][0]["embedding"], i["data"][0]["embedding"])
 }

distances.each_index.select {|i| distances[i] > 0.5 } 

התוצאה כללה די הרבה פוסטים:

  1. איך עובד פרויקט Rails עם ESBuild
  2. אז OpenAI עברו לרמיקס
  3. חמש דקות עם flet
  4. ניסוי דינו - מה אפשר לעשות היום עם Deno ו next.js
  5. ניסוי ריילס: משחק איקס עיגול
  6. השתמשו באיזה ספריות שתרצו
  7. טיפ LLM - איך זה עובד ב X ?
  8. ריאקט או Vue - הדגמה דרך הוק קצר
  9. ואז זה נגמר
  10. ניסוי React - ואם היו Directives?

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

אחרי זה לקחתי את הפוסט "ניסוי דינו - מה אפשר לעשות היום עם Deno ו next.js" וחיפשתי באותה שיטה פוסטים קשורים. הגעתי ל:

  1. ה Killer Feature החדש של node.js (או: האם זה הסוף של דינו?)
  2. הונו הוא כל מה שרציתי מ express
  3. אז OpenAI עברו לרמיקס
  4. דוגמת דינו: שמירת תמונות מויקיפדיה

שזה כבר די מוצלח.

4. הצעד הבא

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