forensics-grpo / code /verifier_m2_extract_clip_test.py
sdzt's picture
Add source code
33569f9 verified
Raw
History Blame Contribute Delete
4.13 kB
"""Extract CLIP features for AF TEST videos (separate from train extraction).
Test-set verifier features are needed ONLY at inference time for the
verifier-as-context experiment (Phase 1). The verifier itself was trained
without test labels (no leakage); this step just runs the trained verifier on
test data so the policy model can see per-second forgery scores as context.
"""
import argparse
import os
import sys
import time
import torch
import torch.nn.functional as F
from decord import VideoReader, cpu
from PIL import Image
from transformers import CLIPModel, CLIPProcessor
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from src.open_r1.data_loader import TEST_GENERATORS, build_examples
from src.open_r1.forgery_head import frame_labels_from_segments
ANNOT = "/mnt/local-fast/zhangt/annot/annot"
VROOT = "/mnt/local-fast/zhangt/video"
CACHE = "/mnt/local-fast/zhangt/forensics_verifier_clip_l14"
MODEL_ID = "openai/clip-vit-large-patch14"
SAMPLE_FPS = 1.0
def decode_at_1fps(video_path: str, duration: float):
vr = VideoReader(video_path, ctx=cpu(0))
fps_video = vr.get_avg_fps()
n_secs = max(1, int(duration))
idxs = [min(int(s * fps_video), len(vr) - 1) for s in range(n_secs)]
frames = vr.get_batch(idxs).asnumpy()
return [Image.fromarray(f) for f in frames], n_secs
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--rank", type=int, default=0)
ap.add_argument("--world_size", type=int, default=1)
ap.add_argument("--device", type=int, default=0)
ap.add_argument("--batch", type=int, default=32)
args = ap.parse_args()
device = f"cuda:{args.device}"
print(f"[rank {args.rank}/{args.world_size}] device={device}", flush=True)
print("loading CLIP ...", flush=True)
t0 = time.time()
clip = CLIPModel.from_pretrained(MODEL_ID, torch_dtype=torch.float32).to(device).eval()
proc = CLIPProcessor.from_pretrained(MODEL_ID)
print(f" loaded in {time.time()-t0:.1f}s", flush=True)
split = "test"
examples = build_examples(
annot_dir=ANNOT, video_root=VROOT, generators=TEST_GENERATORS,
split_prefix=split, preprocessed_data_path=None, require_video_exists=True,
)
examples = [ex for i, ex in enumerate(examples) if i % args.world_size == args.rank]
print(f"[{split}] rank={args.rank} processing {len(examples)} videos", flush=True)
t_start = time.time()
done = skipped = failed = 0
for ex in examples:
sample_id = os.path.splitext(os.path.basename(ex["video_path"]))[0]
out_dir = os.path.join(CACHE, split, ex["generator"], sample_id)
feat_path = os.path.join(out_dir, "clip_feats.pt")
if os.path.exists(feat_path):
skipped += 1
continue
try:
pil_imgs, n_secs = decode_at_1fps(ex["video_path"], ex["durations"])
except Exception:
failed += 1
continue
feats_all = []
for i in range(0, len(pil_imgs), args.batch):
chunk = pil_imgs[i:i + args.batch]
with torch.no_grad():
inputs = proc(images=chunk, return_tensors="pt").to(device)
f = clip.get_image_features(**inputs)
f = F.normalize(f, dim=-1)
feats_all.append(f.cpu())
feats = torch.cat(feats_all, dim=0).float()
labels = frame_labels_from_segments(
ex["solution"], n_secs, fps_to_groups=SAMPLE_FPS
).float()
os.makedirs(out_dir, exist_ok=True)
torch.save(feats, feat_path)
torch.save(labels, os.path.join(out_dir, "clip_labels.pt"))
done += 1
if done % 30 == 0:
elapsed = time.time() - t_start
rate = done / max(1e-6, elapsed)
remaining = (len(examples) - done - skipped) / max(1e-6, rate)
print(f" rank={args.rank} done={done} skipped={skipped} failed={failed} "
f"rate={rate:.2f}/s eta={remaining/60:.1f}min", flush=True)
print(f"rank={args.rank} DONE done={done} skipped={skipped} failed={failed}", flush=True)
if __name__ == "__main__":
main()