AI Knowledge Tree

RBF, polynomial, and string kernels

Build the workhorse kernels, understand their hyperparameters, and combine them with sum/product rules.

⏱ 28 minPrerequisites:The kernel trick

The kernel trick is general; in practice a handful of kernels do most of the work. The RBF is the universal default; the polynomial captures explicit interactions; string kernels lift the framework to non-vector inputs like text and biological sequences[1]. Knowing each kernel's hyperparameters and failure modes is most of using kernel methods well.

The RBF (Gaussian) kernel

k(x,x′)=exp⁡ ⁣(−γ ∥x−x′∥2),γ>0.k(x, x') = \exp\!\left(-\gamma\,\lVert x - x'\rVert^2\right), \qquad \gamma > 0.

Properties:

  • Implicit infinite-dimensional features. The Taylor expansion of exp⁡\exp shows ϕ\phi has infinitely many components.
  • Locality. k(x,x′)→0k(x, x') \to 0 as ∥x−x′∥→∞\lVert x - x'\rVert \to \infty, so each training point influences a region of size ∼1/γ\sim 1/\sqrt\gamma.
  • One hyperparameter, γ\gamma (or σ\sigma with γ=1/(2σ2)\gamma = 1/(2\sigma^2)). Large γ\gamma: tiny influence radius, complex boundaries, risk of overfitting. Small γ\gamma: huge influence radius, smooth boundaries, risk of underfitting.

RBF is the safe first choice when you have no specific structural knowledge about the data[2].

The polynomial kernel

k(x,x′)=(γ x⊤x′+c)p.k(x, x') = (\gamma\, x^\top x' + c)^p.

Hyperparameters: degree pp, scale γ\gamma, offset cc.

The feature map contains all monomials in the features up to degree pp. For p=2p = 2, c=1c = 1 and 2D input, ϕ\phi has components {1,2x1,2x2,x12,x22,2x1x2}\{1, \sqrt 2 x_1, \sqrt 2 x_2, x_1^2, x_2^2, \sqrt 2 x_1 x_2\} (verified in the previous lesson). Good when explicit feature interactions matter and when the relevant degree is small. Bad when the degree is large: the Gram matrix becomes ill-conditioned and the model overfits.

String kernels

For non-vector inputs, define the inner product directly. For sequences (text, DNA, code), the spectrum kernel counts how many length-kk substrings two sequences share:

k(s,t)=∑u∈Σk#u(s) #u(t),k(s, t) = \sum_{u \in \Sigma^k} \#u(s)\, \#u(t),

where #u(s)\#u(s) counts occurrences of substring uu in ss. The induced feature map is the histogram of kk-mers; the kernel is the dot product of histograms, computed in time linear in the lengths via suffix-array data structures.

Variants: gap-weighted string kernels allow non-contiguous substrings with length-decaying weights; mismatch kernels allow approximate matches. These were the dominant approach to sequence classification before deep learning.

Combining kernels: practical recipes

The composition rules from the previous lesson let you build problem-specific kernels:

  • Multiple data sources. Sum kernels from different modalities, k=ktext+kimagek = k_{\text{text}} + k_{\text{image}}, to combine them.
  • Sparse + smooth. Sum a polynomial kernel (captures explicit interactions) and an RBF (captures smooth residual structure).
  • Normalized kernels. Replace k(x,x′)k(x, x') with k(x,x′)/k(x,x) k(x′,x′)k(x, x')/\sqrt{k(x,x)\, k(x',x')} to make the kernel scale-invariant.

Picking hyperparameters

Cross-validate over a grid:

  • RBF: jointly sweep CC (regularization) and γ\gamma (bandwidth), log-scale.
  • Polynomial: degrees 2 or 3 are typical; very rarely above 5.
  • Composite: validate the mixture weights too.

A median-distance heuristic for RBF: set γ=1/median⁡i≠j∥xi−xj∥2\gamma = 1/\operatorname{median}_{i \ne j}\lVert x_i - x_j\rVert^2. It is usually within an order of magnitude of the cross-validated optimum and is a cheap starting point.

The RBF kernel as a similarity-vs-distance curve: the bandwidth γ sets how far similarity reaches.
The RBF kernel as a similarity-vs-distance curve: the bandwidth γ sets how far similarity reaches.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# Sample data
n = 100
X = rng.normal(size=(n, 4))

def rbf(X1, X2, gamma):
    d2 = np.sum(X1**2, 1)[:, None] + np.sum(X2**2, 1) - 2 * X1 @ X2.T
    return np.exp(-gamma * d2)

def poly(X1, X2, gamma=1.0, c=1.0, p=3):
    return (gamma * X1 @ X2.T + c)**p

# Both Gram matrices are PSD
K_rbf = rbf(X, X, gamma=0.5)
K_poly = poly(X, X, p=3)
print(f"RBF min eig:  {np.min(np.linalg.eigvalsh(K_rbf)):.3e}")
print(f"poly min eig: {np.min(np.linalg.eigvalsh(K_poly)):.3e}")

# Median-distance heuristic for gamma
from itertools import combinations
ij = list(combinations(range(n), 2))
dists2 = [np.sum((X[i] - X[j])**2) for i, j in ij[:5000]]
gamma_med = 1 / np.median(dists2)
print(f"median-distance gamma: {gamma_med:.3f}")

# Sum kernel: K = K_rbf + K_poly is still PSD
K_sum = K_rbf + K_poly
print(f"sum kernel min eig: {np.min(np.linalg.eigvalsh(K_sum)):.3e}")

# String kernel (spectrum kernel, k=2): count shared bigrams in short strings
def spectrum_kernel(s, t, k=2):
    def kmers(x):
        bag = {}
        for i in range(len(x) - k + 1):
            bag[x[i:i+k]] = bag.get(x[i:i+k], 0) + 1
        return bag
    a, b = kmers(s), kmers(t)
    return sum(a[u] * b.get(u, 0) for u in a)

strings = ["statistics", "stochastic", "statistician", "biology"]
n_str = len(strings)
K_str = np.array([[spectrum_kernel(s, t, k=2) for t in strings] for s in strings], dtype=float)
print("\nstring-kernel Gram (bigram counts):")
print(K_str.astype(int))

All three Gram matrices come out PSD; the median-distance heuristic gives a reasonable starting γ\gamma; the bigram spectrum kernel correctly puts "statistics" closer to its morphological neighbors than to "biology".

Exercises

Exercise 1. Compute the bigram (k=2k = 2) spectrum kernel k(s,t)k(s, t) by hand for the strings s=s = "aba" and t=t = "abab", and for k(s,s)k(s, s).

Solution

Step 1. List the length-2 substrings (bigrams) of each string with counts. For s=s = "aba": positions give "ab" and "ba", so #ab(s)=1\#\text{ab}(s) = 1, #ba(s)=1\#\text{ba}(s) = 1. For t=t = "abab": "ab", "ba", "ab", so #ab(t)=2\#\text{ab}(t) = 2, #ba(t)=1\#\text{ba}(t) = 1.

Step 2. The spectrum kernel is the dot product of these count histograms over shared bigrams:

k(s,t)=#ab(s) #ab(t)+#ba(s) #ba(t)=(1)(2)+(1)(1)=3.k(s, t) = \#\text{ab}(s)\,\#\text{ab}(t) + \#\text{ba}(s)\,\#\text{ba}(t) = (1)(2) + (1)(1) = 3.

Step 3. The self-similarity uses the same counts for both arguments:

k(s,s)=12+12=2.k(s, s) = 1^2 + 1^2 = 2.

The histogram view makes the kernel a plain inner product of kk-mer counts, which is why it is automatically a valid kernel.

Exercise 2. The product k(x,x′)=k1(x,x′) k2(x,x′)k(x, x') = k_1(x, x')\, k_2(x, x') of two valid kernels is again a valid kernel. Take k1=k2=x⊤x′k_1 = k_2 = x^\top x' on R2\mathbb{R}^2 and exhibit the explicit feature map of the product, confirming it contains the degree-2 monomials.

Solution

Step 1. Write the product out for x=(x1,x2)x = (x_1, x_2), x′=(x1′,x2′)x' = (x'_1, x'_2):

k(x,x′)=(x⊤x′)2=(x1x1′+x2x2′)2=x12x1′2+2x1x2x1′x2′+x22x2′2.k(x, x') = (x^\top x')^2 = (x_1 x'_1 + x_2 x'_2)^2 = x_1^2 x_1'^2 + 2 x_1 x_2 x'_1 x'_2 + x_2^2 x_2'^2.

Step 2. Group each term as (function of xx)(same function of x′x'): x12x1′2=(x12)(x1′2)x_1^2 x_1'^2 = (x_1^2)(x_1'^2), 2x1x2x1′x2′=(2 x1x2)(2 x1′x2′)2 x_1 x_2 x'_1 x'_2 = (\sqrt2\, x_1 x_2)(\sqrt2\, x'_1 x'_2), and x22x2′2=(x22)(x2′2)x_2^2 x_2'^2 = (x_2^2)(x_2'^2). The feature map is

ϕ(x)=(x12, 2 x1x2, x22).\phi(x) = \left(x_1^2,\ \sqrt2\, x_1 x_2,\ x_2^2\right).

Step 3. Its components are exactly the degree-2 monomials in x1,x2x_1, x_2, so ϕ(x)⊤ϕ(x′)=(x⊤x′)2=k(x,x′)\phi(x)^\top \phi(x') = (x^\top x')^2 = k(x, x'). The product of the two linear kernels lifts into the space of quadratic interactions, and being an inner product of ϕ\phi it is a valid kernel.

Exercise 3 (code). Take an RBF Gram matrix and a degree-3 polynomial Gram matrix on random data. Verify that their sum is still positive semidefinite, and that the RBF kernel, after normalization k(x,x′)/k(x,x) k(x′,x′)k(x,x')/\sqrt{k(x,x)\,k(x',x')}, has a unit diagonal and entries within [−1,1][-1, 1].

Solution
python · numpy
import numpy as np

rng = np.random.default_rng(0)
X = rng.normal(size=(50, 4))

def rbf(X1, X2, gamma):
    d2 = np.sum(X1**2, 1)[:, None] + np.sum(X2**2, 1) - 2 * X1 @ X2.T
    return np.exp(-gamma * d2)

def poly(X1, X2, gamma=1.0, c=1.0, p=3):
    return (gamma * X1 @ X2.T + c)**p

K_rbf = rbf(X, X, gamma=0.5)
K_poly = poly(X, X, p=3)

# Sum of two valid kernels stays PSD
K_sum = K_rbf + K_poly
assert np.linalg.eigvalsh(K_sum).min() > -1e-8

# Normalized kernel: unit diagonal, entries in [-1, 1]
d = np.sqrt(np.diag(K_rbf))
K_norm = K_rbf / np.outer(d, d)
assert np.allclose(np.diag(K_norm), 1.0)
assert K_norm.max() <= 1 + 1e-9 and K_norm.min() >= -1 - 1e-9
print(f"sum-kernel min eigenvalue: {np.linalg.eigvalsh(K_sum).min():.4f}")

The summed Gram matrix has only nonnegative eigenvalues (closure under addition), and normalizing the RBF kernel rescales each entry by the geometric mean of the diagonal, forcing self-similarity to 11 and every cross-similarity into [−1,1][-1, 1], the cosine-like form used to compare kernels across scales.

The next lesson takes the kernel idea to its probabilistic extreme: Gaussian processes.


References

  1. [1]Schölkopf, B., & Smola, A. J. (2002). Learning with Kernels. MIT Press. Catalogues the RBF, polynomial, and structured kernels and their composition rules.↩
  2. [2]Bishop, C. M. (2006). Pattern Recognition and Machine Learning. Springer. §6.2 covers kernel construction and the standard families.↩