Sentence Transformers を使用してエンベディングを生成する

ai.google.dev で表示 Google Colab で実行 Kaggle で実行 Vertex AI で開く GitHub でソースを見る

EmbeddingGemma は、モバイルなどの日常的なデバイスで高速かつ高品質な検索を実現するために設計された、軽量のオープン エンベディング モデルです。パラメータ数が 3 億 800 万個しかないため、検索拡張生成(RAG)などの高度な AI 手法をローカル マシンで直接実行するのに十分な効率性があり、インターネット接続は必要ありません。

セットアップ

このチュートリアルを開始する前に、次の手順を完了してください。

  • Hugging Face にログインし、Gemma モデルの [ライセンスを承認] を選択して、Gemma にアクセスします。
  • Hugging Face のアクセス トークンを生成し、それを使用して Colab からログインします。

このノートブックは CPU または GPU で実行されます。

Python パッケージをインストールする

EmbeddingGemma モデルの実行とエンベディングの生成に必要なライブラリをインストールします。Sentence Transformers は、テキストと画像のエンベディング用の Python フレームワークです。詳細については、Sentence Transformers のドキュメントをご覧ください。

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

ライセンスに同意したら、モデルにアクセスするための有効な Hugging Face トークンが必要です。

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

モデルを読み込む

sentence-transformers ライブラリを使用して、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

エンベディングの生成

エンベディングは、単語や文などのテキストの意味を捉えた数値表現です。基本的には、コンピュータが単語の関係とコンテキストを理解できるようにする数値のリスト(ベクトル)です。

EmbeddingGemma が 3 つの異なる単語 ["apple", "banana", "car"] をどのように処理するかを見てみましょう。

EmbeddingGemma は大量のテキストでトレーニングされており、単語とコンセプトの関係を学習しています。

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,)

モデルは、文ごとに数値ベクトルを出力します。実際のベクトルは非常に長い(768)ですが、わかりやすくするために、いくつかのディメンションで示しています。

キーは個々の数値ではなく、ベクトル間の距離です。これらのベクトルを多次元空間にプロットすると、applebanana のベクトルは非常に近い位置に配置されます。car のベクトルは他の 2 つのベクトルから離れています。

類似度を判断する

このセクションでは、エンベディングを使用して、異なる文のセマンティックな類似性を判断します。ここでは、類似度スコアが高い、中程度、低い例を示します。

  • 類似度が高い:

    • 文 A: 「シェフはゲストのために美味しい食事を用意した。」
    • 文 B: 「シェフが来客のために美味しい夕食を作った。」
    • 理由: どちらの文も、異なる単語と文法構造(能動態と受動態)を使用して同じイベントを説明しています。同じコアの意味を伝えます。
  • 中程度の類似度:

    • 文 A: 「彼女は機械学習の専門家です。」
    • 文 B: 「彼は人工知能に強い関心を持っています。」
    • 理由: ML は AI の一分野であるため、文は関連しています。ただし、エンゲージメントのレベル(専門家と興味のある人)が異なるさまざまなユーザーについて説明しています。
  • 類似度が低い:

    • 文 A: 「今日の東京の天気は晴れです。」
    • 文 B: 「今週の食料品を買う必要がある。」
    • 理由: 2 つの文はまったく関連性のないトピックに関するものであり、意味的な重複はありません。
# 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

EmbeddingGemma でプロンプトを使用する

EmbeddingGemma で最適なエンベディングを生成するには、入力テキストの先頭に「指示プロンプト」または「タスク」を追加する必要があります。これらのプロンプトは、ドキュメントの検索や質問応答などの特定のタスクに合わせてエンベディングを最適化し、モデルが検索クエリとドキュメントなどの異なる入力タイプを区別できるようにします。

プロンプトの適用方法

推論時にプロンプトを適用する方法は 3 つあります。

  1. prompt 引数を使用する
    プロンプト文字列全体を encode メソッドに直接渡します。これにより、きめ細かい制御が可能になります。

    embeddings = model.encode(
        sentence,
        prompt="task: sentence similarity | query: "
    )
    
  2. prompt_name 引数を使用する
    名前で事前定義されたプロンプトを選択します。これらのプロンプトは、モデルの構成から読み込まれるか、初期化中に読み込まれます。

    embeddings = model.encode(sentence, prompt_name="STS")
    
  3. デフォルトのプロンプトを使用する
    prompt または prompt_name のいずれも指定しない場合、システムは default_prompt_name として設定されたプロンプトを自動的に使用します。デフォルトが設定されていない場合は、プロンプトは適用されません。

    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

ユースケース: 検索拡張生成(RAG)

RAG システムの場合は、次の prompt_name 値を使用して、クエリとドキュメントの専用エンベディングを作成します。

  • クエリの場合: prompt_name="Retrieval-query" を使用します。

    query_embedding = model.encode(
        "How do I use prompts with this model?",
        prompt_name="Retrieval-query"
    )
    
  • ドキュメントの場合: prompt_name="Retrieval-document" を使用します。ドキュメント エンベディングをさらに改善するには、prompt 引数を直接使用してタイトルを含めることもできます。

    • タイトル付き:
    doc_embedding = model.encode(
        "The document text...",
        prompt="title: Using Prompts in RAG | text: "
    )
    
    • タイトルなし:
    doc_embedding = model.encode(
        "The document text...",
        prompt="title: none | text: "
    )
    

関連情報

分類

分類は、テキストを 1 つ以上の事前定義されたカテゴリまたはラベルに割り当てるタスクです。これは、自然言語処理(NLP)で最も基本的なタスクの 1 つです。

テキスト分類の実際の応用例としては、カスタマー サポート チケットのルーティングがあります。このプロセスにより、お客様の問い合わせが適切な部門に自動的に転送されるため、時間と手作業を節約できます。

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

マトリョーシカ表現学習(MRL)

EmbeddingGemma は MRL を活用して、1 つのモデルから複数のエンベディング サイズを提供します。これは、最も重要な情報がベクトルの先頭に集中する単一の高品質なエンベディングを作成する賢いトレーニング方法です。

つまり、完全なエンベディングの最初の N 次元を取得するだけで、小さくても非常に有用なエンベディングを取得できます。小さい切り捨てられたエンベディングを使用すると、保存コストが大幅に削減され、処理速度が向上しますが、エンベディングの品質が低下する可能性があります。MRL を使用すると、アプリケーションの特定のニーズに合わせて、この速度と精度の最適なバランスを選択できます。

3 つの単語 ["apple", "banana", "car"] を使用して、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

アプリケーションを高速化するために、新しいモデルは必要ありません。完全なエンベディングを最初の 512 次元に切り捨てるだけです。最適な結果を得るには、ベクトルを単位長 1 にスケーリングする normalize_embeddings=True を設定することもおすすめします。

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

制約の厳しい環境では、エンベディングを 256 ディメンションに短縮することもできます。類似度計算には、標準のコサイン類似度ではなく、より効率的な内積を使用することもできます。

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

まとめと次のステップ

これで、EmbeddingGemma と Sentence Transformers ライブラリを使用して高品質のテキスト エンベディングを生成できるようになりました。これらのスキルを適用して、セマンティック類似性、テキスト分類、検索拡張生成(RAG)システムなどの強力な機能を構築し、Gemma モデルで可能なことを引き続き探求します。

次のドキュメントもご覧ください。