Google発のマルチモーダル埋め込みモデル「Gemini Embedding 2」を実際に試してみました。テキスト・画像を同じベクトル空間に埋め込み、テキストで画像を検索するクロスモーダル検索をPythonコード付きで解説します。
目次
1. Gemini Embedding 2 とは
2026年3月10日に Google が公開したgemini-embedding-2-previe は、Gemini API 初のマルチモーダル埋め込みモデルです。テキスト、画像、動画、音声、PDFを同じ埋め込み空間にマッピングできるため、テキストで画像を探す、音声から関連ドキュメントを探す、PDFと画像を横断して検索するといったクロスモーダル検索を、1つのモデルで実装できます。対応言語も100超で、RAG、検索、分類、クラスタリングまで幅広く使えるのが魅力です。
何が新しいのか
従来の gemini-embedding-001 はテキスト専用でしたが、Gemini Embedding 2 は画像・動画・音声・PDF まで対象を広げました。しかも単に入力形式が増えただけではなく、すべてを同じ埋め込み空間に載せられるのがポイントです。これによって、「テキストクエリで画像を検索する」「PDFページと商品画像を同じインデックスで扱う」といった使い方が自然にできます。
ただし、gemini-embedding-001 と gemini-embedding-2-preview の埋め込み空間には互換性がありません。既存のベクトルDBや検索インデックスを移行するなら、旧モデルで作った埋め込みはそのまま比較できないため、全件再埋め込みが前提になります。
現時点で確認できる対応範囲
- ・テキストは最大 8,192トークン。
- ・画像は 1リクエスト最大6枚、形式は PNG / JPEG。
- ・音声は 最大80秒、形式は MP3 / WAV。
- ・PDF は 1ファイル最大6ページ。
- ・動画は公式資料に差分があり、Gemini API 側では 最大128秒、Vertex AI 側では 音声付き80秒 / 無音120秒 と案内されています。Preview 段階なので、実運用では余裕を持って短めに分割して使うのが無難です。
- ・出力次元数はデフォルト3072次元であり、1536や768次元も対応しています。
2. 環境構築
ステップ1:APIキーの取得
Gemini API を使うなら、Google AI Studio で API キーを作成し、Python では公式SDKの google-genai を入れるのが最短です。gemini-embedding-2-preview には Gemini Developer API の無料枠もあります。Google 公式も、まずは Gemini Developer API を基本ルートとして案内しています。
ステップ2:Pythonライブラリのインストール
pip install google-genaiステップ3:環境変数の設定
export GEMINI_API_KEY="your-key-here"ステップ4:クライアントの初期化
import os
import numpy as np
from google import genai
from google.genai import types
client = genai.Client(api_key=os.environ.get("GEMINI_API_KEY"))
MODEL = "gemini-embedding-2-preview"3. テキスト埋め込みと意味検索
最初のステップとして、テキストだけで基本的な意味検索を動かしてみます。
ステップ1:埋め込み関数とコサイン類似度を定義
def embed_text(text: str, task_type: str = "RETRIEVAL_DOCUMENT", dims: int = 768):
"""テキストを埋め込みベクトルに変換"""
result = client.models.embed_content(
model=MODEL,
contents=text,
config=types.EmbedContentConfig(
task_type=task_type,
output_dimensionality=dims,
),
)
return result.embeddings[0].values
def cosine_sim(a, b):
"""2つのベクトルのコサイン類似度を計算(-1〜1、1に近いほど類似)"""
a, b = np.array(a), np.array(b)
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))ステップ2:ドキュメントをインデックス化
docs = [
"東京タワーは1958年に完成した赤い鉄塔です。",
"スカイツリーは634メートルの世界一高い電波塔です。",
"寿司は日本を代表する料理のひとつです。",
"富士山は標高3776メートルの日本最高峰です。",
]
doc_vecs = [embed_text(d, task_type="RETRIEVAL_DOCUMENT") for d in docs]
ステップ3:テキストクエリで検索
query = "東京の高い建物について教えて"
q_vec = embed_text(query, task_type="RETRIEVAL_QUERY")
scores = [(cosine_sim(q_vec, dv), doc) for dv, doc in zip(doc_vecs, docs)]
for score, doc in sorted(scores, reverse=True):
print(f" {score:.4f} | {doc}")ステップ4:実行結果
クエリ「東京の高い建物」の検索結果は以下のようになりました。
クエリ: 「東京の高い建物」
--- 意味検索結果 ---
0.7180 | 東京タワーは1958年に完成した赤い鉄塔です。
0.7176 | スカイツリーは634メートルの世界一高い電波塔です。
0.6541 | 富士山は標高3776メートルの日本最高峰です。
0.5491 | 寿司は日本を代表する料理のひとつです。
「東京の高い建物」というクエリに対して、「スカイツリー」「東京タワー」が上位に来ています。クエリに「スカイツリー」という単語が含まれていなくても意味の近さでヒットする。これがキーワード検索との決定的な違いです。「富士山」も「高い」という意味的な繋がりで中程度のスコアが付き、無関係な「寿司」はしっかり低スコアになっています。
4. 画像の埋め込みとクロスモーダル検索
ここからがGemini Embedding 2の真骨頂です。画像もテキストと同じベクトル空間に埋め込めるため、テキストで画像を検索できます。
ステップ1:画像埋め込み関数を定義
from pathlib import Path
def embed_image(image_path: str) -> list[float]:
"""1枚の画像 → ベクトル"""
with open(image_path, "rb") as f:
data = f.read()
suffix = Path(image_path).suffix.lower()
mime = "image/jpeg" if suffix in (".jpg", ".jpeg") else "image/png"
result = client.models.embed_content(
model=MODEL,
contents=[types.Part.from_bytes(data=data, mime_type=mime)],
config=types.EmbedContentConfig(
task_type="RETRIEVAL_DOCUMENT",
output_dimensionality=768,
),
)
return result.embeddings[0].values
画像の渡し方はシンプルで、ファイルをバイト列で読み込んで Part.from_bytes に渡すだけです。PIL.Imageへの変換は不要です。
ステップ2:フォルダ内の画像をインデックス化
def build_image_index(image_dir: str) -> list[dict]:
"""フォルダ内の画像を全てベクトル化"""
index = []
extensions = {".jpg", ".jpeg", ".png"}
paths = [p for p in Path(image_dir).iterdir() if p.suffix.lower() in extensions]
for path in paths:
vec = embed_image(str(path))
index.append({"path": str(path), "vector": vec})
print(f" indexed: {path.name}")
return indexステップ3:テキストクエリで画像を検索
def search_images_by_text(query: str, index: list[dict], top_k: int = 5):
"""テキストクエリで画像を検索"""
result = client.models.embed_content(
model=MODEL,
contents=query,
config=types.EmbedContentConfig(
task_type="RETRIEVAL_QUERY",
output_dimensionality=768,
),
)
q_vec = result.embeddings[0].values
scored = []
for item in index:
score = cosine_sim(q_vec, item["vector"])
scored.append((score, item["path"]))
scored.sort(reverse=True)
return scored[:top_k]ステップ4:実行結果
7枚の画像(東京タワー×2、スカイツリー、富士山、寿司、りんご、猫)を用意してテキストクエリで検索しました。
- 「食べ物の写真」 で検索:
クエリ: 「食べ物の写真」
0.3817 | sushi.jpg
0.3395 | apple.jpg
0.3085 | Fuji.jpg 0.3007 | cat.jpg
0.2979 | tokyotower_black.jpg
0.2951 | tokyotower_blue.jpg
0.2653 | skytree.jpg寿司とりんごがトップ2。食べ物に関連する画像が上位に来ています。
2. 「赤いもの」 で検索:
クエリ: 「赤いもの」
0.3705 | apple.jpg
0.3474 | tokyotower_blue.jpg
0.3363 | tokyotower_black.jpg
0.3295 | sushi.jpg
0.3018 | Fuji.jpg
0.2864 | cat.jpg
0.2825 | skytree.jpg赤いりんご、赤い東京タワーが上位。色という抽象的な概念でも画像を引っ張れています。
3. 「かわいいもの」 で検索:
クエリ: 「かわいいもの」
0.3273 | cat.jpg
0.3183 | sushi.jpg
0.2986 | apple.jpg
0.2956 | Fuji.jpg
0.2859 | tokyotower_black.jpg
0.2844 | tokyotower_blue.jpg
0.2716 | skytree.jpg猫がトップ。「かわいい」のような主観的・感覚的なクエリでも、それなりに意味のある結果が返ってくるのは面白いところです。
5. テキスト+画像の複合埋め込み
Gemini Embedding 2は、テキストと画像を組み合わせて1つの統合ベクトルを生成することもできます。画像だけの埋め込みと比べてどれくらい精度が変わるのか、実際に比較してみました。
ステップ1:複合埋め込み関数を定義
def embed_text_and_image(text: str, image_path: str) -> list[float]:
"""テキスト + 画像 → 1つの統合ベクトル"""
with open(image_path, "rb") as f:
data = f.read()
suffix = Path(image_path).suffix.lower()
mime = "image/jpeg" if suffix in (".jpg", ".jpeg") else "image/png"
result = client.models.embed_content(
model=MODEL,
contents=[
types.Content(
parts=[
types.Part(text=text),
types.Part.from_bytes(data=data, mime_type=mime),
]
)
],
config=types.EmbedContentConfig(
output_dimensionality=768,
),
)
return result.embeddings[0].values
今回はファイル名をそのままテキストとして入力し、画像のみの埋め込みと比較しました。
ステップ2:実行結果(画像のみ vs テキスト+画像)
- 「食べ物の写真」で検索
クエリ: 「食べ物の写真」
[画像のみ] [テキスト+画像]
0.3817 sushi.jpg 0.4735 sushi.jpg
0.3395 query.jpg 0.4261 apple.jpg
0.3395 apple.jpg 0.4216 query.jpg
0.3085 Fuji.jpg 0.3808 cat.jpg
0.3007 cat.jpg 0.3762 Fuji.jpg
0.2979 tokyotower_black.jpg 0.3504 skytree.jpg
0.2951 tokyotower_blue.jpg 0.3461 tokyotower_blue.jpg
- 「赤いもの」で検索
クエリ: 「赤いもの」
[画像のみ] [テキスト+画像]
0.3705 query.jpg 0.4484 apple.jpg
0.3705 apple.jpg 0.4417 query.jpg
0.3474 tokyotower_blue.jpg 0.3999 sushi.jpg
0.3363 tokyotower_black.jpg 0.3845 tokyotower_black.jpg
0.3295 sushi.jpg 0.3818 tokyotower_blue.jpg
0.3018 Fuji.jpg 0.3772 cat.jpg
0.2864 cat.jpg 0.3683 skytree.jpg
- 「かわいいもの」で検索
クエリ: 「かわいいもの」
[画像のみ] [テキスト+画像]
0.3273 cat.jpg 0.4007 cat.jpg
0.3183 sushi.jpg 0.3973 sushi.jpg
0.2986 query.jpg 0.3930 apple.jpg
0.2986 apple.jpg 0.3823 query.jpg
0.2956 Fuji.jpg 0.3683 skytree.jpg
0.2859 tokyotower_black.jpg 0.3449 tokyotower_black.jp
0.2844 tokyotower_blue.jpg 0.3411 Fuji.jpg
全体的にテキスト+画像の複合埋め込みのほうがスコアが高く出ており、最小限のテキスト情報を足しただけでも、スコアが明確に向上しました。
6. 実際のユースケース
今回試したテキスト検索・画像検索の他にも、さまざまなユースケースが考えられます。
- マルチモーダルRAG :社内マニュアル(PDF)と製品写真をまとめてインデックスし、「赤い部品の取り付け方」で関連するPDFページと写真の両方をヒットさせる横断検索が可能になります。
- 商品画像検索:「ふわふわの白いセーター」のような自然言語から商品画像を直接検索できます。商品説明テキストを事前に用意する手間が省けます。
- 動画・音声のセマンティック検索:会議録画や講義動画をベクトル化しておけば、テキストで中身を検索できます。
7. 注意点
導入前に押さえておくべき制約がいくつかあります。
- ・リージョン制限:Vertex AIでの提供は現時点でus-central1リージョンのみで、日本リージョン(asia-northeast1)は非対応です。Gemini API経由であればリージョンの制約はありません。
- ・既存データとの互換性:gemini-embedding-001とは埋め込み空間に互換性がないため、移行時は全データの再埋め込みが必要です。
- ・プレビュー段階:現在パブリックプレビューのため、APIの仕様が変更される可能性があります。本番環境への導入は一般提供後が安全です。
まとめ
Gemini Embedding 2は、テキストと画像(さらには動画・音声・PDF)を同じベクトル空間に統合できるという点で、埋め込みモデルの使い方を大きく変えるポテンシャルを持っています。
実際に試してみると、APIの使い勝手はシンプルで、数行のコードでクロスモーダル検索が動きます。特に「テキストで画像を検索できる」体験は、従来のCLIPベースのアプローチと比べてもセットアップが圧倒的に簡単です。
まずは手元の画像フォルダで試してみて、マルチモーダルRAGや商品検索への応用を検討してみてはいかがでしょうか。


