From 0db651ac83608ea8f5b94950a391da22f076a142 Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Wed, 22 Jul 2026 13:33:33 -0500 Subject: baseline: add normalized local response mirroring --- experiments/conv_local_smoke.py | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) (limited to 'experiments/conv_local_smoke.py') diff --git a/experiments/conv_local_smoke.py b/experiments/conv_local_smoke.py index 894f9d3..7d24154 100644 --- a/experiments/conv_local_smoke.py +++ b/experiments/conv_local_smoke.py @@ -12,7 +12,9 @@ from sdil.conv import (CIFARHierarchicalFAResNet, CIFARLocalResNet, CIFARSDILResNet, ConvSDILConfig, channel_subspace_apical_calibration, conv_hierarchical_step, conv_local_step, + hierarchical_mirror_observations, hierarchical_parameter_subspace_calibration, + normalized_response_mirror_update, simultaneous_conv_node_perturbation, vectorizer_subspace_apical_calibration) @@ -756,6 +758,52 @@ def hierarchical_parameter_calibration_checks(): } +def normalized_response_mirror_checks(): + """Audit local response estimation and absence of W access in the update.""" + net = CIFARHierarchicalFAResNet( + depth=8, base_width=2, seed=121, dtype=torch.float64, + normalization="batchnorm") + observations = hierarchical_mirror_observations( + net, batch_size=16, noise_std=1.0, + generator=torch.Generator().manual_seed(122)) + metrics, _ = normalized_response_mirror_update( + net, observations, eta=1.0) + pairs = list(zip(net.Q[1:], net.W[1:])) + [ + (net.R_out, -net.W_out.t())] + cosines = [float(F.cosine_similarity( + feedback.flatten(), target.flatten(), dim=0)) + for feedback, target in pairs] + norm_ratios = [float(feedback.norm() / target.norm()) + for feedback, target in pairs] + assert sum(cosines) / len(cosines) > 0.985 + assert min(cosines) > 0.95 + assert min(norm_ratios) > 0.90 and max(norm_ratios) < 1.10 + + # The update consumes observations only. Changing every forward parameter + # after those observations were generated must not change the Q/R update. + left = CIFARHierarchicalFAResNet( + depth=8, base_width=2, seed=123, dtype=torch.float64) + right = CIFARHierarchicalFAResNet( + depth=8, base_width=2, seed=123, dtype=torch.float64) + shared_observations = hierarchical_mirror_observations( + left, batch_size=2, generator=torch.Generator().manual_seed(124)) + for value in right.W + [right.W_out]: + value.normal_(generator=torch.Generator().manual_seed(value.numel())) + normalized_response_mirror_update(left, shared_observations, eta=0.2) + normalized_response_mirror_update(right, shared_observations, eta=0.2) + independence_error = max(float((a - b).abs().max()) for a, b in zip( + left.Q[1:] + [left.R_out], right.Q[1:] + [right.R_out])) + assert independence_error == 0.0 + return { + "mirror_estimate_mean_forward_cosine": sum(cosines) / len(cosines), + "mirror_estimate_min_forward_cosine": min(cosines), + "mirror_estimate_min_norm_ratio": min(norm_ratios), + "mirror_estimate_max_norm_ratio": max(norm_ratios), + "mirror_update_forward_parameter_independence_error": independence_error, + "mirror_update_rms": metrics["mirror_update_rms"], + } + + def apical_learning_checks(): torch.manual_seed(11) net = CIFARSDILResNet(depth=8, base_width=2, seed=6) @@ -863,6 +911,7 @@ def main(): report.update(vectorizer_subspace_estimator_check()) report.update(hierarchical_feedback_checks()) report.update(hierarchical_parameter_calibration_checks()) + report.update(normalized_response_mirror_checks()) report.update(apical_learning_checks()) print(report) print("ALL CONVOLUTIONAL LOCAL-ELIGIBILITY CHECKS PASSED") -- cgit v1.2.3