From 65f617eaf07a87c84475ad0010dea844422163e1 Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Fri, 7 Aug 2026 13:18:15 -0500 Subject: feat: transfer released physical drift profile to Rain EP --- experiments/rain_ep_bias_train.py | 14 +++++++ experiments/rain_ep_dillavou_smoke.py | 61 +++++++++++++++++++++++++++++ experiments/rain_ep_released_profile_r0.sh | 63 ++++++++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100755 experiments/rain_ep_released_profile_r0.sh (limited to 'experiments') diff --git a/experiments/rain_ep_bias_train.py b/experiments/rain_ep_bias_train.py index fa62137..2ba3dd0 100644 --- a/experiments/rain_ep_bias_train.py +++ b/experiments/rain_ep_bias_train.py @@ -19,6 +19,7 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from sdil.rain_ep_adapter import ( # noqa: E402 + DillavouBiasProfile, DillavouUpdateCorrector, RainGradientCorrector, RainLayerStateCorrector, @@ -55,6 +56,9 @@ def parse_args() -> argparse.Namespace: "--dillavou-drift-ratio", type=float, default=0.0, help="zero is the exact fixed update-offset model from Dillavou et al.") parser.add_argument("--dillavou-calibration-steps", type=int, default=1) + parser.add_argument( + "--dillavou-profile-json", type=Path, + help="released physical state-dependence report; omitted means constant B") parser.add_argument("--predictor-rate", type=float, default=0.1) parser.add_argument( "--neutral-cadence", type=int, default=1, @@ -230,12 +234,18 @@ def main() -> None: raise ValueError( "Dillavou calibration probes the local update circuit and " "does not require equilibrium batches") + empirical_profile = None + if args.dillavou_profile_json is not None: + profile_path = args.dillavou_profile_json.resolve() + empirical_profile = DillavouBiasProfile.from_state_dependence_report( + json.loads(profile_path.read_text()), source=str(profile_path)) corrector = DillavouUpdateCorrector( mode=args.mode, bias_ratio=args.bias_ratio, predictor_rate=args.predictor_rate, neutral_cadence=args.neutral_cadence, drift_ratio=args.dillavou_drift_ratio, + empirical_profile=empirical_profile, seed=args.seed + 1729, ) attach_dillavou_to_rain_estimator(estimator, corrector) @@ -354,6 +364,10 @@ def main() -> None: "bias_ratio": args.bias_ratio, "dillavou_drift_ratio": args.dillavou_drift_ratio, "dillavou_calibration_steps": args.dillavou_calibration_steps, + "dillavou_profile": ( + None if args.adapter != "dillavou" + or corrector.empirical_profile is None + else corrector.empirical_profile.as_dict()), "predictor_rate": args.predictor_rate, "neutral_cadence": args.neutral_cadence, "layer_calibration_steps": args.layer_calibration_steps, diff --git a/experiments/rain_ep_dillavou_smoke.py b/experiments/rain_ep_dillavou_smoke.py index 9519210..39bf5fd 100644 --- a/experiments/rain_ep_dillavou_smoke.py +++ b/experiments/rain_ep_dillavou_smoke.py @@ -13,6 +13,7 @@ ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT)) from sdil.rain_ep_adapter import ( # noqa: E402 + DillavouBiasProfile, DillavouUpdateCorrector, attach_dillavou_to_rain_estimator, ) @@ -21,6 +22,9 @@ from sdil.rain_ep_adapter import ( # noqa: E402 def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument("--author-root", type=Path, required=True) + parser.add_argument( + "--profile-json", type=Path, + default=ROOT / "results/physical_bias/p0_state_dependence.json") return parser.parse_args() @@ -92,6 +96,58 @@ def main() -> None: assert constant.debiaser.neutral_observations == 1 assert innovation.debiaser.neutral_observations == 1 + # Build the state-dependent shape only from the committed analysis of the + # released physical traces. With matched neutral observations, an affine + # predictor must generalize across local parameter states better than an + # intercept-only predictor. + import json + profile = DillavouBiasProfile.from_state_dependence_report( + json.loads(args.profile_json.read_text()), + source=str(args.profile_json.resolve()), + ) + profile_constant = DillavouUpdateCorrector( + mode="constant", bias_ratio=0.2, predictor_rate=0.2, + calibration_steps=1, neutral_cadence=1, + empirical_profile=profile, seed=67) + profile_innovation = DillavouUpdateCorrector( + mode="innovation", bias_ratio=0.2, predictor_rate=0.2, + calibration_steps=1, neutral_cadence=1, + empirical_profile=profile, seed=67) + parameter_scale = [ + value.square().mean().sqrt().clamp_min(1e-6) + for value in parameters_a + ] + for _ in range(12): + for displacement in torch.linspace(-1.0, 1.0, 21): + state = [ + value + displacement * scale + for value, scale in zip(parameters_a, parameter_scale) + ] + profile_constant.apply(clean_a, state) + profile_innovation.apply(clean_a, state) + assert ( + profile_constant.debiaser.neutral_observations + == profile_innovation.debiaser.neutral_observations + ) + profile_constant.neutral_cadence = 0 + profile_innovation.neutral_cadence = 0 + held_state = [ + value - 0.55 * scale + for value, scale in zip(parameters_a, parameter_scale) + ] + held_constant = profile_constant.apply(clean_b, held_state) + held_innovation = profile_innovation.apply(clean_b, held_state) + held_constant_error = sum( + float((actual - target).square().sum()) + for actual, target in zip(held_constant, clean_b) + ) + held_innovation_error = sum( + float((actual - target).square().sum()) + for actual, target in zip(held_innovation, clean_b) + ) + assert held_innovation_error < 0.05 * held_constant_error, ( + held_innovation_error, held_constant_error) + # Integration check: the corruption is attached after Rain's hand-written # local EP estimator and introduces no autograd graph. energy, network, cost, augmented, minimizer, estimator = build_estimator( @@ -122,6 +178,11 @@ def main() -> None: "innovation_relative_error": innovation_error, "integrated_bias_to_clean_update_rms": observed_ratio, "neutral_observations": constant.debiaser.neutral_observations, + "released_profile_normalized_offsets": profile.normalized_offsets, + "released_profile_normalized_state_variations": ( + profile.normalized_state_variations), + "released_profile_heldout_mse_ratio_affine_over_constant": ( + held_innovation_error / held_constant_error), "autodiff_used_for_learning": False, }) diff --git a/experiments/rain_ep_released_profile_r0.sh b/experiments/rain_ep_released_profile_r0.sh new file mode 100755 index 0000000..7529fc5 --- /dev/null +++ b/experiments/rain_ep_released_profile_r0.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=/home/yurenh2/sdil +AUTHOR=/scratch/yurenh2/energy-based-learning +PYTHON=/scratch/yurenh2/venvs/burstccn/bin/python +PROFILE="$ROOT/results/physical_bias/p0_state_dependence.json" +OUT="$ROOT/results/ep_bias/released_profile_r0" +mkdir -p "$OUT" +cd "$AUTHOR" + +run_cell() { + local gpu=$1 + local tag=$2 + local mode=$3 + local cadence=$4 + CUBLAS_WORKSPACE_CONFIG=:4096:8 CUDA_VISIBLE_DEVICES="$gpu" "$PYTHON" \ + "$ROOT/experiments/rain_ep_bias_train.py" \ + --author-root "$AUTHOR" --device cuda \ + --adapter dillavou --network-protocol comparative32 \ + --beta-policy fixed_positive --beta-value 0.25 \ + --mode "$mode" --bias-ratio 1 \ + --dillavou-drift-ratio 0 --dillavou-profile-json "$PROFILE" \ + --predictor-rate 1 --dillavou-calibration-steps 1 \ + --neutral-cadence "$cadence" --epochs 1 --schedule-epochs 100 \ + --train-limit 10000 --test-limit 2000 --batch-size 128 \ + --training-iterations 15 --inference-iterations 60 \ + --evaluation-split train_holdout --data-seed 6200 \ + --seed 1988 --beta-seed 7100 --deterministic \ + --output "$OUT/$tag.json" > "$OUT/$tag.log" 2>&1 +} + +run_cell 0 pep_clean clean 0 & +run_cell 1 profile_raw raw 0 & +run_cell 2 intercept_initial constant 0 & +run_cell 3 sdil_initial innovation 0 & +run_cell 4 intercept_c10 constant 10 & +run_cell 5 sdil_c10 innovation 10 & +run_cell 6 intercept_c50 constant 50 & +run_cell 7 sdil_c50 innovation 50 & +wait + +"$PYTHON" - "$OUT" <<'PY' +import json +from pathlib import Path +import sys + +root = Path(sys.argv[1]) +summary = {} +for path in sorted(root.glob("*.json")): + if path.name == "summary.json": + continue + report = json.loads(path.read_text()) + summary[path.stem] = { + "test_accuracy": report["final"]["test_accuracy"], + "test_cost": report["final"]["test_cost"], + "finite": report["final"]["finite"], + "wall_seconds": report["final"]["wall_seconds"], + "corrector": report["final"]["corrector"], + } +(root / "summary.json").write_text(json.dumps(summary, indent=2) + "\n") +print(json.dumps(summary, indent=2)) +PY -- cgit v1.2.3