summaryrefslogtreecommitdiff
path: root/experiments
diff options
context:
space:
mode:
authorYurenHao0426 <Blackhao0426@gmail.com>2026-08-07 13:18:15 -0500
committerYurenHao0426 <Blackhao0426@gmail.com>2026-08-07 13:18:15 -0500
commit65f617eaf07a87c84475ad0010dea844422163e1 (patch)
treed5ddf759838081cf6a9e5167f3ed8ed083cdcef6 /experiments
parent0be7f2d8b71343084da3cd4a97c714b7f74ffc3c (diff)
feat: transfer released physical drift profile to Rain EP
Diffstat (limited to 'experiments')
-rw-r--r--experiments/rain_ep_bias_train.py14
-rw-r--r--experiments/rain_ep_dillavou_smoke.py61
-rwxr-xr-xexperiments/rain_ep_released_profile_r0.sh63
3 files changed, 138 insertions, 0 deletions
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