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.py129
1 files changed, 125 insertions, 4 deletions
diff --git a/experiments/conv_local_smoke.py b/experiments/conv_local_smoke.py
index 4a0f47d..d2428eb 100644
--- a/experiments/conv_local_smoke.py
+++ b/experiments/conv_local_smoke.py
@@ -24,6 +24,11 @@ def architecture_checks():
assert net.n_forward_parameters == parameters
assert len(net.blocks) == 3 * ((depth - 2) // 6)
assert len(net.W) + 1 == depth
+ batchnorm_parameters = {8: 75290, 20: 269722, 32: 464154, 56: 853018}
+ for depth, parameters in batchnorm_parameters.items():
+ net = CIFARLocalResNet(
+ depth=depth, normalization="batchnorm", residual_scale=1.0)
+ assert net.n_forward_parameters == parameters
for depth in (7, 9, 21):
try:
CIFARLocalResNet(depth=depth)
@@ -62,8 +67,10 @@ def exact_local_gradient_check():
teaching = [-batch * hidden.grad for hidden in forward["hiddens"]]
output_error = (torch.softmax(forward["logits"].detach(), dim=1)
- F.one_hot(y, local.n_classes))
- directions, out_direction, bias_direction = local.local_ascent_directions(
+ (directions, gamma_directions, beta_directions,
+ out_direction, bias_direction) = local.local_ascent_directions(
teaching, output_error, forward)
+ assert gamma_directions == beta_directions == []
relative_errors = []
for direction, parameter in zip(directions, local.W):
@@ -94,6 +101,78 @@ def exact_local_gradient_check():
}
+def exact_batchnorm_local_gradient_check():
+ torch.manual_seed(31)
+ batch = 4
+ x = torch.randn(batch, 3, 32, 32)
+ y = torch.tensor([0, 1, 2, 3])
+ common = dict(
+ depth=8, base_width=2, seed=29,
+ normalization="batchnorm", residual_scale=1.0)
+ local = CIFARLocalResNet(**common)
+ bp = CIFARLocalResNet(**common)
+ parameters = local.W + local.gamma + local.beta + [local.W_out, local.b_out]
+ for parameter in parameters:
+ parameter.requires_grad_(True)
+ forward = local.forward(
+ x, return_cache=True, training=True, update_stats=True)
+ for hidden in forward["hiddens"]:
+ hidden.retain_grad()
+ loss = F.cross_entropy(forward["logits"], y)
+ loss.backward()
+ teaching = [-batch * hidden.grad for hidden in forward["hiddens"]]
+ output_error = (torch.softmax(forward["logits"].detach(), dim=1)
+ - F.one_hot(y, 10))
+ (directions, gamma_directions, beta_directions,
+ out_direction, bias_direction) = local.local_ascent_directions(
+ teaching, output_error, forward)
+ groups = (
+ (directions, local.W),
+ (gamma_directions, local.gamma),
+ (beta_directions, local.beta),
+ )
+ relative_errors = []
+ for direction_group, parameter_group in groups:
+ for direction, parameter in zip(direction_group, parameter_group):
+ absolute = (direction + parameter.grad).abs().max()
+ relative_errors.append(float(
+ absolute / parameter.grad.abs().max().clamp_min(1e-12)))
+ assert max(relative_errors) < 3e-5
+ for parameter in parameters:
+ parameter.requires_grad_(False)
+
+ eta = 0.013
+ local.apply_ascent(
+ directions, out_direction, bias_direction, eta,
+ gamma_directions=gamma_directions, beta_directions=beta_directions)
+ bp.bp_step(x, y, eta)
+ parameter_differences = [
+ float((left - right).abs().max())
+ for left, right in zip(
+ local.W + local.gamma + local.beta + [local.W_out, local.b_out],
+ bp.W + bp.gamma + bp.beta + [bp.W_out, bp.b_out])]
+ running_differences = [
+ float((left - right).abs().max())
+ for left, right in zip(
+ local.running_mean + local.running_var,
+ bp.running_mean + bp.running_var)]
+ assert max(parameter_differences) < 2e-7
+ assert max(running_differences) == 0.0
+
+ running_before = [value.clone() for value in local.running_mean + local.running_var]
+ clean = local.forward(x, training=True, update_stats=False)
+ simultaneous_conv_node_perturbation(
+ local, x, y, clean, sigma=1e-3, n_directions=1,
+ generator=torch.Generator(device="cpu").manual_seed(9))
+ assert all(torch.equal(before, after) for before, after in zip(
+ running_before, local.running_mean + local.running_var))
+ assert torch.equal(local.logits(x), local.logits(x))
+ return {
+ "batchnorm_max_relative_local_gradient_error": max(relative_errors),
+ "batchnorm_post_update_parameter_max_error": max(parameter_differences),
+ }
+
+
def perturbation_checks():
net = CIFARLocalResNet(depth=8, base_width=4, seed=3)
x = torch.randn(2, 3, 32, 32)
@@ -132,7 +211,9 @@ def perturbation_estimator_check():
net, x, y, clean, sigma=1e-6, n_directions=1,
generator=generator, return_diagnostics=True)
directions = diagnostics["directions"][0]
- finite_difference = diagnostics["directional_derivatives"][0]
+ derivative = diagnostics["directional_derivatives"][0]
+ assert derivative["coupling"] == "per_example_objective"
+ finite_difference = derivative["scaled_directional"]
exact = sum(
(batch * hidden.grad * direction).flatten(1).sum(dim=1)
for hidden, direction in zip(clean["hiddens"], directions))
@@ -143,7 +224,46 @@ def perturbation_estimator_check():
assert torch.equal(target, expected)
for parameter in parameters:
parameter.requires_grad_(False)
- return float(relative.max())
+ no_norm_relative = float(relative.max())
+
+ batch = 3
+ batchnorm = CIFARSDILResNet(
+ depth=8, base_width=2, seed=14, dtype=torch.float64,
+ normalization="batchnorm", residual_scale=1.0)
+ xb = torch.randn(batch, 3, 32, 32, dtype=torch.float64)
+ yb = torch.tensor([1, 4, 9])
+ parameters = (batchnorm.W + batchnorm.gamma + batchnorm.beta
+ + [batchnorm.W_out, batchnorm.b_out])
+ for parameter in parameters:
+ parameter.requires_grad_(True)
+ clean = batchnorm.forward(xb, training=True, update_stats=False)
+ for hidden in clean["hiddens"]:
+ hidden.retain_grad()
+ F.cross_entropy(clean["logits"], yb).backward()
+ targets, diagnostics = simultaneous_conv_node_perturbation(
+ batchnorm, xb, yb, clean, sigma=1e-6, n_directions=1,
+ generator=torch.Generator(device="cpu").manual_seed(101),
+ return_diagnostics=True)
+ directions = diagnostics["directions"][0]
+ derivative = diagnostics["directional_derivatives"][0]
+ assert derivative["coupling"] == "batch_objective"
+ scaled = derivative["scaled_directional"]
+ exact_sum_directional = sum(
+ float((batch * hidden.grad * direction).sum())
+ for hidden, direction in zip(clean["hiddens"], directions))
+ batchnorm_relative = abs(float(scaled[0]) - exact_sum_directional) / max(
+ abs(exact_sum_directional), 1e-12)
+ assert batchnorm_relative < 2e-6
+ assert torch.equal(scaled, scaled[:1].expand_as(scaled))
+ for target, direction in zip(targets, directions):
+ expected = -scaled[:, None, None, None] * direction
+ assert torch.equal(target, expected)
+ for parameter in parameters:
+ parameter.requires_grad_(False)
+ return {
+ "perturbation_jvp_max_relative_error": no_norm_relative,
+ "batchnorm_batch_objective_jvp_relative_error": batchnorm_relative,
+ }
def apical_learning_checks():
@@ -197,7 +317,8 @@ def main():
architecture_checks()
perturbation_checks()
report = exact_local_gradient_check()
- report["perturbation_jvp_max_relative_error"] = perturbation_estimator_check()
+ report.update(exact_batchnorm_local_gradient_check())
+ report.update(perturbation_estimator_check())
report.update(apical_learning_checks())
print(report)
print("ALL CONVOLUTIONAL LOCAL-ELIGIBILITY CHECKS PASSED")