Math for AI course · Lesson 25 of 180

Numerical Solving for AI: Use Solve Without Explicit Inverses

Solve AI parameter-recovery systems with NumPy, then use residuals, condition numbers, and singularity checks to judge whether the answer is trustworthy.

Share this article

An AI calibration step may need to recover two unknown parameters from two measured responses. The equations are small enough to write on a whiteboard, but production code still has to decide how to solve them and how to check the answer. A plausible-looking vector is not evidence that the data determined it reliably.

This lesson develops that decision with NumPy. You will solve a square linear system, compare numpy.linalg.solve with the explicit-inverse route, compute a residual, measure conditioning, and recognize the singular boundary where a unique parameter estimate does not exist.

Learning outcome: Use numpy.linalg.solve and explain why explicit inversion is usually avoided.

Lesson 22: Identity and inverse transformations showed what an inverse means mathematically. Lesson 24: Row-echelon form reveals structure showed how pivots and free variables expose what a system can determine. Here, we connect that exact algebra to finite-precision computation. The official Mathematics for Machine Learning companion places this work in Section 2.3 of the book PDF; the calibration scenario, numbers, code, visual, and assessments below are original.

A linear system is a parameter-recovery contract

Write the unknown parameter vector as

x=[x1x2]∈R2.x = \begin{bmatrix}x_1\\x_2\end{bmatrix}\in\mathbb{R}^2.

The vector has two coordinates, so each row of a coefficient matrix AA must contain two coefficients. If two observations produce the right-hand side vector bb, a parameter-recovery system has the form

Ax=b,A∈R2×2,b∈R2,x∈R2.A x=b, \qquad A\in\mathbb{R}^{2\times 2}, \quad b\in\mathbb{R}^{2}, \quad x\in\mathbb{R}^{2}.

For a tiny calibration head, x1x_1 might be a scale applied to one signal and x2x_2 an offset-like correction applied to another. More generally, the same notation describes a small linear layer, a sensor correction, or a parameter recovery subproblem inside a larger AI pipeline. The rows of AA are the observed coefficient patterns; bb contains the responses those patterns should produce.

If AA is square and invertible, the algebra from the previous lesson gives

x=A−1b.x=A^{-1}b.

That identity is useful for reasoning. It does not mean that a program should first construct A−1A^{-1} every time it needs xx.

Worked example: recover two calibration parameters

Consider the deliberately small system

A=[3112],b=[81].A=\begin{bmatrix}3&1\\1&2\end{bmatrix}, \qquad b=\begin{bmatrix}8\\1\end{bmatrix}.

The two equations are

3x1+x2=8,x1+2x2=1.3x_1+x_2=8, \qquad x_1+2x_2=1.

Solve it by substitution before asking NumPy to do the same work. The first equation gives x2=8−3x1x_2=8-3x_1. Substitute that expression into the second:

x1+2(8−3x1)=1⟹−5x1=−15⟹x1=3.x_1+2(8-3x_1)=1 \Longrightarrow -5x_1=-15 \Longrightarrow x_1=3.

Then x2=8−3(3)=−1x_2=8-3(3)=-1, so

x=[3−1].x=\begin{bmatrix}3\\-1\end{bmatrix}.

Check both original equations, not only the rearranged one:

Ax=[3112][3−1]=[81]=b.A x = \begin{bmatrix}3&1\\1&2\end{bmatrix} \begin{bmatrix}3\\-1\end{bmatrix} = \begin{bmatrix}8\\1\end{bmatrix}=b.

Why solve is usually the right intermediate

The direct mathematical expression A−1bA^{-1}b hides two different computational tasks:

RouteWhat it computesWhy that matters for one right-hand side
np.linalg.solve(A, b)Factorizes AA and applies the factors to bbComputes only the vector requested.
np.linalg.inv(A) @ bBuilds every column of A−1A^{-1}, then multiplies by bbComputes much more information than this one solve needs.

For a single right-hand side, solve avoids materializing an entire inverse. For several right-hand sides stored as the columns of BB, use np.linalg.solve(A, B) so the same coefficient matrix can be factored once while all requested solutions are computed. An inverse is an object to keep only when the application genuinely needs the inverse itself; it is not a default substitute for a solve.

The current NumPy documentation for solve says that the coefficient matrix must be square and full rank, and documents that the routine uses a LAPACK _gesv implementation. The documentation for inv also warns that an ill-conditioned matrix can produce inaccurate results even when inversion does not raise an error. That is why the implementation choice and the diagnostics belong in the same mental model.

The runtime difference is hardware- and library-dependent, so it is not a portable benchmark. As an illustration, the following authoring-host run used a fixed random 64×6464\times64 matrix, five timing repeats, and twenty calls per repeat:

from timeit import repeat

rng = np.random.default_rng(7)
A_timing = rng.normal(size=(64, 64)) + 64 * np.eye(64)
b_timing = rng.normal(size=64)

solve_time = min(
    repeat(lambda: np.linalg.solve(A_timing, b_timing), repeat=5, number=20)
) / 20
inverse_time = min(
    repeat(lambda: np.linalg.inv(A_timing) @ b_timing, repeat=5, number=20)
) / 20

print(solve_time * 1e6, inverse_time * 1e6, inverse_time / solve_time)

The verified run printed approximately 15.2 µs, 52.2 µs, and 3.43× for the solve time, inverse-route time, and ratio. Treat those numbers as a reproducible shape of the comparison, not as a performance promise: the algorithmic reason to prefer solve is that it avoids computing an unused matrix, and a different BLAS library or matrix size can change the ratio.

A residual is a fit check, not a trust certificate

For the worked example, calculate the residual from the direct route:

r=b−Ax^=[81]−[81]=[00].r=b-A\hat{x} = \begin{bmatrix}8\\1\end{bmatrix} - \begin{bmatrix}8\\1\end{bmatrix} = \begin{bmatrix}0\\0\end{bmatrix}.

The floating-point program reports a norm near 10−1510^{-15} instead of exactly zero. That is excellent evidence that the returned vector fits this bb. However, imagine that the coefficients or responses came from rounded sensor measurements. A solver can find a vector that fits those rounded values very closely while still moving substantially when the measurements are changed within their uncertainty.

Conditioning measures sensitivity

The condition number compares how much a matrix can amplify relative input changes. For an invertible matrix and the 2-norm,

κ2(A)=∥A∥2 ∥A−1∥2=σmax⁡(A)σmin⁡(A),\kappa_2(A)=\lVert A\rVert_2\,\lVert A^{-1}\rVert_2 =\frac{\sigma_{\max}(A)}{\sigma_{\min}(A)},

where σmax⁡\sigma_{\max} and σmin⁡\sigma_{\min} are the largest and smallest singular values. A condition number near 11 indicates balanced directions. A large value means that some small change in the inputs can cause a much larger relative change in the solution. If the smallest singular value is zero, the matrix is singular and the condition number is infinite.

For a perturbed right-hand side, a useful bound has the form

∥δx∥∥x∥≲κ(A)∥δb∥∥b∥,\frac{\lVert\delta x\rVert}{\lVert x\rVert} \lesssim \kappa(A)\frac{\lVert\delta b\rVert}{\lVert b\rVert},

under the usual assumptions for a nonsingular system. The inequality is a warning about possible amplification, not a promise that every perturbation will reach the worst case.

Now use nearly dependent rows:

B=[1111.0001],xtrue=[2−1],b=Bxtrue=[10.9999].B=\begin{bmatrix}1&1\\1&1.0001\end{bmatrix}, \qquad x_{\text{true}}=\begin{bmatrix}2\\-1\end{bmatrix}, \qquad b=B x_{\text{true}}=\begin{bmatrix}1\\0.9999\end{bmatrix}.

The second row is almost a copy of the first. With the exact displayed bb, the solve recovers [2,−1]T[2,-1]^\mathsf{T}. Change only the second response by 10−610^{-6}, then solve again. NumPy reports

condition number: 40002.000074915224
original solution: [ 2. -1.]
perturbed solution: [ 1.99 -0.99]
relative input change: 7.071421374297804e-07
relative solution change: 0.006324555320519327
perturbed residual norm: 0.0

The input changed by about 7.1×10−77.1\times10^{-7} relative, while the parameter vector changed by about 6.3×10−36.3\times10^{-3} relative. That is roughly a four-order-of-magnitude condition number expressing itself as sensitivity. The perturbed solution still has zero residual for the perturbed data: it solved the new equations correctly, but the data did not identify a stable parameter vector.

Static stability map

A small residual can hide a sensitive solution

The residual checks the equation that was solved. The condition number warns how much the solution can move when the input data move.

Residual and conditioning comparisonThree cases compare a well-conditioned system, a nearly dependent system, and a singular system. The well-conditioned and nearly dependent cases both fit their supplied right-hand sides, but a small input change moves the sensitive solution much more. The singular case has no unique solve.A x = bsolveinverse routesmall residual‖b − A x̂‖₂Well-conditionedκ₂(A) = 2.62small Δb: 1.2 × 10⁻⁷small Δx: 2.0 × 10⁻⁷‖r‖₂ ≈ 10⁻¹⁵Ill-conditionedκ₂(A) ≈ 4.0 × 10⁴small Δb: 7.1 × 10⁻⁷Δx: 6.3 × 10⁻³‖r‖₂ = 0 for perturbed bSingularκ₂(A) = ∞solve: LinAlgErrorinverse route: LinAlgErrorno unique x
The same solve pipeline can produce a tiny residual in all three cases. Conditioning determines whether that fit is also a trustworthy parameter estimate.
Exact quantities represented in the stability map
CaseCondition numberRelative input → solution changeResidual and interpretation
Well-conditioned2.621.2 × 10⁻⁷ → 2.0 × 10⁻⁷Residuals around 10⁻¹⁵; both routes agree for this well-conditioned example.
Ill-conditioned4.0002 × 10⁴7.1 × 10⁻⁷ → 6.3 × 10⁻³Residual 0 for the perturbed data, but a 7.1 × 10⁻⁷ input change moves x by 6.3 × 10⁻³.
Singular∞Not applicableκ₂(A) is infinite; solve and inverse raise a singular-matrix error instead of returning a unique answer.

Read the two diagnostics together: residual asks “does this x fit b?”; conditioning asks “how much would x change if b or A were slightly different?”

The visual makes the two questions separate. The residual asks whether the candidate fits the equations currently in memory. The condition number asks whether a small change in the equations could move that candidate. An AI calibration system needs both answers.

Boundary case: singular means “no unique solve”

Consider

C=[1224].C=\begin{bmatrix}1&2\\2&4\end{bmatrix}.

The second row is twice the first, so CC has rank 11, not 22. With b=[3,6]Tb=[3,6]^\mathsf{T}, the equations are consistent but have infinitely many solutions: every (x1,x2)(x_1,x_2) satisfying x1+2x2=3x_1+2x_2=3 works. With b=[3,7]Tb=[3,7]^\mathsf{T}, they are inconsistent because the second equation asks for twice the first left-hand side but gives a different right-hand side.

Neither situation has a unique vector for np.linalg.solve to return. In the bundled runtime, both np.linalg.solve(C, [3, 6]) and np.linalg.inv(C) raise numpy.linalg.LinAlgError: Singular matrix. The NumPy solve documentation specifies the full-rank requirement and points to numpy.linalg.lstsq when a least-squares solution is appropriate for a non-square or rank-deficient problem. That is a different question from solving an invertible square system, so do not silently replace one method with the other.

A disciplined NumPy workflow for AI systems

For a small parameter-recovery subproblem, a defensible workflow is:

  1. Check the contract. Confirm that the dimensions of AA, bb, and the intended parameter vector agree.
  2. Solve directly. Use np.linalg.solve(A, b) for a square, full-rank system; use a method designed for least squares when the problem is not well-determined.
  3. Measure the residual. Compute np.linalg.norm(A @ x - b) and compare it with the scale of b, not with an arbitrary absolute threshold alone.
  4. Inspect conditioning. Compute np.linalg.cond(A) and compare the possible sensitivity with measurement precision and the number of digits you plan to use downstream.
  5. Treat failure as information. A singular error, an enormous condition number, or a residual that is large relative to b should trigger a data, feature, scaling, or model-design investigation.

In a calibration or parameter-recovery task, this workflow turns a vector of weights into an auditable result. A tiny residual can support the claim that the implementation solved the supplied equations. A moderate condition number and a perturbation check provide evidence that those equations constrain the parameters strongly enough for the intended use. Neither diagnostic proves that the broader AI system is accurate; they answer narrower implementation questions that can be tested.

The next lesson steps away from numerical algorithms and asks which algebraic operations are valid over the number system used by common AI tensors. For now, keep the practical rule: solve the system you have, check the residual you got, and ask conditioning whether a nearby system would tell a different parameter story.

Check your understanding

Question 1

For A in R^(m×n), x in R^n, and b in R^m, what does Ax = b ask the program to find?

Show the step-by-step solution

It asks for a vector xx with nn coordinates whose multiplication by the m×nm\times n matrix AA produces the mm-coordinate vector bb. The shape check comes first: each row of AA must have nn entries so that its dot product with xx is defined, and there must be one resulting equation for each entry of bb.

Question 2

Solve 3x_1 + x_2 = 8 and x_1 + 2x_2 = 1 by hand.

Show the step-by-step solution

From the first equation, x2=8−3x1x_2=8-3x_1. Substitute into the second: x1+2(8−3x1)=1x_1+2(8-3x_1)=1, so −5x1=−15-5x_1=-15 and x1=3x_1=3. Then x2=8−3(3)=−1x_2=8-3(3)=-1. The solution is (3,−1)(3,-1), which can be checked in both original equations: 3(3)+(−1)=83(3)+(-1)=8 and 3+2(−1)=13+2(-1)=1.

Question 3

A computed vector has residual r = [0.0001, -0.0002]. What does the residual norm tell you, and what does it not tell you?

Show the step-by-step solution

The residual norm is

∥r∥2=0.00012+(−0.0002)2≈0.0002236.\lVert r\rVert_2 =\sqrt{0.0001^2+(-0.0002)^2} \approx 0.0002236.

It tells us how closely the candidate satisfies the supplied equations. It does not tell us whether the true parameters are close, whether the matrix is well conditioned, or whether the measurements are accurate. Those questions need conditioning, data-scale, and modeling checks.

Question 4

Why can solve(A, b) and inv(A) @ b agree mathematically while solve(A, b) remains the preferred code path?

Show the step-by-step solution

If AA is invertible, the identity x=A−1bx=A^{-1}b proves that both routes target the same mathematical vector. The implementation paths differ: solve factors AA and applies the factors to the requested right-hand side, while inv first constructs every column of the inverse and then multiplies. The inverse route therefore performs extra work and creates an intermediate matrix that the single solve does not need.

Question 5

A system has condition number about 40,000. Should a zero residual make you comfortable reporting many digits of x?

Show the step-by-step solution

No. A zero residual only says that the reported xx fits the supplied bb in the current arithmetic. A condition number near 4×1044\times10^4 says that small relative changes in AA or bb may be amplified by roughly that scale in a worst-case direction. Report precision consistent with measurement uncertainty, inspect singular values or conditioning, and test perturbations before treating many digits as meaningful.

Question 6

What is the difference between a singular system with infinitely many solutions and one with no solutions?

Show the step-by-step solution

Both have dependent rows, so the coefficient matrix cannot determine a unique vector. If the right-hand side has the same dependency, such as b=[3,6]Tb=[3,6]^\mathsf{T} for C=[[1,2],[2,4]]C=[[1,2],[2,4]], the equations are consistent and there are infinitely many solutions. If the right-hand side breaks the dependency, such as b=[3,7]Tb=[3,7]^\mathsf{T}, row reduction produces a contradiction like 0=10=1, so there are no solutions. Neither case is a valid input to a unique solve result.

Question 7

In an AI calibration head, what evidence would you record after calling np.linalg.solve(A, b)?

Show the step-by-step solution

Record the matrix and vector shapes, the returned parameter vector, the residual norm ∥Ax^−b∥\lVert A\hat{x}-b\rVert, and a conditioning diagnostic such as np.linalg.cond(A). Also record the units, scaling, or uncertainty of the measurements if those details affect interpretation. The residual supports a claim about fitting the supplied equations; conditioning and perturbation checks support a separate claim about parameter sensitivity.

Question 8

A code review proposes np.linalg.inv(A) @ b for every request, including matrices that may be rank-deficient. What should you change?

Show the step-by-step solution

Replace the default path with np.linalg.solve(A, b) after checking that the problem is intended to be square and full rank. Catch or surface LinAlgError rather than hiding it, and compute a residual after solving. Add np.linalg.cond(A) or an equivalent conditioning check when input sensitivity matters. If the data are rectangular or rank-deficient, choose and document a least-squares or regularized formulation instead of pretending an inverse exists.

Sources

  1. Mathematics for Machine Learning companion website
  2. Mathematics for Machine Learning book PDF
  3. NumPy documentation: numpy.linalg.solve
  4. NumPy documentation: numpy.linalg.inv
  5. NumPy documentation: numpy.linalg.cond
  6. NumPy documentation: numpy.linalg.norm