openvino/docs/notebooks/228-clip-zero-shot-convert-...

390 lines
16 KiB
ReStructuredText
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Zero-shot Image Classification with OpenAI CLIP and OpenVINO™
=============================================================
Zero-shot image classification is a computer vision task to classify
images into one of several classes without any prior training or
knowledge of the classes.
.. figure:: https://user-images.githubusercontent.com/29454499/207773481-d77cacf8-6cdc-4765-a31b-a1669476d620.png
:alt: zero-shot-pipeline
zero-shot-pipeline
`\**image
source\* <https://huggingface.co/tasks/zero-shot-image-classification>`__
Zero-shot learning resolves several challenges in image retrieval
systems. For example, with the rapid growth of categories on the web, it
is challenging to index images based on unseen categories. We can
associate unseen categories to images with zero-shot learning by
exploiting attributes to models relationship between visual features
and labels. In this tutorial, we will use the `OpenAI
CLIP <https://github.com/openai/CLIP>`__ model to perform zero-shot
image classification. The notebook contains the following steps:
1. Download the model.
2. Instantiate the PyTorch model.
3. Export the ONNX model and convert it to OpenVINO IR, using model
conversion API.
4. Run CLIP with OpenVINO.
.. _top:
**Table of contents**:
- `Instantiate model <#instantiate-model>`__
- `Run PyTorch model inference <#run-pytorch-model-inference>`__
- `Convert model to OpenVINO Intermediate Representation (IR) format. <#convert-model-to-openvino-intermediate-representation-ir-format>`__
- `Run OpenVINO model <#run-openvino-model>`__
- `Select inference device <#select-inference-device>`__
- `Next Steps <#next-steps>`__
Instantiate model `⇑ <#top>`__
###############################################################################################################################
CLIP (Contrastive Language-Image Pre-Training) is a neural network
trained on various (image, text) pairs. It can be instructed in natural
language to predict the most relevant text snippet, given an image,
without directly optimizing for the task. CLIP uses a
`ViT <https://arxiv.org/abs/2010.11929>`__ like transformer to get
visual features and a causal language model to get the text features.
The text and visual features are then projected into a latent space with
identical dimensions. The dot product between the projected image and
text features is then used as a similarity score.
.. figure:: https://raw.githubusercontent.com/openai/CLIP/main/CLIP.png
:alt: clip
clip
`\**image_source\* <https://github.com/openai/CLIP/blob/main/README.md>`__
You can find more information about this model in the `research
paper <https://arxiv.org/abs/2103.00020>`__, `OpenAI
blog <https://openai.com/blog/clip/>`__, `model
card <https://github.com/openai/CLIP/blob/main/model-card.md>`__ and
GitHub `repository <https://github.com/openai/CLIP>`__.
In this notebook, we will use
`openai/clip-vit-base-patch16 <https://huggingface.co/openai/clip-vit-base-patch16>`__,
available via Hugging Face Transformers, but the same steps are
applicable for other CLIP family models.
First, we need to create ``CLIPModel`` class object and initialize it
with model configuration and weights, using ``from_pretrained`` method.
The model will be automatically downloaded from Hugging Face Hub and
cached for the next usage. ``CLIPProcessor`` class is a wrapper for
input data preprocessing. It includes both encoding the text using
tokenizer and preparing the images.
.. code:: ipython3
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")
.. parsed-literal::
Downloading (…)lve/main/config.json: 0%| | 0.00/4.10k [00:00<?, ?B/s]
.. parsed-literal::
Downloading pytorch_model.bin: 0%| | 0.00/599M [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)rocessor_config.json: 0%| | 0.00/316 [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)okenizer_config.json: 0%| | 0.00/905 [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)olve/main/vocab.json: 0%| | 0.00/961k [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)olve/main/merges.txt: 0%| | 0.00/525k [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)/main/tokenizer.json: 0%| | 0.00/2.22M [00:00<?, ?B/s]
.. parsed-literal::
Downloading (…)cial_tokens_map.json: 0%| | 0.00/389 [00:00<?, ?B/s]
Run PyTorch model inference `⇑ <#top>`__
###############################################################################################################################
To perform classification, define labels and load an image in RGB
format. To give the model wider text context and improve guidance, we
extend the labels description using the template “This is a photo of a”.
Both the list of label descriptions and image should be passed through
the processor to obtain a dictionary with input data in the
model-specific format. The model predicts an image-text similarity score
in raw logits format, which can be normalized to the ``[0, 1]`` range
using the ``softmax`` function. Then, we select labels with the highest
similarity score for the final result.
.. code:: ipython3
from PIL import Image
from visualize import visualize_result
image = Image.open('../data/image/coco.jpg')
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])
.. image:: 228-clip-zero-shot-convert-with-output_files/228-clip-zero-shot-convert-with-output_4_0.png
Convert model to OpenVINO Intermediate Representation (IR) format. `⇑ <#top>`__
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
.. figure:: https://user-images.githubusercontent.com/29454499/208048580-8264e54c-151c-43ef-9e25-1302cd0dd7a2.png
:alt: conversion_path
conversion_path
For best results with OpenVINO, it is recommended to convert the model
to OpenVINO IR format. OpenVINO supports PyTorch via ONNX conversion.
The ``torch.onnx.export`` function enables conversion of PyTorch models
to ONNX format. It requires to provide initialized model object, example
of inputs for tracing and path for saving result. The model contains
operations which supported for ONNX tracing starting with opset 14, it
is recommended to use it as ``opset_version`` parameter. Besides that,
we need to have opportunity to provide descriptions various of length
and images with different sizes, for preserving this capability after
ONNX conversion, ``dynamic_axes`` parameter can be used. More
information about PyTorch to ONNX exporting can be found in this
`tutorial <https://pytorch.org/tutorials/advanced/super_resolution_with_onnxruntime.html>`__
and `PyTorch
documentation <https://pytorch.org/docs/stable/onnx.html>`__. We will
use ``mo.convert_model`` functionality to convert the ONNX model. The
``mo.convert_model`` Python function returns an OpenVINO model ready to
load on the device and start making predictions. We can save it on disk
for the next usage with ``openvino.runtime.serialize``.
.. code:: ipython3
import torch
torch.onnx.export(
model, # model being run
# model input in one of acceptable format: torch.Tensor (for single input), tuple or list of tensors for multiple inputs or dictionary with string keys and tensors as values.
dict(inputs),
"clip-vit-base-patch16.onnx", # where to save the model
opset_version=14, # the ONNX version to export the model to
input_names=["input_ids", "pixel_values", "attention_mask"], # the model's input names
output_names=["logits_per_image", "logits_per_text", "text_embeds", "image_embeds"], # the model's output names
dynamic_axes={ # variable length axes
"input_ids": {0: "batch", 1: "sequence"},
"pixel_values": {0: "batch", 1: "num_channels", 2: "height", 3: "width"},
"attention_mask": {0: "batch", 1: "sequence"},
"logits_per_image": {0: "batch"},
"logits_per_text": {0: "batch"},
"text_embeds": {0: "batch"},
"image_embeds": {0: "batch"}
}
)
.. parsed-literal::
/home/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/transformers/models/clip/modeling_clip.py:284: 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/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/transformers/models/clip/modeling_clip.py:324: 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/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/transformers/models/clip/modeling_clip.py:684: TracerWarning: torch.tensor results are registered as constants in the trace. You can safely ignore this warning if you use this function to create tensors out of constant variables that would be the same every time you call this function. In any other case, this might cause the trace to be incorrect.
mask = torch.full((tgt_len, tgt_len), torch.tensor(torch.finfo(dtype).min, device=device), device=device)
/home/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/transformers/models/clip/modeling_clip.py:292: 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/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/transformers/models/clip/modeling_clip.py:301: 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):
/home/adrian/repos/openvino_notebooks/recipes/intelligent_queue_management/venv/lib/python3.10/site-packages/torch/onnx/symbolic_opset9.py:5408: UserWarning: Exporting aten::index operator of advanced indexing in opset 14 is achieved by combination of multiple ONNX operators, including Reshape, Transpose, Concat, and Gather. If indices include negative values, the exported graph will produce incorrect results.
warnings.warn(
.. code:: ipython3
from openvino.runtime import serialize
from openvino.tools import mo
ov_model = mo.convert_model('clip-vit-base-patch16.onnx', compress_to_fp16=True)
serialize(ov_model, 'clip-vit-base-patch16.xml')
Run OpenVINO model `⇑ <#top>`__
###############################################################################################################################
The steps for making predictions with the OpenVINO CLIP model are
similar to the PyTorch model. Let us check the model result using the
same input data from the example above with PyTorch.
.. code:: ipython3
from scipy.special import softmax
from openvino.runtime import Core
# create OpenVINO core object instance
core = Core()
Select inference device `⇑ <#top>`__
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Select device from dropdown list for running inference using OpenVINO:
.. code:: ipython3
import ipywidgets as widgets
device = widgets.Dropdown(
options=core.available_devices + ["AUTO"],
value='AUTO',
description='Device:',
disabled=False,
)
device
.. parsed-literal::
Dropdown(description='Device:', index=3, options=('CPU', 'GPU.0', 'GPU.1', 'AUTO'), value='AUTO')
.. code:: ipython3
# 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])
.. image:: 228-clip-zero-shot-convert-with-output_files/228-clip-zero-shot-convert-with-output_12_0.png
Great! Looks like we got the same result.
Now, it is your turn! You can provide your own image and comma-separated
list of labels for zero-shot classification.
Feel free to upload an image, using the file upload window and type
label names into the text field, using comma as the separator (for
example, ``cat,dog,bird``)
.. code:: ipython3
import ipywidgets as widgets
style = {'description_width': 'initial'}
image_widget = widgets.FileUpload(
accept='',
multiple=False,
description='Upload image',
style=style
)
labels_widget = widgets.Textarea(
value='cat,dog,bird',
placeholder='Type something',
description='Enter your classes separated by ,:',
disabled=False,
style=style
)
widgets.VBox(children=[image_widget, labels_widget])
.. parsed-literal::
VBox(children=(FileUpload(value=(), description='Upload image'), Textarea(value='cat,dog,bird', description='E…
Run the next cell to get the result for your submitted data:
.. code:: ipython3
import io
# read uploaded image
image = Image.open(io.BytesIO(image_widget.value[-1]['content'])) if image_widget.value else image
# obtain list of labels
labels = labels_widget.value.split(',')
# convert labels to text description
text_descriptions = [f"This is a photo of a {label}" for label in labels]
# preprocess input
inputs = processor(text=text_descriptions, images=[image], return_tensors="np", padding=True)
# run inference
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, labels, probs[0])
.. image:: 228-clip-zero-shot-convert-with-output_files/228-clip-zero-shot-convert-with-output_17_0.png
Next Steps `⇑ <#top>`__
###############################################################################################################################
Open the
`228-clip-zero-shot-quantize <228-clip-zero-shot-quantize.ipynb>`__
notebook to quantize the IR model with the Post-training Quantization
API of NNCF and compare ``FP16`` and ``INT8`` models.