OpenAI CLIP と OpenVINO™ によるゼロショット画像分類

この Jupyter ノートブックは、ローカルへのインストール後にのみ起動できます。

GitHub

ゼロショット画像分類は、事前のトレーニングやクラスの知識なしに、画像を複数のクラスに分類するコンピューター・ビジョン・タスクです。

zero-shot-pipeline

zero-shot-pipeline

**画像出典*

ゼロショット学習は、画像検索システムにおけるいくつかの課題を解決します。例えば、ウェブ上のカテゴリーが急速に増加しているため、目に見えないカテゴリーに基づいて画像をインデックスすることは困難です。属性を利用して視覚的特徴とラベルの関係をモデル化することで、ゼロショット学習で目に見えないカテゴリーを画像に関連付けることができます。このチュートリアルでは、OpenAI CLIP モデルを使用してゼロショット画像分類を実行します。これには次の手順が含まれます。

  1. モデルのダウンロード。

  2. PyTorch モデルをインスタンス化します。

  3. モデル変換 API を使用して、モデルを OpenVINO IR に変換します。

  4. OpenVINO で CLIP を実行します。

目次

モデルのインスタンス化

CLIP (Contrastive Language-Image Pre-Training) は、さまざまな (画像、テキスト) ペアでトレーニングされたニューラル・ネットワークです。タスクを直接最適化することなく、与えられた画像に対して最も関連性の高いテキストを予測するように自然言語で指示できます。CLIP は、ViT のようなトランスフォーマーを使用して視覚的特徴を取得し、因果言語モデルによってテキスト特徴を取得します。次に、テキストと視覚的特徴は、同一の次元を持つ潜在空間に投影されます。投影された画像とテキストの特徴間のドット積は、類似度スコアとして使用されます。

clip

clip

**画像出典*

このモデルの詳細については、研究論文OpenAI ブログモデルカード、GitHub リポジトリーをご覧ください。

このノートブックでは、Hugging Face トランスフォーマーから入手できる openai/clip-vit-base-patch16 を使用しますが、他の CLIP ファミリーモデルにも同じ手順を適用できます。

まず、CLIPModel クラス・オブジェクトを作成し、from_pretrained メソッドを使用してモデル構成と重みで初期化する必要があります。モデルは Hugging Face Hub から自動的にダウンロードされ、次回の使用のためにキャッシュされます。CLIPProcessor クラスは、入力データの前処理用のラッパーです。トークナイザーを使用してテキストをエンコードするのと、画像の準備の両方が含まれます。

%pip install -q --extra-index-url https://download.pytorch.org/whl/cpu gradio "openvino>=2023.1.0" "transformers[torch]>=4.30"
from transformers import CLIPProcessor, CLIPModel

# load pre-trained model
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch16")
# load preprocessor for model input
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch16")
/home/ea/work/ov_venv/lib/python3.8/site-packages/torch/cuda/__init__.py:138: UserWarning: CUDA initialization: The NVIDIA driver on your system is too old (found version 11080). Please update your GPU driver by downloading and installing a new version from the URL: http://www.nvidia.com/Download/index.aspx Alternatively, go to: https://pytorch.org to install a PyTorch version that has been compiled with your version of the CUDA driver. (Triggered internally at ../c10/cuda/CUDAFunctions.cpp:108.)
  return torch._C._cuda_getDeviceCount() > 0
2023-10-26 14:25:33.940360: I tensorflow/core/util/port.cc:110] oneDNN custom operations are on. You may see slightly different numerical results due to floating-point round-off errors from different computation orders. To turn them off, set the environment variable TF_ENABLE_ONEDNN_OPTS=0.
2023-10-26 14:25:33.975867: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 AVX512F AVX512_VNNI FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2023-10-26 14:25:34.675789: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT

PyTorch モデル推論を実行

分類を実行するには、ラベルを定義し、RGB 形式で画像を読み込みます。モデルに幅広いテキスト・コンテキストを提供し、ガイダンスを改善するため、“This is a photo of a” というテンプレートを使用してラベルの説明を拡張します。モデル固有の形式の入力データを含む辞書を取得するには、ラベルの説明リストと画像の両方をプロセッサーに渡す必要があります。このモデルは、生のロジット形式で画像とテキストの類似性スコアを予測します。これは、softmax 関数を使用して [0, 1] の範囲に正規化できます。次に、最終結果に対して、類似度スコアが最も高いラベルを選択します。

from urllib.request import urlretrieve
from pathlib import Path

from PIL import Image

urlretrieve(
    "https://raw.githubusercontent.com/openvinotoolkit/openvino_notebooks/main/notebooks/228-clip-zero-shot-image-classification/visualize.py",
    filename='visualize.py'
)
from visualize import visualize_result


sample_path = Path("data/coco.jpg")
sample_path.parent.mkdir(parents=True, exist_ok=True)
urlretrieve(
    "https://storage.openvinotoolkit.org/repositories/openvino_notebooks/data/data/image/coco.jpg",
    sample_path,
)
image = Image.open(sample_path)

input_labels = ['cat', 'dog', 'wolf', 'tiger', 'man', 'horse', 'frog', 'tree', 'house', 'computer']
text_descriptions = [f"This is a photo of a {label}" for label in input_labels]

inputs = processor(text=text_descriptions, images=[image], return_tensors="pt", padding=True)

results = model(**inputs)
logits_per_image = results['logits_per_image']  # this is the image-text similarity score
probs = logits_per_image.softmax(dim=1).detach().numpy()  # we can take the softmax to get the label probabilities
visualize_result(image, input_labels, probs[0])
../_images/228-clip-zero-shot-convert-with-output_5_0.png

モデルを OpenVINO 中間表現 (IR) 形式に変換

OpenVINO で最良の結果を得るには、モデルを OpenVINO IR 形式に変換することを推奨します。OpenVINO はモデル変換 API を介して PyTorch をサポートします。PyTorch モデルを OpenVINO IR 形式に変換するには、モデル変換 APIov.convert_model を使用します。ov.convert_model 関数は、デバイスにロードして予測を開始できる状態の OpenVINO モデル・オブジェクトを返します。ov.save_model を使用して、次回の利用のためディスクに保存できます。

import openvino as ov

model.config.torchscript = True
ov_model = ov.convert_model(model, example_input=dict(inputs))
ov.save_model(ov_model, 'clip-vit-base-patch16.xml')
WARNING:tensorflow:Please fix your imports. Module tensorflow.python.training.tracking.base has been moved to tensorflow.python.trackable.base. The old module will be deleted in version 2.11.
[ WARNING ]  Please fix your imports. Module %s has been moved to %s. The old module will be deleted in version %s.
/home/ea/work/ov_venv/lib/python3.8/site-packages/transformers/models/clip/modeling_clip.py:287: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!
  if attn_weights.size() != (bsz * self.num_heads, tgt_len, src_len):
/home/ea/work/ov_venv/lib/python3.8/site-packages/transformers/models/clip/modeling_clip.py:327: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!
  if attn_output.size() != (bsz * self.num_heads, tgt_len, self.head_dim):
/home/ea/work/ov_venv/lib/python3.8/site-packages/transformers/models/clip/modeling_clip.py:295: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!
  if causal_attention_mask.size() != (bsz, 1, tgt_len, src_len):
/home/ea/work/ov_venv/lib/python3.8/site-packages/transformers/models/clip/modeling_clip.py:304: TracerWarning: Converting a tensor to a Python boolean might cause the trace to be incorrect. We can't record the data flow of Python values, so this value will be treated as a constant in the future. This means that the trace might not generalize to other inputs!
  if attention_mask.size() != (bsz, 1, tgt_len, src_len):

OpenVINO モデルを実行

OpenVINO CLIP モデルを使用して予測を行う手順は、PyTorch モデルと同様です。上記の例と同じ入力データを PyTorch で使用してモデルの結果を確認します。

from scipy.special import softmax

# create OpenVINO core object instance
core = ov.Core()

推論デバイスの選択

OpenVINO を使用して推論を実行するためにドロップダウン・リストからデバイスを選択します

import ipywidgets as widgets

device = widgets.Dropdown(
    options=core.available_devices + ["AUTO"],
    value='AUTO',
    description='Device:',
    disabled=False,
)

device
Dropdown(description='Device:', index=2, options=('CPU', 'GPU', 'AUTO'), value='AUTO')
# compile model for loading on device
compiled_model = core.compile_model(ov_model, device.value)
# obtain output tensor for getting predictions
logits_per_image_out = compiled_model.output(0)
# run inference on preprocessed data and get image-text similarity score
ov_logits_per_image = compiled_model(dict(inputs))[logits_per_image_out]
# perform softmax on score
probs = softmax(ov_logits_per_image, axis=1)
# visualize prediction
visualize_result(image, input_labels, probs[0])
../_images/228-clip-zero-shot-convert-with-output_12_0.png

これで完了です! 同じ結果になったようです。

さあ、あなたの番です!ゼロショット分類用に、独自の画像とカンマで区切ったラベルのリストを提供できます。

ファイル・アップロード・ウィンドウを使用して画像をアップロードし、テキストフィールドにラベル名を入力します。区切り文字としてカンマを使用します (例: cat、dog、bird)

import gradio as gr


def classify(image, text):
    """Classify image using classes listing.
    Args:
        image (np.ndarray): image that needs to be classified in CHW format.
        text (str): comma-separated list of class labels
    Returns:
        (dict): Mapping between class labels and class probabilities.
    """
    labels = text.split(",")
    text_descriptions = [f"This is a photo of a {label}" for label in labels]
    inputs = processor(text=text_descriptions, images=[image], return_tensors="np", padding=True)
    ov_logits_per_image = compiled_model(dict(inputs))[logits_per_image_out]
    probs = softmax(ov_logits_per_image, axis=1)[0]

    return {label: float(prob) for label, prob in zip(labels, probs)}


demo = gr.Interface(
    classify,
    [
        gr.Image(label="Image", type="pil"),
        gr.Textbox(label="Labels", info="Comma-separated list of class labels"),
    ],
    gr.Label(label="Result"),
    examples=[[sample_path, "cat,dog,bird"]],
)
try:
    demo.launch(debug=False)
except Exception:
    demo.launch(share=True, debug=False)
# if you are launching remotely, specify server_name and server_port
# demo.launch(server_name='your server name', server_port='server port in int')
# Read more in the docs: https://gradio.app/docs/
Running on local URL:  http://127.0.0.1:7861
Rerunning server... use close() to stop if you need to change launch() parameters.
----
huggingface/tokenizers: The current process just got forked, after parallelism has already been used. Disabling parallelism to avoid deadlocks...
To disable this warning, you can either:
    - Avoid using tokenizers before the fork if possible
    - Explicitly set the environment variable TOKENIZERS_PARALLELISM=(true | false)
Running on public URL: https://4ec3df1c48219763b1.gradio.live

This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run gradio deploy from Terminal to deploy to Spaces (https://huggingface.co/spaces)

次のステップ

228-clip-zero-shot-quantize ノートブックを開き、NNCF のトレーニング後の量子化 API を使用して IR モデルを量子化し、FP16 モデルと INT8 モデルを比較します。