Files
rUv aac4155584 feat(bfi): add validated capture and bounded research tooling (#2055)
* feat(bfi): add validated capture and bounded research tooling

* fix(deps): patch Wasmtime for RUSTSEC-2026-0316
2026-09-29 23:53:48 -04:00

659 lines
36 KiB
Python

"""Synthetic-only boundary, leakage and CPU smoke tests; no hardware claims."""
import io
import copy
import json
import struct
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import zipfile
import numpy as np
import train
class TrainingContractTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory(prefix="ruview-training-test-")
self.root = Path(self.temporary.name)
self.npz = self.root / "input.npz"
self.meta_path = self.root / "input.json"
self.frames = np.random.default_rng(12).normal(size=(600, 4)).astype(np.float32)
self.times = 1_700_000_000 + np.arange(600, dtype=np.float64) / 100
self.sequence = np.arange(600, dtype=np.uint32)
self.meta = {"schema_version": 1, "provenance": "synthetic", "source_kind": "rac1",
"label_status": "unlabeled", "frame_count": 600, "feature_count": 4,
"source_files": [{"sha256": "a" * 64, "size_bytes": 123}], "format": {},
"sessions": [{"session_id": "synthetic_1", "start": 0, "end": 600}]}
self.save()
def tearDown(self):
self.temporary.cleanup()
def save(self, **arrays):
np.savez(self.npz, frames=arrays.get("frames", self.frames),
timestamps=arrays.get("timestamps", self.times), sequence=arrays.get("sequence", self.sequence))
self.meta["dataset_sha256"] = train.sha256(self.npz.read_bytes())
self.meta_path.write_text(json.dumps(self.meta), encoding="utf-8")
def load(self):
return train.load_dataset(self.npz, self.meta_path)
def args(self, *extra):
return train.parser().parse_args(["--dataset", str(self.npz), "--metadata", str(self.meta_path),
"--output", str(self.root / "output"), "--allow-synthetic", "--window", "8", "--hidden", "4",
"--epochs", "2", "--batch-size", "16", "--max-train-windows", "32",
"--max-eval-windows", "16", "--max-seconds", "30", "--threads", "1", *extra])
def identity_data(self, public=False):
self.meta.update(provenance="real", label_status="identity_labeled",
split_unit="source_file" if public else "acquisition_session",
acquisition_session_disjointness="unknown" if public else "verified")
self.meta["sessions"] = [
{"session_id": f"recording_{i}", "start": i * 100, "end": (i + 1) * 100,
"identity": str(i % 2), "split": train.SPLITS[i // 2]}
for i in range(6)]
self.meta["source_files"] = []
for i, s in enumerate(self.meta["sessions"]):
digest = train.sha256(f"synthetic-fixture-{i}".encode())
s.update(source_sha256=digest, acquisition_session_id=f"acquisition-{i}")
self.meta["source_files"].append({"sha256": digest, "size_bytes": 123})
if public:
self.meta.update(source_kind="ntu_fi_humanid", label_origin="published_dataset", time_unit="sample_index",
split_protocol="archive_directory_custom")
self.times = np.tile(np.arange(1, 101, dtype=np.float64), 6)
self.meta["source_files"] = []
for i, s in enumerate(self.meta["sessions"]):
digest = train.sha256(f"synthetic-fixture-{i}".encode())
s.update(source_sha256=digest, source_split="test" if s["split"] == "test" else "train")
self.meta["source_files"].append({"sha256": digest, "size_bytes": 123})
self.save()
return self.load()
def test_numeric_load(self):
result = self.load()
self.assertEqual(result["frames"].shape, (600, 4))
self.assertEqual(result["metadata"]["label_status"], "unlabeled")
def test_wrong_hash_rejected(self):
self.meta["dataset_sha256"] = "0" * 64
self.meta_path.write_text(json.dumps(self.meta))
with self.assertRaisesRegex(ValueError, "hash mismatch"):
self.load()
def test_object_pickle_rejected_before_load(self):
self.save(frames=np.empty((600, 4), dtype=object))
with self.assertRaisesRegex(ValueError, "numeric dtype"):
self.load()
def test_unexpected_dtype_and_dimensions(self):
for value in (self.frames.astype(np.float64), self.frames[:, :2], self.frames.reshape(600, 2, 2)):
self.save(frames=value)
with self.assertRaisesRegex(ValueError, "shape or numeric dtype"):
self.load()
def test_nonfinite_features(self):
for value in (np.nan, np.inf, 1e7):
self.frames[0, 0] = value
self.save()
with self.assertRaisesRegex(ValueError, "frame values"):
self.load()
def test_timestamp_regression_and_duplicate(self):
for delta in (0, -1):
self.times[5] = self.times[4] + delta
self.save()
with self.assertRaisesRegex(ValueError, "timestamps must increase"):
self.load()
def test_duplicate_json_key(self):
self.meta_path.write_text('{"schema_version":1,"schema_version":1}')
with self.assertRaisesRegex(ValueError, "duplicate JSON"):
self.load()
def test_npy_shape_allocation_bomb(self):
header = io.BytesIO()
np.lib.format.write_array_header_1_0(header, {"descr": "<f4", "fortran_order": False,
"shape": (10**12, 4)})
good = self.npz.read_bytes()
with zipfile.ZipFile(io.BytesIO(good)) as old:
members = {name: old.read(name) for name in old.namelist()}
members["frames.npy"] = header.getvalue()
with zipfile.ZipFile(self.npz, "w") as out:
for name, raw in members.items():
out.writestr(name, raw)
self.meta["dataset_sha256"] = train.sha256(self.npz.read_bytes())
self.meta_path.write_text(json.dumps(self.meta))
with self.assertRaisesRegex(ValueError, "shape or numeric dtype"):
self.load()
def malicious_zip(self, change):
raw = bytearray(self.npz.read_bytes())
change(raw)
self.npz.write_bytes(raw)
self.meta["dataset_sha256"] = train.sha256(raw)
self.meta_path.write_text(json.dumps(self.meta))
def test_zip_large_count_and_size_rejected_before_zipfile(self):
for change in (lambda raw: struct.pack_into("<HH", raw, len(raw) - 14, 60000, 60000),
lambda raw: struct.pack_into("<I", raw, len(raw) - 10, 100_000_000)):
self.save()
self.malicious_zip(change)
with patch.object(train.zipfile, "ZipFile", side_effect=AssertionError("constructor must not run")):
with self.assertRaisesRegex(ValueError, "entry count|byte budget"):
self.load()
def test_zip_count_lie_rejected_before_zipfile(self):
# EOCD claims three entries, but the physical central directory has four.
self.save()
with zipfile.ZipFile(self.npz, "a") as archive:
archive.writestr("extra.npy", b"x")
self.malicious_zip(lambda raw: struct.pack_into("<HH", raw, len(raw) - 14, 3, 3))
with patch.object(train.zipfile, "ZipFile", side_effect=AssertionError("constructor must not run")):
with self.assertRaisesRegex(ValueError, "count/size disagreement"):
self.load()
def test_archive_zip64_and_multidisk_fail_closed_before_zipfile(self):
def locator(raw):
raw[-22:-22] = struct.pack("<4sIQI", b"PK\x06\x07", 0, 0, 1)
changes = [lambda raw: struct.pack_into("<HH", raw, len(raw) - 14, 0xffff, 0xffff),
lambda raw: struct.pack_into("<I", raw, len(raw) - 10, 0xffffffff),
lambda raw: struct.pack_into("<H", raw, len(raw) - 18, 1), locator]
for change in changes:
self.save()
self.malicious_zip(change)
with patch.object(train.zipfile, "ZipFile", side_effect=AssertionError("constructor must not run")):
with self.assertRaisesRegex(ValueError, "ZIP64|disk"):
self.load()
def test_zip64_central_size_cannot_override_preflight(self):
def change(raw):
offset = struct.unpack_from("<I", raw, len(raw) - 6)[0]
struct.pack_into("<I", raw, offset + 24, 0xffffffff)
self.malicious_zip(change)
with patch.object(train.zipfile, "ZipFile", side_effect=AssertionError("constructor must not run")):
with self.assertRaisesRegex(ValueError, "ZIP64 central"):
self.load()
def test_normal_npz_zip64_local_headers_and_compression_supported(self):
for compressed in (False, True):
writer = np.savez_compressed if compressed else np.savez
writer(self.npz, frames=self.frames, timestamps=self.times, sequence=self.sequence)
raw = self.npz.read_bytes()
self.assertEqual(struct.unpack_from("<II", raw, 18), (0xffffffff, 0xffffffff))
self.meta["dataset_sha256"] = train.sha256(raw)
self.meta_path.write_text(json.dumps(self.meta))
np.testing.assert_array_equal(self.load()["frames"], self.frames)
def test_checkpoint_preflight_precedes_zipfile_allocation(self):
checkpoint = self.root / "checkpoint.npz"
mean, scale = np.zeros(4), np.ones(4)
np.savez(checkpoint, normalization_mean=mean, normalization_scale=scale)
raw = checkpoint.read_bytes()
self.assertEqual(train.load_checkpoint(checkpoint, {}, train.sha256(raw), mean, scale, None), {})
bad = bytearray(raw)
struct.pack_into("<HH", bad, len(bad) - 14, 60000, 60000)
checkpoint.write_bytes(bad)
with patch.object(train.zipfile, "ZipFile", side_effect=AssertionError("constructor must not run")):
with self.assertRaisesRegex(ValueError, "entry count"):
train.load_checkpoint(checkpoint, {}, train.sha256(bad), mean, scale, None)
def test_sessions_cover_all_rows_without_overlap(self):
self.meta["sessions"].append({"session_id": "duplicate", "start": 100, "end": 600})
self.save()
with self.assertRaisesRegex(ValueError, "without overlap"):
self.load()
def test_purged_windows_have_no_shared_rows(self):
splits = train.build_splits(self.load(), 8, 8)
occupied = {}
for name, targets in splits["targets"].items():
occupied[name] = {int(i) for t in targets for i in range(t - 8, t + 1)}
self.assertFalse(occupied["train"] & occupied["validation"])
self.assertFalse(occupied["validation"] & occupied["test"])
self.assertGreaterEqual(min(occupied["validation"]) - max(occupied["train"]) - 1, 8)
self.assertGreaterEqual(min(occupied["test"]) - max(occupied["validation"]) - 1, 8)
def test_purge_less_than_window_rejected(self):
with self.assertRaisesRegex(ValueError, "purge"):
train.build_splits(self.load(), 8, 7)
def test_gap_excludes_all_crossing_windows(self):
self.times[100:] += 5
self.save()
targets = train.build_splits(self.load(), 8, 8)["targets"]["train"]
self.assertFalse(any(100 <= t < 108 for t in targets))
self.assertIn(108, targets)
def test_normalization_ignores_validation_and_test(self):
dataset = self.load()
splits = train.build_splits(dataset, 8, 8)
ranges = splits["intervals"]["train"]
mean, scale = train.fit_normalization(dataset["frames"], ranges)
changed = dataset["frames"].copy()
changed[360:] = 900000
other_mean, other_scale = train.fit_normalization(changed, ranges)
np.testing.assert_array_equal(mean, other_mean)
np.testing.assert_array_equal(scale, other_scale)
def test_demean_is_per_recording_and_does_not_mutate_input(self):
data = self.identity_data(public=True)
original = data["frames"].copy()
changed, receipt = train.transform_recordings(data, "demean", "original", 1729, "identity")
for record in data["metadata"]["sessions"]:
block = changed[record["start"]:record["end"]]
np.testing.assert_allclose(block.mean(axis=0, dtype=np.float64), 0, atol=1e-7)
np.testing.assert_array_equal(original, data["frames"])
self.assertEqual(changed.dtype, np.float32)
self.assertFalse(receipt["labels_used"])
self.assertFalse(receipt["forecasting_supported"])
raw, _ = train.transform_recordings(data, "raw", "original", 1729, "identity")
np.testing.assert_array_equal(raw, original)
def test_shuffle_preserves_rows_distribution_and_admin_coordinates(self):
data = self.identity_data(public=True)
original = data["frames"].copy()
times, sequences = data["timestamps"].copy(), data["sequence"].copy()
shuffled, receipt = train.transform_recordings(data, "raw", "shuffle", 1729, "identity")
again, same = train.transform_recordings(data, "raw", "shuffle", 1729, "identity")
other, different = train.transform_recordings(data, "raw", "shuffle", 1730, "identity")
for record in data["metadata"]["sessions"]:
a, b = record["start"], record["end"]
self.assertEqual(sorted(map(tuple, original[a:b])), sorted(map(tuple, shuffled[a:b])))
np.testing.assert_array_equal(shuffled, again)
self.assertEqual(receipt["permutation_sha256"], same["permutation_sha256"])
self.assertNotEqual(receipt["permutation_sha256"], different["permutation_sha256"])
self.assertFalse(np.array_equal(shuffled, original))
self.assertFalse(np.array_equal(shuffled, other))
np.testing.assert_array_equal(data["frames"], original)
np.testing.assert_array_equal(data["timestamps"], times)
np.testing.assert_array_equal(data["sequence"], sequences)
self.assertIn("chronology_destroyed", receipt["timestamp_sequence_semantics"])
def test_controls_never_use_identity_labels(self):
data = self.identity_data(public=True)
relabeled = copy.deepcopy(data)
for record in relabeled["metadata"]["sessions"]:
record["identity"] = "1" if record["identity"] == "0" else "0"
first, info = train.transform_recordings(data, "demean", "shuffle", 1729, "identity")
second, other = train.transform_recordings(relabeled, "demean", "shuffle", 1729, "identity")
np.testing.assert_array_equal(first, second)
self.assertEqual(info, other)
def test_controls_do_not_mix_partitions_or_fit_heldout_statistics(self):
data = self.identity_data(public=True)
altered = copy.deepcopy(data)
altered["frames"][400:] = altered["frames"][400:] * 100 + 1000
first, _ = train.transform_recordings(data, "demean", "shuffle", 1729, "identity")
second, _ = train.transform_recordings(altered, "demean", "shuffle", 1729, "identity")
np.testing.assert_array_equal(first[:400], second[:400])
split = train.build_splits(data, 8, 8, "identity", identity_protocol="published_files")
other = train.build_splits(altered, 8, 8, "identity", identity_protocol="published_files")
for name in train.SPLITS:
np.testing.assert_array_equal(split["targets"][name], other["targets"][name])
mean, scale = train.fit_normalization(first, split["intervals"]["train"])
other_mean, other_scale = train.fit_normalization(second, split["intervals"]["train"])
np.testing.assert_array_equal(mean, other_mean)
np.testing.assert_array_equal(scale, other_scale)
def test_recording_controls_reject_forecasting_and_private_csi(self):
data = self.load()
for representation, order in (("demean", "original"), ("raw", "shuffle")):
with self.assertRaisesRegex(ValueError, "public NTU custom identity"):
train.transform_recordings(data, representation, order, 1729, "pretrain")
data = self.identity_data(public=True)
with self.assertRaisesRegex(ValueError, "public NTU custom identity"):
train.transform_recordings(data, "demean", "shuffle", 1729, "pretrain")
def test_identity_never_invents_labels(self):
with self.assertRaisesRegex(ValueError, "explicitly labeled"):
train.build_splits(self.load(), 8, 8, "identity")
def test_identity_requires_real_provenance(self):
data = self.identity_data()
data["metadata"]["provenance"] = "synthetic"
with self.assertRaisesRegex(ValueError, "requires real"):
train.build_splits(data, 8, 8, "identity")
def test_identity_requires_two_people_and_each_split(self):
data = self.identity_data()
for session in data["metadata"]["sessions"]:
session["identity"] = "one"
with self.assertRaisesRegex(ValueError, "2..64"):
train.build_splits(data, 8, 8, "identity")
data = self.identity_data()
data["metadata"]["sessions"][-1]["identity"] = "0"
with self.assertRaisesRegex(ValueError, "separate session"):
train.build_splits(data, 8, 8, "identity")
def test_unknown_sessions_cannot_claim_strict_protocol(self):
data = self.identity_data(public=True)
with self.assertRaisesRegex(ValueError, "verified acquisition"):
train.build_splits(data, 8, 8, "identity")
def test_strict_sessions_need_linked_hashes_and_no_shared_acquisition(self):
data = self.identity_data()
data["metadata"]["sessions"][-1]["source_sha256"] = data["metadata"]["sessions"][0]["source_sha256"]
with self.assertRaisesRegex(ValueError, "recording hashes"):
train.build_splits(data, 8, 8, "identity")
data = self.identity_data()
data["metadata"]["sessions"][-1]["acquisition_session_id"] = "acquisition-0"
with self.assertRaisesRegex(ValueError, "acquisition session crosses"):
train.build_splits(data, 8, 8, "identity")
data = self.identity_data()
del data["metadata"]["sessions"][-1]["source_sha256"]
with self.assertRaisesRegex(ValueError, "recording hashes"):
train.build_splits(data, 8, 8, "identity")
def test_pretrain_cannot_consume_declared_holdout(self):
for public in (False, True):
with self.assertRaisesRegex(ValueError, "must not repartition"):
train.build_splits(self.identity_data(public=public), 8, 8)
def test_public_split_keeps_declared_test_separate(self):
data = self.identity_data(public=True)
split = train.build_splits(data, 8, 8, "identity", identity_protocol="published_files", stride=8)
self.assertTrue(all(t >= 400 for t in split["targets"]["test"]))
data["metadata"]["sessions"][-1]["split"] = "validation"
with self.assertRaisesRegex(ValueError, "declared source test"):
train.build_splits(data, 8, 8, "identity", identity_protocol="published_files")
def test_public_requires_explicit_custom_protocol(self):
self.identity_data(public=True)
del self.meta["split_protocol"]
self.save()
with self.assertRaisesRegex(ValueError, "archive_directory_custom"):
self.load()
def test_public_split_rejects_reused_source_files(self):
data = self.identity_data(public=True)
data["metadata"]["sessions"][-1]["source_sha256"] = data["metadata"]["sessions"][0]["source_sha256"]
with self.assertRaisesRegex(ValueError, "hashes must be unique"):
train.build_splits(data, 8, 8, "identity", identity_protocol="published_files")
def test_hard_limits_and_nonfinite_options(self):
for option, value in (("--epochs", "1001"), ("--max-seconds", "nan"), ("--max-seconds", "43201"),
("--hidden", "129"), ("--batch-size", "129"), ("--stride", "0")):
args = self.args(option, value)
args.purge = args.window
with self.assertRaises(ValueError):
train.validate_config(args, 4)
def test_git_output_refused(self):
repo = self.root / "repository"
repo.mkdir()
(repo / ".git").write_text("gitdir: elsewhere")
with self.assertRaisesRegex(ValueError, "outside Git"):
train.outside_git(repo / "private-data")
def test_pretrain_cpu_smoke_leaves_test_unread(self):
result = train.run(self.args())
self.assertEqual(result["status"], "COMPLETED")
self.assertIsNone(result["test"])
self.assertFalse(result["identity_validated"])
self.assertFalse(result["test_used_for_selection"])
self.assertEqual(result["evidence"], "SYNTHETIC")
self.assertEqual(result["model_architecture"]["encoder"], "lstm")
self.assertEqual(result["parameters"], 4 * 4 * (4 + 4 + 2) + 4 * 4 + 4)
self.assertIn("persistence_normalized_mse", result["history"][-1]["validation"])
self.assertEqual(result["latest_validation"]["loss"], result["history"][-1]["validation"]["loss"])
checkpoint = self.root / "output" / "checkpoint.npz"
self.assertEqual(result["checkpoint_sha256"], train.sha256(checkpoint.read_bytes()))
with np.load(checkpoint, allow_pickle=False) as saved:
self.assertIn("normalization_mean", saved.files)
self.assertTrue(all(not saved[name].dtype.hasobject for name in saved.files))
def test_reproducible_cpu_checkpoint_and_explicit_test(self):
first = train.run(self.args("--evaluate-test"))
args = self.args("--evaluate-test")
args.output = str(self.root / "second")
second = train.run(args)
self.assertEqual(first["test"], second["test"])
self.assertEqual(first["history"], second["history"])
with np.load(self.root / "output" / "checkpoint.npz") as a, np.load(self.root / "second" / "checkpoint.npz") as b:
for name in a.files:
np.testing.assert_array_equal(a[name], b[name])
def test_identity_published_file_cpu_smoke(self):
self.identity_data(public=True) # Contract fixture only; never used as measured evidence.
result = train.run(self.args("--mode", "identity", "--identity-protocol", "published_files", "--stride", "8"))
self.assertIsNone(result["test"])
self.assertEqual(result["acquisition_session_disjointness"], "unknown")
self.assertIn("majority_baseline_accuracy", result["history"][-1]["validation"])
self.assertEqual(result["history"][-1]["validation"]["recordings"], 2)
self.assertEqual(result["history"][-1]["validation"]["recording_majority_baseline_accuracy"], 0.5)
self.assertEqual(result["history"][-1]["validation"]["recordings_skipped_by_window_cap"], 0)
self.assertNotIn("confusion_matrix", result["history"][-1]["validation"])
self.assertIn("confusion_matrix", result["best_validation"])
self.assertIn("recording_confusion_matrix", result["latest_validation"])
self.assertFalse(result["identity_validated"])
def test_frozen_checkpoint_scores_without_optimizer_and_refuses_repeat(self):
training = train.run(self.args())
original = (self.root / "output" / "checkpoint.npz").read_bytes()
args = self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation"))
with patch("torch.optim.Adam", side_effect=AssertionError("must not train during evaluation")):
evaluation = train.run(args)
self.assertEqual(evaluation["run_kind"], "frozen_checkpoint_evaluation")
self.assertEqual(evaluation["steps"], 0)
self.assertIsNotNone(evaluation["test"])
self.assertEqual(evaluation["checkpoint_sha256"], training["checkpoint_sha256"])
self.assertEqual(original, (self.root / "output" / "checkpoint.npz").read_bytes())
args.output = str(self.root / "second_evaluation")
with self.assertRaises(FileExistsError):
train.run(args)
def test_frozen_checkpoint_hash_mismatch_fails_before_holdout(self):
train.run(self.args())
with (self.root / "output" / "checkpoint.npz").open("ab") as stream:
stream.write(b"changed")
args = self.args("--evaluate-checkpoint", str(self.root / "output"), "--output", str(self.root / "evaluation"))
with self.assertRaisesRegex(ValueError, "checkpoint hash mismatch"):
train.run(args)
self.assertFalse((self.root / "output" / "holdout-evaluation.json").exists())
def test_frozen_incomplete_config_cannot_change_protocol(self):
train.run(self.args())
path = self.root / "output" / "metrics.json"
receipt = json.loads(path.read_text())
del receipt["config"]["window"]
path.write_text(json.dumps(receipt))
args = self.args("--evaluate-checkpoint", str(self.root / "output"), "--output", str(self.root / "evaluation"))
with self.assertRaisesRegex(ValueError, "incomplete or unknown"):
train.run(args)
def test_gru_cpu_train_and_frozen_checkpoint_load(self):
trained = train.run(self.args("--encoder", "gru"))
self.assertIsNone(trained["test"])
self.assertEqual(trained["parameters"], 3 * 4 * (4 + 4 + 2) + 4 * 4 + 4)
self.assertEqual(trained["model_architecture"]["encoder"], "gru")
evaluated = train.run(self.args("--encoder", "lstm", "--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertEqual(evaluated["model_architecture"]["encoder"], "gru")
self.assertEqual(evaluated["parameters"], trained["parameters"])
self.assertEqual(evaluated["steps"], 0)
self.assertFalse(evaluated["legacy_encoder_default_applied"])
def test_gru_identity_validation_only(self):
self.identity_data(public=True)
result = train.run(self.args("--encoder", "gru", "--mode", "identity",
"--identity-protocol", "published_files", "--stride", "8"))
self.assertIsNone(result["test"])
self.assertEqual(result["model_architecture"]["encoder"], "gru")
self.assertEqual(result["model_architecture"]["output_features"], 2)
self.assertIn("recording_accuracy", result["best_validation"])
def test_recognized_legacy_defaults_only_to_lstm(self):
train.run(self.args())
path = self.root / "output" / "metrics.json"
record = json.loads(path.read_text())
del record["config"]["encoder"]
del record["config"]["representation"]
del record["config"]["temporal_order"]
del record["model_architecture"]
del record["input_controls"]
record["trainer_sha256"] = "0b94c5f05b7004fcf54de0888ae1072af6148932e3198bbffa0188e0d16eca0e"
path.write_text(json.dumps(record))
evaluated = train.run(self.args("--encoder", "gru", "--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertEqual(evaluated["model_architecture"]["encoder"], "lstm")
self.assertTrue(evaluated["legacy_encoder_default_applied"])
self.assertTrue(evaluated["legacy_controls_defaults_applied"])
def test_frozen_controls_restore_demean_and_shuffle(self):
self.identity_data(public=True)
trained = train.run(self.args("--mode", "identity", "--identity-protocol", "published_files",
"--representation", "demean", "--temporal-order", "shuffle"))
self.assertIsNone(trained["test"])
evaluated = train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertEqual(evaluated["config"]["representation"], "demean")
self.assertEqual(evaluated["config"]["temporal_order"], "shuffle")
self.assertEqual(evaluated["input_controls"], trained["input_controls"])
self.assertEqual(evaluated["steps"], 0)
self.assertFalse(evaluated["legacy_controls_defaults_applied"])
def test_f6a_legacy_controls_restore_raw_original(self):
self.identity_data(public=True)
train.run(self.args("--mode", "identity", "--identity-protocol", "published_files", "--encoder", "gru"))
path = self.root / "output" / "metrics.json"
record = json.loads(path.read_text())
for name in ("representation", "temporal_order"):
del record["config"][name]
del record["input_controls"]
record["trainer_sha256"] = "f6a352e9dd91875edfb8ff61f0e323c2cfc4405fdffd02a37b452e3e3923c007"
path.write_text(json.dumps(record))
evaluated = train.run(self.args("--representation", "demean", "--temporal-order", "shuffle",
"--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertEqual(evaluated["config"]["representation"], "raw")
self.assertEqual(evaluated["config"]["temporal_order"], "original")
self.assertEqual(evaluated["model_architecture"]["encoder"], "gru")
self.assertTrue(evaluated["legacy_controls_defaults_applied"])
self.assertFalse(evaluated["legacy_encoder_default_applied"])
def test_frozen_control_receipt_mismatch_fails_before_holdout(self):
self.identity_data(public=True)
train.run(self.args("--mode", "identity", "--identity-protocol", "published_files"))
path = self.root / "output" / "metrics.json"
record = json.loads(path.read_text())
record["input_controls"]["temporal_order"] = "shuffle"
path.write_text(json.dumps(record))
with self.assertRaisesRegex(ValueError, "frozen input controls mismatch"):
train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertFalse((self.root / "output" / "holdout-evaluation.json").exists())
def test_unknown_legacy_cannot_omit_encoder(self):
train.run(self.args())
path = self.root / "output" / "metrics.json"
record = json.loads(path.read_text())
del record["config"]["encoder"]
record["trainer_sha256"] = "c" * 64
path.write_text(json.dumps(record))
with self.assertRaisesRegex(ValueError, "recognized legacy"):
train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
def test_frozen_encoder_and_tensor_shapes_must_match(self):
train.run(self.args())
path = self.root / "output" / "metrics.json"
record = json.loads(path.read_text())
record["config"]["encoder"] = "gru"
path.write_text(json.dumps(record))
with self.assertRaisesRegex(ValueError, "architecture mismatch"):
train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
record["model_architecture"]["encoder"] = "gru"
path.write_text(json.dumps(record))
with self.assertRaisesRegex(ValueError, "tensor shape/dtype mismatch"):
train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertFalse((self.root / "output" / "holdout-evaluation.json").exists())
def test_recordings_each_get_one_vote_despite_unequal_windows(self):
self.identity_data(public=True)
# Two test files, but the first has only two eligible windows.
self.meta["sessions"][4]["end"] = 410
self.meta["sessions"][5]["start"] = 410
self.times = 1_700_000_000 + np.arange(600, dtype=np.float64) / 100
self.save()
train.run(self.args("--mode", "identity", "--identity-protocol", "published_files"))
checkpoint = self.root / "output" / "checkpoint.npz"
with np.load(checkpoint, allow_pickle=False) as loaded:
values = {name: loaded[name].copy() for name in loaded.files}
values["head.weight"][:] = 0
values["head.bias"][:] = [10, -10] # Always predict first class.
np.savez(checkpoint, **values)
metrics = self.root / "output" / "metrics.json"
receipt = json.loads(metrics.read_text())
receipt["checkpoint_sha256"] = train.sha256(checkpoint.read_bytes())
metrics.write_text(json.dumps(receipt))
result = train.run(self.args("--evaluate-checkpoint", str(self.root / "output"),
"--output", str(self.root / "evaluation")))
self.assertEqual(result["test"]["recordings"], 2)
self.assertEqual(result["test"]["recording_accuracy"], 0.5)
self.assertLess(result["test"]["accuracy"], 0.5)
self.assertLess(result["test"]["minimum_windows_per_evaluated_recording"],
result["test"]["maximum_windows_per_evaluated_recording"])
def test_prepare_public_to_trainer_schema(self):
import prepare
items = []
for i in range(6):
source = self.root / f"source-{i}.mat"
source.write_bytes(f"synthetic-MAT-stub-{i}".encode())
items.append({"path": source, "identity": str(i % 2), "source_split": "test" if i >= 4 else "train",
"source_folder": "test_amp" if i >= 4 else "train_amp",
"split": train.SPLITS[i // 2], "sha256": prepare.sha_file(source), "size_bytes": source.stat().st_size})
with patch.object(prepare, "public_inventory", return_value=items), \
patch.object(prepare, "load_public_mat", side_effect=[np.full((500, 342), i + 1, dtype=np.float32) for i in range(6)]):
prepare.prepare_public(self.root, self.root / "prepared")
data = train.load_dataset(self.root / "prepared" / "dataset-000.npz", self.root / "prepared" / "dataset-000.json")
splits = train.build_splits(data, 32, 32, "identity", identity_protocol="published_files", stride=8)
self.assertEqual(data["frames"].shape, (3000, 342))
self.assertEqual(splits["classes"], ["0", "1"])
self.assertTrue(all(t >= 2000 for t in splits["targets"]["test"]))
def test_max_epochs_and_classes_fit_receipt_reader_budget(self):
matrix = [[50000] * 64 for _ in range(64)]
validation = {"loss": 0.25, "accuracy": 0.9, "recording_accuracy": 0.8,
"windows": 50000, "recordings": 1024, "confusion_matrix": matrix,
"recording_confusion_matrix": matrix}
history = [train.validation_history_entry(i, 0.1, 200000, validation) for i in range(1, 1001)]
receipt = {"history": history, "best_validation": validation, "latest_validation": validation}
path = self.root / "long-receipt.json"
train.write_json(path, receipt)
self.assertLessEqual(path.stat().st_size, train.MAX_METRICS_BYTES)
reloaded = json.loads(train.read_bounded(path, train.MAX_METRICS_BYTES))
self.assertEqual(len(reloaded["history"]), 1000)
self.assertEqual(reloaded["best_validation"]["confusion_matrix"], matrix)
self.assertNotIn("recording_confusion_matrix", reloaded["history"][-1]["validation"])
def test_receipt_writer_matches_reader_bound_and_preserves_previous(self):
import run_batch
self.assertEqual(train.MAX_METRICS_BYTES, run_batch.MAX_METRICS_BYTES)
path = self.root / "receipt.json"
train.write_json(path, {"previous": True})
before = path.read_bytes()
with self.assertRaisesRegex(ValueError, "reader byte budget"):
train.write_json(path, {"oversized": "x" * train.MAX_METRICS_BYTES})
self.assertEqual(path.read_bytes(), before)
def test_step_cap_honored(self):
result = train.run(self.args("--epochs", "10", "--max-steps", "1"))
self.assertEqual(result["steps"], 1)
self.assertEqual(result["stop_reason"], "step_limit")
def test_existing_output_never_overwritten(self):
(self.root / "output").mkdir()
with self.assertRaisesRegex(ValueError, "new private"):
train.run(self.args())
if __name__ == "__main__":
unittest.main()