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.
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
The vector has two coordinates, so each row of a coefficient matrix must contain two coefficients. If two observations produce the right-hand side vector , a parameter-recovery system has the form
For a tiny calibration head, might be a scale applied to one signal and 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 are the observed coefficient patterns; contains the responses those patterns should produce.
If is square and invertible, the algebra from the previous lesson gives
That identity is useful for reasoning. It does not mean that a program should first construct every time it needs .
Worked example: recover two calibration parameters
Consider the deliberately small system
The two equations are
Solve it by substitution before asking NumPy to do the same work. The first equation gives . Substitute that expression into the second:
Then , so
Check both original equations, not only the rearranged one:
Why solve is usually the right intermediate
The direct mathematical expression hides two different computational tasks:
| Route | What it computes | Why that matters for one right-hand side |
|---|---|---|
np.linalg.solve(A, b) | Factorizes and applies the factors to | Computes only the vector requested. |
np.linalg.inv(A) @ b | Builds every column of , then multiplies by | Computes 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 , 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 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:
The floating-point program reports a norm near instead of exactly zero. That is excellent evidence that the returned vector fits this . 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,
where and are the largest and smallest singular values. A condition number near 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
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:
The second row is almost a copy of the first. With the exact displayed , the solve recovers . Change only the second response by , 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 relative, while the parameter vector changed by about 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.
| Case | Condition number | Relative input → solution change | Residual and interpretation |
|---|---|---|---|
| Well-conditioned | 2.62 | 1.2 × 10⁻⁷ → 2.0 × 10⁻⁷ | Residuals around 10⁻¹⁵; both routes agree for this well-conditioned example. |
| Ill-conditioned | 4.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
The second row is twice the first, so has rank , not . With , the equations are consistent but have infinitely many solutions: every satisfying works. With , 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:
- Check the contract. Confirm that the dimensions of , , and the intended parameter vector agree.
- 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. - Measure the residual. Compute
np.linalg.norm(A @ x - b)and compare it with the scale ofb, not with an arbitrary absolute threshold alone. - 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. - Treat failure as information. A singular error, an enormous condition
number, or a residual that is large relative to
bshould 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 with coordinates whose multiplication by the matrix produces the -coordinate vector . The shape check comes first: each row of must have entries so that its dot product with is defined, and there must be one resulting equation for each entry of .
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, . Substitute into the second: , so and . Then . The solution is , which can be checked in both original equations: and .
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
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 is invertible, the identity proves that both routes target
the same mathematical vector. The implementation paths differ: solve factors
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 fits the supplied in the current arithmetic. A condition number near says that small relative changes in or 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
for , the equations are consistent and
there are infinitely many solutions. If the right-hand side breaks the
dependency, such as , row reduction produces a contradiction
like , 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 , 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.