summaryrefslogtreecommitdiff
path: root/sdil
diff options
context:
space:
mode:
Diffstat (limited to 'sdil')
-rw-r--r--sdil/core.py35
1 files changed, 22 insertions, 13 deletions
diff --git a/sdil/core.py b/sdil/core.py
index 66f2d0a..3cd8a97 100644
--- a/sdil/core.py
+++ b/sdil/core.py
@@ -278,19 +278,28 @@ def simultaneous_node_perturbation_targets(net, x, y, sigma=1e-2,
with torch.no_grad():
base = net.forward(x)
h = base["h"]
- qs = [torch.zeros_like(hl) for hl in h[1:-1]]
- L0 = loss_ce(h[-1], y)
- for _ in range(n_dirs):
- xis = [torch.empty_like(q).bernoulli_(0.5).mul_(2).sub_(1) for q in qs]
- Lplus = _loss_with_hidden_noise(net, x, y, xis, sigma)
- if antithetic:
- Lminus = _loss_with_hidden_noise(net, x, y, xis, -sigma)
- coeff = (Lplus - Lminus) / (2.0 * sigma)
- else:
- coeff = (Lplus - L0) / sigma
- for l, xi in enumerate(xis):
- qs[l].add_(-coeff.unsqueeze(1) * xi)
- return [q / n_dirs for q in qs]
+ batch = x.shape[0]
+ xis = [torch.empty((n_dirs,) + hl.shape, device=hl.device, dtype=hl.dtype)
+ .bernoulli_(0.5).mul_(2).sub_(1) for hl in h[1:-1]]
+
+ # Fold direction and antithetic sign into the batch dimension. This is
+ # mathematically identical to 2*n_dirs small forwards but turns dozens
+ # of tiny Pascal-GPU launches into one efficient matrix multiplication
+ # per layer.
+ copies = 2 * n_dirs if antithetic else n_dirs
+ x_batch = x.repeat(copies, 1)
+ y_batch = y.repeat(copies)
+ if antithetic:
+ noise_batch = [torch.cat((xi, -xi), dim=0).flatten(0, 1) for xi in xis]
+ else:
+ noise_batch = [xi.flatten(0, 1) for xi in xis]
+ losses = _loss_with_hidden_noise(net, x_batch, y_batch, noise_batch, sigma)
+ losses = losses.view(copies, batch)
+ if antithetic:
+ coeff = (losses[:n_dirs] - losses[n_dirs:]) / (2.0 * sigma)
+ else:
+ coeff = (losses - loss_ce(h[-1], y).unsqueeze(0)) / sigma
+ return [-(coeff.unsqueeze(2) * xi).mean(0) for xi in xis]
def _loss_with_hidden_noise(net, x, y, noises, scale):