summaryrefslogtreecommitdiff
path: root/experiments/resnet_crossover_smoke.py
diff options
context:
space:
mode:
Diffstat (limited to 'experiments/resnet_crossover_smoke.py')
-rw-r--r--experiments/resnet_crossover_smoke.py85
1 files changed, 84 insertions, 1 deletions
diff --git a/experiments/resnet_crossover_smoke.py b/experiments/resnet_crossover_smoke.py
index aadede5..8cdcb0c 100644
--- a/experiments/resnet_crossover_smoke.py
+++ b/experiments/resnet_crossover_smoke.py
@@ -8,7 +8,11 @@ import torch
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sdil.conv import CIFARLocalResNet
-from sdil.conv_crossover import CIFARDualPropResNet, CIFARPEPITAResNet
+from sdil.conv_crossover import (
+ CIFARDualPropResNet,
+ CIFARForwardForwardResNet,
+ CIFARPEPITAResNet,
+)
def relative_error(actual, expected):
@@ -167,9 +171,88 @@ def audit_pepita():
}
+def audit_forward_forward():
+ common = dict(
+ depth=8, base_width=2, seed=91, normalization="batchnorm",
+ residual_scale=1.0, dtype=torch.float64)
+ reference = CIFARLocalResNet(**common)
+ net = CIFARForwardForwardResNet(
+ **common, threshold=2.0, learning_rate=0.03,
+ score_from_layer=1)
+ if net.n_forward_parameters != reference.n_forward_parameters:
+ raise AssertionError("Forward-Forward changed forward parameter count")
+ generator = torch.Generator().manual_seed(92)
+ image = torch.randn(4, 3, 32, 32, generator=generator,
+ dtype=torch.float64)
+ labels = torch.tensor([1, 2, 6, 9])
+ negative_labels = (labels + 3) % 10
+ target = 1
+ with torch.no_grad():
+ positive = net.ff_forward(
+ net.ff_overlay(image, labels),
+ training=True, update_stats=False)
+ negative = net.ff_forward(
+ net.ff_overlay(image, negative_labels),
+ training=True, update_stats=False)
+ positive_output, negative_output = net._ff_local_outputs(
+ target, positive, negative)
+ axes = tuple(range(1, positive_output.ndim))
+ positive_goodness = positive_output.square().mean(dim=axes)
+ negative_goodness = negative_output.square().mean(dim=axes)
+ explicit_loss = (
+ torch.nn.functional.softplus(-positive_goodness + net.ff_threshold)
+ + torch.nn.functional.softplus(
+ negative_goodness - net.ff_threshold)
+ ).mean()
+ parameters = (
+ net.W + net.gamma + net.beta + [net.W_out, net.b_out])
+ before = [parameter.detach().clone() for parameter in parameters]
+ metrics = net.ff_train_layer(
+ target, image, labels, learning_rate=0.03,
+ negative_labels=negative_labels)
+ changed = [
+ not torch.equal(old, parameter.detach())
+ for old, parameter in zip(before, parameters)]
+ target_indices = {target}
+ if net.normalization == "batchnorm":
+ target_indices.update({
+ len(net.W) + target,
+ len(net.W) + len(net.gamma) + target,
+ })
+ non_target_changes = [
+ index for index, value in enumerate(changed)
+ if value and index not in target_indices]
+ non_target_gradients = [
+ index for index, parameter in enumerate(parameters)
+ if index not in target_indices and parameter.grad is not None]
+ scores = net.ff_candidate_scores(image)
+ loss_error = abs(metrics["loss"] - float(explicit_loss.detach()))
+ if (loss_error >= 1e-12 or non_target_changes
+ or non_target_gradients or not any(
+ changed[index] for index in target_indices)
+ or scores.shape != (4, 10)
+ or not torch.isfinite(scores).all()):
+ raise AssertionError({
+ "loss_error": loss_error,
+ "non_target_changes": non_target_changes,
+ "non_target_gradients": non_target_gradients,
+ "changed": changed,
+ "score_shape": list(scores.shape),
+ })
+ return {
+ "local_objective_absolute_error": loss_error,
+ "non_target_parameter_changes": len(non_target_changes),
+ "non_target_gradients": len(non_target_gradients),
+ "candidate_score_shape": list(scores.shape),
+ "matched_forward_parameter_count": net.n_forward_parameters,
+ "uses_only_target_layer_autograd": True,
+ }
+
+
def main():
print(json.dumps({
"dualprop": audit_dualprop(),
+ "forward_forward": audit_forward_forward(),
"pepita": audit_pepita(),
},
indent=2, sort_keys=True))