⛏️

OpenAIのCLIPによる画像埋め込みベクトル生成で画像類似度を求めてみる

2024/12/10に公開

執筆日

2024/12/10

概要

今回はOpenAIが公開している画像とテキストのマルチモーダル埋め込みモデルCLIP(Contrastive Language-Image Pre-Training) を使って画像同士の類似度評価を行います。
最近テキストの埋め込みだと同じくOpenAI社のtext-embedding-3-large(small)を使う機会が多いのですが、マルチモーダル埋め込みベクトルを生成できるCLIPであれば画像からデータの検索もできるためその使い方と性能を軽く調べてみました。
https://github.com/openai/CLIP

環境

  • Google Colab Pro (L4 ランタイム)
  • Torchバージョン: 2.5.1+cu121
  • 使用モデル: CLIP ViT-B/32

ライブラリインストール

pip install ftfy regex tqdm
pip install git+https://github.com/openai/CLIP.git

Notebook

公式GithubのNotebookを参考にしました。
元のNotebookは画像とテキストの類似度を判定しています。テキストから画像、画像からテキストの検索も簡単に実装できます。

スクリプト
install
! pip install ftfy regex tqdm
! pip install git+https://github.com/openai/CLIP.git
import, モデルロード
import torch
import clip
from PIL import Image
import numpy as np
from pkg_resources import packaging

print("Torch version:", torch.__version__)

print("available models:\n", clip.available_models())

model, preprocess = clip.load("ViT-B/32")
model.cuda().eval()
input_resolution = model.visual.input_resolution
context_length = model.context_length
vocab_size = model.vocab_size

print("Model parameters:", f"{np.sum([int(np.prod(p.shape)) for p in model.parameters()]):,}")
print("Input resolution:", input_resolution)
print("Context length:", context_length)
print("Vocab size:", vocab_size)

clipのモジュールはモデルロード時に前処理のための関数もロードしてくれるため画像を食わせるだけでいいの便利ですね。

画像読み込み・確認
import os, time
from IPython.display import display
import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

images_dir = "./images"
filenames = [filename for filename in os.listdir(images_dir)]
filenames = sorted(filenames)
for filename in filenames: print(filename)
images = []
original_images = []
cols = 3
rows = (len(filenames) + cols - 1) // cols # 切り上げ
plt.figure(figsize=(16, 5))

for i, filename in enumerate(filenames):
    image = Image.open(os.path.join(images_dir, filename)).convert("RGB")
    plt.subplot(rows, cols, len(images) + 1)
    plt.imshow(image)
    plt.title(f"{filename}")
    plt.xticks([])
    plt.yticks([])
    original_images.append(image)
    images.append(preprocess(image))
plt.tight_layout()
plt.show()

類似度はコサイン類似度を使っています。最大値で正規化して転置の内積を取っているだけです。

コサイン類似度計算
image_input = torch.tensor(np.stack(images)).cuda()
with torch.no_grad():
    image_features = model.encode_image(image_input).float()
image_features /= image_features.norm(dim=-1, keepdim=True)
similarity = image_features.cpu().numpy() @ image_features.cpu().numpy().T
結果の可視化
count = len(filenames)

plt.figure(figsize=(20, 14))
plt.imshow(similarity, vmin=similarity.min(), vmax=similarity.max(), cmap="coolwarm")
plt.colorbar()
plt.yticks(range(count), filenames, fontsize=10)
plt.xticks([])
for i, image in enumerate(original_images):
    plt.imshow(image, extent=(i - 0.5, i + 0.5, -1.6, -0.6), origin="lower")
for x in range(similarity.shape[1]):
    for y in range(similarity.shape[0]):
        plt.text(x, y, f"{similarity[y, x]:.2f}", ha="center", va="center", size=12)

for side in ["left", "top", "right", "bottom"]:
  plt.gca().spines[side].set_visible(False)

plt.xlim([-0.5, count - 0.5])
plt.ylim([count + 0.5, -2])

plt.title("Cosine similarity between images features", size=20)

結果

比較用にDELL-3に犬と猫の画像を描いてもらいました。

以下が類似度判定の結果です。犬は犬、猫は猫同士で類似度が高くなりました。犬同士でも犬種(画風もありますが…)が同じもの同士で類似度が高くなりました。この枚数とレベルであれば問題なく類似度ランキングを作ることができました。

おわり

今回はOpenAIのCLIPを使って画像類似度を求めてみました。今回使った画像レベルにはっきりわかりやすいものであれば問題なく類似度を判定できそうです。実用としては違いが細かいものの判定や、大量のデータに対して欲しい画像がちゃんとランキング上位に来るかなども試す必要がありますが選択肢の一つとして面白いと思いました。
具体的なスコア比較はしていませんがCLIPのテキスト比較は最近のモデルよりは劣ると思われるためすべてCLIPで作ったベクトルでやるのか、画像も含む場合のみ別でベクトルを用意するのかなども検討しなければならない点には注意が必要です。そもそもCLIPはマルチリンガルではないみたいです。(間違ってもCLIPで作ったベクトルとtext-embedding-3などの他のモデルで作ったベクトルで類似度計算しようとしないようにしましょう)
最近JINA社が新しいCLIPのモデルを出していたようなのでその辺りもまた試してみようと思います。(OpenAIはもう新しいマルチモーダル埋め込みモデルは作らないんでしょうか。知らないだけ? AOAIで使えるやつがあると嬉しいんですが……)

ヘッドウォータース

Discussion