Dictionary of Applied Machine Learning · feature

feature — Python demo

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

One block per paragraph of the entry (marked [P...]): each block verifies numerically what the corresponding statement asserts. Self-contained (numpy/matplotlib only), fixed seed.

Run it with python3 feature.py, from any directory — it writes its output files into the current directory. Requires NumPy and Matplotlib only, and uses fixed seeds, so the printed numbers reproduce exactly. Download feature.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

"""
feature.py — numerical companion to the glossary entry 'feature'.

One block per paragraph of the entry (marked [P...]): each block verifies
numerically what the corresponding statement asserts. Self-contained
(numpy/matplotlib only), fixed seed.

Blocks
------
[P-featmap]   Features are assembled into a feature vector by a feature
              map acting on the data point (its raw features): sampled
              signal values of an audio-like waveform form the feature
              vector x = Phi(z).
[P-transform] New features from arithmetic transformations of existing
              ones: augmenting a scalar feature x with x^2 turns an
              unlearnable quadratic relation into one a linear hypothesis map
              fits (the average training loss collapses).
[P-dtft]      DFT-magnitude features are unchanged by time shifts of the
              signal, while raw signal-value features change — the
              shift-invariance claim, checked numerically.
[P-activation] The activation of a neuron is a derived feature: a fixed
              random one-layer network turns raw features into
              activations, and a linear hypothesis map on these activations fits
              a nonlinear relation better than on the raw features.
[P-mechfeat]  The narrower mechanistic-interpretability sense: a feature
              as the projection of the activation vector onto a
              direction — the projection of activations onto a planted
              direction recovers a planted concept (correlation with
              the concept indicator is high).

Outputs
-------
feature.png : preview figure (checking only).

Data generated by pythondemos/feature.py.
"""

import numpy as np
import matplotlib

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

rng = np.random.default_rng(42)
report = []


def check(name, ok):
    report.append((name, bool(ok)))
    print(f"  [{'ok' if ok else 'FAIL'}] {name}")

P-featmap

Features are assembled into a feature vector by a feature map acting on the data point (its raw features): sampled signal values of an audio-like waveform form the feature vector x = Phi(z).

print("[P-featmap] feature map: data point -> feature vector")
t = np.arange(64)
signal = np.sin(2 * np.pi * 5 * t / 64) + 0.3 * np.sin(2 * np.pi * 11 * t / 64)
phi = lambda z: z.copy()                          # signal values as features
x = phi(signal)
check("feature vector collects the d = 64 signal values", x.size == 64)
check("the map is deterministic: same data point, same features",
      np.array_equal(phi(signal), x))
[P-featmap] feature map: data point -> feature vector
  [ok] feature vector collects the d = 64 signal values
  [ok] the map is deterministic: same data point, same features

P-transform

New features from arithmetic transformations of existing ones: augmenting a scalar feature x with x^2 turns an unlearnable quadratic relation into one a linear hypothesis map fits (the average training loss collapses).

print("[P-transform] transformed features make new features")
m = 120
u = rng.uniform(-2, 2, m)
yq = 1.5 * u**2 + 0.1 * rng.normal(size=m)        # quadratic relation
fit_err = lambda X: np.mean((yq - np.c_[X, np.ones(m)] @ np.linalg.lstsq(
    np.c_[X, np.ones(m)], yq, rcond=None)[0]) ** 2)
err_raw = fit_err(u[:, None])
err_aug = fit_err(np.c_[u, u**2])
print(f"    average training loss raw: {err_raw:.3f}, with x^2 feature: "
      f"{err_aug:.4f}")
check("adding the x^2 feature collapses the average training loss",
      err_aug < 0.05 * err_raw)
[P-transform] transformed features make new features
    average training loss raw: 2.633, with x^2 feature: 0.0098
  [ok] adding the x^2 feature collapses the average training loss

P-dtft

DFT-magnitude features are unchanged by time shifts of the signal, while raw signal-value features change — the shift-invariance claim, checked numerically.

print("[P-dtft] DFT magnitudes are shift-invariant features")
shifted = np.roll(signal, 17)                     # time shift
mag = lambda z: np.abs(np.fft.rfft(z))
check("raw signal-value features change under the shift",
      not np.allclose(signal, shifted))
check("DFT-magnitude features are unchanged by the shift",
      np.allclose(mag(signal), mag(shifted), atol=1e-10))
[P-dtft] DFT magnitudes are shift-invariant features
  [ok] raw signal-value features change under the shift
  [ok] DFT-magnitude features are unchanged by the shift

P-activation

The activation of a neuron is a derived feature: a fixed random one-layer network turns raw features into activations, and a linear hypothesis map on these activations fits a nonlinear relation better than on the raw features.

print("[P-activation] neuron activations as derived features")
W1 = rng.normal(size=(40, 1))
b1 = rng.normal(size=40)
act = lambda X: np.maximum(W1 @ X.T + b1[:, None], 0).T   # ReLU layer
err_act = np.mean((yq - np.c_[act(u[:, None]), np.ones(m)] @
                   np.linalg.lstsq(np.c_[act(u[:, None]), np.ones(m)],
                                   yq, rcond=None)[0]) ** 2)
print(f"    average training loss on activations: {err_act:.4f}")
check("linear hypothesis map on activations beats raw features",
      err_act < 0.1 * err_raw)
[P-activation] neuron activations as derived features
    average training loss on activations: 0.0081
  [ok] linear hypothesis map on activations beats raw features

P-mechfeat

The narrower mechanistic-interpretability sense: a feature as the projection of the activation vector onto a direction — the projection of activations onto a planted direction recovers a planted concept (correlation with the concept indicator is high).

print("[P-mechfeat] feature = projection of activations onto a direction")
d_act = 30
concept = (rng.uniform(size=500) > 0.5).astype(float)   # planted concept
direction = rng.normal(size=d_act); direction /= np.linalg.norm(direction)
acts = rng.normal(size=(500, d_act)) + 4.0 * concept[:, None] * direction
proj = acts @ direction                            # scalar feature
corr = np.corrcoef(proj, concept)[0, 1]
print(f"    corr(projection, concept) = {corr:.2f}")
check("projection onto the direction recovers the planted concept",
      corr > 0.8)
other = rng.normal(size=d_act); other -= (other @ direction) * direction
other /= np.linalg.norm(other)
check("projection onto an orthogonal direction does not",
      abs(np.corrcoef(acts @ other, concept)[0, 1]) < 0.2)

# ------------------------------------------------------------ preview
fig, ax = plt.subplots(1, 2, figsize=(8.4, 3.0))
ax[0].plot(t, signal, "-", lw=1, label="signal")
ax[0].plot(t, shifted, ":", lw=1, label="shifted")
ax[0].legend(frameon=False); ax[0].set_title("[P-dtft] signals")
ax[1].plot(mag(signal), "o-", ms=3, label="|DFT| original")
ax[1].plot(mag(shifted), "x", ms=4, label="|DFT| shifted")
ax[1].legend(frameon=False); ax[1].set_title("equal magnitudes")
fig.tight_layout()
fig.savefig("feature.png", dpi=110)
print(f"\n{sum(ok for _, ok in report)}/{len(report)} checks passed")
assert all(ok for _, ok in report)
[P-mechfeat] feature = projection of activations onto a direction
    corr(projection, concept) = 0.89
  [ok] projection onto the direction recovers the planted concept
  [ok] projection onto an orthogonal direction does not

8/8 checks passed
Preview figure produced by feature.py
The preview figure the block P-mechfeat writes when the script runs