pairs = jnp.stack(jnp.meshgrid(jnp.arange(K), jnp.arange(K), indexing='ij'),
-1).reshape(-1, 2) # all 1024 pairs
patterns = jnp.array([[True, False], [False, True], [True, True]])
def exact_bound(model): # the bound averaged over the true table, all steps and patterns
total = 0.0
for t in range(1, T + 1):
weight = (alpha_bar[t - 1] - alpha_bar[t]) / (1 - alpha_bar[t])
for masked in patterns:
masked = jnp.broadcast_to(masked, pairs.shape)
z = jnp.where(masked, MASK, pairs)
logp = jax.nn.log_softmax(model(z, jnp.full((len(pairs),), t)), -1)
logp = jnp.take_along_axis(logp, pairs[..., None], -1)[..., 0]
n_masked = masked[0].sum()
prob = (1 - alpha_bar[t]) ** n_masked * alpha_bar[t] ** (2 - n_masked)
total += prob * weight * (table.flatten() * (-logp * masked).sum(1)).sum()
return total
@nnx.jit
def masked_step(model, optimizer, x0, t, key):
loss, grads = nnx.value_and_grad(
lambda m: masked_loss(m, x0, t, key).mean())(model)
optimizer.update(model, grads)
return loss
model = MaskedMLP(rngs=nnx.Rngs(1))
schedule = optax.piecewise_constant_schedule(1e-3, {4000: 0.2})
optimizer = nnx.Optimizer(model, optax.adam(schedule), wrt=nnx.Param)
for step in range(6000):
key, k1, k2, k3 = jax.random.split(key, 4)
x0 = data[jax.random.randint(k1, (512,), 0, len(data))]
t = jax.random.randint(k2, (512,), 1, T + 1)
loss = masked_step(model, optimizer, x0, t, k3)
if step % 2000 == 0:
print(f'step {step:>4}: exact bound {exact_bound(model):.3f} nats')
print(f'final exact bound {exact_bound(model):.3f} nats; entropy {entropy:.3f} nats')