S3D MIL-NCE と OpenVINO によるテキストからのビデオ検索#

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

GitHub

このチュートリアルは 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.71Rank #2, Score:-1.63Rank #3, Score:-4.17

入力クエリー: playing guitar

結果:
Rank #1, Score:6.50Rank #2, Score:-1.79Rank #3, Score:-2.67

入力クエリー: car drifting

結果:
Rank #1, Score:8.78Rank #2, Score:-1.07Rank #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.71Rank #2, Score:-1.63Rank #3, Score:-4.17

入力クエリー: playing guitar

結果:
Rank #1, Score:6.50Rank #2, Score:-1.79Rank #3, Score:-2.67

入力クエリー: car drifting

結果:
Rank #1, Score:8.78Rank #2, Score:-1.07Rank #3, Score:-2.17

NNCF トレーニング後の量子化 API を使用してモデルを最適化#

NNCF は、精度の低下を最小限に抑えながら、OpenVINO でニューラル・ネットワーク推論を最適化する一連の高度なアルゴリズムを提供します。トレーニング後モード (微調整パイプラインなし) で 8 ビット量子化を使用します。最適化プロセスには次の手順が含まれます:

  1. 量子化用のデータセットを作成します。

  2. nncf.quantize を実行して、最適化されたモデルを取得します。

  3. 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.71Rank #2, Score:-1.63Rank #3, Score:-4.17

入力クエリー: playing guitar

結果:
Rank #1, Score:6.50Rank #2, Score:-1.79Rank #3, Score:-2.67

入力クエリー: car drifting

結果:
Rank #1, Score:8.78Rank #2, Score:-1.07Rank #3, Score:-2.17