Where a Weight Lives, in JAX/Flax NNX

· 7 min read

#ml#kernels#rkhs#representer-theorem#jax#flax#nnx#implementation#yat#deep-learning

Part 2 of 4Weights in Kernel Space
  1. 1The Readout is a Convex Combination of Prototypes
  2. 2Where Does a Weight Live?this post's explainer
  3. 3What Can a Weight Be?
  4. 4The MLP Block Can Be a Kernel Memory
Explainer companionWhere Does a Weight Live?Want the full intuition first? This is the runnable companion to the explainer.Read the explainer

There is no feature map anywhere in this file. The explainer argued that a standard weight is a direction in a space apart from the data, and that a reproducing kernel Hilbert space repairs that by giving the weight an address among the data; what the code shows is how cheaply that is bought. A kernel in three lines of JAX, the Gram matrix, one linear solve for the coefficients, and the weight comes out as iαik(xi,)\sum_i \alpha_i\, k(x_i, \cdot). Then two demonstrations of why the shared space is worth the trouble, both of them measured here rather than quoted from the explainer.

Prefer a notebook? Run the whole thing on Kaggle: the same code, block by block, with the math written out and every figure reproduced from a real run.

The kernel, and the space it builds

Where does the shared space come from? One function: a reproducing kernel is any positive-definite similarity, and the whole RKHS hangs off it. We use the radial basis kernel, a bump that is largest when two points coincide and decays with their distance. In JAX it is one expression, and it gives the inner product ϕ(x),ϕ(y)\langle \phi(x), \phi(y)\rangle without ever forming ϕ\phi.

import jax, jax.numpy as jnp

SIG = 0.5

@jax.jit
def gram(X, Y):                                        # k(xᵢ, yⱼ) for every pair
    d2 = ((X[:, None, :] - Y[None, :, :]) ** 2).sum(-1)
    return jnp.exp(-d2 / (2 * SIG ** 2))

That gram(X, X) is the entire geometry of the data in the RKHS: entry (i, j) is how close points i and j sit once lifted by φ. Nothing about the weight yet, only the space the weight will live in.

So where is the weight in all this? The representer theorem promises the optimal one is f(x)=iαik(xi,x)f(x) = \sum_i \alpha_i\, k(x_i, x), a combination of the data. Fitting the weight is therefore not a gradient descent through some separate weight space; it is solving for the coefficients αi\alpha_i that make the combination match the labels. With a ridge penalty that is a single linear system, (G+λI)α=y(G + \lambda I)\,\alpha = y.

def kernel_ridge(X, y, lam=0.4):                       # the representer coefficients
    G = gram(X, X)
    return jnp.linalg.solve(G + lam * jnp.eye(len(X)), y)

The vector alpha gives the kernel-ridge solution in the spanning family of training-point sections. If the Gram matrix is singular, coefficients need not be unique even when the fitted function is. Each αi\alpha_i acts like a charge on xix_i; evaluation is gram(grid, X) @ alpha.

A two-moons dataset where the kernel-ridge decision surface builds up term by term as data points are added, the boundary forming, reaching 99 percent
The weight assembling from the data. Each frame adds one more term αik(xi,)\alpha_i\, k(x_i, \cdot) of the real kernel-ridge solution, heaviest first; the field is their running sum and the black line is where it crosses zero. The weight is never anything but a combination of the points, and the full sum reads the two moons at 99%.

Why the shared space is worth it

The explainer closed on the claim that a placed weight can bend around nested classes where any direction sits at chance; here is that claim as two numbers. The best linear weight is found by scikit-learn’s logistic regression (the one non-JAX import in this post; everything kernel is plain JAX), and it lands at a coin flip, because no direction separates an inner disk from an outer ring. The kernel weight, the same α solve, wraps a closed boundary around the inner ring.

from sklearn.datasets import make_circles
from sklearn.linear_model import LogisticRegression

X, y = make_circles(260, noise=0.07, factor=0.4, random_state=0)
ys = jnp.where(jnp.asarray(y) == 1, 1.0, -1.0)

lin = LogisticRegression().fit(X, y)                   # a direction
alpha = kernel_ridge(jnp.asarray(X), ys, lam=0.2)      # a placed combination
k_pred = jnp.sign(gram(jnp.asarray(X), jnp.asarray(X)) @ alpha)
print(f"line: {100*lin.score(X, y):.0f}%   kernel: {100*(k_pred == ys).mean():.0f}%")
# line: 50%   kernel: 100%
Two panels on nested rings: the best linear weight reaches 50 percent while the placed kernel weight reaches 100 percent with a circular boundary
The same rings, two weights. On the left the best linear weight, a direction, lands at 50%: no angle separates classes that are nested rather than side by side. On the right the kernel weight, a combination placed among the data, closes a boundary around the inner ring and reaches 100%. Both panels are drawn from the fit printed above them, and the coloured field is the score each machine assigns, not a sketch of one. The staged reveal introduces the nested data, the failed linear boundary, and the fitted kernel boundary.

How the placed weight reads a point

Notice what the rings demo never did: it never built a single new coordinate. There is no explicit feature vector anywhere in this code, no lifted space we construct by hand. The prediction at a query is the reproducing property made literal, f(q)=f,ϕ(q)=iαik(xi,q)f(q) = \langle f, \phi(q)\rangle = \sum_i \alpha_i\, k(x_i, q): the query is compared to each data point by the kernel, those similarities are scaled by the coefficients, and the signed sum is the score. The feature map ϕ\phi exists in the theory, but the code only ever touches it through kk.

q = jnp.array([1.7, -0.35])
kq = jnp.exp(-((jnp.asarray(X) - q) ** 2).sum(1) / (2 * SIG ** 2))   # k(xᵢ, q)
fq = (alpha * kq).sum()                                             # f(q) = Σ αᵢ k(xᵢ, q)
print(f"f(q) = {fq:+.2f}  ->  {'orange' if fq >= 0 else 'blue'} class")
A query point moving through two moons; at each position it links to its most-similar data points and the signed kernel-weighted sum decides its class
Every prediction is a similarity-weighted vote. The query (the star) is read by its kernel similarity k(xi,q)k(x_i, q) to each data point; the links show the points that vote loudest, their width set by that similarity and their color by the sign of their contribution αik(xi,q)\alpha_i\, k(x_i, q). The signed sum f(q)f(q) decides the class. Watch which links thicken as the star crosses between the moons: the electorate turns over completely, and no coordinate is ever built to make that happen.

The weight never left the data. It is a list of coefficients on the points you already have, solved in one shot, read by similarity. That is the whole of what it means to have a reproducing kernel Hilbert space: the weight and the data finally share a space, and the weight is just the data, recombined.


The RKHS is Aronszajn (1950); the representer theorem is Schölkopf, Herbrich and Smola (2001); the textbook is Schölkopf and Smola (2002); the input-space-center kernel is Bouhsine (2026). The conceptual companion is Where Does a Weight Live?.

Cite as

Bouhsine, T. (). Where a Weight Lives, in JAX/Flax NNX. Records of the !mmortal Data Scientist. https://tahabouhsine.com/blog/where-does-a-weight-live-jax-flax-nnx/

BibTeX
@misc{bouhsine2026wheredoesaweightlivejaxflaxnnx,
  author       = {Bouhsine, Taha},
  title        = {Where a Weight Lives, in JAX/Flax NNX},
  year         = {2026},
  month        = {jun},
  howpublished = {\url{https://tahabouhsine.com/blog/where-does-a-weight-live-jax-flax-nnx/}},
  note         = {Blog post, Records of the !mmortal Data Scientist}
}

References

  1. Aronszajn, N. (1950). Theory of Reproducing Kernels. Transactions of the American Mathematical Society.
  2. Schölkopf, B., Herbrich, R., Smola, A. J. (2001). A Generalized Representer Theorem. COLT 2001.
  3. Schölkopf, B., Smola, A. J. (2002). Learning with Kernels. MIT Press.
  4. Bouhsine, T. (2026). A Universal Reproducing Kernel Hilbert Space from Polynomial Alignment and IMQ Distance. arXiv:2605.03262