summaryrefslogtreecommitdiff
path: root/experiments/conv_local_smoke.py
diff options
context:
space:
mode:
Diffstat (limited to 'experiments/conv_local_smoke.py')
-rw-r--r--experiments/conv_local_smoke.py49
1 files changed, 49 insertions, 0 deletions
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")