The recipe book (copy-paste NumPy)

A reference card of the small, sharp building blocks you reach for in every AI project. Each recipe is self-contained, vectorized (no Python loops where NumPy can do it), and shown with exact input and output. The similarity/activation/encoding recipes also live, runnable, in code/recipes.py, listed in full at the end of this chapter. Copy, paste, ship.

Convention: X is a feature matrix of shape (n_samples, n_features), axis 0 = samples, axis 1 = features (Chapter 2).

Similarity & distance

import numpy as np

def cosine_sim(a, b, eps=1e-12):                 # direction only, [-1, 1]
    a, b = np.asarray(a, float), np.asarray(b, float)
    return float(a @ b / (np.linalg.norm(a)*np.linalg.norm(b) + eps))

def euclidean(a, b):                             # straight-line distance, >= 0
    return float(np.sqrt(np.sum((np.asarray(a,float)-np.asarray(b,float))**2)))

def rbf(a, b, gamma=1.0):                        # distance -> similarity in (0,1]
    d2 = np.sum((np.asarray(a,float)-np.asarray(b,float))**2)
    return float(np.exp(-gamma * d2))

print(round(cosine_sim([1,0,1], [1,0,0]), 3),
      round(euclidean([0,0], [3,4]), 3),
      round(rbf([0,0], [1,0], gamma=0.5), 3))

Output:

0.707 5.0 0.607

All-pairs (the matrix versions you actually ship)

def normalize_rows(X, eps=1e-12):                # each row -> unit L2 length
    return X / (np.linalg.norm(X, axis=1, keepdims=True) + eps)

def cosine_matrix(X):                            # (n,n) all-pairs cosine
    Xn = normalize_rows(X); return Xn @ Xn.T

def pairwise_sq_dists(X, Y=None):                # (n,m) squared Euclidean, no loops
    Y = X if Y is None else Y
    xx = np.sum(X**2, 1)[:, None]; yy = np.sum(Y**2, 1)[None, :]
    return np.maximum(xx + yy - 2.0*(X @ Y.T), 0.0)

X = np.array([[1.,0.], [0.9,0.1], [0.,1.]])
print(np.round(cosine_matrix(X), 3))

Output:

[[1.    0.994 0.   ]
 [0.994 1.    0.11 ]
 [0.    0.11  1.   ]]

Ranking: top-k (the retrieval primitive)

def top_k(scores, k):                            # indices of k largest, best-first
    scores = np.asarray(scores); k = min(k, len(scores))
    part = np.argpartition(-scores, k-1)[:k]     # O(n) partial select
    return part[np.argsort(-scores[part])]       # then sort just those k

print(top_k([0.1, 0.9, 0.3, 0.7, 0.2], 3))

Output:

[1 3 2]

Activations

def sigmoid(x):  return 1.0 / (1.0 + np.exp(-np.asarray(x, float)))
def softmax(x):                                  # numerically stable (subtract max!)
    x = np.asarray(x, float); e = np.exp(x - x.max()); return e / e.sum()
def relu(x):     return np.maximum(0.0, np.asarray(x, float))

print("sigmoid:", np.round(sigmoid([-2, 0, 2]), 3))
print("softmax:", np.round(softmax([1, 2, 3]), 3), "sums to", round(softmax([1,2,3]).sum(),3))

Output:

sigmoid: [0.119 0.5   0.881]
softmax: [0.09  0.245 0.665] sums to 1.0

Cross-entropy (numerically stable)

def binary_cross_entropy(p, y, eps=1e-12):       # mean BCE on probabilities
    p = np.clip(np.asarray(p, float), eps, 1 - eps)
    y = np.asarray(y, float)
    return float(-np.mean(y * np.log(p) + (1 - y) * np.log(1 - p)))

def cross_entropy_logits(z, y):                  # softmax + CE in one, via log-sum-exp
    z = np.asarray(z, float)
    z = z - z.max(1, keepdims=True)              # the stability shift
    logp = z - np.log(np.exp(z).sum(1, keepdims=True))
    return float(-logp[np.arange(len(y)), y].mean())

print("BCE:", round(binary_cross_entropy([0.9, 0.2, 0.8], [1, 0, 1]), 4))
print("CE :", round(cross_entropy_logits([[2., 0., -1.], [0., 3., 1.]], [0, 1]), 4))

Output:

BCE: 0.1839
CE : 0.1698

Encodings & scaling

def one_hot(labels, n=None):                     # ints -> one-hot rows
    labels = np.asarray(labels); n = n or labels.max()+1
    out = np.zeros((len(labels), n)); out[np.arange(len(labels)), labels] = 1.0
    return out

def standardize(X, eps=1e-12):                   # per column: mean 0, std 1
    X = np.asarray(X, float); return (X - X.mean(0)) / (X.std(0) + eps)

def min_max(X, eps=1e-12):                        # per column: rescale to [0,1]
    X = np.asarray(X, float); lo, hi = X.min(0), X.max(0); return (X-lo)/(hi-lo+eps)

print(one_hot([0, 2, 1]).tolist())
print(np.round(standardize([[1.,10],[2,20],[3,30]]), 3).tolist())

Output:

[[1.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 1.0, 0.0]]
[[-1.225, -1.225], [0.0, 0.0], [1.225, 1.225]]

L2-normalize a single vector

v = np.array([3., 4.])
print("unit:", v / np.linalg.norm(v), " norm:", np.linalg.norm(v))

Output:

unit: [0.6 0.8]  norm: 5.0

Train / test split (shuffle, then slice)

def train_test_split(X, y, test=0.25, seed=0):
    rng = np.random.default_rng(seed); idx = rng.permutation(len(X))
    n = int(len(X) * test)
    te, tr = idx[:n], idx[n:]
    return X[tr], X[te], y[tr], y[te]

X = np.arange(8).reshape(8, 1); y = np.arange(8)
Xtr, Xte, ytr, yte = train_test_split(X, y, test=0.25)
print("train y:", ytr, " test y:", yte)

Output:

train y: [3 6 5 0 1 7]  test y: [2 4]

k-fold cross-validation loop

def kfold(n, k, seed=0):
    idx = np.random.default_rng(seed).permutation(n)
    for f in np.array_split(idx, k):
        yield np.setdiff1d(idx, f), f            # train indices, validation fold

y = np.array([0, 1] * 6)
scores = []
for tr, va in kfold(len(y), k=3):
    guess = y[tr].mean() > 0.5                   # "model": predict the majority class
    scores.append(float((guess == y[va]).mean()))
print("fold accuracies:", np.round(scores, 3), " mean:", round(float(np.mean(scores)), 3))

Output:

fold accuracies: [0.5  0.25 0.25]  mean: 0.333

Swap the majority-class "model" for a real fit/predict pair and this is the k-fold CV loop from Chapter 9.

Mini-batch iterator

def batches(X, y, bs, seed=0):
    rng = np.random.default_rng(seed); idx = rng.permutation(len(X))
    for s in range(0, len(X), bs):
        b = idx[s:s+bs]; yield X[b], y[b]

for i, (xb, yb) in enumerate(batches(np.arange(10).reshape(10,1), np.arange(10), 4)):
    print(f"batch {i}: y={yb}")

Output:

batch 0: y=[4 6 2 7]
batch 1: y=[3 5 9 0]
batch 2: y=[8 1]

Classification metrics from scratch

def accuracy(probs, y_true):                     # probs shape (n, classes)
    return float((probs.argmax(1) == y_true).mean())

probs = np.array([[0.7,0.2,0.1], [0.1,0.8,0.1], [0.2,0.2,0.6]])
print("accuracy:", round(accuracy(probs, np.array([0, 1, 0])), 3))

def precision_recall(y_pred, y_true):            # binary 0/1 arrays
    TP = ((y_pred==1)&(y_true==1)).sum(); FP = ((y_pred==1)&(y_true==0)).sum()
    FN = ((y_pred==0)&(y_true==1)).sum()
    return TP/(TP+FP), TP/(TP+FN)

p, r = precision_recall(np.array([1,1,0,0,1]), np.array([1,0,0,1,1]))
print(f"precision={p:.3f} recall={r:.3f}")

Output:

accuracy: 0.667
precision=0.667 recall=0.667

Smoothing a loss curve: exponential moving average

def ema(values, alpha=0.3):                      # EMA, for noisy training curves
    out = [values[0]]
    for v in values[1:]:
        out.append(alpha*v + (1-alpha)*out[-1])
    return np.array(out)

print(np.round(ema(np.array([1.,2,3,10,3,2,1]), alpha=0.3), 3))

Output:

[1.    1.3   1.81  4.267 3.887 3.321 2.625]

The spike at 10 is smoothed to 4.267 instead of dominating, exactly what you want when reading a jittery loss curve.

Numerical gradient check (verify any hand-derived gradient)

def grad_check(f, x, eps=1e-6):                  # central difference
    g = np.zeros_like(x, dtype=float)
    for i in range(x.size):
        d = np.zeros_like(x, dtype=float); d.flat[i] = eps
        g.flat[i] = (f(x+d) - f(x-d)) / (2*eps)
    return g

f = lambda v: v[0]**2 + 3*v[0]*v[1]              # analytic grad: [2x+3y, 3x]
print("numeric:", np.round(grad_check(f, np.array([2., 1.])), 5))

Output:

numeric: [7. 6.]

Two more one-liners worth memorizing

print("clip to [0,1]:", np.clip([-2., 0.5, 3.], 0, 1))       # clamp / saturate
print("running mean :", np.cumsum([2.,4,6,8]) / np.arange(1, 5))

Output:

clip to [0,1]: [0.  0.5 1. ]
running mean : [2. 3. 4. 5.]

Full source: code/recipes.py

The complete, runnable program, exactly as verified for this chapter:

"""
recipes.py — the AI-foundations recipe book, as runnable NumPy.

Every function here is a one-screen building block you will reach for in real
projects: similarities, kernels, normalizers, softmax, top-k, one-hot, pairwise
distances. Import them, or run this file to print a guided tour with exact I/O:

    python recipes.py

Only dependency: numpy.
"""
from __future__ import annotations

import numpy as np


# --------------------------------------------------------------------------- #
# Similarity & distance                                                        #
# --------------------------------------------------------------------------- #
def dot(a, b):
    """Linear similarity = raw inner product. Sensitive to vector length."""
    return float(np.dot(a, b))


def cosine_sim(a, b, eps=1e-12):
    """Cosine similarity = dot product of the length-normalized vectors.

    Measures *direction only* (angle), ignoring magnitude. Range [-1, 1].
    """
    a = np.asarray(a, dtype=float)
    b = np.asarray(b, dtype=float)
    na = np.linalg.norm(a)
    nb = np.linalg.norm(b)
    return float(np.dot(a, b) / (na * nb + eps))


def euclidean(a, b):
    """Straight-line (L2) distance. 0 = identical, larger = farther apart."""
    a = np.asarray(a, dtype=float)
    b = np.asarray(b, dtype=float)
    return float(np.sqrt(np.sum((a - b) ** 2)))


def manhattan(a, b):
    """City-block (L1) distance: sum of absolute coordinate differences."""
    a = np.asarray(a, dtype=float)
    b = np.asarray(b, dtype=float)
    return float(np.sum(np.abs(a - b)))


def rbf_kernel(a, b, gamma=1.0):
    """Gaussian / RBF kernel: 1 when identical, fading to 0 as points differ.

    k(a, b) = exp(-gamma * ||a - b||^2). A *similarity*, not a distance.
    """
    d2 = np.sum((np.asarray(a, float) - np.asarray(b, float)) ** 2)
    return float(np.exp(-gamma * d2))


def jaccard(a, b):
    """Set overlap: |intersection| / |union|. For binary/boolean vectors or sets."""
    a, b = set(a), set(b)
    union = a | b
    return len(a & b) / len(union) if union else 1.0


# --------------------------------------------------------------------------- #
# Matrix versions (the ones you actually ship)                                 #
# --------------------------------------------------------------------------- #
def normalize_rows(X, eps=1e-12):
    """Rescale every row to unit L2 length. Cosine becomes a plain dot product."""
    X = np.asarray(X, dtype=float)
    norms = np.linalg.norm(X, axis=1, keepdims=True)
    return X / (norms + eps)


def cosine_matrix(X):
    """All-pairs cosine similarity for the rows of X. Shape (n, n)."""
    Xn = normalize_rows(X)
    return Xn @ Xn.T


def pairwise_sq_dists(X, Y=None):
    """All-pairs *squared* Euclidean distances via the ||a-b||^2 identity."""
    X = np.asarray(X, dtype=float)
    Y = X if Y is None else np.asarray(Y, dtype=float)
    xx = np.sum(X ** 2, axis=1)[:, None]
    yy = np.sum(Y ** 2, axis=1)[None, :]
    d2 = xx + yy - 2.0 * (X @ Y.T)
    return np.maximum(d2, 0.0)  # clip tiny negatives from rounding


def top_k(scores, k):
    """Indices of the k largest scores, sorted best-first. The ranking primitive."""
    scores = np.asarray(scores)
    k = min(k, scores.shape[0])
    part = np.argpartition(-scores, k - 1)[:k]      # k best, unordered  (O(n))
    return part[np.argsort(-scores[part])]          # then sort just those k


# --------------------------------------------------------------------------- #
# Activations & encodings                                                      #
# --------------------------------------------------------------------------- #
def sigmoid(x):
    """Squash any real number into (0, 1). The logistic function."""
    return 1.0 / (1.0 + np.exp(-np.asarray(x, dtype=float)))


def softmax(x):
    """Turn a vector of scores (logits) into a probability distribution.

    The `- max` shift is the numerically-stable trick — same answer, no overflow.
    """
    x = np.asarray(x, dtype=float)
    z = x - np.max(x)
    e = np.exp(z)
    return e / np.sum(e)


def one_hot(labels, num_classes=None):
    """Integer labels -> one-hot rows. labels=[0,2] -> [[1,0,0],[0,0,1]]."""
    labels = np.asarray(labels)
    n = num_classes or int(labels.max()) + 1
    out = np.zeros((labels.shape[0], n))
    out[np.arange(labels.shape[0]), labels] = 1.0
    return out


def standardize(X, eps=1e-12):
    """Per-column z-score: subtract the mean, divide by the std. Mean 0, std 1."""
    X = np.asarray(X, dtype=float)
    return (X - X.mean(axis=0)) / (X.std(axis=0) + eps)


def min_max(X, eps=1e-12):
    """Per-column rescale to the [0, 1] range."""
    X = np.asarray(X, dtype=float)
    lo = X.min(axis=0)
    hi = X.max(axis=0)
    return (X - lo) / (hi - lo + eps)


# --------------------------------------------------------------------------- #
# Losses & validation                                                          #
# --------------------------------------------------------------------------- #
def binary_cross_entropy(p, y, eps=1e-12):
    """Mean binary cross-entropy on probabilities (clipped for stability)."""
    p = np.clip(np.asarray(p, dtype=float), eps, 1 - eps)
    y = np.asarray(y, dtype=float)
    return float(-np.mean(y * np.log(p) + (1 - y) * np.log(1 - p)))


def cross_entropy_logits(z, y):
    """Softmax + cross-entropy in one stable step, via log-sum-exp."""
    z = np.asarray(z, dtype=float)
    z = z - z.max(axis=1, keepdims=True)
    logp = z - np.log(np.exp(z).sum(axis=1, keepdims=True))
    return float(-logp[np.arange(len(y)), y].mean())


def kfold(n, k, seed=0):
    """Yield (train_indices, validation_fold) pairs for k-fold cross-validation."""
    idx = np.random.default_rng(seed).permutation(n)
    for f in np.array_split(idx, k):
        yield np.setdiff1d(idx, f), f


# --------------------------------------------------------------------------- #
# Guided tour                                                                  #
# --------------------------------------------------------------------------- #
def _tour():
    np.set_printoptions(precision=3, suppress=True)

    print("== Similarity & distance ==")
    a = np.array([1.0, 0.0])
    b = np.array([2.0, 0.0])      # same direction, twice as long
    c = np.array([0.0, 1.0])      # perpendicular
    print("a, b, c           =", a, b, c)
    print("dot(a, b)         =", dot(a, b))          # 2.0 -> length matters
    print("cosine(a, b)      =", round(cosine_sim(a, b), 3))   # 1.0 -> same direction
    print("cosine(a, c)      =", round(cosine_sim(a, c), 3))   # 0.0 -> perpendicular
    print("euclidean(a, b)   =", euclidean(a, b))    # 1.0
    print("manhattan(a, c)   =", manhattan(a, c))    # 2.0
    print("rbf(a, b, g=0.5)  =", round(rbf_kernel(a, b, gamma=0.5), 3))
    print("jaccard sets      =", round(jaccard([1, 2, 3], [2, 3, 4]), 3))

    print("\n== Matrix versions ==")
    X = np.array([[1.0, 0.0],
                  [0.9, 0.1],
                  [0.0, 1.0]])
    print("cosine_matrix(X) =\n", cosine_matrix(X))
    print("pairwise_sq_dists(X) =\n", pairwise_sq_dists(X))
    print("top_k([0.1,0.9,0.3,0.7], 2) =", top_k([0.1, 0.9, 0.3, 0.7], 2))

    print("\n== Activations & encodings ==")
    print("sigmoid([-2,0,2]) =", sigmoid([-2.0, 0.0, 2.0]))
    print("softmax([1,2,3])  =", softmax([1.0, 2.0, 3.0]))
    print("softmax sums to   =", round(float(softmax([1.0, 2.0, 3.0]).sum()), 6))
    print("one_hot([0,2,1])  =\n", one_hot([0, 2, 1]))
    print("standardize cols  =\n", standardize([[1.0, 100.0],
                                                 [2.0, 200.0],
                                                 [3.0, 300.0]]))
    print("min_max cols      =\n", min_max([[1.0, 100.0],
                                            [2.0, 200.0],
                                            [3.0, 300.0]]))

    print("\n== Losses & validation ==")
    print("bce               =", round(binary_cross_entropy([0.9, 0.2, 0.8], [1, 0, 1]), 4))
    print("ce (logits)       =", round(cross_entropy_logits([[2.0, 0.0, -1.0],
                                                             [0.0, 3.0, 1.0]], [0, 1]), 4))
    folds = [(len(tr), len(va)) for tr, va in kfold(12, 3)]
    print("kfold(12, 3) sizes =", folds)


if __name__ == "__main__":
    _tour()

The takeaway

These are the verbs of practical AI: normalize, similarity, top-k, softmax, one-hot, standardize, split, batch, score, smooth, grad-check. They show up in every project in this series. Keep this page open in a tab. One chapter left, the words an interviewer will assume you already know. 👉