Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

The Moore-Penrose Pseudoinverse

Populating the interactive namespace from numpy and matplotlib

The Moore-Penrose Pseudoinverse

We saw that not all matrices have an inverse. It is unfortunate because the inverse is used to solve system of equations. In some cases, a system of equation has no solution, and thus the inverse doesn’t exist. However it can be useful to find a value that is almost a solution (in term of minimizing the error). We will see for instance how we can find the best-fit line of a set of data points with the pseudoinverse.

The Moore-Penrose Pseudoinverse

The Moore-Penrose pseudoinverse is a direct application of the SVD (see 2.8). But before all, we have to remind that systems of equations can be expressed under the matrix form.

As we have seen in 2.3, the inverse of a matrix A\boldsymbol{A} can be used to solve the equation Ax=b\boldsymbol{Ax}=\boldsymbol{b}:

A−1Ax=A−1b\boldsymbol{A}^{-1}\boldsymbol{Ax}=\boldsymbol{A}^{-1}\boldsymbol{b}
Inx=A−1b\boldsymbol{I}_n\boldsymbol{x}=\boldsymbol{A}^{-1}\boldsymbol{b}
x=A−1b\boldsymbol{x}=\boldsymbol{A}^{-1}\boldsymbol{b}

But in the case where the set of equations have 0 or many solutions the inverse cannot be found and the equation cannot be solved. The pseudoinverse is A+\boldsymbol{A}^+ such as:

AA+≈In\boldsymbol{A}\boldsymbol{A}^+\approx\boldsymbol{I_n}

minimizing

∥AA+−In∥2\left\lVert \boldsymbol{A}\boldsymbol{A}^+-\boldsymbol{I_n} \right\rVert_2

The following formula can be used to find the pseudoinverse:

A+=VD+UT\boldsymbol{A}^+= \boldsymbol{VD}^+\boldsymbol{U}^T

with U\boldsymbol{U}, D\boldsymbol{D} and V\boldsymbol{V} respectively the left singular vectors, the singular values and the right singular vectors of A\boldsymbol{A} (see the SVD in 2.8). A+\boldsymbol{A}^+ is the pseudoinverse of A\boldsymbol{A} and D+\boldsymbol{D}^+ the pseudoinverse of D\boldsymbol{D}. We saw that D\boldsymbol{D} is a diagonal matrix and thus D+\boldsymbol{D}^+ can be calculated by taking the reciprocal of the non zero values of D\boldsymbol{D}.

This is a bit crude but we will see some examples to clarify all of this.

Example 1.

Let’s see how to implement that. We will create a non square matrix A\boldsymbol{A}, calculate its singular value decomposition and its pseudoinverse.

A=[723453]\boldsymbol{A}=\begin{bmatrix} 7 & 2\\\\ 3 & 4\\\\ 5 & 3 \end{bmatrix}
array([[ 0.16666667, -0.10606061, 0.03030303], [-0.16666667, 0.28787879, 0.06060606]])

We can now check with the pinv() function from Numpy that the pseudoinverse is correct:

array([[ 0.16666667, -0.10606061, 0.03030303], [-0.16666667, 0.28787879, 0.06060606]])

It looks good! We can now check that it is really the near inverse of A\boldsymbol{A}. Since we know that

A−1A=In\boldsymbol{A}^{-1}\boldsymbol{A}=\boldsymbol{I_n}

with

I2=[1001]\boldsymbol{I_2}=\begin{bmatrix} 1 & 0 \\\\ 0 & 1 \end{bmatrix}
array([[1.00000000e+00, 2.70616862e-16], [2.28983499e-16, 1.00000000e+00]])

This is not bad! This is almost the identity matrix!

A difference with the real inverse is that A+A≈I\boldsymbol{A}^+\boldsymbol{A}\approx\boldsymbol{I} but AA+≠I\boldsymbol{A}\boldsymbol{A}^+\neq\boldsymbol{I}.

Another way of computing the pseudoinverse is to use this formula:

(ATA)−1AT(\boldsymbol{A}^T\boldsymbol{A})^{-1}\boldsymbol{A}^T

The result is less acurate than the SVD method and Numpy pinv() uses the SVD (cf Numpy doc). Here is an example from the same matrix A\boldsymbol{A}:

array([[ 0.16666667, -0.10606061, 0.03030303], [-0.16666667, 0.28787879, 0.06060606]])

In this case the result is the same as with the SVD way.

Using the pseudoinverse to solve a overdetermined system of linear equations

In general there is no solution to overdetermined systems (see 2.4 ; Overdetermined systems). In the following picture, there is no point at the intersection of the three lines corresponding to three equations:

Example of three linear equations in 2 dimensions: this is an overdetermined system There is more equations (3) than unknowns (2) so this is an overdetermined system of equations

The pseudoinverse solve the system in the least square error perspective: it finds the solution that minimize the error. We will see this more explicitly with an example.

The pseudoinverse solve the system in the least square error perspective

Example 2.

For this example we will consider this set of three equations with two unknowns:

{−2x1+2=x24x1+8=x2−1x1+2=x2⇔{−2x1−x2=−24x1−x2=−8−1x1−x2=−2\begin{cases} -2x_1 + 2 = x_2 \\\\ 4x_1 + 8 = x_2 \\\\ -1x_1 + 2 = x_2 \end{cases} \Leftrightarrow \begin{cases} -2x_1 - x_2 = -2 \\\\ 4x_1 - x_2 = -8 \\\\ -1x_1 - x_2 = -2 \end{cases}

Let’s see their graphical representation:

<Figure size 288x288 with 1 Axes>

We actually see that there is no solution.

Putting this into the matrix form we have:

A=[−2−14−1−1−1]\boldsymbol{A}= \begin{bmatrix} -2 & -1 \\\\ 4 & -1 \\\\ -1 & -1 \end{bmatrix}
x=[x1x2]\boldsymbol{x}= \begin{bmatrix} x_1 \\\\ x_2 \end{bmatrix}

and

b=[−2−8−2]\boldsymbol{b}= \begin{bmatrix} -2 \\\\ -8 \\\\ -2 \end{bmatrix}

So we have:

Ax=b⇔[−2−14−1−1−1][x1x2]=[−2−8−2]\boldsymbol{Ax} = \boldsymbol{b} \Leftrightarrow \begin{bmatrix} -2 & -1 \\\\ 4 & -1 \\\\ -1 & -1 \end{bmatrix} \begin{bmatrix} x_1 \\\\ x_2 \end{bmatrix} = \begin{bmatrix} -2 \\\\ -8 \\\\ -2 \end{bmatrix}

We will now calculate the pseudoinverse of A\boldsymbol{A}:

array([[-0.11290323, 0.17741935, -0.06451613], [-0.37096774, -0.27419355, -0.35483871]])

Now that we have calculated the pseudoinverse of A\boldsymbol{A}:

A+=[−0.112903230.17741935−0.06451613−0.37096774−0.27419355−0.35483871]\boldsymbol{A}^+= \begin{bmatrix} -0.11290323 & 0.17741935 & -0.06451613 \\\\ -0.37096774 & -0.27419355 & -0.35483871 \end{bmatrix}

we can use it to find x\boldsymbol{x} knowing that:

x=A+b\boldsymbol{x}=\boldsymbol{A}^+\boldsymbol{b}

with:

x=[x1x2]\boldsymbol{x} = \begin{bmatrix} x1 \\\\ x2 \end{bmatrix}
array([[-1.06451613], [ 3.64516129]])

So we have

A+b=[−0.112903230.17741935−0.06451613−0.37096774−0.27419355−0.35483871][−2−8−2]=[−1.064516133.64516129]\begin{align*} \boldsymbol{A}^+\boldsymbol{b}&= \begin{bmatrix} -0.11290323 & 0.17741935 & -0.06451613 \\\\ -0.37096774 & -0.27419355 & -0.35483871 \end{bmatrix} \begin{bmatrix} -2 \\\\ -8 \\\\ -2 \end{bmatrix}\\\\ &= \begin{bmatrix} -1.06451613 \\\\ 3.64516129 \end{bmatrix} \end{align*}

In our two dimensions, the coordinates of x\boldsymbol{x} are

[−1.064516133.64516129]\begin{bmatrix} -1.06451613 \\\\ 3.64516129 \end{bmatrix}

Let’s plot this point along with the equations lines:

<Figure size 288x288 with 1 Axes>

Maybe you would have expected the point being at the barycenter of the triangle (cf. Least square solution in the triangle center). This is not the case becase the equations are not scaled the same way. Actually the point is at the intersection of the three symmedians of the triangle.

Example 3.

This method can also be used to fit a line to a set of points. Let’s take the following data points:

Representation of a set of data points We want to fit a line to this set of data points

We have this set of x\boldsymbol{x} and y\boldsymbol{y} and we are looking for the line y=mx+by=mx+b that minimizes the error. The error can be evaluated as the sum of the differences between the fit and the actual data points. We can represent the data points with a matrix equations:

Ax=b⇔[011121313141][mb]=[240253]\boldsymbol{Ax} = \boldsymbol{b} \Leftrightarrow \begin{bmatrix} 0 & 1 \\\\ 1 & 1 \\\\ 2 & 1 \\\\ 3 & 1 \\\\ 3 & 1 \\\\ 4 & 1 \end{bmatrix} \begin{bmatrix} m \\\\ b \end{bmatrix} = \begin{bmatrix} 2 \\\\ 4 \\\\ 0 \\\\ 2 \\\\ 5 \\\\ 3 \end{bmatrix}

Note that here the matrix A\boldsymbol{A} represents the values of the coefficients. The column of 1 correspond to the intercepts (without it the fit would have the constraint to cross the origin). It gives the following set of equations:

{0m+1b=21m+1b=42m+1b=03m+1b=23m+1b=54m+1b=3\begin{cases} 0m + 1b = 2 \\\\ 1m + 1b = 4 \\\\ 2m + 1b = 0 \\\\ 3m + 1b = 2 \\\\ 3m + 1b = 5 \\\\ 4m + 1b = 3 \end{cases}

We have the set of equations mx+b=ymx+b=y. The ones are used to give back the intercept parameter. For instance, in the first equation corresponding to the first point we have well x=0x=0 and y=2y=2. This can be confusing because here the vector x\boldsymbol{x} corresponds to the coefficients. This is because the problem is different from the other examples: we are looking for the coefficients of a line and not for xx and yy unknowns. We kept this notation to indicate the similarity with the last examples.

So we will construct these matrices and try to use the pseudoinverse to find the equation of the line minimizing the error (difference between the line and the actual data points).

Let’s start with the creation of the matrix of A\boldsymbol{A} and b\boldsymbol{b}:

array([[0, 1], [1, 1], [2, 1], [3, 1], [3, 1], [4, 1]])
array([[2], [4], [0], [2], [5], [3]])

We can now calculate the pseudoinverse of A\boldsymbol{A}:

array([[-2.00000000e-01, -1.07692308e-01, -1.53846154e-02, 7.69230769e-02, 7.69230769e-02, 1.69230769e-01], [ 6.00000000e-01, 4.00000000e-01, 2.00000000e-01, 4.00160154e-17, 4.00160154e-17, -2.00000000e-01]])

and apply it to the result to find the coefficients with the formula:

x=A+b\boldsymbol{x}=\boldsymbol{A}^+\boldsymbol{b}
array([[0.21538462], [2.2 ]])

These are the parameters of the fit. The slope is m=0.21538462m=0.21538462 and the intercept is b=2.2b=2.2. We will plot the data points and the regression line:

<Figure size 288x288 with 1 Axes>

If you are not sure about the result. Just check it with another method. For instance, I double-checked with R:

a <- data.frame(x=c(0, 1, 2, 3, 3, 4),
                y=c(2, 4, 0, 2, 5, 3))

ggplot(data=a, aes(x=x, y=y)) +
  geom_point() +
  stat_smooth(method = "lm", col = "red") +
  xlim(-1, 5) +
  ylim(-1, 6)

outputs:

Fitting a line with another method (in R) Just checking with another method

You can also do the fit with the Numpy polyfit() to check the parameters:

array([[0.21538462], [2.2 ]])

That’s good! We have seen how to use the pseudoinverse in order to solve a simple regression problem. Let’s see with a more realistic case.

Example 4.

To see the process with more data points we can generate data (see this nice blog post for other methods of fitting).

We will generate a column vector (see reshape() bellow) containing 100 points with random xx values and pseudo-random yy values. The function seed() from the Numpy.random package is used to freeze the randomisation and be able to reproduce the results:

We will create the matrix A\boldsymbol{A} from x\boldsymbol{x} by adding a column of ones exactly like we did in the example 3.

array([[3.48234593, 1. ], [1.43069667, 1. ], [1.13425727, 1. ], [2.75657385, 1. ], [3.59734485, 1. ], [2.1155323 , 1. ], [4.90382099, 1. ], [3.42414869, 1. ], [2.40465951, 1. ], [1.96058759, 1. ]])

We can now find the pseudoinverse of A\boldsymbol{A} and calculate the coefficients of the regression line:

array([[1.9461907 ], [1.16994745]])

We can finally draw the point and the regression line:

<Figure size 288x288 with 1 Axes>

Looks good!

Conclusion

You can see that the pseudoinverse can be very useful for this kind of problems! The series is not completely finished since we still have 3 chapters to cover. However, we have done the hardest part! We will now see two very light chapters before going to a nice example using all the linear algebra we have learn: the PCA.