Langevin Mean-Field Attention Heads / experiment.py
Mechanism failed
1import math, random, time
2import numpy as np
3import torch
4from torch import nn
5import torch.nn.functional as F
6
7SEED = 153
8
9def seed_all(seed=SEED):
10 random.seed(seed); np.random.seed(seed); torch.manual_seed(seed)
11
12
13def math_check():
14 seed_all(7)
15 beta, eta, steps, n, dim = 0.07, 0.013, 80, 12000, 3
16 z = torch.zeros(n, dim)
17 amp = math.sqrt(2 * beta * eta)
18 for _ in range(steps):
19 z += amp * torch.randn_like(z)
20 empirical = z.var(0, unbiased=True).mean().item()
21 predicted = 2 * beta * eta * steps
22 x = torch.ones(n)
23 for _ in range(steps):
24 x *= (1 - eta)
25 expected = (1 - eta) ** steps
26 return {
27 'diffusion_empirical_variance': empirical,
28 'diffusion_predicted_variance': predicted,
29 'diffusion_relative_error': abs(empirical - predicted) / predicted,
30 'quadratic_contraction_empirical': x.mean().item(),
31 'quadratic_contraction_expected': expected,
32 }
33
34
35class ParticleAttention(nn.Module):
36 def __init__(self, d_model=24, heads=8, particle=False, beta=0.0, noise_eta=0.03):
37 super().__init__()
38 assert d_model % heads == 0
39 self.d, self.h, self.dk = d_model, heads, d_model // heads
40 self.particle, self.beta, self.noise_eta = particle, beta, noise_eta
41 self.q = nn.Parameter(torch.randn(heads, d_model, self.dk) * .12)
42 self.k = nn.Parameter(torch.randn(heads, d_model, self.dk) * .12)
43 self.v = nn.Parameter(torch.randn(heads, d_model, self.dk) * .12)
44 self.o = nn.Parameter(torch.randn(heads, self.dk, d_model) * .12)
45 self.norm = nn.LayerNorm(d_model)
46
47 def forward(self, x, noise=True):
48 # x: batch, sequence, model dimension. Each particle is one attention head.
49 q = torch.einsum('bnd,hdk->bhnk', x, self.q)
50 k = torch.einsum('bnd,hdk->bhnk', x, self.k)
51 v = torch.einsum('bnd,hdk->bhnk', x, self.v)
52 a = torch.softmax(torch.einsum('bhnk,bhmk->bhnm', q, k) / math.sqrt(self.dk), dim=-1)
53 hv = torch.einsum('bhnm,bhmk->bhnk', a, v)
54 outs = torch.einsum('bhnk,hkd->bhnd', hv, self.o)
55 y = outs.mean(1)
56 return self.norm(x + y), a, outs
57
58 @torch.no_grad()
59 def langevin_noise(self):
60 if self.particle and self.beta > 0:
61 amp = math.sqrt(2 * self.beta * self.noise_eta)
62 for p in (self.q, self.k, self.v, self.o):
63 p.add_(amp * torch.randn_like(p))
64
65 @torch.no_grad()
66 def diversity(self):
67 # Mean pairwise cosine similarity of flattened particle parameters.
68 w = torch.cat([z.flatten(1) for z in (self.q, self.k, self.v, self.o)], dim=1)
69 c = F.normalize(w, dim=1) @ F.normalize(w, dim=1).T
70 h = self.h
71 return ((c.sum() - h) / (h * (h - 1))) if h > 1 else 1.0
72
73 def dropped_forward(self, x, keep):
74 q = torch.einsum('bnd,hdk->bhnk', x, self.q[:keep])
75 k = torch.einsum('bnd,hdk->bhnk', x, self.k[:keep])
76 v = torch.einsum('bnd,hdk->bhnk', x, self.v[:keep])
77 a = torch.softmax(torch.einsum('bhnk,bhmk->bhnm', q, k) / math.sqrt(self.dk), dim=-1)
78 hv = torch.einsum('bhnm,bhmk->bhnk', a, v)
79 y = torch.einsum('bhnk,hkd->bhnd', hv, self.o[:keep]).mean(1)
80 return self.norm(x + y)
81
82
83class TinyClassifier(nn.Module):
84 def __init__(self, heads, beta):
85 super().__init__()
86 self.attn = ParticleAttention(heads=heads, particle=(beta > 0), beta=beta)
87 self.fc = nn.Sequential(nn.Linear(24, 32), nn.Tanh(), nn.Linear(32, 2))
88 def forward(self, x):
89 y, a, outs = self.attn(x)
90 return self.fc(y[:, 0]), a, outs
91
92
93def data(seed=SEED):
94 g = torch.Generator().manual_seed(seed)
95 ntr, nte, seq, d = 768, 256, 8, 24
96 # Label is encoded by a shared direction at token zero; other tokens are distractors.
97 direction = F.normalize(torch.randn(d, generator=g), dim=0)
98 def make(n):
99 y = torch.randint(0, 2, (n,), generator=g)
100 x = .8 * torch.randn(n, seq, d, generator=g)
101 x[:, 0] += (2*y.float()-1)[:, None] * direction[None, :] * 1.4
102 return x, y
103 return make(ntr), make(nte)
104
105
106def run_one(heads, beta, epochs=18):
107 seed_all(SEED + heads + int(beta * 1e6))
108 (xt, yt), (xv, yv) = data()
109 model = TinyClassifier(heads, beta)
110 opt = torch.optim.Adam(model.parameters(), lr=0.012)
111 bs = 64; losses=[]; t0=time.time()
112 for ep in range(epochs):
113 perm = torch.randperm(len(xt))
114 model.train()
115 for ix in perm.split(bs):
116 logits, _, _ = model(xt[ix])
117 loss = F.cross_entropy(logits, yt[ix])
118 opt.zero_grad(); loss.backward(); opt.step()
119 model.attn.langevin_noise()
120 losses.append(loss.item())
121 model.eval()
122 with torch.no_grad():
123 logits, maps, outs = model(xv)
124 val_loss = F.cross_entropy(logits, yv).item()
125 acc = (logits.argmax(1) == yv).float().mean().item()
126 keep = max(1, heads // 2)
127 drop_acc = (model.attn.dropped_forward(xv, keep)[:,0] if False else
128 model.fc(model.attn.dropped_forward(xv, keep)[:,0]).argmax(1) == yv).float().mean().item()
129 # Attention map similarity across particles, averaged over examples/queries.
130 flat = maps.permute(1,0,2,3).reshape(heads, -1)
131 sim = (F.normalize(flat, dim=1) @ F.normalize(flat, dim=1).T)
132 map_sim = ((sim.sum()-heads)/(heads*(heads-1))).item() if heads > 1 else 1.0
133 return {'heads':heads, 'beta':beta, 'val_loss':val_loss, 'val_accuracy':acc,
134 'half_particle_accuracy':drop_acc, 'parameter_cosine':float(model.attn.diversity()),
135 'attention_map_cosine':map_sim, 'last_train_loss':float(np.mean(losses[-20:])),
136 'seconds':time.time()-t0}
137
138
139def main():
140 print({'math_check': math_check()})
141 results=[]
142 # Standard fixed 8-head attention versus the same particle population with Langevin noise.
143 for beta in (0.0, 0.001, 0.005):
144 results.append(run_one(8, beta))
145 print({'results':results})
146
147if __name__ == '__main__':
148 main()