c66855adfc
Sync to site1 / sync (push) Has been cancelled
- 移除 JEPA/lejepa-identifiability 子模块 gitlink - 移除 research/multiply/MultiPLY 子模块 gitlink - 删除 .gitmodules(不再有外部 URL 依赖) - 两个目录内容作为普通文件纳入主仓库追踪 - 删除各自内部 .git 目录,消除嵌套 git 仓库
236 lines
8.8 KiB
Python
236 lines
8.8 KiB
Python
"""
|
|
Generate LaTeX tables for the paper, matching scaling table style.
|
|
|
|
Usage:
|
|
python analysis/make_table_reacher.py --results_dir results/reacher
|
|
"""
|
|
|
|
import json
|
|
import argparse
|
|
import numpy as np
|
|
from pathlib import Path
|
|
from collections import defaultdict
|
|
|
|
|
|
def load_all_results(results_dir):
|
|
ou, traj = [], []
|
|
for p in Path(results_dir).rglob("result.json"):
|
|
r = json.load(open(p))
|
|
if "delta" in r and r.get("rho") is None:
|
|
traj.append(r)
|
|
elif "rho" in r:
|
|
ou.append(r)
|
|
return ou, traj
|
|
|
|
|
|
def best_lambda_per_x(results, x_key):
|
|
grouped = defaultdict(list)
|
|
for r in results:
|
|
grouped[(r[x_key], r["lamb"])].append(r)
|
|
|
|
best = {}
|
|
for x in sorted(set(k[0] for k in grouped)):
|
|
best_mean, best_lamb = -np.inf, None
|
|
for lamb in set(k[1] for k in grouped if k[0] == x):
|
|
m = np.mean([r["r2_hz"] for r in grouped[(x, lamb)]])
|
|
if m > best_mean:
|
|
best_mean, best_lamb = m, lamb
|
|
best[x] = {
|
|
"lamb": best_lamb,
|
|
"runs": grouped[(x, best_lamb)],
|
|
}
|
|
return best
|
|
|
|
|
|
def pm(vals, fmt=".2f"):
|
|
"""Format as value\\tiny{±std} matching paper style."""
|
|
m, s = np.mean(vals), np.std(vals)
|
|
return f"{m:{fmt}}\\tiny{{$\\pm${s:.0e}}}"
|
|
|
|
|
|
def make_combined_table(ou_results, traj_results):
|
|
ou_best = best_lambda_per_x(ou_results, "rho")
|
|
traj_best = best_lambda_per_x(traj_results, "delta")
|
|
|
|
lines = []
|
|
lines.append(r"\begin{table}[t]")
|
|
lines.append(r"\centering")
|
|
lines.append(r"\caption{")
|
|
lines.append(r" \textbf{Pixel-observation identifiability on DMC Reacher} "
|
|
r"(mean $\pm$ std over 3 seeds, best $\lambda$ per condition).")
|
|
lines.append(r" \textbf{Left:} OU process with Gaussian marginals. "
|
|
r"$R^2$ increases monotonically with $\rho$, reaching $0.95$ "
|
|
r"at $\rho = 0.99$, confirming linear identifiability from pixels.")
|
|
lines.append(r" \textbf{Right:} Real SAC trajectories with non-Gaussian marginals. "
|
|
r"The two joints have different autocorrelation timescales ($\rho_0 \neq \rho_1$) "
|
|
r"and the wrist has a near-uniform marginal distribution, "
|
|
r"leading to anisotropic and reduced identifiability.")
|
|
lines.append(r"}")
|
|
lines.append(r"\label{tab:reacher}")
|
|
lines.append(r"\resizebox{\textwidth}{!}{%")
|
|
lines.append(r"\begin{tabular}{r cc | r cc ccc}")
|
|
lines.append(r" \multicolumn{3}{c}{\textbf{OU (Gaussian)}} & "
|
|
r"\multicolumn{6}{c}{\textbf{Trajectory (non-Gaussian)}} \\")
|
|
lines.append(r"\cmidrule(lr){1-3} \cmidrule(lr){4-9}")
|
|
lines.append(r"$\rho$ & $R^2(z \to h)$ & $R^2(h \to z)$ & "
|
|
r"$\delta$ & $\rho_0$ & $\rho_1$ & "
|
|
r"$R^2(z \to h)$ & $R^2(h \to z_0)$ & $R^2(h \to z_1)$ \\")
|
|
lines.append(r"\midrule")
|
|
|
|
ou_rhos = sorted(ou_best.keys())
|
|
traj_deltas = sorted(traj_best.keys())
|
|
n_rows = max(len(ou_rhos), len(traj_deltas))
|
|
|
|
for i in range(n_rows):
|
|
# OU columns
|
|
if i < len(ou_rhos):
|
|
rho = ou_rhos[i]
|
|
runs = ou_best[rho]["runs"]
|
|
r2_zh = pm([r["r2_zh"] for r in runs])
|
|
r2_hz = pm([r["r2_hz"] for r in runs])
|
|
ou_str = f" {rho:.2f} & {r2_zh} & {r2_hz}"
|
|
else:
|
|
ou_str = r" & &"
|
|
|
|
# Traj columns
|
|
if i < len(traj_deltas):
|
|
delta = traj_deltas[i]
|
|
runs = traj_best[delta]["runs"]
|
|
rho0 = runs[0].get("rho_shoulder", None)
|
|
rho1 = runs[0].get("rho_wrist", None)
|
|
rho0_s = f"{rho0:.3f}" if rho0 is not None else "---"
|
|
rho1_s = f"{rho1:.3f}" if rho1 is not None else "---"
|
|
r2_zh = pm([r["r2_zh"] for r in runs])
|
|
r2_d0 = pm([r["r2_hz_per_dim"][0] for r in runs])
|
|
r2_d1 = pm([r["r2_hz_per_dim"][1] for r in runs])
|
|
traj_str = f"{delta} & {rho0_s} & {rho1_s} & {r2_zh} & {r2_d0} & {r2_d1}"
|
|
else:
|
|
traj_str = r"& & & & &"
|
|
|
|
lines.append(f"{ou_str} & {traj_str} \\\\")
|
|
|
|
lines.append(r"\bottomrule")
|
|
lines.append(r"\end{tabular}}")
|
|
lines.append(r"\vspace{5pt}")
|
|
lines.append(r"\vspace{-20pt}")
|
|
lines.append(r"\end{table}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def make_ou_table_standalone(ou_results):
|
|
"""Standalone OU table for appendix if needed."""
|
|
ou_best = best_lambda_per_x(ou_results, "rho")
|
|
|
|
lines = []
|
|
lines.append(r"\begin{table}[t]")
|
|
lines.append(r"\centering")
|
|
lines.append(r"\begin{tabular}{r c cc}")
|
|
lines.append(r"\toprule")
|
|
lines.append(r" \multicolumn{1}{c}{\textbf{Correlation}} & "
|
|
r"\multicolumn{1}{c}{\textbf{Regularizer}} & "
|
|
r"\multicolumn{2}{c}{\textbf{Linear identifiability}} \\")
|
|
lines.append(r"\cmidrule(lr){1-1} \cmidrule(lr){2-2} \cmidrule(lr){3-4}")
|
|
lines.append(r"$\rho$ & $\lambda$ & $R^2(z \to h)$ & $R^2(h \to z)$ \\")
|
|
lines.append(r"\midrule")
|
|
|
|
for rho in sorted(ou_best.keys()):
|
|
runs = ou_best[rho]["runs"]
|
|
lamb = ou_best[rho]["lamb"]
|
|
r2_zh = pm([r["r2_zh"] for r in runs])
|
|
r2_hz = pm([r["r2_hz"] for r in runs])
|
|
lines.append(f" {rho:.2f} & {lamb:.0e} & {r2_zh} & {r2_hz} \\\\")
|
|
|
|
lines.append(r"\bottomrule")
|
|
lines.append(r"\end{tabular}")
|
|
lines.append(r"\vspace{5pt}")
|
|
lines.append(r"\caption{")
|
|
lines.append(r" \textbf{OU (Gaussian) identifiability from pixels} "
|
|
r"(mean $\pm$ std over 3 seeds).")
|
|
lines.append(r" $R^2$ increases monotonically with temporal correlation $\rho$, "
|
|
r"reaching $0.95$ at $\rho = 0.99$.")
|
|
lines.append(r"}")
|
|
lines.append(r"\label{tab:reacher_ou}")
|
|
lines.append(r"\end{table}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def make_traj_table_standalone(traj_results):
|
|
"""Standalone traj table for appendix if needed."""
|
|
traj_best = best_lambda_per_x(traj_results, "delta")
|
|
|
|
lines = []
|
|
lines.append(r"\begin{table}[t]")
|
|
lines.append(r"\centering")
|
|
lines.append(r"\resizebox{\textwidth}{!}{%")
|
|
lines.append(r"\begin{tabular}{r cc c c cc}")
|
|
lines.append(r"\toprule")
|
|
lines.append(r" \multicolumn{1}{c}{\textbf{Stride}} & "
|
|
r"\multicolumn{2}{c}{\textbf{Autocorrelation}} & "
|
|
r"\multicolumn{1}{c}{\textbf{Regularizer}} & "
|
|
r"\multicolumn{1}{c}{\textbf{Identifiability}} & "
|
|
r"\multicolumn{2}{c}{\textbf{Per-dimension}} \\")
|
|
lines.append(r"\cmidrule(lr){1-1} \cmidrule(lr){2-3} \cmidrule(lr){4-4} "
|
|
r"\cmidrule(lr){5-5} \cmidrule(lr){6-7}")
|
|
lines.append(r"$\delta$ & $\rho_0$ & $\rho_1$ & $\lambda$ & "
|
|
r"$R^2(z \to h)$ & $R^2(h \to z_0)$ & $R^2(h \to z_1)$ \\")
|
|
lines.append(r"\midrule")
|
|
|
|
for delta in sorted(traj_best.keys()):
|
|
runs = traj_best[delta]["runs"]
|
|
lamb = traj_best[delta]["lamb"]
|
|
rho0 = runs[0].get("rho_shoulder", None)
|
|
rho1 = runs[0].get("rho_wrist", None)
|
|
rho0_s = f"{rho0:.3f}" if rho0 is not None else "---"
|
|
rho1_s = f"{rho1:.3f}" if rho1 is not None else "---"
|
|
r2_zh = pm([r["r2_zh"] for r in runs])
|
|
r2_d0 = pm([r["r2_hz_per_dim"][0] for r in runs])
|
|
r2_d1 = pm([r["r2_hz_per_dim"][1] for r in runs])
|
|
lines.append(f" {delta} & {rho0_s} & {rho1_s} & {lamb:.0e} "
|
|
f"& {r2_zh} & {r2_d0} & {r2_d1} \\\\")
|
|
|
|
lines.append(r"\bottomrule")
|
|
lines.append(r"\end{tabular}}")
|
|
lines.append(r"\vspace{5pt}")
|
|
lines.append(r"\caption{")
|
|
lines.append(r" \textbf{Trajectory (non-Gaussian) identifiability from pixels} "
|
|
r"(mean $\pm$ std over 3 seeds).")
|
|
lines.append(r" The shoulder ($z_0$) and wrist ($z_1$) have different "
|
|
r"autocorrelation timescales and marginal distributions, "
|
|
r"leading to anisotropic identifiability.")
|
|
lines.append(r"}")
|
|
lines.append(r"\label{tab:reacher_traj}")
|
|
lines.append(r"\end{table}")
|
|
return "\n".join(lines)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--results_dir", type=str, default="results/reacher")
|
|
args = parser.parse_args()
|
|
|
|
ou, traj = load_all_results(args.results_dir)
|
|
print(f"Loaded {len(ou)} OU runs, {len(traj)} traj runs\n")
|
|
|
|
if ou and traj:
|
|
print("=" * 70)
|
|
print("COMBINED TABLE (for main text)")
|
|
print("=" * 70)
|
|
print(make_combined_table(ou, traj))
|
|
print()
|
|
|
|
if ou:
|
|
print("=" * 70)
|
|
print("OU TABLE (standalone, for appendix)")
|
|
print("=" * 70)
|
|
print(make_ou_table_standalone(ou))
|
|
print()
|
|
|
|
if traj:
|
|
print("=" * 70)
|
|
print("TRAJ TABLE (standalone, for appendix)")
|
|
print("=" * 70)
|
|
print(make_traj_table_standalone(traj))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |