S3D MIL-NCE と OpenVINO によるテキストからのビデオ検索#
この Jupyter ノートブックは、ローカルへのインストール後にのみ起動できます。
このチュートリアルは TensorFlow チュートリアルに基づいており、TensorFlow Hub の S3D MIL-NCE モデルを使用してテキストからビデオの検索を実行し、指定されたテキストクエリーに最も類似したビデオを見つける方法を示します。
MIL-NCE は、Multiple Instance Learning (MIL) と Noise Contrastive Estimation (NCE) を継承します。この方法は、キュレーションされていない教育ビデオからの視覚的にずれたナレーションに対処することができます。異なる 3D CNN バックボーンを持つ 2 つのモデル・バリエーションが利用できます: I3D と S3D。このチュートリアルでは、S3D バリエーションを使用します。トレーニングとモデルの詳細については、論文 End-to-End Learning of Visual Representations from Uncurated Instructional Videos (英語) をご覧ください。
このチュートリアルでは、OpenVINO を使用して S3D MIL-NCE モデルを実行および最適化する手順を段階的に説明します。追加部分では、推論を高速化するため NNCF を使用して量子化を実行する方法を示します。
このチュートリアルは次のステップで構成されます:
目次:
必要条件#
import platform
%pip install -Uq pip
%pip install --upgrade --pre openvino-tokenizers openvino --extra-index-url "https://storage.openvinotoolkit.org/simple/wheels/nightly"
%pip install -q "tensorflow-macos>=2.5; sys_platform == 'darwin' and platform_machine == 'arm64' and python_version > '3.8'" # macOS M1 and M2
%pip install -q "tensorflow-macos>=2.5,<=2.12.0; sys_platform == 'darwin' and platform_machine == 'arm64' and python_version <= '3.8'" # macOS M1 and M2
%pip install -q "tensorflow>=2.5; sys_platform == 'darwin' and platform_machine != 'arm64' and python_version > '3.8'" # macOS x86
%pip install -q "tensorflow>=2.5,<=2.12.0; sys_platform == 'darwin' and platform_machine != 'arm64' and python_version <= '3.8'" # macOS x86
%pip install -q "tensorflow>=2.5; sys_platform != 'darwin' and python_version > '3.8'"
%pip install -q "tensorflow>=2.5,<=2.12.0; sys_platform != 'darwin' and python_version <= '3.8'"
%pip install -q tensorflow_hub tf_keras numpy "opencv-python" "nncf>=2.10.0"
if platform.system() != "Windows":
%pip install -q "matplotlib>=3.4"
else:
%pip install -q "matplotlib>=3.4,<3.7"
Note: you may need to restart the kernel to use updated packages.
Looking in indexes: https://pypi.org/simple, https://storage.openvinotoolkit.org/simple/wheels/nightly
Requirement already satisfied: openvino-tokenizers in /opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages (2024.4.0.0.dev20240712)
Requirement already satisfied: openvino in /opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages (2024.4.0.dev20240712)
Requirement already satisfied: numpy<2.0.0,>=1.16.6 in /opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages (from openvino) (1.23.5)
Requirement already satisfied: openvino-telemetry>=2023.2.1 in /opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages (from openvino) (2024.1.0) Requirement already satisfied: packaging in /opt/home/k8sworker/ci-ai/cibuilds/ov-notebook/OVNotebookOps-727/.workspace/scm/ov-notebook/.venv/lib/python3.8/site-packages (from openvino) (24.1)
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
Note: you may need to restart the kernel to use updated packages.
import os
from pathlib import Path
import tensorflow as tf
import tensorflow_hub as hub
import numpy as np
import cv2
from IPython import display
import math
os.environ["TFHUB_CACHE_DIR"] = str(Path("./tfhub_modules").resolve())
2024-07-13 02:43:13.726530: 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. 2024-07-13 02:43:13.762325: 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.2024-07-13 02:43:14.360751: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
モデルのダウンロード
hub_handle = "https://www.kaggle.com/models/deepmind/mil-nce/TensorFlow1/s3d/1"
hub_model = hub.load(hub_handle)
2024-07-13 02:43:22.100111: E tensorflow/compiler/xla/stream_executor/cuda/cuda_driver.cc:266] failed call to cuInit: CUDA_ERROR_COMPAT_NOT_SUPPORTED_ON_DEVICE: forward compatibility was attempted on non supported HW
2024-07-13 02:43:22.100148: I tensorflow/compiler/xla/stream_executor/cuda/cuda_diagnostics.cc:168] retrieving CUDA diagnostic information for host: iotg-dev-workstation-07
2024-07-13 02:43:22.100152: I tensorflow/compiler/xla/stream_executor/cuda/cuda_diagnostics.cc:175] hostname: iotg-dev-workstation-07
2024-07-13 02:43:22.100286: I tensorflow/compiler/xla/stream_executor/cuda/cuda_diagnostics.cc:199] libcuda reported version is: 470.223.2
2024-07-13 02:43:22.100302: I tensorflow/compiler/xla/stream_executor/cuda/cuda_diagnostics.cc:203] kernel reported version is: 470.182.3
2024-07-13 02:43:22.100306: E tensorflow/compiler/xla/stream_executor/cuda/cuda_diagnostics.cc:312] kernel version 470.182.3 does not match DSO version 470.223.2 -- cannot find working devices in this configuration
このモデルには 2 つの署名があり、1 つはビデオ埋め込みを生成するためのもので、もう 1 つはテキスト埋め込みを生成するためのものです。元のチュートリアルと同様に、これらの埋め込みを使用して、埋め込み空間内の最も近い近傍を検索します。以下で補助機能を定義します
def generate_embeddings(model, input_frames, input_words):
"""Generate embeddings from the model from video frames and input words."""
# Input_frames は [0, 1] で正規化され、形状はバッチ x T x H x W x 3 である必要があります
vision_output = model.signatures["video"](tf.constant(tf.cast(input_frames, dtype=tf.float32)))
text_output = model.signatures["text"](tf.constant(input_words))
return vision_output["video_embedding"], text_output["text_embedding"]
# @title Define video loading and visualization functions { display-mode: "form" }
# CV2 を使用してビデオファイルを開くユーティリティー
def crop_center_square(frame):
y, x = frame.shape[0:2]
min_dim = min(y, x)
start_x = (x // 2) - (min_dim // 2)
start_y = (y // 2) - (min_dim // 2)
return frame[start_y : start_y + min_dim, start_x : start_x + min_dim]
def load_video(video_url, max_frames=32, resize=(224, 224)):
path = tf.keras.utils.get_file(os.path.basename(video_url)[-128:], video_url)
cap = cv2.VideoCapture(path)
frames = []
try:
while True:
ret, frame = cap.read()
if not ret:
break
frame = crop_center_square(frame)
frame = cv2.resize(frame, resize)
frame = frame[:, :, [2, 1, 0]]
frames.append(frame)
if len(frames) == max_frames:
break
finally:
cap.release()
frames = np.array(frames)
if len(frames) < max_frames:
n_repeat = int(math.ceil(max_frames / float(len(frames))))
frames = frames.repeat(n_repeat, axis=0)
frames = frames[:max_frames]
return frames / 255.0
def display_video(urls):
html = "<table>"
html += "<tr><th>Video 1</th><th>Video 2</th><th>Video 3</th></tr><tr>"
for url in urls:
html += "<td>"
html += '<img src="{}" height="224">'.format(url)
html += "</td>"
html += "</tr></table>"
return display.HTML(html)
def display_query_and_results_video(query, urls, scores):
"""Display a text query and the top result videos and scores."""
sorted_ix = np.argsort(-scores)
html = ""
html += "<h2>Input query: <i>{}</i> </h2><div>".format(query)
html += "Results: <div>" html += "<table>"
html += "<tr><th>Rank #1, Score:{:.2f}</th>".format(scores[sorted_ix[0]])
html += "<th>Rank #2, Score:{:.2f}</th>".format(scores[sorted_ix[1]])
html += "<th>Rank #3, Score:{:.2f}</th></tr><tr>".format(scores[sorted_ix[2]])
for i, idx in enumerate(sorted_ix):
url = urls[sorted_ix[i]]
html += "<td>"
html += '<img src="{}" height="224">'.format(url)
html += "</td>"
html += "</tr></table>"
return html
# @title Load example videos and define text queries { display-mode: "form" }
video_1_url = "https://upload.wikimedia.org/wikipedia/commons/b/b0/YosriAirTerjun.gif" # @param {type:"string"}
video_2_url = "https://upload.wikimedia.org/wikipedia/commons/e/e6/Guitar_solo_gif.gif" # @param {type:"string"}
video_3_url = "https://upload.wikimedia.org/wikipedia/commons/3/30/2009-08-16-autodrift-by-RalfR-gif-by-wau.gif" # @param {type:"string"}
video_1 = load_video(video_1_url)
video_2 = load_video(video_2_url)
video_3 = load_video(video_3_url)
all_videos = [video_1, video_2, video_3]
query_1_video = "waterfall" # @param {type:"string"}
query_2_video = "playing guitar" # @param {type:"string"}
query_3_video = "car drifting" # @param {type:"string"}
all_queries_video = [query_1_video, query_2_video, query_3_video]
all_videos_urls = [video_1_url, video_2_url, video_3_url]
display_video(all_videos_urls)
ビデオ 1 | ビデオ 2 | ビデオ 3 |
---|---|---|
![]() | ![]() | ![]() |
元の推論#
# ビデオ入力を準備
videos_np = np.stack(all_videos, axis=0)
# テキスト入力を準備
words_np = np.array(all_queries_video)
# ビデオとテキストの埋め込みを生成
video_embd, text_embd = generate_embeddings(hub_model, videos_np, words_np)
# ビデオとテキスト間のスコアはドット積によって計算されます
all_scores = np.dot(text_embd, tf.transpose(video_embd))
# 結果を表示
html = ""
for i, words in enumerate(words_np):
html += display_query_and_results_video(words, all_videos_urls, all_scores[i, :])
html += "<br>"
display.HTML(html)
入力クエリー: waterfall
Rank #1, Score:4.71 | Rank #2, Score:-1.63 | Rank #3, Score:-4.17 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: playing guitar
Rank #1, Score:6.50 | Rank #2, Score:-1.79 | Rank #3, Score:-2.67 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: car drifting
Rank #1, Score:8.78 | Rank #2, Score:-1.07 | Rank #3, Score:-2.17 |
---|---|---|
![]() | ![]() | ![]() |
モデルを OpenVINO IR に変換#
OpenVINO は、中間表現 (IR) への変換により TensorFlow モデルをサポートします。OpenVINO ov.Model
オブジェクト・インスタンスを取得するには、モデル・オブジェクト、モデルトレース用の入力データを ov.convert_model
関数に提供する必要があります。ov.save_model
関数を使用して、次回のデプロイのためにモデルをディスクに保存できます。
import openvino_tokenizers # NOQA Need to import conversion and operation extensions
import openvino as ov
model_path = hub.resolve(hub_handle)
# ランダムデータから推論
images_data = np.random.rand(3, 32, 224, 224, 3).astype(np.float32)
words_data = np.array(["First sentence", "Second one", "Abracadabra"], dtype=str)
ov_model = ov.convert_model(model_path, input=[("words", [3]), ("images", [3, 32, 224, 224, 3])])
モデルのコンパイル#
このモデルでは、入力として文字列を使用するため、CPU のみがサポートされます。
core = ov.Core()
compiled_model = core.compile_model(ov_model, device_name="CPU")
推論#
# コンパイル IR モデルを使用できるように `generate_embeddings` 関数を再定義
def generate_embeddings(model, input_frames, input_words):
"""Generate embeddings from the model from video frames and input words."""
# Input_frames は [0, 1] で正規化され、形状はバッチ x T x H x W x 3 である必要があります
output = compiled_model({"words": input_words, "images": tf.cast(input_frames, dtype=tf.float32)})
return output["video_embedding"], output["text_embedding"]
# ビデオとテキストの埋め込みを生成
video_embd, text_embd = generate_embeddings(compiled_model, videos_np, words_np)
# ビデオとテキスト間のスコアはドット積によって計算されます
all_scores = np.dot(text_embd, tf.transpose(video_embd))
# 結果を表示
html = ""
for i, words in enumerate(words_np):
html += display_query_and_results_video(words, all_videos_urls, all_scores[i, :])
html += "<br>"
display.HTML(html)
入力クエリー: waterfall
Rank #1, Score:4.71 | Rank #2, Score:-1.63 | Rank #3, Score:-4.17 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: playing guitar
Rank #1, Score:6.50 | Rank #2, Score:-1.79 | Rank #3, Score:-2.67 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: car drifting
Rank #1, Score:8.78 | Rank #2, Score:-1.07 | Rank #3, Score:-2.17 |
---|---|---|
![]() | ![]() | ![]() |
NNCF トレーニング後の量子化 API を使用してモデルを最適化#
NNCF は、精度の低下を最小限に抑えながら、OpenVINO でニューラル・ネットワーク推論を最適化する一連の高度なアルゴリズムを提供します。トレーニング後モード (微調整パイプラインなし) で 8 ビット量子化を使用します。最適化プロセスには次の手順が含まれます:
量子化用のデータセットを作成します。
nncf.quantize
を実行して、最適化されたモデルを取得します。ov.save_model
関数を使用して、OpenVINO IR モデルをシリアル化します。
データセットの準備#
このモデルでは、キャリブレーションに大きなデータセットは必要ありません。これにはサンプルビデオのみを使用します。NNCF は、量子化パイプラインでネイティブ・フレームワーク・データローダーを使用する nncf.Dataset
ラッパーを提供します。さらに、モデルが期待する形式で入力データを準備する変換関数を指定します。
import nncf
dataset = nncf.Dataset(((words_np, tf.cast(videos_np, dtype=tf.float32)),))
INFO:nncf:NNCF initialized successfully.Supported frameworks detected: torch, tensorflow, onnx, openvino
モデル量子化の実行#
nncf.quantize
関数は、モデル量子化のインターフェイスを提供します。OpenVINO モデルのインスタンスと量子化データセットが必要です。オプションで、量子化プロセスの追加パラメーター (量子化のサンプル数、プリセット、無視される範囲など) を提供できます。
MODEL_DIR = Path("model/")
MODEL_DIR.mkdir(exist_ok=True)
quantized_model_path = MODEL_DIR / "quantized_model.xml"
if not quantized_model_path.exists():
quantized_model = nncf.quantize(model=ov_model, calibration_dataset=dataset, model_type=nncf.ModelType.TRANSFORMER)
ov.save_model(quantized_model, quantized_model_path)
Output()
Output()
INFO:nncf:39 ignored nodes were found by names in the NNCFGraph
Output()
Output()
量子化モデルの推論を実行#
量子化を適用した後、モデルの使用法に変化はありません。以前使用した例でモデルの動作を確認します。
int8_model = core.compile_model(quantized_model_path, device_name="CPU")
# ビデオとテキストの埋め込みを生成
video_embd, text_embd = generate_embeddings(int8_model, videos_np, words_np)
# ビデオとテキスト間のスコアはドット積によって計算されます
all_scores = np.dot(text_embd, tf.transpose(video_embd))
# 結果を表示
html = ""
for i, words in enumerate(words_np):
html += display_query_and_results_video(words, all_videos_urls, all_scores[i, :])
html += "<br>"
display.HTML(html)
入力クエリー: waterfall
Rank #1, Score:4.71 | Rank #2, Score:-1.63 | Rank #3, Score:-4.17 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: playing guitar
Rank #1, Score:6.50 | Rank #2, Score:-1.79 | Rank #3, Score:-2.67 |
---|---|---|
![]() | ![]() | ![]() |
入力クエリー: car drifting
Rank #1, Score:8.78 | Rank #2, Score:-1.07 | Rank #3, Score:-2.17 |
---|---|---|
![]() | ![]() | ![]() |