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.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/experiments/resnet_crossover_smoke.py b/experiments/resnet_crossover_smoke.py
index 8cdcb0c..6d18c7f 100644
--- a/experiments/resnet_crossover_smoke.py
+++ b/experiments/resnet_crossover_smoke.py
@@ -10,6 +10,7 @@ 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,
+ CIFAREquilibriumPropResNet,
CIFARForwardForwardResNet,
CIFARPEPITAResNet,
)
@@ -249,9 +250,88 @@ def audit_forward_forward():
}
+def audit_equilibrium_propagation():
+ common = dict(
+ depth=8, base_width=2, seed=101, normalization="batchnorm",
+ residual_scale=1.0, dtype=torch.float64)
+ reference = CIFARLocalResNet(**common)
+ net = CIFAREquilibriumPropResNet(
+ **common, ep_beta=0.5, dt=0.5, free_steps=8,
+ nudge_steps=2, random_beta_sign=False)
+ generator = torch.Generator().manual_seed(102)
+ image = torch.randn(3, 3, 32, 32, generator=generator,
+ dtype=torch.float64)
+ labels = torch.tensor([0, 5, 9])
+ one_hot = torch.nn.functional.one_hot(labels, 10).to(torch.float64)
+ with torch.no_grad():
+ free = net.ep_settle(
+ image, one_hot, beta=0.0, steps=net.ep_free_steps)
+ signed_beta = net.ep_beta
+ nudged = net.ep_settle(
+ image, one_hot, beta=signed_beta, steps=net.ep_nudge_steps,
+ initial_states=free)
+ parameters = (
+ net.W + net.gamma + net.beta + [net.W_out, net.b_out])
+ for parameter in parameters:
+ parameter.requires_grad_(True)
+
+ def phase_phi(states):
+ clipped_image = net.ep_rho(image)
+ hidden = [net.ep_rho(value.detach()) for value in states[:-1]]
+ value = image.new_zeros(())
+ for index in range(net.n_hidden):
+ prediction, _ = net._node_prediction(
+ index, hidden, clipped_image)
+ value += torch.sum(
+ prediction * net.ep_rho(states[index].detach()))
+ features = hidden[-1].mean(dim=(2, 3))
+ output_prediction = features @ net.W_out.t() + net.b_out
+ value += torch.sum(
+ output_prediction * net.ep_rho(states[-1].detach()))
+ return value
+
+ objective = (
+ phase_phi(free) - phase_phi(nudged)
+ ) / (signed_beta * image.shape[0])
+ gradients = torch.autograd.grad(objective, parameters)
+ (directions, gamma_directions, beta_directions,
+ output_weight, output_bias) = net.ep_ascent_directions(
+ image, free, nudged, signed_beta)
+ actual = (
+ directions + gamma_directions + beta_directions
+ + [output_weight, output_bias])
+ errors = [
+ relative_error(direction, -gradient)
+ for direction, gradient in zip(actual, gradients)]
+ for parameter in parameters:
+ parameter.requires_grad_(False)
+ state_bounds = [
+ (float(value.min()), float(value.max()))
+ for value in free + nudged]
+ if (net.n_forward_parameters != reference.n_forward_parameters
+ or max(errors) >= 2e-12
+ or any(low < 0 or high > 1 for low, high in state_bounds)
+ or not all(torch.isfinite(value).all()
+ for value in free + nudged)):
+ raise AssertionError({
+ "direction_errors": errors,
+ "state_bounds": state_bounds,
+ })
+ return {
+ "contrastive_direction_max_relative_error": max(errors),
+ "matched_forward_parameter_count": net.n_forward_parameters,
+ "free_steps": net.ep_free_steps,
+ "nudge_steps": net.ep_nudge_steps,
+ "state_minimum": min(low for low, _ in state_bounds),
+ "state_maximum": max(high for _, high in state_bounds),
+ "uses_reverse_task_loss_graph": False,
+ }
+
+
def main():
print(json.dumps({
"dualprop": audit_dualprop(),
+ "equilibrium_propagation": audit_equilibrium_propagation(),
"forward_forward": audit_forward_forward(),
"pepita": audit_pepita(),
},