概要
3月にリリースされた gemini-embedding-2-preview は、テキスト・画像・動画・音声をインプットとして受け付け、同じフォーマットのエンベディングを出力できます。
これにより理論上、画像同士の類似度比較や、画像とテキストの類似度比較など、非常に多くの用途が生まれると考えられます。
今回は上記の用途について軽く検証してみたいと思います。
検証内容
-
face1.jpgからface5.jpgまでは、Pexelsでダウンロードしたすべて異なる人物の表情画像(テストのため、それぞれ何の表情であるかは明記しません) -
face16.jpgは1人の人物の表情が4x4に分割された画像で、face16.pyを使って個別の画像に分割します。
※補足:画像ソースはPexelsから入手したため、著作権的な問題はありません。
検証1: 画像×テキスト 表情類似度(複数人)
-
face1.jpg~face5.jpg(5人の異なる表情)を使用 - 各画像を「怒」「哀」「楽」のテキストとそれぞれコサイン類似度で比較(「喜」は「楽」とほぼ同じなので省略)
- 5×3 の類似度行列を作成し、モデルが画像とテキスト間の表情マッチングをどの程度捉えられるか検証
検証2: 画像×テキスト 表情類似度(同一人物)
-
face16.jpgをface16.pyで4×4分割した16枚を使用(同一人物の異なる表情) - 各画像を「怒」「哀」「楽」のテキストとそれぞれコサイン類似度で比較(「喜」は「楽」とほぼ同じなので省略)
- 16×3 の類似度行列を作成し、同一人物で表情だけ変わった場合の検出精度を検証
検証3: 画像×画像 顔識別
-
face1.jpg~face5.jpgの5枚 +face16.jpgから分割した中から5枚、合計10枚を使用 - 10枚間すべてのペアでコサイン類似度を比較(10×10 の類似度行列)
- 異なる人物 vs 同一人物(face16分割)のスコア差から、モデルの顔識別能力を検証
主なソースコード
画像をエンベディングに変換
def embed_image(
client: genai.Client,
image_path: str | os.PathLike,
max_retries: int = 3,
sleep_sec: float = 10.0,
) -> np.ndarray:
image_path = Path(image_path)
with open(image_path, "rb") as f:
image_bytes = f.read()
def _call() -> np.ndarray:
result = client.models.embed_content(
model=EMBEDDING_MODEL,
contents=[
types.Part.from_bytes(
data=image_bytes,
mime_type="image/jpeg",
)
],
)
return np.array(result.embeddings[0].values, dtype=np.float32)
return run_with_retry(_call, max_retries=max_retries, sleep_sec=sleep_sec) # 失敗の自動処理のためにリトライを組まれた
テキストをエンベディングに変換
def embed_text(
client: genai.Client,
text: str,
max_retries: int = 3,
sleep_sec: float = 10.0,
) -> np.ndarray:
"""テキスト 1 件を `gemini-embedding-2-preview` で embedding する."""
def _call() -> np.ndarray:
result = client.models.embed_content(
model=EMBEDDING_MODEL,
contents=[text],
)
return np.array(result.embeddings[0].values, dtype=np.float32)
return run_with_retry(_call, max_retries=max_retries, sleep_sec=sleep_sec)
類似度の計算
def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
denom = float(np.linalg.norm(a) * np.linalg.norm(b))
if denom == 0.0:
return 0.0
return float(np.dot(a, b) / denom)
def build_similarity_matrix(
rows: Sequence[np.ndarray],
cols: Sequence[np.ndarray] | None = None,
) -> np.ndarray:
col_vectors = cols if cols is not None else rows
matrix = np.zeros((len(rows), len(col_vectors)), dtype=np.float32)
for i, r in enumerate(rows):
for j, c in enumerate(col_vectors):
matrix[i, j] = cosine_similarity(r, c)
return matrix
結果
まず、こちらが検証に使用したサンプル画像です。
face16.jpg(これをface_01からface_16までに分割)

検証1
日本語テキストとの類似度はあまり良くない結果でした。
英語(Angry, Sad, Happyなど)を使ってもう1回分析してみたところ、日本語より少し良くなりました。これならなんとなく使えるレベルだと思います。
検証2
同一人物であれば、顔立ちによる変数(ノイズ)がなくなるので、より正確に測れると思います。
何だかんだで精度が良くなったと感じます。
そもそも、なぜ「怒」・「哀」・「楽」のスコアの差がこれほど小さいのかについて考えてみました。
1つの画像から抽出されるエンベディングには非常に多くの情報が含まれており、「人間」「顔」「表情」などもその一部です。「怒」・「哀」・「楽」は表情としてはそれぞれ違いますが、どれも「表情」という枠組みのサブセットに過ぎないため、全体のエンベディングの中で比較するとそこまで大きな差が出ないというのも合理的だと言えます。
検証3
明らかに同一人物同士だと類似度が高くなることが確認できました。
最後に
以上の検証はマルチモーダルなエンベディングモデルのほんの一部の用途に過ぎないので、今後また他の用途についても検証していきたいです。









