Skip to main content

Explain Matrix Inversion with Implementation Python (NumPy, PyTorch, TensorFlow)

Matrix inversion is a fundamental operation in linear algebra and shows up in many areas: solving linear systems, change of coordinates, control systems, optimization, computer graphics, and more. If you’ve ever solved a linear system 

Ax=b and written the formal solution x=A1b, you used the idea of an inverse matrix — the “undo” for a linear transformation.

For engineers, data scientists, and ML practitioners, knowing how to compute a matrix inverse in code is essential. But it’s also important to understand the math behind it and the numerical caveats (e.g., when the matrix is nearly singular). This tutorial teaches the math short-hand, gives practical implementations for a 3×3 example, and explains every line of the code so you can copy, run, and adapt it safely.

Table of Contents

  1. Introduction — Why matrix inversion matters (SEO-friendly)

  2. Quick math recap: what is matrix inversion?

  3. When a matrix is invertible — determinant, rank, and condition number

  4. Why use libraries (NumPy / PyTorch / TensorFlow)?

  5. Implementation 1 — NumPy: code + line-by-line explanation

  6. Implementation 2 — PyTorch: code + line-by-line explanation

  7. Implementation 3 — TensorFlow: code + line-by-line explanation

  8. Verification and practical tip: prefer solve over explicit inverse for linear systems

  9. Numerical stability, condition number, and common errors

  10. Example outputs (3×3 example) and interpretation

  11. Conclusion & next steps

  12. FAQ (short)

2. Quick math recap: what is matrix inversion?

For a square matrix ARn×nA \in \mathbb{R}^{n\times n}, its inverse A1A^{-1} (if it exists) is the matrix such that

AA1=A1A=In,A A^{-1} = A^{-1} A = I_n,

where InI_n is the n×nn\times n identity matrix. Only square matrices can have two-sided inverses. A matrix is invertible (or nonsingular) iff det(A)0\det(A) \ne 0. When det(A)=0\det(A) = 0, the matrix is singular and no inverse exists.

A small, concrete reminder for 2×2:

A=(abcd)A1=1adbc(dbca)A=\begin{pmatrix}a & b\\ c & d\end{pmatrix}\quad\Rightarrow\quad A^{-1}=\frac{1}{ad-bc}\begin{pmatrix}d & -b\\ -c & a\end{pmatrix}

if adbc0ad-bc \ne 0. For higher dimensions we rely on Gaussian elimination, LU/QR factorizations, or library routines.

3. When a matrix is invertible — determinant, rank, and condition number

  • Determinant: det(A)0\det(A) \ne 0 → invertible. If det(A)=0\det(A) = 0 → singular.

  • Rank: rank(A)=n\operatorname{rank}(A)=n for invertibility.

  • Condition number κ(A)=AA1\kappa(A)=\|A\|\|A^{-1}\|: measures sensitivity. Large κ(A)\kappa(A) (>>1) → small perturbations in AA or bb may produce large changes in solutions. Even if a matrix is invertible, it may be ill-conditioned.

Numerically, even when det(A)\det(A) is nonzero theoretically, floating point errors and a large condition number can make matrix inversion unstable.

4. Why use libraries (NumPy / PyTorch / TensorFlow)?

  • NumPy: the go-to for numerical linear algebra in Python. Efficient, simple API for small-to-medium matrices.

  • PyTorch: primary for ML researchers and GPU acceleration. Use it when your workflow involves tensors, GPUs, or autograd.

  • TensorFlow: similar story to PyTorch but with a different ecosystem; use it for TensorFlow-centric pipelines or when you want integration with TF graphs.

All three provide linalg.inv (or equivalent) for direct inversion of square matrices. However, in practice for solving systems you should prefer specialized solvers (see section 8).

5. Implementation 1 — NumPy (3×3) — code + detailed explanation

Code (NumPy)

import numpy as np

# Define a 3x3 matrix
A = np.array([[4, 7, 2],
              [3, 6, 1],
              [2, 5, 3]], dtype=float)

# Compute inverse
A_inv = np.linalg.inv(A)

print("Original Matrix (A):\n", A)
print("\nInverse of A:\n", A_inv)

# Verify A * A_inv = Identity
identity_check = np.dot(A, A_inv)
print("\nVerification (A * A_inv):\n", identity_check)

Line-by-line explanation

  1. import numpy as np

    • Imports NumPy and gives it the conventional alias np. NumPy provides arrays and linear algebra utilities.

  2. A = np.array([[4, 7, 2], [3, 6, 1], [2, 5, 3]], dtype=float)

    • Creates a 3×3 NumPy array for matrix AA. We explicitly set dtype=float so arithmetic uses floating point (important for inversion). The shape is (3, 3).

  3. A_inv = np.linalg.inv(A)

    • Calls NumPy’s linear algebra inverse function. Internally, NumPy uses LAPACK routines (e.g., LU factorization) for efficiency. If A is singular, NumPy raises np.linalg.LinAlgError.

  4. print("Original Matrix (A):\n", A)

    • Prints the original matrix. Useful to confirm the input.

  5. print("\nInverse of A:\n", A_inv)

    • Prints the computed inverse matrix.

  6. identity_check = np.dot(A, A_inv)

    • Multiplies A and its purported inverse; result should be the identity matrix (within floating point tolerances).

  7. print("\nVerification (A * A_inv):\n", identity_check)

    • Prints the product to show verification.

Notes & best practices:

  • Use dtype=float or float64 to avoid integer division or unexpected rounding.

  • Wrap np.linalg.inv in a try/except to handle singular matrices gracefully:

    try:
        A_inv = np.linalg.inv(A)
    except np.linalg.LinAlgError:
        print("Matrix is singular and cannot be inverted.")
    

6. Implementation 2 — PyTorch (3×3) — code + detailed explanation

Code (PyTorch)

import torch

# Define a 3x3 tensor (matrix)
A = torch.tensor([[4., 7., 2.],
                  [3., 6., 1.],
                  [2., 5., 3.]])

# Compute inverse
A_inv = torch.linalg.inv(A)

print("Original Matrix (A):\n", A)
print("\nInverse of A:\n", A_inv)

# Verify A * A_inv = Identity
identity_check = torch.matmul(A, A_inv)
print("\nVerification (A * A_inv):\n", identity_check)

Line-by-line explanation

  1. import torch

    • Imports PyTorch. PyTorch exposes tensor operations and linear algebra functions; many operations can run on GPU if tensors are moved to a CUDA device.

  2. A = torch.tensor([[4., 7., 2.], [3., 6., 1.], [2., 5., 3.]])

    • Creates a 3×3 torch.Tensor. Notice we put decimals like 4. to make PyTorch create floating-point tensor (dtype=torch.float32 by default). Using integers would produce an integer tensor and torch.linalg.inv requires floats.

  3. A_inv = torch.linalg.inv(A)

    • Computes matrix inverse using PyTorch’s linear algebra (relies on underlying libraries). If A is singular, a runtime error is raised.

  4. print("Original Matrix (A):\n", A)

    • Prints the original tensor.

  5. print("\nInverse of A:\n", A_inv)

    • Prints the inverse tensor.

  6. identity_check = torch.matmul(A, A_inv)

    • Matrix multiply (works for 2-D tensors). torch.matmul is the correct multiplication primitive in PyTorch.

  7. print("\nVerification (A * A_inv):\n", identity_check)

    • Prints the product; close to identity within numerical tolerance.

PyTorch-specific notes:

  • To use double precision, create the tensor with dtype=torch.float64 or call .double():

    A = torch.tensor(..., dtype=torch.float64)
    
  • To run on GPU:

    device = torch.device('cuda')
    A = A.to(device)
    A_inv = torch.linalg.inv(A)
    
  • If you need gradients through inverse, ensure A is a torch.Tensor with requires_grad=True (but be careful: inverse has nontrivial gradients).

7. Implementation 3 — TensorFlow (3×3) — code + detailed explanation

Code (TensorFlow)

import tensorflow as tf

# Define a 3x3 constant tensor
A = tf.constant([[4., 7., 2.],
                 [3., 6., 1.],
                 [2., 5., 3.]])

# Compute inverse
A_inv = tf.linalg.inv(A)

print("Original Matrix (A):\n", A.numpy())
print("\nInverse of A:\n", A_inv.numpy())

# Verify A * A_inv = Identity
identity_check = tf.matmul(A, A_inv)
print("\nVerification (A * A_inv):\n", identity_check.numpy())

Line-by-line explanation

  1. import tensorflow as tf

    • Imports TensorFlow. If you use TF 2.x, eager execution is on by default and tensors behave similarly to NumPy arrays.

  2. A = tf.constant([[4., 7., 2.], [3., 6., 1.], [2., 5., 3.]])

    • Creates a constant 3×3 tensor. Dtype defaults to tf.float32 when floats are provided.

  3. A_inv = tf.linalg.inv(A)

    • Computes inverse using TensorFlow’s linear algebra routines. If A is singular, TensorFlow raises an error.

  4. print("Original Matrix (A):\n", A.numpy())

    • A.numpy() converts the tensor to a NumPy array for readable printing in eager mode.

  5. print("\nInverse of A:\n", A_inv.numpy())

    • Prints the inverse as a NumPy array.

  6. identity_check = tf.matmul(A, A_inv)

    • Matrix multiplication in TensorFlow. Returns a TF tensor.

  7. print("\nVerification (A * A_inv):\n", identity_check.numpy())

    • Prints the product converted to NumPy.

TensorFlow-specific notes:

  • If you plan to compute gradients with respect to A through A_inv, use tf.Variable and tf.GradientTape. Inverse is differentiable where A is invertible.

  • For graph-mode usage or TF 1.x compatibility, wrap operations accordingly. With TF 2.x eager mode, the code above runs directly.

8. Verification and practical tip: prefer solve over explicit inverse

Although mathematically x=A1bx = A^{-1}b, numerically computing A_inv and then performing matrix multiplication is often less stable and slower than directly solving Ax = b. Both NumPy and other libraries provide solvers:

NumPy prefered pattern:

# Given A and b:
x = np.linalg.solve(A, b)

np.linalg.solve uses factorizations directly and avoids explicitly forming A_inv. For repeated solves with same A but different b, factor once (e.g., LU) and reuse.

Demonstration (for the example matrix)

  • Let b=[1,2,3]Tb = [1, 2, 3]^T.

  • x_via_inv = A_inv.dot(b) (explicit inverse then multiply).

  • x_via_solve = np.linalg.solve(A, b) (recommended).

Both give essentially the same result, but np.linalg.solve is numerically preferred.

9. Numerical stability, condition number, and common errors

Condition number measures how “close to singular” a matrix is. A high condition number (e.g., 10610^6 or higher) indicates potential numerical trouble. You can compute it in NumPy:

np.linalg.cond(A)

Common errors:

  • np.linalg.LinAlgError: Singular matrixA is singular or too ill-conditioned for inversion.

  • In PyTorch, mismatched dtypes (e.g., integer tensor) will cause errors; cast to float.

  • Passing non-square matrices to inv — only square matrices can be inverted.

  • Forgetting floating dtype (integers) leads to integer tensor and errors.

What to do if a matrix is singular or nearly singular:

  • Use the Moore–Penrose pseudoinverse (np.linalg.pinv(A)) for least-squares or pseudo-solutions.

  • Use SVD-based solvers that manage rank deficiency gracefully.

10. Example outputs (3×3 example) and interpretation

Using our example matrix:

A=[472361253]A = \begin{bmatrix} 4 & 7 & 2\\ 3 & 6 & 1\\ 2 & 5 & 3 \end{bmatrix}
  • Determinant (computed numerically): det(A)=9\det(A) = 9
    ⇒ Non-zero, so AA is invertible.

  • Inverse (numerical value computed via libraries):

A1[1.444444441.222222220.555555560.777777780.888888890.222222220.333333330.666666670.33333333]A^{-1} \approx \begin{bmatrix} 1.44444444 & -1.22222222 & -0.55555556\\[4pt] -0.77777778 & 0.88888889 & 0.22222222\\[4pt] 0.33333333 & -0.66666667 & 0.33333333 \end{bmatrix}
  • Verification A @ A_inv yields the identity matrix up to tiny floating point residuals (values like 101610^{-16} appear due to rounding).

  • Condition number computed for this matrix is approximately 29.13 — a modest number, so the problem is not ill-conditioned.

Interpretation: The matrix is well-behaved (determinant 9, moderate condition number), so standard inversion routines return accurate results.

11. Conclusion & next steps

You now have:

  • a clear mathematical refresher on what matrix inversion is,

  • hands-on implementations of matrix inversion for a 3×3 matrix in NumPy, PyTorch, and TensorFlow,

  • line-by-line explanations so you can understand and adapt the code,

  • practical tips (use np.linalg.solve for solving linear systems; check condition number; handle singular matrices).

Next steps you might want to try:

  • Implement Gauss–Jordan elimination by hand for educational purposes.

  • Explore np.linalg.pinv and tf.linalg.pinv to handle singular / rectangular matrices.

  • Try using GPU acceleration in PyTorch to invert large matrices (be careful with memory & performance).

  • Practice computing inverses for matrices that are ill-conditioned and observe the numerical instability.

12. FAQ (short)

Q: Can I invert a non-square matrix?
A: No two-sided inverse exists for non-square matrices. Use the Moore–Penrose pseudoinverse (pinv) for least-squares solutions.

Q: Why is np.linalg.solve preferred over np.linalg.inv?
A: solve uses direct factorization and is faster and more numerically stable than computing the inverse and then multiplying.

Q: What if np.linalg.inv raises an error?
A: The matrix is singular or numerically rank-deficient. Use np.linalg.pinv or diagnose by checking np.linalg.cond and np.linalg.det.

Q: Are there GPU benefits for inversion?
A: Yes for very large matrices, but inversion is O(n^3) and may be memory-bound. Consider whether you need the full inverse or only solve linear systems.

Comments