0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

NexarのBADAS 2.0論文まとめ & 旧世代公開モデルBADAS-Openを試す

0
Posted at

はじめに

以前、Nexar Dashcam Crash Prediction Challenge 上位解法まとめ という記事で、ドライブレコーダー大手のNexarが主催した「ドラレコ映像からの事故予測Kaggleコンペ」について解説しました。

あのコンペティションからも分かるように、実世界の泥臭いドラレコ映像から「未来の衝突」を予測するタスクは極めて難易度が高いです。しかし2026年4月、Nexarは自社の持つ膨大な走行データを注ぎ込んだ 次世代の衝突予測エッジAI「BADAS 2.0 (Beyond ADAS)」 を発表しました。

特筆すべきは、わずか2,200万(22M)パラメータの超軽量モデルでありながら、衝突予測タスク向けにfine-tuneしたNVIDIA Cosmos系大規模モデル(2B)をベンチマークで上回ったという結果です。

本記事では、BADAS 2.0の技術的概要を論文をもとにまとめながら、公開されているv1モデル「BADAS-Open」をGoogle Colabで実際に動かして試した結果もあわせてご紹介します。

BADAS 2.0とは?

BADAS 2.0は、前世代のBADAS 1.0(V-JEPA2をベースにした ego-centric 衝突予測モデル)を土台に、3つの軸で進化した衝突予測システムです。

論文では「データ収集 → モデル強化 → エッジ蒸留 → Explainability」を一貫した設計思想として各ステージを積み上げる構成が示されています。

軸① Long-tail benchmark and accuracy(ロングテール対応と精度)

BADAS 1.0の主な弱点はロングテールカバレッジでした。動物の飛び出し、夜間交差点、悪天候など稀なシナリオが学習データに少なく、これらのケースでリコールが不安定でした。

BADAS 2.0はこの問題を2つの戦略で解決しています。まずBADAS 1.0をactive mining oracleとして使い、何百万件もの未ラベルのNexar走行データに対してリスクスコアを付け、閾値を超えたクリップを人間レビュー(HITL)に回して効率的にアノテーション。次に Nexar Atlasプラットフォーム による地理空間クエリで稀なシナリオを狙い撃ちで収集。これにより学習データは40,000本から 178,500本のラベル付き動画(内部的には約200万クリップ相当) に拡大し、animal / cyclist / fog / infrastructure / intersection / motorcyclist / passing / pedestrian / rain / snow の10グループからなるロングテールベンチマークで全グループにわたって精度が向上しました。

軸② Knowledge distillation to edge(エッジへの知識蒸留)

300Mの精度を保ちながら車載チップで動かすため、2.25M件の未ラベル走行動画でドメイン特化の自己教師あり事前学習を行い、そのSSL重みを出発点にして知識蒸留を実施。その結果が:

  • BADAS 2.0 Flash(86M):7〜12倍のスピードアップ
  • BADAS 2.0 Flash Lite(22M):さらに軽量、AP 98.4%でCosmos-BADAS(2B)を上回る

クラウド通信なしのローカル推論(車載チップ)でも最高峰の予測モデルを遅延なく動かすことが可能になりました。

軸③ Explainability(説明可能性)

BADAS 1.0はスカラーのリスクスコアしか出力せず、「どのオブジェクトが予測を引き起こしたか」の根拠が一切提供されないという問題がありました。論文でも「the scalar risk score provided no explanation of which object triggered」と未解決課題として明記されています。

BADAS 2.0はこれを2段階で解決しています。

Attention Attribution(attention に基づく空間ヒートマップ):late encoder layersのself-attention weightsに exponential temporal weighting(後半フレームを強調する指数重み)を適用し、どのシーン要素が予測を駆動したかをリスク対象周辺を局所化する attention heatmap としてリアルタイムで出力します。追加の学習不要(training-free)な手法で、論文では「唯一の実行時要件は eager attention mode」と説明されています。

この手法はBADAS-1.0でも同様に動作します。論文のFigure 3ではBADAS-1.0からBADAS-2.0-Flash-Liteまで全バリアントのヒートマップを比較しており、BADAS-1.0では活性化が比較的 diffuse(散漫)であると指摘されています。なお論文によると、ローカライズ精度が最も高いのはBADAS-2.0 300Mではなく SSL事前学習ありの蒸留モデル(Flash/Flash-Lite) です。ViT-L(300M)は汎用的な映像データから初期化されているためattentionが散漫になりやすく、Nexarの走行映像でSSL事前学習した蒸留モデルの方が運転シーンに特化した鋭いローカライズを示します。

BADAS-Reason(自然言語による推論):Attention Attributionの出力をさらに発展させた別コンポーネントです。peak-riskフレームとAttentionヒートマップを入力として受け取り、「前方車両が急ブレーキ。直ちに制動してください」といったドライバーアクションと構造化されたテキスト推論を生成します。Qwen3-VL-4BをQLoRAでファインチューニングしたVLMで、学習データの作成にはGemini 2.5 Proをアノテーターとして使用しています。

NVIDIA Cosmosとのベンチマーク比較

要注目なのが、サイズと精度のトレードオフを大きく崩したこの比較結果です。

Nexarチームは、NVIDIAの Cosmos-Reason2 (2B) に対して、BADASと同じデータセットでファインチューニングした「Cosmos-BADAS (2B)」を作成し、自社の軽量モデルと比較しました。

Average Precision (AP) の比較

モデル パラメータ数 AP(平均適合率)
BADAS 2.0 Flash Lite 22M 98.4%
Cosmos-BADAS 2,000M (2B) 94.0%
Cosmos-Reason2 (Base) 2,000M (2B) 75.6%

※ 数値は論文記載のロングテールベンチマーク上の評価結果。Cosmos-Reason2 (Base) はBADASデータセットでのファインチューニングなしのベースライン性能。

エッジデバイス向けに極限まで蒸留・最適化された 22Mモデルが、約91倍のパラメータ差がある2Bモデルに +4.4ポイントの差をつけて上回っています。これにより、クラウド通信なしのローカル推論(車載チップ)でも、最高峰の予測モデルを遅延なく動かすことが可能になりました。

誤検知(False Positive)の劇的な削減

前世代からの進化として、再現率(Recall)を一切落とさずに、誤検知率(FPR)を大幅に削減しました。

バージョン FPR(誤検知率)
v1.0 17.7%
v2.0 4.6%(74%削減)

※ 内部テストセットでの数値。Kaggleベンチマーク上では 10.9%→4.6%(58%削減)と異なる数値が示されており、評価データセットによって削減率が変わります。

「ピーピーうるさくて切ってしまう」という旧来のADASの大きなUX的課題を、高精度なAIモデルによって解決しています。

展開されている3つのモデルバリアント

導入先のコンピュートリソースに合わせて、以下の3サイズが展開されています。

モデル パラメータ数 スコア 用途
BADAS 2.0 300M F1 = 96.4% 最高精度。複雑なロングテールシナリオ対応
BADAS 2.0 Flash 86M F1 = 93.8% 性能と計算リソースのバランス重視
BADAS 2.0 Flash Lite 22M AP = 98.4% 超小型エッジデバイス向け

※ 論文ではモデルごとに報告指標が異なります(Flash Liteは論文上F1未記載、APで評価)。

BADAS-Openで実際に動かしてみた

BADAS 2.0 の論文とベンチマーク結果を読んで、実際に動かしてみたくなりました。しかし BADAS 2.0 のモデルウェイトは非公開です。

そこで調べてみると、ひと世代前にあたる v1 系のモデル「BADAS-Open」 が Hugging Face で公開されていることが分かりました。v1 の論文(arXiv:2510.14876)によると、BADAS-Open は 1,500件の公開動画 で学習された公開版バリアントで、同じ v1 系でも非公開の BADAS 1.0(4万件の独自データで学習)とは区別されています。

モデル 世代 学習データ 公開状況
BADAS-Open v1 1,500件(公開動画) ✅ HuggingFace公開
BADAS 1.0 v1 40,000件(独自データ) ❌ 非公開
BADAS 2.0 系 v2 178,500件(約200万クリップ) ❌ 非公開

今回 Colab で動かすのは「最小の公開版」であり、BADAS 2.0 の最新性能をそのまま再現するものではありません。とはいえ、同じ V-JEPA2 バックボーンと Ego-Centric アーキテクチャを持つ直系の祖先モデルなので、BADAS シリーズの動作原理や Attention の挙動を体験する題材としては十分です。

事前準備

利用規約への同意が必要

nexar-ai/BADAS-Open リポジトリはHugging Face上でゲートされており、事前にモデルページで利用規約への同意が必要です。同意なしにモデルをダウンロードしようとすると認証エラーになります。こちらのページから同意したうえで、HF_TOKENをColabのシークレットに登録してください。

実行環境:Google Colab T4

推論・Attentionフック処理ともに、Google Colab の T4 GPU で問題なく動作しました。

使用データセット

今回の推論に使用した動画は、Kaggleで公開されている CARLA2COSMOS データセットから取得しました。

Step 1: セットアップとモデルのロード

公式HuggingFaceページの手順に従います。badas_loader.py をリポジトリから直接ダウンロードして使うのが正しい公式の使い方です。

⚠️ ハマりポイント:predict() にはパス文字列を渡す
model.predict() の引数は動画ファイルへのパス文字列です。動画をあらかじめテンソルに変換したものを渡すと TypeErrorRuntimeError が発生します。前処理は内部で自動的に行われるため、パス文字列をそのまま渡すだけで問題ありません。

!pip install -q torch torchvision transformers huggingface_hub opencv-python numpy pillow albumentations

from huggingface_hub import hf_hub_download, login
from google.colab import userdata
import sys, os

# HuggingFaceにログイン(利用規約への同意が必要)
login(token=userdata.get("HF_TOKEN"))

# 公式ローダーをダウンロード
loader_path = hf_hub_download(
    repo_id="nexar-ai/BADAS-Open",
    filename="badas_loader.py"
)
sys.path.insert(0, os.path.dirname(loader_path))

# モデルのロード
from badas_loader import load_badas_model
model = load_badas_model()

video_path = "/content/video01/video.mp4"
print("✅ BADAS-Open モデルのロードが完了しました!")

Step 2: モデル構造の確認

print(model.model) で内部アーキテクチャを確認できます。実際の出力は以下の通りです(長いため要所を抜粋)。

EnhancedVideoClassifier(
  (backbone): VJEPA2Model(
    (encoder): VJEPA2Encoder(
      (embeddings): VJEPA2PatchEmbeddings3D(
        (proj): Conv3d(3, 1024, kernel_size=(2, 16, 16), stride=(2, 16, 16))
      )
      (layer): ModuleList(
        (0): VJEPA2Layer(
          (attention): VJEPA2RopeAttention(
            (query): Linear(in_features=1024, out_features=1024, bias=True)
            (key):   Linear(in_features=1024, out_features=1024, bias=True)
            (value): Linear(in_features=1024, out_features=1024, bias=True)
            (proj):  Linear(in_features=1024, out_features=1024, bias=True)
          )
          (mlp): VJEPA2MLP(
            (fc1): Linear(in_features=1024, out_features=4096, bias=True)
            (fc2): Linear(in_features=4096, out_features=1024, bias=True)
          )
        )
        # ... (1)〜(23) まで同構造、drop_path の p が 0.0→0.1 に線形増加
      )
    )
    (predictor): VJEPA2Predictor(  # 1024→384次元の軽量予測器(マスク予測タスク用)
      ...
      (proj): Linear(in_features=384, out_features=1024, bias=True)
    )
  )
  (temporal_processor): AttentionProcessor(
    (attention): MultiheadAttention(1024→1024)  # 時系列統合
  )
  (classifier): MLPHead(
    (classifier): Sequential(
      1024 → 768 → 768 → 2  # 衝突あり/なしの2クラス出力
    )
  )
)

骨格は Meta の V-JEPA2(Video Joint Embedding Predictive Architecture) をバックボーンとして採用しています。Conv3d(kernel=(2,16,16)) で映像を時空間パッチに分割し、24層の VJEPA2RopeAttention(RoPE位置埋め込み付きSelf-Attention)で特徴抽出します。その後 temporal_processor でフレーム間の時系列統合を行い、MLP分類ヘッドで2クラスに落とす構成です。

Drop Pathの確率が layer 0 の p=0.0 から layer 23 の p=0.1 へ線形増加する Stochastic Depth 構成で、深い層ほど積極的に正則化をかけています。

Step 3: 推論と衝突リスクの時系列グラフ

model.predict() にはパス文字列をそのまま渡します。返り値はスライディングウィンドウごとの衝突確率スコアのリストで、BADASが動画を内部で8fpsにリサンプリングしたうえで16フレームずつウィンドウをスライドさせた結果です。

import matplotlib.pyplot as plt

predictions = model.predict(video_path)

for frame_idx, prob in enumerate(predictions):
    if prob > 0.8:
        print(f"⚠️ 【警告】ウィンドウ {frame_idx} で衝突リスク検出: {prob:.2%}")

plt.figure(figsize=(10, 5))
plt.plot(predictions, label='Collision Risk Probability', color='red', linewidth=2)
plt.axhline(y=0.8, color='orange', linestyle='--', label='Warning Threshold (80%)')
plt.title('BADAS-Open: Collision Risk over Time')
plt.xlabel('Frame Index (8fps換算)')
plt.ylabel('Collision Probability')
plt.ylim(0, 1.05)
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()

出力グラフ:

image.png

ウィンドウ36付近を境にスコアが急上昇し、80%の警告閾値を超えます。最終ウィンドウでは約93%に達しました。

グラフのX軸がウィンドウ15から始まっているのは、おそらく16フレームのコンテキスト窓が揃うまでの最初の数個がスキップされるためと考えられます(内部実装依存)。また今回の動画は 24fps・約5.3秒 という短いクリップで、BADASが想定する約40秒の動画とは大きく異なります。加えてCARLA合成映像という点でも分布外入力になっており、二重のOOD状況での結果です。

Step 4: 最高リスクフレームの画像取得

predictions のインデックスはBADASが内部で8fpsにリサンプリングした後のウィンドウ番号です。元動画のフレーム番号に変換するには src_fps / 8.0 で乗算する必要があります。

⚠️ ハマりポイント:フレーム番号の変換が必要
ウィンドウ番号をそのまま cap.set(POS_FRAMES, idx) に渡すと、全く別の時刻のフレームを取得してしまいます。max_window_idx=41src_fps=24 の場合、元動画のフレーム番号は 41 × (24/8) = 123 です。

import cv2
import numpy as np
from IPython.display import display
from PIL import Image

preds_np = np.array(predictions, dtype=float)
max_window_idx = int(np.nanargmax(preds_np))
max_risk = preds_np[max_window_idx]
print(f"🔥 最高リスク: ウィンドウ {max_window_idx} (スコア: {max_risk:.2%})")

cap = cv2.VideoCapture(video_path)
src_fps = cap.get(cv2.CAP_PROP_FPS)

# 8fps換算のウィンドウ番号 → 元動画のフレーム番号に変換
frame_idx = int(max_window_idx * src_fps / 8.0)
print(f"対応する元動画フレーム: {frame_idx} ({frame_idx / src_fps:.1f}秒地点)")

cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ret, frame = cap.read()
cap.release()

if ret:
    display(Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)))

image.png

交差点を右折してきた対向車が自車に向かってきているように見えます。視覚的にも確かに危険に見えるシーンであり、スコアが上昇したタイミングと一致しています。

Step 5: Attention Heatmapの可視化

BADAS 2.0の論文(arXiv:2604.05767)には、attention に基づく空間ヒートマップの生成方法が明記されています。「late encoder layers(ViT-Lの場合はlayer 12〜20)からself-attention weightsを抽出し、exponential temporal weightingを適用する」方法です。論文では『唯一の実行時要件は eager attention mode』と説明されており、追加の学習は不要(training-free)です。

ここでViT-LとはV-JEPA2のエンコーダサイズのことで、BADAS-Open は ViT-L ベース(24層・hidden dim 1024)であるため layer 12〜20 が対象です。

実装のポイントは2つです。まずBADAS-OpenはデフォルトでFlashAttentionが有効なため、Attentionの重み行列が保存されません(output[1]None になる)。config._attn_implementation = "eager" に変更することで重み行列が露出します(現行実装依存)。次に forward()output_attentions=True を渡すことで、Attention weightsが取り出せます。

import torch
import cv2
import numpy as np
import matplotlib.pyplot as plt

# attention weights 取得のため eager attention に切り替え
for layer in model.model.backbone.encoder.layer:
    layer.attention.config._attn_implementation = "eager"

TARGET_LAYERS = list(range(12, 21))  # ViT-L: layer 12〜20
attn_weights_per_layer = {}
hooks = []

def make_hook(layer_idx):
    def hook(module, input, output):
        if len(output) == 2 and output[1] is not None:
            attn_weights_per_layer[layer_idx] = output[1].detach().cpu()
    return hook

for layer_idx in TARGET_LAYERS:
    hooks.append(
        model.model.backbone.encoder.layer[layer_idx].attention.register_forward_hook(
            make_hook(layer_idx)
        )
    )

# output_attentions=Trueを強制するパッチ
original_forwards = {}
for layer_idx in TARGET_LAYERS:
    attn = model.model.backbone.encoder.layer[layer_idx].attention
    original_forwards[layer_idx] = attn.forward
    def make_patched_forward(orig_fwd):
        def patched_forward(hidden_states, position_mask=None, output_attentions=False):
            return orig_fwd(hidden_states, position_mask=position_mask, output_attentions=True)
        return patched_forward
    attn.forward = make_patched_forward(attn.forward)

_ = model.predict(video_path)

for h in hooks:
    h.remove()
for layer_idx in TARGET_LAYERS:
    model.model.backbone.encoder.layer[layer_idx].attention.forward = original_forwards[layer_idx]

# 論文記述を参考にした exponential temporal weighting による集約
# 警告時刻に近い後半フレームを強調する指数重みを使用
# 今回の入力設定ではトークン数が 8×16×16=2048
num_frames, patch_h, patch_w = 8, 16, 16
T = 2.0
t = torch.arange(num_frames).float()
frame_weights = torch.exp((t - (num_frames - 1)) / T)
frame_weights = frame_weights / frame_weights.sum()

spatial_map_accum = np.zeros((patch_h, patch_w))

for layer_idx in TARGET_LAYERS:
    attn = attn_weights_per_layer[layer_idx]  # (1, 16, 2048, 2048)
    attn_score = attn.squeeze(0).mean(dim=0).mean(dim=0)  # (2048,)
    attn_3d = attn_score.reshape(num_frames, patch_h, patch_w)
    weighted = (attn_3d * frame_weights[:, None, None]).sum(dim=0)
    spatial_map_accum += weighted.numpy()

spatial_map_accum = (spatial_map_accum - spatial_map_accum.min()) / \
                    (spatial_map_accum.max() - spatial_map_accum.min() + 1e-8)

cap = cv2.VideoCapture(video_path)
src_fps = cap.get(cv2.CAP_PROP_FPS)
frame_idx = int(max_window_idx * src_fps / 8.0)
cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
ret, frame = cap.read()
cap.release()
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)

heatmap_resized = cv2.resize(spatial_map_accum,
                             (frame_rgb.shape[1], frame_rgb.shape[0]),
                             interpolation=cv2.INTER_CUBIC)
heatmap_color = cv2.applyColorMap(np.uint8(255 * heatmap_resized), cv2.COLORMAP_JET)
heatmap_color = cv2.cvtColor(heatmap_color, cv2.COLOR_BGR2RGB)
superimposed = cv2.addWeighted(frame_rgb, 0.6, heatmap_color, 0.4, 0)

plt.figure(figsize=(14, 7))
plt.subplot(1, 2, 1); plt.title(f"Original Frame {frame_idx}")
plt.imshow(frame_rgb); plt.axis('off')
plt.subplot(1, 2, 2); plt.title("Attention Heatmap (layers 12-20 aggregate, sample implementation based on the paper)")
plt.imshow(superimposed); plt.axis('off')
plt.tight_layout()
plt.show()

出力結果:

image.png

自車正面の青い対向車に赤いhotspotが集中しています。論文のFigure 3ではBADAS-1.0は「most diffuse, scattered activations」と評されていますが、今回の結果はリスク対象にある程度集中した結果となりました。BADASが自車周辺のリスク要因に注目する Ego-Centric な設計であることが、ヒートマップからも確認できます。なお論文によると、ローカライズ精度が最も高いのはBADAS-2.0 300Mではなく SSL事前学習ありの蒸留モデル(Flash/Flash-Lite) です。ViT-L(300M)は汎用的な映像データから初期化されているためattentionが散漫になりやすく、Nexarの走行映像でSSL事前学習した蒸留モデルの方が鋭いローカライズを示します。

BADAS 2.0 は試せるか

BADAS 2.0 のモデルウェイトや学習コードは現時点で オープン化されていません。 ビジネスモデルとしてはB2B(Vay社のような遠隔運転企業やフリート管理向け)が前提のプロプライエタリな技術です。

ただし、公式サイト(badas.nexar.app)でライブデモが公開されており、自前のドラレコ動画をアップロードして BADAS 2.0 に推論させることができます。

試しに今回と同じCARLA合成映像を公式デモに投げてみました。

image.png

結果は peak 32%・LOW判定 で、BADAS-Openの93%・高リスク判定と大きく異なります。ヒートマップも交差点全体をオレンジでカバーする広い範囲になっており、スポット集中したBADAS-Openの結果とは対照的です。

一つの可能性として、BADAS 2.0がCARLA合成映像に対してより保守的な判定をしていることが考えられます。論文ではv1比でFPR(誤検知率)の大幅な削減が報告されており、その傾向と整合的にも見えます(ただしこれは1サンプルの観察であり断定はできません)。

まとめ

「パラメータ数を巨大化して汎用性を高める」という最近のLLM/VLMのメインストリームに対して、NexarのBADAS 2.0は 「圧倒的に高品質なドメイン特化データを使えば、たった22Mの極小モデルでも大規模基盤モデルを上回れる」 ということを示しました。

今回BADAS-Openを実際に動かしてみて、v1とv2の差を体感する機会にもなりました。同じCARLA合成映像に対してBADAS-Openは93%の高リスクを出力しましたが、BADAS 2.0の公式デモは32%のLOW判定でした。この差は、軸①のロングテール対応と軸②の知識蒸留によってOOD入力への過剰反応が抑制された結果の一つとも読めます。また、Attention Heatmapの可視化を実装する過程でFlashAttentionの仕組みや論文記述の attention attribution を理解できた点も収穫でした。

特に印象的なのは、単なるモデル圧縮ではなく、「運転ドメイン特化のSSL事前学習 → 知識蒸留 → エッジ展開」を一つのパイプラインとして最適化している点です。Kaggleのコンペでも実感しましたが、自動運転や車載エッジAIにおいて「質の高い現実のデータパイプライン」が持つ価値は計り知れません。今後のNexarの技術展開に引き続き注目していきたいと思います。

参考リンク

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?