🎉 75% of content is free forever — Unlock Premium from $10/mo →
CW
Search courses…
💼 Servicesℹ️ About✉️ ContactView Pricing Plansfrom $10

Error Analysis

Numerical MethodsErrors🟢 Free Lesson

Advertisement

Error Analysis


Definitions


Formulas


Types of Errors

Truncation Error

Roundoff Error

Combined Error Model

The total error is approximately: total_error ≈ truncation_error + roundoff_error

For a method with truncation error O(h^p) and step size h:

  • Decreasing h reduces truncation error but increases roundoff error (more operations)
  • Optimal h balances both error sources
  • Too small h leads to subtractive cancellation and roundoff dominates

Theorems


Python Implementation

import numpy as np
from typing import Callable, Tuple


class ErrorAnalyzer:
    """Comprehensive error analysis toolkit."""

    @staticmethod
    def condition_number_matrix(A: np.ndarray) -> float:
        """Compute 2-norm condition number of a matrix."""
        return np.linalg.cond(A, ord=2)

    @staticmethod
    def relative_error(computed: float, exact: float) -> float:
        """Compute relative error."""
        if exact == 0:
            return abs(computed)
        return abs(computed - exact) / abs(exact)

    @staticmethod
    def finite_difference_error(f: Callable, df: Callable, x: float,
                                h_range: Tuple[float, float] = (1e-16, 1)):
        """Analyze truncation vs roundoff error in finite differences."""
        h_values = np.logspace(np.log10(h_range[0]), np.log10(h_range[1]), 200)
        errors = []

        exact = df(x)
        for h in h_values:
            # Central difference
            approx = (f(x + h) - f(x - h)) / (2 * h)
            error = abs(approx - exact)
            errors.append(error)

        errors = np.array(errors)
        optimal_h = h_values[np.argmin(errors)]

        return h_values, errors, optimal_h

    @staticmethod
    def demonstrate_cancellation():
        """Demonstrate catastrophic cancellation."""
        print("\n--- Catastrophic Cancellation Demo ---")
        x = 1.0
        h_values = np.logspace(-1, -16, 16)

        print(f"Computing derivative of f(x) = sin(x) at x = 1")
        exact = np.cos(1.0)

        for h in h_values:
            # Forward difference (suffers from cancellation)
            fwd = (np.sin(1 + h) - np.sin(1)) / h
            # Central difference (better but still has issues)
            cen = (np.sin(1 + h) - np.sin(1 - h)) / (2 * h)

            fwd_err = abs(fwd - exact)
            cen_err = abs(cen - exact)

            print(f"  h={h:.0e}: Forward err={fwd_err:.2e}, "
                  f"Central err={cen_err:.2e}")

    @staticmethod
    def welford_online(data: np.ndarray) -> Tuple[float, float]:
        """Welford's online algorithm for mean and variance."""
        n = 0
        mean = 0.0
        M2 = 0.0

        for x in data:
            n += 1
            delta = x - mean
            mean += delta / n
            delta2 = x - mean
            M2 += delta * delta2

        variance = M2 / n if n > 1 else 0.0
        return mean, variance

    @staticmethod
    def naive_mean_variance(data: np.ndarray) -> Tuple[float, float]:
        """Naive two-pass mean and variance (unstable)."""
        n = len(data)
        mean = np.mean(data)
        variance = np.mean((data - mean) ** 2)
        return mean, variance

    @staticmethod
    def demonstrate_stability():
        """Compare stable vs unstable algorithms."""
        print("\n--- Algorithm Stability Demo ---")

        # Generate data with large mean and small variance
        np.random.seed(42)
        data = np.random.uniform(1e7, 1e7 + 1, 1_000_000)
        true_mean = np.mean(data)
        true_var = 1/12  # Variance of uniform(0,1)

        # Naive approach
        naive_mean, naive_var = ErrorAnalyzer.naive_mean_variance(data)

        # Welford
        welford_mean, welford_var = ErrorAnalyzer.welford_online(data)

        print(f"True mean:       {true_mean:.10f}")
        print(f"Naive mean:      {naive_mean:.10f}")
        print(f"Welford mean:    {welford_mean:.10f}")
        print(f"Mean error (naive):   {abs(naive_mean - true_mean):.2e}")
        print(f"Mean error (Welford): {abs(welford_mean - true_mean):.2e}")

        # Variance comparison
        naive_var_pass1 = np.mean(data**2) - np.mean(data)**2  # One-pass (unstable)
        print(f"\nNaive one-pass var: {naive_var_pass1:.6f}")
        print(f"Naive two-pass var: {naive_var:.6f}")
        print(f"Welford var:        {welford_var:.6f}")
        print(f"True var:           {true_var:.6f}")

    @staticmethod
    def demonstrate_condition_number():
        """Show effect of condition number on linear system solving."""
        print("\n--- Condition Number Effect on Linear Systems ---")

        # Well-conditioned
        A1 = np.array([[1, 0], [0, 2]])
        b1 = np.array([1, 1])
        x1 = np.linalg.solve(A1, b1)

        # Ill-conditioned
        A2 = np.array([[1, 1], [1, 1.0001]])
        b2 = np.array([2, 2.0001])
        x2 = np.linalg.solve(A2, b2)

        print(f"Well-conditioned (κ={np.linalg.cond(A1):.1f}):")
        print(f"  Solution: {x1}")

        print(f"Ill-conditioned (κ={np.linalg.cond(A2):.0f}):")
        print(f"  Solution: {x2}")

        # Perturbation experiment
        print(f"\nPerturbation experiment:")
        delta_b = np.array([1e-10, -1e-10])

        x1_pert = np.linalg.solve(A1, b1 + delta_b)
        x2_pert = np.linalg.solve(A2, b2 + delta_b)

        print(f"  Well-conditioned: Δx = {np.linalg.norm(x1_pert - x1):.2e}")
        print(f"  Ill-conditioned:  Δx = {np.linalg.norm(x2_pert - x2):.2e}")

    @staticmethod
    def demonstrate_backward_error():
        """Show backward error interpretation."""
        print("\n--- Backward Error Demonstration ---")

        # Solve Ax = b with known solution
        A = np.array([[1, 2], [3, 4]], dtype=float)
        x_true = np.array([1, 1])
        b = A @ x_true

        # Solve with perturbed matrix
        delta_A = np.array([[1e-10, 0], [0, 1e-10]])
        A_pert = A + delta_A
        x_computed = np.linalg.solve(A_pert, b)

        # Backward error: what perturbation to b would give this answer?
        delta_b = b - A @ x_computed
        backward_error = np.linalg.norm(delta_b) / np.linalg.norm(b)

        print(f"True solution:     {x_true}")
        print(f"Computed solution: {x_computed}")
        print(f"Forward error:     {np.linalg.norm(x_computed - x_true):.2e}")
        print(f"Backward error:    {backward_error:.2e}")
        print(f"Condition number:  {np.linalg.cond(A):.1f}")


def full_error_analysis():
    """Run comprehensive error analysis demonstrations."""
    print("=" * 60)
    print("NUMERICAL ERROR ANALYSIS")
    print("=" * 60)

    analyzer = ErrorAnalyzer()

    # 1. Condition numbers
    print("\n--- Condition Number Examples ---")
    matrices = {
        "Identity": np.eye(3),
        "Hilbert (5×5)": np.linalg.inv(np.vander(np.arange(5), increasing=True)),
        "Random 3×3": np.random.randn(3, 3),
        "Near-singular": np.array([[1, 1], [1, 1.0001]]),
    }

    for name, A in matrices.items():
        kappa = analyzer.condition_number_matrix(A)
        print(f"  {name:<20}: κ = {kappa:.4e}")

    # 2. Finite difference error
    print("\n--- Finite Difference Error Analysis ---")
    f = np.sin
    df = np.cos
    x = 1.0

    h_vals, errors, optimal_h = analyzer.finite_difference_error(f, df, x)
    print(f"  Optimal step size h: {optimal_h:.2e}")
    print(f"  Minimum error:       {np.min(errors):.2e}")
    print(f"  Theoretical optimal: {np.sqrt(np.finfo(float).eps):.2e}")

    # 3. Cancellation
    analyzer.demonstrate_cancellation()

    # 4. Stability comparison
    analyzer.demonstrate_stability()

    # 5. Condition number effect
    analyzer.demonstrate_condition_number()

    # 6. Backward error
    analyzer.demonstrate_backward_error()


full_error_analysis()

Applications in AI/ML

Error Sources in ML

Error TypeML EquivalentMitigation
TruncationModel bias (underfitting)Increase model complexity
RoundoffFP16 overflow in gradientsLoss scaling, FP32 accumulators
ConditioningIll-conditioned loss landscapeNormalization, adaptive learning rates
StabilityTraining divergenceGradient clipping, learning rate scheduling
CancellationVanishing gradientsSkip connections, careful initialization

Condition Numbers in Optimization

The condition number of the Hessian matrix determines convergence speed of gradient descent:

  • κ(H) = 1: All directions have equal curvature -> fastest convergence
  • κ(H) ≫ 1: Elongated loss landscape -> slow convergence, oscillation
  • Preconditioning reduces effective κ, accelerating convergence

Numerical Precision in Deep Learning

TechniquePrecisionTrade-off
FP32 training32-bitBaseline accuracy
Mixed precisionFP16 + FP322× speed, memory savings
Quantization (INT8)8-bit4× memory reduction, possible accuracy loss
Quantization (INT4)4-bit8× memory, significant accuracy risk
PruningSparseMemory reduction, hardware-dependent speedup

Gradient Clipping as Stability Control

Gradient clipping (max norm or value clipping) is the ML equivalent of step-size control in ODE solvers. It prevents the instability that arises when the effective condition number of the optimization problem becomes too large during training.


Common Mistakes

MistakeProblemSolution
Ignoring condition numberAmplification of errors invisible until failureAlways check κ(A) before solving linear systems
Using forward difference with small hRoundoff dominates for h < √εUse central differences; choose h ≈ √ε ·
Naive variance computationCatastrophic cancellation for large meanUse Welford's algorithm or two-pass method
Trusting FP16 gradientsOverflow/underflow in accumulationKeep gradient accumulators in FP32
Not checking algorithm stabilitySilent corruption of resultsPrefer backward-stable algorithms (e.g., QR over normal equations)
Using more precision than neededWasted memory and computeMatch precision to problem requirements
Ignoring error propagationSmall errors compound in iterationsAnalyze error growth; use compensated summation
Assuming double precision is always sufficientSome problems require arbitrary precisionCheck if κ(A) × ε_mach > tolerance

Interview Questions

Q1: What is the difference between forward and backward error? A: Forward error measures how far the computed answer is from the true answer: |x̃ − x|. Backward error measures the smallest perturbation to the input that would produce the computed answer: find δ such that f(x + δ) = x̃. Backward error is often more meaningful because it tells you whether the computed answer is correct for a slightly different problem. A backward-stable algorithm produces the exact answer for a nearby problem.

Q2: Explain condition number and its practical significance. A: The condition number κ measures problem sensitivity. For a linear system Ax = b, κ(A) = ‖A‖·‖A⁻¹‖. If κ = 10^k, you lose about k digits of accuracy in the solution. For example, κ = 10^6 in double precision (15 digits) leaves about 9 correct digits. Large κ means the problem is ill-conditioned — no algorithm can produce accurate results without extra precision.

Q3: Why is Gaussian elimination with partial pivoting backward stable but the normal equations are not? A: Gaussian elimination with partial pivoting has a small backward error bound: ‖δA‖/‖A‖ ≤ n·ρ·ε. The normal equations (A^T A)x = A^T b square the condition number: κ(A^T A) = κ(A)². This means solving via normal equations loses twice as many digits, making it numerically unstable for ill-conditioned problems. Use QR factorization instead.

Q4: How do you choose the optimal step size for finite differences? A: The error has two components: truncation error O(h) and roundoff error O(ε/h). The total error is minimized when h_opt ≈ √(ε·|x|) for forward differences, or h_opt ≈ ε^(1/3)·|x| for central differences. For float64 (ε ≈ 10⁻¹⁶), h_opt ≈ 10⁻⁸ for forward and h_opt ≈ 10⁻⁵·|x| for central differences.

Q5: What is catastrophic cancellation and how do you detect it? A: Catastrophic cancellation occurs when subtracting nearly equal numbers, causing significant digits to cancel and amplifying relative error. Detect it by computing the condition number of the subtraction: κ = (|a| + |b|)/|a − b|. If κ ≫ 1, cancellation is occurring. Solutions: reformulate to avoid the subtraction, use higher precision, or apply algebraic identities (e.g., a² − b² = (a−b)(a+b)).

Q6: Explain the connection between condition number and floating point precision. A: If a problem has condition number κ, and the input has relative error ε, the output has relative error approximately κ·ε. In float64 with ε ≈ 10⁻¹⁶, a problem with κ = 10^10 loses 10 digits, leaving about 5 correct digits. If κ > 10^16, no digits are correct. This is why checking κ before solving is essential — it determines whether double precision is sufficient.


Practice Problems


Quick Reference


Cross-References

  • 086-Floating-Point — Roundoff error is defined by IEEE 754 properties; machine epsilon determines achievable accuracy
  • 087-Root-Finding — Condition numbers determine sensitivity of root locations; Newton's method convergence depends on f''(x*)/f'(x*)
  • 088-Integration — Quadrature error analysis uses truncation error bounds; adaptive quadrature controls error via subdivision
  • 089-Interpolation — Interpolation error depends on derivatives of the function and node distribution
  • Linear Algebra — Condition numbers of matrices determine accuracy of linear system solutions, eigenvalue computations, and least squares
  • Optimization — Condition number of the Hessian determines convergence speed of gradient descent; preconditioning reduces effective condition number
  • ODE Solvers: Stability regions determine maximum step size; stiff systems require implicit solvers

Need Expert Mathematics Help?

Get personalized tutoring, project support, or professional consulting.

Advertisement