Genera embedding con Sentence Transformers

Visualizza su ai.google.dev Esegui in Google Colab Esegui in Kaggle Apri in Vertex AI Visualizza l'origine su GitHub

EmbeddingGemma è un modello di embedding open source e leggero progettato per un recupero rapido e di alta qualità su dispositivi di uso quotidiano come i cellulari. Con soli 308 milioni di parametri, è abbastanza efficiente da eseguire tecniche di AI avanzate, come la generazione aumentata dal recupero (RAG), direttamente sulla tua macchina locale senza richiedere una connessione a internet.

Configurazione

Prima di iniziare questo tutorial, completa i seguenti passaggi:

  • Accedi a Gemma effettuando l'accesso a Hugging Face e selezionando Accetta licenza per un modello Gemma.
  • Genera un token di accesso Hugging Face e utilizzalo per accedere da Colab.

Questo notebook verrà eseguito su CPU o GPU.

Installa i pacchetti Python

Installa le librerie necessarie per eseguire il modello EmbeddingGemma e generare gli embedding. Sentence Transformers è un framework Python per gli incorporamenti di testo e immagini. Per saperne di più, consulta la documentazione di Sentence Transformers.

pip install -U sentence-transformers git+https://github.com/huggingface/transformers@v4.56.0-Embedding-Gemma-preview

Dopo aver accettato la licenza, è necessario un token Hugging Face valido per accedere al modello.

# Login into Hugging Face Hub
from huggingface_hub import login
login()

Carica modello

Utilizza le librerie sentence-transformers per creare un'istanza di una classe di modello con EmbeddingGemma.

import torch
from sentence_transformers import SentenceTransformer

device = "cuda" if torch.cuda.is_available() else "cpu"

model_id = "google/embeddinggemma-300M"
model = SentenceTransformer(model_id).to(device=device)

print(f"Device: {model.device}")
print(model)
print("Total number of parameters in the model:", sum([p.numel() for _, p in model.named_parameters()]))
Device: cuda:0
SentenceTransformer(
  (0): Transformer({'max_seq_length': 2048, 'do_lower_case': False, 'architecture': 'Gemma3TextModel'})
  (1): Pooling({'word_embedding_dimension': 768, 'pooling_mode_cls_token': False, 'pooling_mode_mean_tokens': True, 'pooling_mode_max_tokens': False, 'pooling_mode_mean_sqrt_len_tokens': False, 'pooling_mode_weightedmean_tokens': False, 'pooling_mode_lasttoken': False, 'include_prompt': True})
  (2): Dense({'in_features': 768, 'out_features': 3072, 'bias': False, 'activation_function': 'torch.nn.modules.linear.Identity'})
  (3): Dense({'in_features': 3072, 'out_features': 768, 'bias': False, 'activation_function': 'torch.nn.modules.linear.Identity'})
  (4): Normalize()
)
Total number of parameters in the model: 307581696

Generazione dell'incorporamento

Un embedding è una rappresentazione numerica di un testo, ad esempio una parola o una frase, che ne acquisisce il significato semantico. In sostanza, si tratta di un elenco di numeri (un vettore) che consente ai computer di comprendere le relazioni e il contesto delle parole.

Vediamo come EmbeddingGemma elaborerebbe tre parole diverse ["apple", "banana", "car"].

EmbeddingGemma è stato addestrato su grandi quantità di testo e ha appreso le relazioni tra parole e concetti.

words = ["apple", "banana", "car"]

# Calculate embeddings by calling model.encode()
embeddings = model.encode(words)

print(embeddings)
for idx, embedding in enumerate(embeddings):
  print(f"Embedding {idx+1} (shape): {embedding.shape}")
[[-0.18476306  0.00167681  0.03773484 ... -0.07996225 -0.02348064
   0.00976741]
 [-0.21189538 -0.02657359  0.02513712 ... -0.08042689 -0.01999852
   0.00512146]
 [-0.18924113 -0.02551468  0.04486253 ... -0.06377774 -0.03699806
   0.03973572]]
Embedding 1: (768,)
Embedding 2: (768,)
Embedding 3: (768,)

Il modello restituisce un vettore numerico per ogni frase. I vettori effettivi sono molto lunghi (768), ma per semplicità vengono presentati con poche dimensioni.

La chiave non sono i singoli numeri, ma la distanza tra i vettori. Se dovessimo tracciare questi vettori in uno spazio multidimensionale, i vettori per apple e banana sarebbero molto vicini tra loro. Il vettore per car sarebbe lontano dagli altri due.

Determinare la somiglianza

In questa sezione utilizziamo gli incorporamenti per determinare la somiglianza semantica tra diverse frasi. Di seguito sono riportati esempi con punteggi di somiglianza alti, medi e bassi.

  • Similarità elevata:

    • Frase A: "Lo chef ha preparato un pasto delizioso per gli ospiti".
    • Frase B: "Lo chef ha preparato una cena gustosa per i visitatori."
    • Motivazione: entrambe le frasi descrivono lo stesso evento utilizzando parole e strutture grammaticali diverse (forma attiva e passiva). Trasmettono lo stesso significato fondamentale.
  • Somiglianza media:

    • Frase A: "È un'esperta di machine learning".
    • Frase B: "Ha un profondo interesse per l'intelligenza artificiale".
    • Motivazione: le frasi sono correlate perché il machine learning è una branca secondaria dell'intelligenza artificiale. Tuttavia, parlano di persone diverse con diversi livelli di coinvolgimento (esperto e interesse).
  • Similarità bassa:

    • Frase A: "Oggi a Tokyo c'è il sole."
    • Frase B: "Devo comprare la spesa per la settimana".
    • Motivazione: le due frasi riguardano argomenti completamente diversi e non condividono alcuna sovrapposizione semantica.
# The sentences to encode
sentence_high = [
    "The chef prepared a delicious meal for the guests.",
    "A tasty dinner was cooked by the chef for the visitors."
]
sentence_medium = [
    "She is an expert in machine learning.",
    "He has a deep interest in artificial intelligence."
]
sentence_low = [
    "The weather in Tokyo is sunny today.",
    "I need to buy groceries for the week."
]

for sentence in [sentence_high, sentence_medium, sentence_low]:
  print("🙋‍♂️")
  print(sentence)
  embeddings = model.encode(sentence)
  similarities = model.similarity(embeddings[0], embeddings[1])
  print("`-> 🤖 score: ", similarities.numpy()[0][0])
🙋‍♂️
['The chef prepared a delicious meal for the guests.', 'A tasty dinner was cooked by the chef for the visitors.']
`-> 🤖 score:  0.8002148
🙋‍♂️
['She is an expert in machine learning.', 'He has a deep interest in artificial intelligence.']
`-> 🤖 score:  0.45417833
🙋‍♂️
['The weather in Tokyo is sunny today.', 'I need to buy groceries for the week.']
`-> 🤖 score:  0.22262995

Utilizzare i prompt con EmbeddingGemma

Per generare gli incorporamenti migliori con EmbeddingGemma, devi aggiungere un "prompt istruzionale" o un "task" all'inizio del testo di input. Questi prompt ottimizzano gli incorporamenti per attività specifiche, come il recupero di documenti o la risposta alle domande, e aiutano il modello a distinguere tra diversi tipi di input, come una query di ricerca e un documento.

Come applicare i prompt

Puoi applicare un prompt durante l'inferenza in tre modi.

  1. Utilizzo dell'argomento prompt
    Passa la stringa di prompt completa direttamente al metodo encode. In questo modo hai un controllo preciso.

    embeddings = model.encode(
        sentence,
        prompt="task: sentence similarity | query: "
    )
    
  2. Utilizzo dell'argomento prompt_name
    Seleziona un prompt predefinito in base al nome. Questi prompt vengono caricati dalla configurazione del modello o durante l'inizializzazione.

    embeddings = model.encode(sentence, prompt_name="STS")
    
  3. Utilizzo del prompt predefinito
    Se non specifichi prompt o prompt_name, il sistema utilizzerà automaticamente il prompt impostato come default_prompt_name. Se non è impostato alcun valore predefinito, non viene applicato alcun prompt.

    embeddings = model.encode(sentence)
    
print("Available tasks:")
for name, prefix in model.prompts.items():
  print(f" {name}: \"{prefix}\"")
print("-"*80)

for sentence in [sentence_high, sentence_medium, sentence_low]:
  print("🙋‍♂️")
  print(sentence)
  embeddings = model.encode(sentence, prompt_name="STS")
  similarities = model.similarity(embeddings[0], embeddings[1])
  print("`-> 🤖 score: ", similarities.numpy()[0][0])
Available tasks:
 query: "task: search result | query: "
 document: "title: none | text: "
 BitextMining: "task: search result | query: "
 Clustering: "task: clustering | query: "
 Classification: "task: classification | query: "
 InstructionRetrieval: "task: code retrieval | query: "
 MultilabelClassification: "task: classification | query: "
 PairClassification: "task: sentence similarity | query: "
 Reranking: "task: search result | query: "
 Retrieval: "task: search result | query: "
 Retrieval-query: "task: search result | query: "
 Retrieval-document: "title: none | text: "
 STS: "task: sentence similarity | query: "
 Summarization: "task: summarization | query: "
--------------------------------------------------------------------------------
🙋‍♂️
['The chef prepared a delicious meal for the guests.', 'A tasty dinner was cooked by the chef for the visitors.']
`-> 🤖 score:  0.9363755
🙋‍♂️
['She is an expert in machine learning.', 'He has a deep interest in artificial intelligence.']
`-> 🤖 score:  0.6425841
🙋‍♂️
['The weather in Tokyo is sunny today.', 'I need to buy groceries for the week.']
`-> 🤖 score:  0.38587403

Caso d'uso: Retrieval Augmented Generation (RAG)

Per i sistemi RAG, utilizza i seguenti valori di prompt_name per creare incorporamenti specializzati per le query e i documenti:

  • Per le query:utilizza prompt_name="Retrieval-query".

    query_embedding = model.encode(
        "How do I use prompts with this model?",
        prompt_name="Retrieval-query"
    )
    
  • Per Documenti:utilizza prompt_name="Retrieval-document". Per migliorare ulteriormente gli incorporamenti dei documenti, puoi anche includere un titolo utilizzando direttamente l'argomento prompt:

    • Con un titolo:
    doc_embedding = model.encode(
        "The document text...",
        prompt="title: Using Prompts in RAG | text: "
    )
    
    • Senza titolo:
    doc_embedding = model.encode(
        "The document text...",
        prompt="title: none | text: "
    )
    

Per approfondire

Classificazione

La classificazione è l'attività di assegnazione di un testo a una o più categorie o etichette predefinite. È una delle attività più fondamentali nell'elaborazione del linguaggio naturale (NLP).

Un'applicazione pratica della classificazione del testo è il routing dei ticket di assistenza clienti. Questa procedura indirizza automaticamente le query dei clienti al reparto corretto, risparmiando tempo e riducendo il lavoro manuale.

labels = ["Billing Issue", "Technical Support", "Sales Inquiry"]

sentence = [
  "Excuse me, the app freezes on the login screen. It won't work even when I try to reset my password.",
  "I would like to inquire about your enterprise plan pricing and features for a team of 50 people.",
]

# Calculate embeddings by calling model.encode()
label_embeddings = model.encode(labels, prompt_name="Classification")
embeddings = model.encode(sentence, prompt_name="Classification")

# Calculate the embedding similarities
similarities = model.similarity(embeddings, label_embeddings)
print(similarities)

idx = similarities.argmax(1)
print(idx)

for example in sentence:
  print("🙋‍♂️", example, "-> 🤖", labels[idx[sentence.index(example)]])
tensor([[0.4673, 0.5145, 0.3604],
        [0.4191, 0.5010, 0.5966]])
tensor([1, 2])
🙋‍♂️ Excuse me, the app freezes on the login screen. It won't work even when I try to reset my password. -> 🤖 Technical Support
🙋‍♂️ I would like to inquire about your enterprise plan pricing and features for a team of 50 people. -> 🤖 Sales Inquiry

Matryoshka Representation Learning (MRL)

EmbeddingGemma sfrutta MRL per fornire più dimensioni di incorporamento da un unico modello. Si tratta di un metodo di addestramento intelligente che crea un unico embedding di alta qualità in cui le informazioni più importanti sono concentrate all'inizio del vettore.

Ciò significa che puoi ottenere un embedding più piccolo, ma comunque molto utile, prendendo semplicemente le prime N dimensioni dell'embedding completo. L'utilizzo di incorporamenti più piccoli e troncati è molto più economico da archiviare e più rapido da elaborare, ma questa efficienza ha un costo: la potenziale qualità inferiore degli incorporamenti. MRL ti consente di scegliere il bilanciamento ottimale tra velocità e accuratezza per le esigenze specifiche della tua applicazione.

Utilizziamo tre parole ["apple", "banana", "car"] e creiamo incorporamenti semplificati per vedere come funziona MRL.

def check_word_similarities():
  # Calculate the embedding similarities
  print("similarity function: ", model.similarity_fn_name)
  similarities = model.similarity(embeddings[0], embeddings[1:])
  print(similarities)

  for idx, word in enumerate(words[1:]):
    print("🙋‍♂️ apple vs.", word, "-> 🤖 score: ", similarities.numpy()[0][idx])

# Calculate embeddings by calling model.encode()
embeddings = model.encode(words, prompt_name="STS")

check_word_similarities()
similarity function:  cosine
tensor([[0.7510, 0.6685]])
🙋‍♂️ apple vs. banana -> 🤖 score:  0.75102395
🙋‍♂️ apple vs. car -> 🤖 score:  0.6684626

Ora, per un'applicazione più rapida, non è necessario un nuovo modello. Basta troncare gli incorporamenti completi alle prime 512 dimensioni. Per risultati ottimali, ti consigliamo anche di impostare normalize_embeddings=True, che ridimensiona i vettori a una lunghezza unitaria di 1.

embeddings = model.encode(words, truncate_dim=512, normalize_embeddings=True)

for idx, embedding in enumerate(embeddings):
  print(f"Embedding {idx+1}: {embedding.shape}")

print("-"*80)
check_word_similarities()
Embedding 1: (512,)
Embedding 2: (512,)
Embedding 3: (512,)
--------------------------------------------------------------------------------
similarity function:  cosine
tensor([[0.7674, 0.7041]])
🙋‍♂️ apple vs. banana -> 🤖 score:  0.767427
🙋‍♂️ apple vs. car -> 🤖 score:  0.7040509

In ambienti estremamente vincolati, puoi ridurre ulteriormente gli incorporamenti a sole 256 dimensioni. Puoi anche utilizzare il prodotto scalare più efficiente per i calcoli di similarità anziché la similarità coseno standard.

model = SentenceTransformer(model_id, truncate_dim=256, similarity_fn_name="dot").to(device=device)
embeddings = model.encode(words, prompt_name="STS", normalize_embeddings=True)

for idx, embedding in enumerate(embeddings):
  print(f"Embedding {idx+1}: {embedding.shape}")

print("-"*80)
check_word_similarities()
Embedding 1: (256,)
Embedding 2: (256,)
Embedding 3: (256,)
--------------------------------------------------------------------------------
similarity function:  dot
tensor([[0.7855, 0.7382]])
🙋‍♂️ apple vs. banana -> 🤖 score:  0.7854644
🙋‍♂️ apple vs. car -> 🤖 score:  0.7382126

Riepilogo e passaggi successivi

Ora hai gli strumenti per generare incorporamenti di testo di alta qualità utilizzando EmbeddingGemma e la libreria Sentence Transformers. Applica queste competenze per creare funzionalità potenti come la similarità semantica, la classificazione del testo e i sistemi RAG (Retrieval-Augmented Generation) e continua a esplorare le possibilità offerte dai modelli Gemma.

Consulta i seguenti documenti: