Analysis
How many windows a head needs, and what bytes cost
On the trainer's fixture, train heads on random sets of 77 to 120 windows and score them on 30 held out, with the demo's penalty and with a heavier one that also uses no labels. Then round both heads to bytes, as the device runs them, and measure what rounding changes.
import json
import numpy as np
F = "tests/fixtures/train/" # in the demos' source
X = np.array(json.load(open(F + "embeddings.json")), float) # 150 windows of 76, as integers
y = np.array(json.load(open(F + "targets.json")), float)
rng = np.random.default_rng(7)
def penalty(Xt, rule):
"""Label-free penalties: the demo's gap ridge, or the Gram matrix's mean eigenvalue."""
ev = np.linalg.eigvalsh((Xt - Xt.mean(0)).T @ (Xt - Xt.mean(0)))
return np.sqrt(max(ev[0], 1) * ev[1]) if rule == "gap" else ev.mean()
def fit(Xt, yt, lam):
m = Xt.mean(0)
xc = Xt - m
w = np.linalg.solve(xc.T @ xc + lam * np.eye(X.shape[1]), xc.T @ (yt - yt.mean()))
return w, yt.mean() - m @ w
def quantized(w, b):
"""The head as the device runs it: weights to signed bytes, the bias in the same units."""
scale = np.abs(w).max() / 127
return np.clip(np.rint(w / scale), -127, 127), np.rint(b / scale), scale
# 1. A learning curve: held-out accuracy against the number of training windows.
for n in (77, 80, 88, 100, 120): # more windows than weights, always
acc = {"gap": [], "mean": []}
for _ in range(200):
order = rng.permutation(len(y))
train, test = order[:n], order[120:] # the last 30 of each shuffle are held out
for rule in acc:
w, b = fit(X[train], y[train], penalty(X[train], rule))
acc[rule].append(np.mean((X[test] @ w + b > 0.5) == (y[test] == 1)))
print(f"{n:3d} windows: gap ridge {np.mean(acc['gap']):.2f}, "
f"mean eigenvalue {np.mean(acc['mean']):.2f}")
# 2. What rounding to bytes costs, at each penalty, on all 150 windows.
for rule in ("gap", "mean"):
w, b = fit(X, y, penalty(X, rule))
q, qb, scale = quantized(w, b)
exact, device = X @ w + b, scale * (X @ q + qb)
print(f"{rule}: rounding moves scores by up to {np.abs(exact - device).max():.4f}, "
f"and flips {np.sum((exact > 0.5) != (device > 0.5))} of 150 decisions")
You should seeWith the demo's penalty, held-out accuracy of 0.55 at 77 windows, 0.65 at 88 and 0.75 at 120. With the heavier penalty, 0.70 to 0.73 throughout. Rounding moves a score by less than 0.01 at either penalty, and flips no decision.
Then report: why the demo's penalty is worst just above 76 windows, and what you would tell someone choosing how long to teach a head.