Dictionary of Applied Machine Learning · Hilbert space

Hilbert space — Python demo

Numerical companion to the entry Hilbert space: it recomputes what the entry states and prints one line per check

Backs the entry's completeness discussion and its two examples: it generates the Cauchy sequence shown in the entry's figure (converging to a limit that again belongs to the space), and it verifies that the expectation E{x x'} is an inner product on the finite-variance random variables of a common probability space, once random variables that are equal with probability one are identified. Self-contained (numpy only), fixed seeds.

Run it with python3 pythondemos/hilbertspace.py, from the repository root — it writes its data files under pythondemos/. Requires NumPy and Matplotlib only, and uses fixed seeds, so the printed numbers reproduce exactly. Download hilbertspace.py

The script, block by block

One cell per block of the script: the code, and what that code printed when it last ran here

setup

"""
hilbertspace.py — numerical companion to the glossary entry 'Hilbert space'.

Purpose
-------
Backs the entry's completeness discussion and its two examples: it
generates the Cauchy sequence shown in the entry's figure (converging
to a limit that again belongs to the space), and it verifies that the
expectation E{x x'} is an inner product on the finite-variance random
variables of a common probability space, once random variables that
are equal with probability one are identified.  Self-contained (numpy
only), fixed seeds.

Blocks
------
[B-cauchy]  The sequence w^(r) = w + 0.82^r * 2.3 * (cos(0.65 r + 2.7),
            sin(0.65 r + 2.7)) in R^2 is a Cauchy sequence: the
            diameter of the tail {w^(r) : r >= N} shrinks
            monotonically to 0.  Its limit is w = (-1.2, 1.6), an
            element of R^2 with finite induced norm — the limit stays
            in the space (completeness of the Euclidean space).
[B-rvspace] On a finite probability space (7 outcomes, one of them
            with probability zero), random variables are vectors in
            R^7 and the inner product <x, x'> = E{x x'} is the
            probability-weighted inner product of the vectors.  It is
            symmetric, bilinear and positive semi-definite; two random
            variables that differ only on the zero-probability outcome
            satisfy E{(x - x')^2} = 0 and have zero induced distance —
            they are identified (equal with probability one), and on
            the identified space the inner product is positive
            definite.
[B-proj]    Orthogonality principle in the Hilbert space of
            finite-variance random variables: the best linear
            approximation of y by a x (in the induced norm) has
            coefficient a* = E{x y} / E{x^2}, and the residual
            y - a* x is orthogonal to x, E{(y - a* x) x} = 0 —
            optimal estimation is orthogonal projection.

Outputs
-------
hilbertspace_cauchy.csv : points w^(1), ..., w^(14) of the Cauchy
                          sequence (columns x,y) for the entry's
                          pgfplots figure; the limit w = (-1.2, 1.6)
                          is drawn in TikZ directly.
hilbertspace.png        : matplotlib preview (checking only).
"""

import numpy as np                  # the only numerical dependency
import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt

report = []                         # collects (check name, pass/fail) pairs


def check(name, ok):                # records and prints one verification
    report.append((name, bool(ok)))
    print(f"  [{'ok' if ok else 'FAIL'}] {name}")

B-cauchy

The sequence w^(r) = w + 0.82^r * 2.3 * (cos(0.65 r + 2.7), sin(0.65 r + 2.7)) in R^2 is a Cauchy sequence: the diameter of the tail {w^(r) : r >= N} shrinks monotonically to 0. Its limit is w = (-1.2, 1.6), an element of R^2 with finite induced norm — the limit stays in the space (completeness of the Euclidean space).

w_lim = np.array([-1.2, 1.6])       # the limit, an element of R^2
r = np.arange(1, 15)                # sequence indices r = 1, ..., 14
# a wide, slowly contracting spiral: the early elements sweep through the
# left half of the figure before closing in on the limit
pts = w_lim + (0.82 ** r)[:, None] * 2.3 * np.c_[
    np.cos(0.65 * r + 2.7), np.sin(0.65 * r + 2.7)]

# tail diameters diam{w^(m) : m >= N} shrink monotonically to zero
diams = [np.max([np.linalg.norm(p - q) for p in pts[N:] for q in pts[N:]])
         for N in range(0, 10)]
check("[B-cauchy]  tail diameters shrink monotonically (Cauchy property)",
      all(d1 > d2 for d1, d2 in zip(diams, diams[1:])) and diams[-1] < 0.5)
check("[B-cauchy]  the sequence converges to its limit in the space",
      np.linalg.norm(pts[-1] - w_lim) < 0.2
      and np.isfinite(np.sqrt(w_lim @ w_lim)))

with open("pythondemos/hilbertspace_cauchy.csv", "w") as fh:
    fh.write("x,y\n")               # pgfplots table for the entry's figure
    for p in pts:
        fh.write(f"{p[0]:.4f},{p[1]:.4f}\n")
  [ok] [B-cauchy]  tail diameters shrink monotonically (Cauchy property)
  [ok] [B-cauchy]  the sequence converges to its limit in the space

B-rvspace

On a finite probability space (7 outcomes, one of them with probability zero), random variables are vectors in R^7 and the inner product <x, x'> = E{x x'} is the probability-weighted inner product of the vectors. It is symmetric, bilinear and positive semi-definite; two random variables that differ only on the zero-probability outcome satisfy E{(x - x')^2} = 0 and have zero induced distance — they are identified (equal with probability one), and on the identified space the inner product is positive definite.

rng = np.random.default_rng(7)
K = 7                               # 7 outcomes, the last has probability zero
p = np.array([0.10, 0.15, 0.20, 0.25, 0.18, 0.12, 0.00])


def E(x):                           # expectation of a random variable
    return p @ x


def ip(x, y):                       # the entry's inner product <x, y> = E{x y}
    return E(x * y)


x, y, z = rng.standard_normal((3, K)) * 2.0          # three random variables
a, b = rng.standard_normal(2)                         # random coefficients

ok_sym = np.isclose(ip(x, y), ip(y, x))               # symmetry
ok_lin = np.isclose(ip(a * x + b * z, y),             # bilinearity
                    a * ip(x, y) + b * ip(z, y))
ok_psd = ip(x, x) >= 0                                # positive semi-definite
# E{x y} = probability-weighted inner product of the outcome vectors
ok_rep = np.isclose(ip(x, y), np.sum(p * x * y))
check("[B-rvspace] E{x y}: symmetric, bilinear, psd, weighted inner "
      "product", ok_sym and ok_lin and ok_psd and ok_rep)

xp = x.copy()
xp[-1] += 5.0                       # differs only on the probability-0 outcome
ok_ident = (np.isclose(ip(x - xp, x - xp), 0.0)       # E{(x - x')^2} = 0
            and np.isclose(np.sqrt(ip(x - xp, x - xp)), 0.0))
ok_pd = ip(x, x) > 0                # positive definite after identification
check("[B-rvspace] x and x' equal with probability one are identified",
      ok_ident and ok_pd)
  [ok] [B-rvspace] E{x y}: symmetric, bilinear, psd, weighted inner product
  [ok] [B-rvspace] x and x' equal with probability one are identified

B-proj

Orthogonality principle in the Hilbert space of finite-variance random variables: the best linear approximation of y by a x (in the induced norm) has coefficient a* = E{x y} / E{x^2}, and the residual y - a* x is orthogonal to x, E{(y - a* x) x} = 0 — optimal estimation is orthogonal projection.

a_star = ip(x, y) / ip(x, x)        # best linear approximation of y by a x
resid = y - a_star * x              # estimation error
ok_orth = np.isclose(ip(resid, x), 0.0)               # orthogonality principle
# a* minimizes the induced norm of the residual over a grid of coefficients
grid = a_star + np.linspace(-1.0, 1.0, 201)
norms = [ip(y - a * x, y - a * x) for a in grid]
check("[B-proj]    residual of the best linear estimator is orthogonal "
      "to x", ok_orth and np.argmin(norms) == 100)

# ------------------------------------------------------------------ preview
fig, ax = plt.subplots(figsize=(4.2, 3.4))
ax.plot(pts[:, 0], pts[:, 1], "k.-", ms=4, lw=0.6)
ax.annotate("", xy=w_lim, xytext=(0, 0),
            arrowprops=dict(arrowstyle="-|>", lw=2))
ax.plot(*w_lim, "k*", ms=9)
ax.set_aspect("equal")
ax.set_axis_off()
fig.tight_layout()
fig.savefig("pythondemos/hilbertspace.png", dpi=110)
  [ok] [B-proj]    residual of the best linear estimator is orthogonal to x
Preview figure produced by hilbertspace.py
The preview figure the block B-proj writes when the script runs

B-rkhs

# Kernel methods work in an RKHS: the Gaussian kernel's Gram matrix on
# any point set is symmetric psd (it defines an inner product on the
# span of kernel sections), and for f = sum_i a_i k(x_i, .) the
# reproducing property <f, k(x_j, .)> = f(x_j) holds — the inner product
# computed via the Gram matrix equals the pointwise evaluation computed
# directly from the kernel function.
rng_r = np.random.default_rng(3)
Pk = rng_r.normal(size=(8, 2))
kfun = lambda u, v: np.exp(-np.sum((u - v) ** 2) / 2.0)
K = np.array([[kfun(Pk[i], Pk[j]) for j in range(8)] for i in range(8)])
check("[B-rkhs]   Gaussian Gram matrix is symmetric psd (an inner "
      "product on kernel sections)",
      np.allclose(K, K.T) and np.linalg.eigvalsh(K).min() > -1e-10)
a_coef = rng_r.normal(size=8)
inner_via_gram = K @ a_coef                # <f, k(x_j,.)> = (K a)_j
f_pointwise = np.array([sum(a_coef[i] * kfun(Pk[i], Pk[j])
                            for i in range(8)) for j in range(8)])
check("[B-rkhs]   reproducing property <f, k(x_j,.)> = f(x_j)",
      np.allclose(inner_via_gram, f_pointwise))

n_ok = sum(ok for _, ok in report)
print(f"\n{n_ok}/{len(report)} checks pass")
print("wrote pythondemos/hilbertspace_cauchy.csv, pythondemos/hilbertspace.png")
if n_ok != len(report):
    raise SystemExit(1)
  [ok] [B-rkhs]   Gaussian Gram matrix is symmetric psd (an inner product on kernel sections)
  [ok] [B-rkhs]   reproducing property <f, k(x_j,.)> = f(x_j)

7/7 checks pass
wrote pythondemos/hilbertspace_cauchy.csv, pythondemos/hilbertspace.png