はじめに
DINOv3(Self-distillation with no labels version 3)は、Meta AIが開発した自己教師あり学習による高性能なビジョントランスフォーマーモデルです。
本記事では、Hugging Faceで公開されているDINOv3モデルを使用して、画像の特徴量を可視化する3つの方法を試してみました。
- PCA(主成分分析)による可視化
- K-means クラスタリングによるセグメンテーション
- コサイン類似度による領域検索
今更な内容で、DINO v3についてはすでにこちらに完璧な解説があるのですが、それを参考に公式GitHubのNotebookを試したところ、現時点でモデルがダウンロードできずに挫折してしまいました・・・。
次にHugging Faceのモデルを使おうとしましたが、Tensorの取得後どうすればいいかわからなかったので試行錯誤して動かしました。
環境構築
必要なライブラリをインストールします。
pip install torch transformers Pillow matplotlib scikit-learn scipy numpy
DINOv3モデルの読み込み
Hugging Faceのtransformersライブラリを使用してDINOv3モデルを読み込みます。
import torch
from transformers import AutoImageProcessor, AutoModel
from transformers.image_utils import load_image
# 利用可能なモデル一覧
# - facebook/dinov3-vits16-pretrain-lvd1689m (Small)
# - facebook/dinov3-vitl16-pretrain-lvd1689m (Large)
# - facebook/dinov3-vitl16plus-pretrain-lvd1689m (Large+)
# - facebook/dinov3-vit7b16-pretrain-lvd1689m (7B)
pretrained_model_name = "facebook/dinov3-vitl16-pretrain-lvd1689m"
processor = AutoImageProcessor.from_pretrained(pretrained_model_name)
model = AutoModel.from_pretrained(
pretrained_model_name,
device_map="auto",
)
DINOv3には複数のサイズのモデルがありますが、今回はLarge(Large-16)モデルを使用します。
画像特徴量の抽出
DINOv3はパッチサイズ16のVision Transformerです。
入力画像のサイズは16の倍数に調整することで、適切な特徴抽出ができます。
しかし、transformerにデフォルトで渡すと224 x 224に変換されるので、元画像を渡したい時は明示的に指定が必要です。
*今回は元画像がVGAだったので、そのサイズをモデルに渡せばそのサイズのまま特徴抽出してくれました。
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = load_image(url)
# 16の倍数に調整する関数
def get_optimal_size(width, height, patch_size=16):
"""画像サイズをパッチサイズの倍数に調整"""
optimal_width = round(width / patch_size) * patch_size
optimal_height = round(height / patch_size) * patch_size
optimal_width = max(optimal_width, patch_size)
optimal_height = max(optimal_height, patch_size)
return optimal_width, optimal_height
original_width, original_height = image.size
optimal_width, optimal_height = get_optimal_size(original_width, original_height)
# サイズパラメータを指定することで、デフォルトの224x224を上書き
inputs = processor(images=image, size={"height": optimal_height, "width": optimal_width}, return_tensors="pt").to(model.device)
# 特徴量を抽出
with torch.inference_mode():
outputs = model(**inputs, output_hidden_states=True)
last_hidden_state = outputs.last_hidden_state # shape: [batch_size, num_tokens, hidden_dim]
今回は、最後の特徴量のみを使います。転移学習などの時は途中の特徴量(隠れ層)も使うとのことです。
トークンの分解
DINOv3の出力は以下の3種類のトークンで構成されています:
- CLSトークン:画像全体を表すグローバル特徴量、画像認識に使う (1個)
- レジスタトークン:DINOv3で導入された特殊トークン(4個)
- 画像パッチトークン:画像特徴量、今回の可視化に使う。 (画像サイズ / 16 / 16個)
# トークンの分割
cls_token = last_hidden_state[0, 0:1, :] # CLSトークン(1個)
reg_tokens = last_hidden_state[0, 1:5, :] # レジスタトークン(4個)
image_features = last_hidden_state[0, 5:, :] # 画像パッチトークン (画像サイズ / 16 / 16個)
# 画像特徴を2Dグリッドに変換
num_patches_h = optimal_height // 16
num_patches_w = optimal_width // 16
image_features_2d = image_features.reshape(num_patches_h, num_patches_w, -1)
今回は画像パッチトークンだけを使います。
可視化方法1:PCA(主成分分析)
PCAを使用して高次元の特徴量を3次元に圧縮し、RGB画像として可視化します。
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
import numpy as np
from scipy.ndimage import zoom
# 特徴量を平坦化
features_flat = image_features.cpu().numpy()
# PCAで3成分に圧縮
pca = PCA(n_components=3)
pca_features = pca.fit_transform(features_flat)
# 2Dグリッドに戻す
pca_features_2d = pca_features.reshape(num_patches_h, num_patches_w, 3)
# 各成分を[0, 1]に正規化
pca_normalized = np.zeros_like(pca_features_2d)
for i in range(3):
component = pca_features_2d[:, :, i]
pca_normalized[:, :, i] = (component - component.min()) / (component.max() - component.min())
# 4倍にアップスケール(線形補間)
pca_resized = zoom(pca_normalized, (4, 4, 1), order=1)
PCA可視化の結果
こんな感じです。猫が見えますね。
可視化方法2:K-meansクラスタリング
K-meansを使用して特徴量空間をクラスタリングし、画像をセグメンテーションします。
from sklearn.cluster import KMeans
# パラメータ
upscale_factor = 4
n_clusters = 3 # クラスタ数
# 特徴量を4倍にアップスケール
image_features_upscaled = zoom(image_features_2d.cpu().numpy(), (upscale_factor, upscale_factor, 1), order=1)
# 平坦化
features_upscaled_flat = image_features_upscaled.reshape(-1, image_features_upscaled.shape[-1])
# K-meansクラスタリング
kmeans = KMeans(n_clusters=n_clusters, random_state=42, n_init=10)
cluster_labels = kmeans.fit_predict(features_upscaled_flat)
# 2Dグリッドに戻す
upscaled_h = num_patches_h * upscale_factor
upscaled_w = num_patches_w * upscale_factor
cluster_map = cluster_labels.reshape(upscaled_h, upscaled_w)
結果がこちらです。猫、猫の寝床、ソファの背もたれ(?)がざっくり分けられてますね。
可視化方法3:コサイン類似度による領域検索
画像中の特定のポイントと類似した領域を検索します。
コサイン類似度をプロットするだけです。
from sklearn.metrics.pairwise import cosine_similarity
# クエリポイントの座標
query_x = upscaled_w // 4
query_y = upscaled_h // 3
# クエリポイントの特徴量を取得
query_feature = image_features_upscaled[query_y, query_x, :].reshape(1, -1)
# すべてのピクセルとのコサイン類似度を計算
all_features = image_features_upscaled.reshape(-1, image_features_upscaled.shape[-1])
similarities = cosine_similarity(query_feature, all_features)[0]
# 2Dマップに変換
similarity_map = similarities.reshape(upscaled_h, upscaled_w)
# 閾値処理で類似領域を抽出
threshold_value = 60 # パーセンタイル
threshold = np.percentile(similarities, threshold_value)
similarity_thresholded = (similarity_map > threshold).astype(float)
こんな感じでした。星の位置が今回のクエリポイントです。
UIを作ればこちらのようにInteractiveに計算しても表示できますが、今回はそこまではやりませんでした。
まとめ
本記事では、Hugging Faceで公開されているDINOv3モデルを使用して、画像の特徴量を可視化する3つの方法を紹介しました:
- PCA:特徴空間の主要な方向を可視化
- K-meansクラスタリング:教師なしセグメンテーション
- コサイン類似度:類似領域の検索
やはり、とてもキレイな結果が得られるので、転移学習やアノテーションへの利用など、高い可能性を感じますね。
セグメンテーションなどの転移学習も試してみたいものです。


