פיתוח צ'אט בוט באמצעות Gemma

להצגה ב-ai.google.dev הפעלה ב-Google Colab פתיחה ב-Vertex AI הצגת המקור ב-GitHub

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

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

במדריך הזה תלמדו איך לפתח צ'אט בוט באמצעות וריאנט של מודל Gemma שמכוונן לפי הוראות.

הגדרה

הגדרת Gemma

כדי להשלים את המדריך, צריך קודם לבצע את הוראות ההגדרה שמפורטות במאמר הגדרת Gemma. בהוראות ההגדרה של Gemma מוסבר איך לבצע את הפעולות הבאות:

  • אפשר לקבל גישה ל-Gemma בכתובת kaggle.com.
  • בחירת זמן ריצה של Colab עם מספיק משאבים להרצה במודל Gemma 2B.
  • יצירה והגדרה של שם משתמש ומפתח API ב-Kaggle.

אחרי שמשלימים את ההגדרה של Gemma, עוברים לקטע הבא, שבו מגדירים משתני סביבה לסביבת Colab.

הגדרה של משתני סביבה

הגדרה של משתני סביבה בשביל KAGGLE_USERNAME ו-KAGGLE_KEY.

import os
from google.colab import userdata

# Note: `userdata.get` is a Colab API. If you're not using Colab, set the env
# vars as appropriate for your system.
os.environ["KAGGLE_USERNAME"] = userdata.get('KAGGLE_USERNAME')
os.environ["KAGGLE_KEY"] = userdata.get('KAGGLE_KEY')

יחסי תלות בהתקנות

מתקינים את Keras ו-KerasNLP.

# Install Keras 3 last. See https://keras.io/getting_started/ for more details.
pip install -q tensorflow-cpu
pip install -q -U keras-nlp tensorflow-hub
pip install -q -U "keras>=3"
pip install -q -U tensorflow-text

בחירת קצה עורפי

Keras הוא ממשק API ללמידה עמוקה (Deras) ברמה גבוהה של כמה פריימים, שנועד לפשט וקלות שימוש. ב-Keras 3 אפשר לבחור את הקצה העורפי: TensorFlow, JAX או PyTorch. כל השלושה יתאימו למדריך הזה.

import os

# Select JAX as the backend
os.environ["KERAS_BACKEND"] = "jax"

# Pre-allocate 100% of TPU memory to minimize memory fragmentation
os.environ["XLA_PYTHON_CLIENT_MEM_FRACTION"] = "1.0"

ייבוא חבילות

לייבא את Keras ו-KerasNLP.

import keras
import keras_nlp

# for reproducibility
keras.utils.set_random_seed(42)

יצירת המודל

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

יוצרים מופע של המודל באמצעות השיטה from_preset:

gemma_lm = keras_nlp.models.GemmaCausalLM.from_preset("gemma2_instruct_2b_en")

הפונקציה GemmaCausalLM.from_preset() יוצרת את המודל מארכיטקטורה ומשקולות מוגדרות מראש. בקוד שלמעלה, המחרוזת "gemma2_instruct_2b_en" מציינת את ההגדרה הקבועה מראש של מודל Gemma 2 2B עם 2 מיליארד פרמטרים. יש גם מודלים של Gemma עם פרמטרים של 7B, 9B ו-27B. מחרוזות הקוד של דגמים של Gemma מופיעים בכרטיסי המוצר של וריאציית המודל ב-Kaggle.

משתמשים בשיטה summary כדי לקבל מידע נוסף על המודל:

gemma_lm.summary()

כמו שאפשר לראות בסיכום, למודל יש 2.6 מיליארד פרמטרים שניתן לאמן.

הגדרת פונקציות עזר לעיצוב

from IPython.display import Markdown
import textwrap

def display_chat(prompt, text):
  formatted_prompt = "<font size='+1' color='brown'>🙋‍♂️<blockquote>" + prompt + "</blockquote></font>"
  text = text.replace('', '  *')
  text = textwrap.indent(text, '> ', predicate=lambda _: True)
  formatted_text = "<font size='+1' color='teal'>🤖\n\n" + text + "\n</font>"
  return Markdown(formatted_prompt+formatted_text)

def to_markdown(text):
  text = text.replace('', '  *')
  return Markdown(textwrap.indent(text, '> ', predicate=lambda _: True))

פיתוח הצ'אט בוט

המודל gemma2_instruct_2b_en שעבר כוונון לפי הוראה של Gemma עבר כוונון עדין כדי להבין את אסימוני הפנייה הבאים:

<start_of_turn>user\n  ... <end_of_turn>\n
<start_of_turn>model\n ... <end_of_turn>\n

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

יצירת עוזר דיגיטלי בצ'אט לניהול מצב השיחה

class ChatState():
  """
  Manages the conversation history for a turn-based chatbot
  Follows the turn-based conversation guidelines for the Gemma family of models
  documented at https://ai.google.dev/gemma/docs/formatting
  """

  __START_TURN_USER__ = "<start_of_turn>user\n"
  __START_TURN_MODEL__ = "<start_of_turn>model\n"
  __END_TURN__ = "<end_of_turn>\n"

  def __init__(self, model, system=""):
    """
    Initializes the chat state.

    Args:
        model: The language model to use for generating responses.
        system: (Optional) System instructions or bot description.
    """
    self.model = model
    self.system = system
    self.history = []

  def add_to_history_as_user(self, message):
      """
      Adds a user message to the history with start/end turn markers.
      """
      self.history.append(self.__START_TURN_USER__ + message + self.__END_TURN__)

  def add_to_history_as_model(self, message):
      """
      Adds a model response to the history with start/end turn markers.
      """
      self.history.append(self.__START_TURN_MODEL__ + message)

  def get_history(self):
      """
      Returns the entire chat history as a single string.
      """
      return "".join([*self.history])

  def get_full_prompt(self):
    """
    Builds the prompt for the language model, including history and system description.
    """
    prompt = self.get_history() + self.__START_TURN_MODEL__
    if len(self.system)>0:
      prompt = self.system + "\n" + prompt
    return prompt

  def send_message(self, message):
    """
    Handles sending a user message and getting a model response.

    Args:
        message: The user's message.

    Returns:
        The model's response.
    """
    self.add_to_history_as_user(message)
    prompt = self.get_full_prompt()
    response = self.model.generate(prompt, max_length=2048)
    result = response.replace(prompt, "")  # Extract only the new response
    self.add_to_history_as_model(result)
    return result

שיחה בצ'אט עם המודל

מתחילים להתכתב בצ'אט עם המודל.

chat = ChatState(gemma_lm)
message = "Tell me, in a few words,  how to compute all prime numbers up to 1000?"
display_chat(message, chat.send_message(message))

🙋‍♂️

איך לחשב את כל המספרים הראשוניים עד 1000?
🤖

Sieve of Eratosthenes.

ממשיכים את השיחה.

message = "Now in Python! No numpy, please!"
display_chat(message, chat.send_message(message))

🙋‍♂️

עכשיו ב-Python! בלי מספרי,
🤖

def sieve_of_eratosthenes(n):
  """Returns a list of prime numbers up to n."""
  primes = [True] * (n + 1)
  primes[0] = primes[1] = False
  for i in range(2, int(n**0.5) + 1):
    if primes[i]:
      for j in range(i * i, n + 1, i):
        primes[j] = False
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)

הסבר:

  1. אתחול:

    • primes = [True] * (n + 1): יוצר רשימה primes של ערכים בוליאניים, בהנחה שכל המספרים הם ראשוניים.
    • primes[0] = primes[1] = False: מגדיר את 0 ו-1 כלא ראשוניים.
  2. חזרה:

    • for i in range(2, int(n**0.5) + 1):: איטרציה מ-2 לשורש הריבועי של n. צריך לבדוק רק עד השורש הריבועי, כי לכל מספר מרוכב צריך להיות גורם ראשוני שקטן מהשורש הריבועי או שווה לו.
    • if primes[i]:: אם i מסומן כראשי:
      • for j in range(i * i, n + 1, i):: סימון כל הכפולות של i כ'לא ראשוניים'.
  3. תוצאה:

    • return [i for i, is_prime in enumerate(primes) if is_prime]: יצירת רשימה של מדדים שבהם primes[i] מוגדר כ-True, שמייצגים את המספרים הראשוניים.

אשמח לקבל הסבר מפורט על חלק כלשהו.

message = "Thank you, it works! Can you explain the code in French?"
display_chat(message, chat.send_message(message))

🙋‍♂️

תודה, זה עובד! יש לך אפשרות להסביר את הקוד בצרפתית?
🤖

Bien sÖr ! Voici une explication du code en français :

def sieve_of_eratosthenes(n):
  """Retourne une liste de nombres premiers jusqu'à n."""
  primes = [True] * (n + 1)
  primes[0] = primes[1] = False
  for i in range(2, int(n**0.5) + 1):
    if primes[i]:
      for j in range(i * i, n + 1, i):
        primes[j] = False
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)

הסבר:

  1. אתחול:

    • primes = [True] * (n + 1): Crée une liste primes de valeurs booléennes, primary supposant que tous les nombres sont Premiers.
    • primes[0] = primes[1] = False: Définit 0 et 1 comme non-Premiers.
  2. חזרה:

    • for i in range(2, int(n**0.5) + 1):: Itère de 2 jusqu'à la racine carrée de n. Nous ne devons vérifier que jusqu'à la racine carrée car tout nombre האחרים doit avoir unFacteur premier inférieur ou égal à sa racine carrée.
    • if primes[i]:: Si i est considéré comme premier:
      • for j in range(i * i, n + 1, i):: Marquer tous les multis de i comme לא שידורי בכורה.
  3. תוצאה:

    • return [i for i, is_prime in enumerate(primes) if is_prime]: Crée une liste des indices où primes[i] est vrai, représentant les nombres premiers.

N'hésitez pas à medemander si vous avez besoin d'une explication and détaillée de quelque choose!

message = "Great! Now add those explanations as comments in the code."
display_chat(message, chat.send_message(message))

🙋‍♂️

מעולה! עכשיו צריך להוסיף את ההסברים האלה כתגובות בקוד.
🤖

def sieve_of_eratosthenes(n):
  """Retourne une liste de nombres premiers jusqu'à n."""
  # Initialise une liste de boolean avec True pour tous les nombres de 0 à n
  primes = [True] * (n + 1)
  # Définit 0 et 1 comme non-premiers
  primes[0] = primes[1] = False
  # Itère de 2 à la racine carrée de n
  for i in range(2, int(n**0.5) + 1):
    # Si i est considéré comme premier
    if primes[i]:
      # Itère sur tous les multiples de i
      for j in range(i * i, n + 1, i):
        # Définit les multiples de i comme non-premiers
        primes[j] = False
  # Retourne la liste des indices des nombres premiers
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)

הסבר:

  • אתחול:
    • primes = [True] * (n + 1): Crée une liste primes de valeurs booléennes, primary supposant que tous les nombres sont Premiers.
    • primes[0] = primes[1] = False: Définit 0 et 1 comme non-Premiers.
  • חזרה:
    • for i in range(2, int(n**0.5) + 1):: Itère de 2 jusqu'à la racine carrée de n. Nous ne devons vérifier que jusqu'à la racine carrée car tout nombre האחרים doit avoir unFacteur premier inférieur ou égal à sa racine carrée.
    • if primes[i]:: Si i est considéré comme premier:
      • for j in range(i * i, n + 1, i):: Marquer tous les multis de i comme לא שידורי בכורה.
  • תוצאה:
    • return [i for i, is_prime in enumerate(primes) if is_prime]: Crée une liste des indices où primes[i] est vrai, représentant les nombres premiers.

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

def sieve_of_eratosthenes(n):
  """Retourne une liste de nombres premiers jusqu'à n."""
  # Initialise une liste de boolean avec True pour tous les nombres de 0 à n
  primes = [True] * (n + 1)
  # Définit 0 et 1 comme non-premiers
  primes[0] = primes[1] = False
  # Itère de 2 à la racine carrée de n
  for i in range(2, int(n**0.5) + 1):
    # Si i est considéré comme premier
    if primes[i]:
      # Itère sur tous les multiples de i
      for j in range(i * i, n + 1, i):
        # Définit les multiples de i comme non-premiers
        primes[j] = False
  # Retourne la liste des indices des nombres premiers
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]

אפשר להשתמש בשיטה get_history כדי לראות איך כל ההקשר נשמר על ידי המחלקה Chat.

print(chat.get_history())
<start_of_turn>user
Tell me, in a few words,  how to compute all prime numbers up to 1000?<end_of_turn>
<start_of_turn>model
**Sieve of Eratosthenes.** 
<end_of_turn><start_of_turn>user
Now in Python! No numpy, please!<end_of_turn>
<start_of_turn>model

```python
def sieve_of_eratosthenes(n):
  """Returns a list of prime numbers up to n."""
  primes = [True] * (n + 1)
  primes[0] = primes[1] = False
  for i in range(2, int(n**0.5) + 1):
    if primes[i]:
      for j in range(i * i, n + 1, i):
        primes[j] = False
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)
```

**Explanation:**

1. **Initialization:**
   - `primes = [True] * (n + 1)`: Creates a list `primes` of boolean values, initially assuming all numbers are prime.
   - `primes[0] = primes[1] = False`: Sets 0 and 1 as non-prime.

2. **Iteration:**
   - `for i in range(2, int(n**0.5) + 1):`: Iterates from 2 to the square root of `n`. We only need to check up to the square root because any composite number must have a prime factor less than or equal to its square root.
   - `if primes[i]:`: If `i` is marked as prime:
     - `for j in range(i * i, n + 1, i):`: Marks all multiples of `i` as non-prime.

3. **Result:**
   - `return [i for i, is_prime in enumerate(primes) if is_prime]`: Creates a list of indices where `primes[i]` is True, representing the prime numbers.


Let me know if you'd like a more detailed explanation of any part! 
<end_of_turn><start_of_turn>user
Thank you, it works! Can you explain the code in French?<end_of_turn>
<start_of_turn>model
Bien sûr ! Voici une explication du code en français :

```python
def sieve_of_eratosthenes(n):
  """Retourne une liste de nombres premiers jusqu'à n."""
  primes = [True] * (n + 1)
  primes[0] = primes[1] = False
  for i in range(2, int(n**0.5) + 1):
    if primes[i]:
      for j in range(i * i, n + 1, i):
        primes[j] = False
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)
```

**Explication:**

1. **Initialisation:**
   - `primes = [True] * (n + 1)`: Crée une liste `primes` de valeurs booléennes, initialement supposant que tous les nombres sont premiers.
   - `primes[0] = primes[1] = False`: Définit 0 et 1 comme non-premiers.

2. **Itération:**
   - `for i in range(2, int(n**0.5) + 1):`: Itère de 2 jusqu'à la racine carrée de `n`. Nous ne devons vérifier que jusqu'à la racine carrée car tout nombre composite doit avoir un facteur premier inférieur ou égal à sa racine carrée.
   - `if primes[i]:`: Si `i` est considéré comme premier:
     - `for j in range(i * i, n + 1, i):`:  Marquer tous les multiples de `i` comme non-premiers.

3. **Resultat:**
   - `return [i for i, is_prime in enumerate(primes) if is_prime]`: Crée une liste des indices où `primes[i]` est vrai, représentant les nombres premiers.


N'hésitez pas à me demander si vous avez besoin d'une explication plus détaillée de quelque chose! 
<end_of_turn><start_of_turn>user
Great! Now add those explanations as comments in the code.<end_of_turn>
<start_of_turn>model

```python
def sieve_of_eratosthenes(n):
  """Retourne une liste de nombres premiers jusqu'à n."""
  # Initialise une liste de boolean avec True pour tous les nombres de 0 à n
  primes = [True] * (n + 1)
  # Définit 0 et 1 comme non-premiers
  primes[0] = primes[1] = False
  # Itère de 2 à la racine carrée de n
  for i in range(2, int(n**0.5) + 1):
    # Si i est considéré comme premier
    if primes[i]:
      # Itère sur tous les multiples de i
      for j in range(i * i, n + 1, i):
        # Définit les multiples de i comme non-premiers
        primes[j] = False
  # Retourne la liste des indices des nombres premiers
  return [i for i, is_prime in enumerate(primes) if is_prime]

primes = sieve_of_eratosthenes(1000)
print(primes)
```

**Explication:**

* **Initialisation:**
    * `primes = [True] * (n + 1)`:  Crée une liste `primes` de valeurs booléennes, initialement supposant que tous les nombres sont premiers.
    * `primes[0] = primes[1] = False`: Définit 0 et 1 comme non-premiers.
* **Itération:**
    * `for i in range(2, int(n**0.5) + 1):`: Itère de 2 jusqu'à la racine carrée de `n`. Nous ne devons vérifier que jusqu'à la racine carrée car tout nombre composite doit avoir un facteur premier inférieur ou égal à sa racine carrée.
    * `if primes[i]:`: Si `i` est considéré comme premier:
        * `for j in range(i * i, n + 1, i):`:  Marquer tous les multiples de `i` comme non-premiers.
* **Resultat:**
    * `return [i for i, is_prime in enumerate(primes) if is_prime]`: Crée une liste des indices où `primes[i]` est vrai, représentant les nombres premiers. 



<end_of_turn>

סיכום וקריאה נוספת

במדריך הזה למדתם איך להתכתב בצ'אט עם המודל המכוונן לפי הנחיות Gemma 2B באמצעות Keras ב-JAX.

כדי לקבל מידע נוסף על Gemma, כדאי לעיין במדריכים ובמדריכים האלה: