summaryrefslogtreecommitdiff
path: root/sdil/baselines.py
blob: f809f830c8be6118c000dbfef544c4f80fe71f61 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
"""Baselines sharing SDILNet's exact architecture and initialisation, so
comparisons are apples-to-apples.

- BP: exact backprop (autograd) -- the alignment/performance upper bound.
- DFA is NOT a separate class: it is exactly SDIL with a fixed-random apical
  vectorizer, no residual, no predictor, no perturbation. See dfa_config().
"""

import torch
import torch.nn.functional as F

from .core import SDILNet, SDILConfig


class BPNet(SDILNet):
    """Same tanh MLP, trained by exact backprop via autograd."""

    def bp_step(self, x, y, eta, momentum=0.0):
        for W in self.W:
            W.requires_grad_(True)
        for b in self.b:
            b.requires_grad_(True)
        logits = self.logits(x)
        loss = F.cross_entropy(logits, y)
        grads = torch.autograd.grad(loss, self.W + self.b)
        nW = len(self.W)
        with torch.no_grad():
            for i in range(nW):
                if momentum:
                    self.mW[i].mul_(momentum).add_(grads[i])
                    self.mb[i].mul_(momentum).add_(grads[nW + i])
                    self.W[i] -= eta * self.mW[i]
                    self.b[i] -= eta * self.mb[i]
                else:
                    self.W[i] -= eta * grads[i]
                    self.b[i] -= eta * grads[nW + i]
        for W in self.W:
            W.requires_grad_(False)
        for b in self.b:
            b.requires_grad_(False)
        return loss.item()


def dfa_config(eta=0.05, momentum=0.0):
    """SDIL configured to reproduce Direct Feedback Alignment exactly."""
    return SDILConfig(eta=eta, use_residual=False, learn_A=False, learn_P=False,
                      pert_every=10**9, momentum=momentum)


@torch.no_grad()
def evaluate(net, test_loader):
    correct, total, tot_loss = 0, 0, 0.0
    for x, y in test_loader:
        logits = net.logits(x)
        tot_loss += F.cross_entropy(logits, y, reduction="sum").item()
        correct += (logits.argmax(1) == y).sum().item()
        total += y.shape[0]
    return correct / total, tot_loss / total