← 기사 목록
日本語https://qiita.com/tags/llm/feed

gemini-embedding-2-previewでテキスト・画像の類似度比較を検証

추출된 키워드

13
gemini-embedding-2-preview·5エンベディング·5類似度比較·5コサイン類似度·4マルチモーダル·4顔識別·4表情マッチング·3類似度行列·3Pexels·2np.ndarray·2genai.Client·2mime_type·2image/jpeg·2

원문

5,492
gemini-embedding-2-previewでテキスト・画像の類似度比較を検証

概要

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
までに分割)
face16.jpg

検証1

日本語テキストとの類似度はあまり良くない結果でした。

英語(Angry, Sad, Happyなど)を使ってもう1回分析してみたところ、日本語より少し良くなりました。これならなんとなく使えるレベルだと思います。

検証2

同一人物であれば、顔立ちによる変数(ノイズ)がなくなるので、より正確に測れると思います。

何だかんだで精度が良くなったと感じます。

そもそも、なぜ「怒」・「哀」・「楽」のスコアの差がこれほど小さいのかについて考えてみました。
1つの画像から抽出されるエンベディングには非常に多くの情報が含まれており、「人間」「顔」「表情」などもその一部です。「怒」・「哀」・「楽」は表情としてはそれぞれ違いますが、どれも「表情」という枠組みのサブセットに過ぎないため、全体のエンベディングの中で比較するとそこまで大きな差が出ないというのも合理的だと言えます。

検証3

明らかに同一人物同士だと類似度が高くなることが確認できました。

最後に

以上の検証はマルチモーダルなエンベディングモデルのほんの一部の用途に過ぎないので、今後また他の用途についても検証していきたいです。