BRAGA

02 Writing02-02 Notes02-02-157

Condition Number (matrix conditioning)

Published 26 Oct 20269 min read
Contents
  1. 01Deep Breakdown
  2. 02Flowchart
  3. 03Real-world use
  4. 04The formula, piece by piece
  5. 05The formula in words
  6. 06Critical Concepts
  7. 07Step-by-step calculation (tiny example)
  8. 08Code examples
κ(A)=∥A∥2 ∥A−1∥2=σmax⁡(A)σmin⁡(A),∥δx∥∥x∥≤κ(A) ∥δb∥∥b∥\kappa(A) = \lVert A \rVert_2 \, \lVert A^{-1} \rVert_2 = \frac{\sigma_{\max}(A)}{\sigma_{\min}(A)}, \qquad \frac{\lVert \delta x \rVert}{\lVert x \rVert} \le \kappa(A)\, \frac{\lVert \delta b \rVert}{\lVert b \rVert}
(1)

, where σmax⁡\sigma_{\max} and σmin⁡\sigma_{\min} are the largest and smallest singular values of AA, and Ax=bAx = b, A(x+δx)=b+δbA(x + \delta x) = b + \delta b

Deep Breakdown

What it tells me (Situation Awareness): The condition number measures how much a matrix can amplify relative errors when we solve Ax=bAx = b (or invert AA, or fit a regression). It is the ratio of the strongest to the weakest stretching the matrix applies to any direction. κ=1\kappa = 1 (orthogonal matrices) is perfect: a 1% change in the data moves the solution by at most 1%. A large κ\kappa means some direction is almost squashed flat, so tiny noise in bb along that direction gets divided by a tiny σmin⁡\sigma_{\min} and explodes in xx. A matrix can have a determinant that looks harmless and still be terribly conditioned (the determinant depends on scale, κ\kappa does not), and κ=∞\kappa = \infty means singular. Rule of thumb: you lose about log⁡10κ\log_{10}\kappa significant digits; with float64 (about 16 digits), κ≈1012\kappa \approx 10^{12} leaves only about 4 trustworthy digits. In DA/DS: diagnosing multicollinearity in linear and logistic regression (unstable, sign-flipping coefficients), choosing ridge regularization, deciding to standardize features, explaining why the normal equations X⊤Xβ=X⊤yX^\top X\beta = X^\top y are worse than QR/SVD solvers (κ(X⊤X)=κ(X)2\kappa(X^\top X) = \kappa(X)^2), checking covariance matrices before inverting them (Mahalanobis distance, Gaussian models, portfolio optimization), and understanding slow convergence of gradient descent on ill-conditioned losses.

Flowchart

Matrix A ordesign matrix XSVD: singularvalues σ_max ...σ_minκ = σ_max /σ_minκ near 1:well-conditioned,stableκ large: smallnoise in bbecomes bigerror in xLose aboutlog10 κ digits ofaccuracyFix: scalefeatures, dropcollinear onesFix: ridge addsλ, κ = (σ_max² +λ)/(σ_min² + λ)

Real-world use

Where it is used

Regression diagnostics (numpy.linalg.cond(X), statsmodels warns "The condition number is large ... strong multicollinearity"); numerical linear algebra libraries choosing solvers; covariance estimation in finance and Gaussian models (inverting a near-singular covariance); optimization (gradient descent converges in a number of steps roughly proportional to κ\kappa of the Hessian); calibration and sensor-fusion systems; scientific computing error analysis.

Problem it solves

A model can produce precise-looking numbers that are pure noise. With strongly correlated features (e.g. marketing_spend and impressions), the fitted coefficients can swing wildly or flip sign when a handful of rows change, even though predictions barely move. The condition number quantifies that fragility before we trust or report the coefficients.

How it solves the problem

Write A=UΣV⊤A = U \Sigma V^\top (SVD). Solving Ax=bAx = b divides each component of U⊤bU^\top b by a singular value. Noise in the direction of the smallest singular value is amplified by 1/σmin⁡1/\sigma_{\min} while the signal is typically carried by large singular values; the worst-case ratio of relative output error to relative input error is exactly σmax⁡/σmin⁡\sigma_{\max}/\sigma_{\min}. Once diagnosed, we can improve conditioning: standardize columns, remove or combine collinear features, use an orthogonal decomposition (QR/SVD) instead of forming X⊤XX^\top X, or add ridge regularization X⊤X+λIX^\top X + \lambda I, which lifts every squared singular value by λ\lambda and so shrinks κ\kappa dramatically.

Example in a business

A marketing-mix model regresses weekly sales on TV spend, online spend and total spend, where total spend is almost the sum of the other two. Even after standardizing, κ(X)\kappa(X) is about 89 (so κ(X⊤X)\kappa(X^\top X) is about 7,900), and refitting on bootstrap samples gives a TV coefficient that ranges from negative to strongly positive, so the CMO hears "TV hurts sales" one week and "TV is our best channel" the next. Dropping the redundant column and adding a small ridge penalty brings κ\kappa down to single digits, and the channel coefficients become stable enough to inform budget allocation.

The formula, piece by piece

  • AA: square matrix of a linear system Ax=bAx = b (or the design matrix XX in regression, using its singular values)
  • ∥A∥2=σmax⁡(A)\lVert A \rVert_2 = \sigma_{\max}(A): the largest factor by which AA can stretch a vector
  • ∥A−1∥2=1/σmin⁡(A)\lVert A^{-1} \rVert_2 = 1/\sigma_{\min}(A): the largest factor by which the inverse can stretch, huge when AA is nearly singular
  • σmax⁡,σmin⁡\sigma_{\max}, \sigma_{\min}: largest and smallest singular values (see Singular Value Decomposition); for symmetric positive definite AA these are the largest and smallest eigenvalues
  • κ(A)≥1\kappa(A) \ge 1: equals 1 for orthogonal/scaled-orthogonal matrices, ∞\infty for singular matrices
  • δb\delta b: perturbation of the right-hand side (measurement noise, rounding, a few changed rows)
  • δx\delta x: resulting change in the solution
  • ∥δx∥∥x∥≤κ∥δb∥∥b∥\frac{\lVert \delta x \rVert}{\lVert x \rVert} \le \kappa \frac{\lVert \delta b \rVert}{\lVert b \rVert}: the amplification bound; κ\kappa is the worst-case multiplier of relative error
  • κ(X⊤X)=κ(X)2\kappa(X^\top X) = \kappa(X)^2: forming the normal equations squares the condition number
  • Ridge: κ(X⊤X+λI)=σmax⁡2+λσmin⁡2+λ\kappa(X^\top X + \lambda I) = \frac{\sigma_{\max}^2 + \lambda}{\sigma_{\min}^2 + \lambda}, always smaller than κ(X)2\kappa(X)^2 for λ>0\lambda \gt 0 (see Ridge Regression Solution (L₂ Penalty))

The formula in words

"The condition number is the worst-case factor by which a relative error in the inputs can grow in the solution; it equals the ratio of the largest to the smallest stretch the matrix applies." Tiny example: A=diag(10,0.1)A = \text{diag}(10, 0.1) stretches one axis by 10 and squashes the other to 0.1, so κ=10/0.1=100\kappa = 10/0.1 = 100; a 1% error in bb can become up to a 100% error in xx.

Critical Concepts

  • Scale-free, unlike the determinant: det⁡(cA)=cndet⁡(A)\det(cA) = c^n \det(A) but κ(cA)=κ(A)\kappa(cA) = \kappa(A). A tiny determinant does not by itself mean ill-conditioning, and a moderate one does not rule it out. Use κ\kappa, not det⁡\det, to judge near-singularity (see Inverse matrix (2×2)).
  • Digits lost: relative error in the computed solution is roughly κ×\kappa \times machine epsilon (2.2×10−162.2 \times 10^{-16} for float64). κ=103\kappa = 10^{3} is harmless; κ>1010\kappa \gt 10^{10} deserves attention; κ≈1016\kappa \approx 10^{16} means numerically singular.
  • Never form the inverse to solve: use np.linalg.solve, lstsq, QR or Cholesky. Explicit inversion and the normal equations square or worsen the error.
  • Multicollinearity in regression: a large κ(X)\kappa(X) after standardizing columns signals near-linear dependence among features. Complement with VIFs to identify which features are involved. Statsmodels' warning is based on κ\kappa of the unscaled design, so standardize first to separate units problems from real collinearity.
  • Ridge improves conditioning: adding λI\lambda I raises the smallest eigenvalue of X⊤XX^\top X from σmin⁡2\sigma_{\min}^2 to σmin⁡2+λ\sigma_{\min}^2 + \lambda, the reason ridge gives stable coefficients under collinearity, at the price of some bias.
  • Optimization link: for a quadratic loss with Hessian HH, gradient descent needs on the order of κ(H)\kappa(H) iterations; feature scaling and preconditioning (Adam, Newton-type methods) attack exactly this.

Step-by-step calculation (tiny example)

A=(1111.01)A = \begin{pmatrix} 1 & 1 \\ 1 & 1.01 \end{pmatrix}, b=(2,2.01)b = (2, 2.01). Exact solution x=(1,1)x = (1, 1).

Step 1: Singular values AA is symmetric positive definite, so its singular values are its eigenvalues: σmax⁡=2.0050\sigma_{\max} = 2.0050, σmin⁡=0.0049875\sigma_{\min} = 0.0049875.

Step 2: Condition number κ=2.0050/0.0049875=402.0\kappa = 2.0050 / 0.0049875 = 402.0. Expect to lose about log⁡10402=2.6\log_{10} 402 = 2.6 digits.

Step 3: Perturb b slightly b′=(2,2.02)b' = (2, 2.02), so δb=(0,0.01)\delta b = (0, 0.01) and ∥δb∥/∥b∥=0.01/2.8355=0.0035\lVert \delta b \rVert / \lVert b \rVert = 0.01 / 2.8355 = 0.0035 (0.35%).

Step 4: New solution Solving Ax′=b′A x' = b' gives x′=(0,2)x' = (0, 2), so δx=(−1,1)\delta x = (-1, 1) and ∥δx∥/∥x∥=1.4142/1.4142=1\lVert \delta x \rVert / \lVert x \rVert = 1.4142 / 1.4142 = 1 (100%).

Step 5: Check the bound Amplification =1/0.003527=283.6≤κ=402.0= 1 / 0.003527 = 283.6 \le \kappa = 402.0. ✓\checkmark A 0.35% change in the data produced a 100% change in the answer.

Step 6: Normal equations and ridge κ(A⊤A)=402.02=161,610\kappa(A^\top A) = 402.0^2 = 161{,}610. With ridge λ=0.1\lambda = 0.1: κ(A⊤A+0.1I)=(4.0201+0.1)/(0.0000249+0.1)=41.2\kappa(A^\top A + 0.1 I) = (4.0201 + 0.1)/(0.0000249 + 0.1) = 41.2, about 3,900 times better conditioned.

Code examples

Beginner example (Python)

python
import numpy as np

A = np.array([[1.0, 1.0], [1.0, 1.01]])
b = np.array([2.0, 2.01])
b_perturbed = np.array([2.0, 2.02])

s = np.linalg.svd(A, compute_uv=False)
kappa = s[0] / s[-1]
print(f"singular values: {s.round(7)}")                     # [2.0050125 0.0049875]
print(f"kappa = {kappa:.1f} (np.linalg.cond: {np.linalg.cond(A):.1f})")  # 402.0

x = np.linalg.solve(A, b)
x_new = np.linalg.solve(A, b_perturbed)
rel_in = np.linalg.norm(b_perturbed - b) / np.linalg.norm(b)
rel_out = np.linalg.norm(x_new - x) / np.linalg.norm(x)
print(f"x = {x.round(4)}, x' = {x_new.round(4)}")           # [1. 1.] -> [0. 2.]
print(f"relative change in b: {rel_in:.4f}, in x: {rel_out:.4f}, amplification: {rel_out / rel_in:.1f}")

AtA = A.T @ A
print(f"kappa(A^T A) = {np.linalg.cond(AtA):,.0f}")                         # 161,610
print(f"kappa(A^T A + 0.1 I) = {np.linalg.cond(AtA + 0.1 * np.eye(2)):.1f}")  # 41.2

Beginner example (R)

r
A <- matrix(c(1, 1, 1, 1.01), nrow = 2, byrow = TRUE)
s <- svd(A)$d
cat(sprintf("kappa = %.1f\n", s[1] / s[2]))            # 402.0
print(solve(A, c(2, 2.01)))                            # 1 1
print(solve(A, c(2, 2.02)))                            # 0 2
AtA <- t(A) %*% A
e <- eigen(AtA + 0.1 * diag(2))$values
cat(sprintf("kappa with ridge 0.1 = %.1f\n", max(e) / min(e)))  # 41.2

Professional example (Python)

python
import numpy as np
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.preprocessing import StandardScaler

rng = np.random.default_rng(42)
n = 200

# Marketing-mix style data: total_spend is almost tv + online -> strong collinearity
tv = rng.normal(100, 20, n)
online = rng.normal(80, 15, n)
total = tv + online + rng.normal(0, 0.5, n)
sales = 3.0 * tv + 2.0 * online + rng.normal(0, 40, n)
X = np.column_stack([tv, online, total])
names = ["tv", "online", "total"]

Xs = StandardScaler().fit_transform(X)
print(f"kappa(X standardized)        = {np.linalg.cond(Xs):,.1f}")
print(f"kappa(X^T X)                 = {np.linalg.cond(Xs.T @ Xs):,.0f}")
for lam in [1.0, 10.0, 100.0]:
    print(f"kappa(X^T X + {lam:>5} I)       = {np.linalg.cond(Xs.T @ Xs + lam * np.eye(3)):,.1f}")
print(f"kappa(X without 'total')     = {np.linalg.cond(Xs[:, :2]):.2f}")


def bootstrap_coefs(model, X, y, n_boot=200):
    coefs = []
    for _ in range(n_boot):
        idx = rng.integers(0, len(y), len(y))
        coefs.append(model.fit(X[idx], y[idx]).coef_.copy())
    return np.array(coefs)


ols = bootstrap_coefs(LinearRegression(), Xs, sales)
ridge = bootstrap_coefs(Ridge(alpha=10.0), Xs, sales)
print("\nBootstrap coefficient spread (standardized features):")
print(f"{'feature':<8}{'OLS mean':>10}{'OLS sd':>9}{'OLS sign flips':>16}{'Ridge mean':>12}{'Ridge sd':>10}")
for j, name in enumerate(names):
    flips = np.mean(np.sign(ols[:, j]) != np.sign(np.median(ols[:, j])))
    print(f"{name:<8}{ols[:, j].mean():>10.1f}{ols[:, j].std():>9.1f}{flips:>16.0%}"
          f"{ridge[:, j].mean():>12.1f}{ridge[:, j].std():>10.1f}")

# Normal equations vs lstsq on an ill-conditioned Vandermonde (polynomial) design
t = np.linspace(0, 1, 50)
V = np.vander(t, 12, increasing=True)
beta_true = rng.normal(size=12)
y = V @ beta_true
beta_ne = np.linalg.solve(V.T @ V, V.T @ y)
beta_ls = np.linalg.lstsq(V, y, rcond=None)[0]
print(f"\nVandermonde kappa(V) = {np.linalg.cond(V):.2e}, kappa(V^T V) = {np.linalg.cond(V.T @ V):.2e}")
print(f"coef error, normal equations: {np.linalg.norm(beta_ne - beta_true):.2e}")
print(f"coef error, lstsq (SVD):      {np.linalg.norm(beta_ls - beta_true):.2e}")

Source: domain knowledge

#linear-algebra#matrices-and-determinants#determinants-and-inverses

Keyboard shortcuts

Single keys, ignored while typing in a field or with Cmd, Ctrl or Alt held.

0
Home
1 2 3 4
Work, Writing, Tools, About
j / k
Next / previous section on this page
n / p
Next / previous project or essay
u
Up one level
l
Deutsch
t
Light or dark theme
?
This list
Esc
Close