tsfp-repro-code / audit_claim1.py
riteshhf's picture
Upload folder using huggingface_hub
2188a91 verified
Raw
History Blame Contribute Delete
4.74 kB
"""Claim 1 audit: TS-Fingerprint compresses a variable-length MedTS into a fixed set of
k learnable Fingerprint Tokens via a learnable query set Q in R^{k x d}, with k << T.
Checks:
1. the encoder output is exactly (B, k, d) for every input length T;
2. Q is a genuine nn.Parameter that receives gradient (i.e. it is *learnable*),
and the parameter count of the bottleneck is independent of T;
3. k << T holds at the paper's settings for all 7 benchmark datasets of Table 5;
4. the decoder sees *only* F' plus positional mask tokens, so the data-processing
chain X -> F' -> X_hat is strict: perturbing a masked input patch can change
X_hat only through F'. We verify this by freezing F' and re-running the decoder.
"""
import json
import os
import sys
import torch
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from model import TSFingerprint # noqa: E402
# Table 5 of the paper: (#Timestamps T, #Channel C, patch size)
DATASETS = {
"ADFTD": (256, 19, 8), "PTB": (300, 15, 10), "PTB-XL": (250, 12, 10),
"APAVA": (256, 16, 8), "FLAAP": (100, 6, 10), "UCIHAR": (128, 9, 32),
"Sleep-EDF": (3000, 1, 60),
}
K, D = 8, 128
rep = {}
torch.manual_seed(0)
m = TSFingerprint(c_in=19, patch_size=8, n_classes=3, d_model=D, k=K, max_patches=4096)
# --- 1. shape invariance to T -------------------------------------------------
shapes = {}
for T in (64, 128, 256, 512, 1024, 2048, 4096, 8192):
with torch.no_grad():
f = m.encoder(torch.randn(3, T, 19))
shapes[T] = {"n_patches": T // 8, "F_shape": list(f.shape)}
rep["shape_invariance"] = shapes
rep["all_outputs_are_k_by_d"] = all(v["F_shape"] == [3, K, D] for v in shapes.values())
# --- 2. Q is learnable, and bottleneck size is T-independent -------------------
rep["Q_shape"] = list(m.encoder.Q.shape)
rep["Q_is_parameter"] = isinstance(m.encoder.Q, torch.nn.Parameter)
rep["Q_requires_grad"] = bool(m.encoder.Q.requires_grad)
m.zero_grad()
loss, _, _ = m.pretrain_step(torch.randn(4, 256, 19))
loss.backward()
g = m.encoder.Q.grad
rep["Q_grad_norm_after_backward"] = float(g.norm())
rep["Q_receives_gradient"] = bool(g is not None and g.norm() > 0)
rep["encoder_params_excluding_pos_embed"] = sum(
p.numel() for n, p in m.encoder.named_parameters() if n != "pos")
# --- 3. k << T on every benchmark ---------------------------------------------
ratios = {}
for name, (T, C, ps) in DATASETS.items():
n_patch = T // ps
ratios[name] = {"T": T, "C": C, "patch": ps, "n_patches": n_patch, "k": K,
"k_over_T": round(K / T, 5), "k_over_n_patches": round(K / n_patch, 4),
"k_much_less_than_T": bool(K < T / 4)}
rep["compression_per_dataset"] = ratios
rep["k_ll_T_on_all_datasets"] = all(v["k_much_less_than_T"] for v in ratios.values())
# compression factor: raw floats in vs floats out
rep["compression_factor_TxC_to_kxd"] = {
n: round((v["T"] * v["C"]) / (K * D), 3) for n, v in ratios.items()}
# --- 4. strict bottleneck: decoder output depends on x only through F' ---------
m.eval()
x = torch.randn(2, 256, 19)
with torch.no_grad():
f = m.encoder(x)
rec_a = m.decoder(f, 32)
x2 = x.clone()
x2[:, 100:140] += 5.0 # large perturbation of the input
f2 = m.encoder(x2)
rec_b = m.decoder(f2, 32) # differs, because F' differs
rec_c = m.decoder(f, 32) # same F' -> must be bit-identical
rep["decoder_conditioned_solely_on_F"] = {
"delta_recon_when_F_changes": float((rec_a - rec_b).abs().max()),
"delta_recon_when_F_held_fixed": float((rec_a - rec_c).abs().max()),
"strict_chain_X_to_F_to_Xhat": bool((rec_a - rec_c).abs().max() == 0.0
and (rec_a - rec_b).abs().max() > 0),
}
# the decoder signature takes only (F', n_patches) -- no path for raw patches
import inspect # noqa: E402
rep["decoder_forward_signature"] = str(inspect.signature(m.decoder.forward))
print(json.dumps(rep, indent=2))
os.makedirs("results", exist_ok=True)
with open("results/claim1_audit.json", "w") as fh:
json.dump(rep, fh, indent=2)
print("\n---- verdict ----")
print(f"encoder output is (B, {K}, {D}) for every T in "
f"{sorted(shapes)}: {rep['all_outputs_are_k_by_d']}")
print(f"Q in R^(k x d) = {rep['Q_shape']}, learnable nn.Parameter, "
f"grad norm {rep['Q_grad_norm_after_backward']:.4f}")
print(f"k << T on all 7 Table-5 datasets: {rep['k_ll_T_on_all_datasets']} "
f"(k/T from {min(v['k_over_T'] for v in ratios.values()):.5f} to "
f"{max(v['k_over_T'] for v in ratios.values()):.5f})")
print("decoder is strictly conditioned on F': "
f"{rep['decoder_conditioned_solely_on_F']['strict_chain_X_to_F_to_Xhat']}")