| """Compare point-KD teacher quality with the reproduced no-KD student by participant.""" |
| from __future__ import annotations |
|
|
| import csv |
| import json |
| from pathlib import Path |
| import sys |
|
|
| ROOT = Path(r"E:\Gaze_estimation") |
| sys.path.insert(0, str(ROOT / ".codex_deps")) |
|
|
| import h5py |
| import numpy as np |
| import pandas as pd |
|
|
|
|
| CACHE_ROOT = ROOT / "data" / "processed_kd_clean_v1" / "cache" |
| SAMPLES = ROOT / "artifacts" / "kd-teacher-trap-diagnostic" / "per_sample_diagnostics.csv" |
| OUT = ROOT / "artifacts" / "kd-teacher-trap-diagnostic" |
|
|
|
|
| def vectors(pitch_deg: np.ndarray, yaw_deg: np.ndarray) -> np.ndarray: |
| pitch, yaw = np.deg2rad(pitch_deg), np.deg2rad(yaw_deg) |
| return np.column_stack( |
| (-np.cos(pitch) * np.sin(yaw), -np.sin(pitch), -np.cos(pitch) * np.cos(yaw)) |
| ) |
|
|
|
|
| def angular(first: np.ndarray, second: np.ndarray) -> np.ndarray: |
| first = first / np.linalg.norm(first, axis=1, keepdims=True) |
| second = second / np.linalg.norm(second, axis=1, keepdims=True) |
| return np.rad2deg(np.arccos(np.clip(np.sum(first * second, axis=1), -1.0, 1.0))) |
|
|
|
|
| def main() -> None: |
| samples = pd.read_csv(SAMPLES) |
| subject_reports = [] |
| quartile_rows = [] |
| available = sorted(path.name.split(".")[0] for path in CACHE_ROOT.glob("p??.official448_pointkd.h5")) |
| for subject in available: |
| cache_path = CACHE_ROOT / f"{subject}.official448_pointkd.h5" |
| with h5py.File(cache_path, "r") as cache: |
| teacher_vector = cache["teacher_target_vector"][:].astype(np.float64) |
| target_deg = np.rad2deg(cache["left_gaze"][:].astype(np.float64)) |
| target_vector = vectors(target_deg[:, 0], target_deg[:, 1]) |
| teacher_error = angular(teacher_vector, target_vector) |
| student = samples[(samples.subject == subject) & (samples.id == 5)].sort_values("row_index") |
| student_vector = vectors(student.student_pitch_deg.to_numpy(), student.student_yaw_deg.to_numpy()) |
| student_error = angular(student_vector, target_vector) |
| if len(student_error) != len(teacher_error): |
| raise RuntimeError(f"row mismatch for {subject}") |
| quartiles = np.asarray(pd.qcut(teacher_error, 4, labels=False)) |
| for quartile in range(4): |
| mask = quartiles == quartile |
| quartile_rows.append( |
| { |
| "subject": subject, |
| "teacher_error_quartile": quartile + 1, |
| "samples": int(mask.sum()), |
| "teacher_3d_error_mean_deg": float(teacher_error[mask].mean()), |
| "student_id5_3d_error_mean_deg": float(student_error[mask].mean()), |
| "teacher_minus_student_deg": float( |
| teacher_error[mask].mean() - student_error[mask].mean() |
| ), |
| "teacher_better_fraction": float( |
| (teacher_error[mask] < student_error[mask]).mean() |
| ), |
| } |
| ) |
| subject_reports.append( |
| { |
| "subject": subject, |
| "samples": len(teacher_error), |
| "teacher_3d_error_mean_deg": float(teacher_error.mean()), |
| "student_id5_3d_error_mean_deg": float(student_error.mean()), |
| "teacher_minus_student_deg": float(teacher_error.mean() - student_error.mean()), |
| "teacher_better_fraction": float((teacher_error < student_error).mean()), |
| "teacher_student_error_pearson": float(np.corrcoef(teacher_error, student_error)[0, 1]), |
| } |
| ) |
| csv_path = OUT / "pointkd_gating_quartiles.csv" |
| with csv_path.open("w", encoding="utf-8", newline="") as stream: |
| writer = csv.DictWriter(stream, fieldnames=quartile_rows[0].keys()) |
| writer.writeheader() |
| writer.writerows(quartile_rows) |
| report = { |
| "teacher_protocol": ( |
| "strict 322/322 load; 448px; named heads; 4-degree bins; expected 3D vector " |
| "rotated into left-eye target coordinates" |
| ), |
| "subjects": subject_reports, |
| "quartiles": quartile_rows, |
| "standard_kd_decision": "FAIL: teacher is globally worse than the reproduced no-KD student on every audited subject", |
| "quality_gated_kd_decision": ( |
| "PREFLIGHT_JUSTIFIED_ONLY: low-error teacher strata may help, but gate thresholds " |
| "must be selected using training participants and evaluated on held-out participants." |
| ), |
| } |
| output = OUT / "pointkd_gating_audit.json" |
| output.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8", newline="\n") |
| print(json.dumps(report, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|