A matrix-vector product is a weighted sum of the columns of , and at the same time, is a function that maps the point to the point . In the last chapter we used the inverse , the function that undoes , to check whether a point lies inside a triangle.
In this chapter we multiply a matrix with another matrix, and meet the powerful idea of composing and decomposing transformations.
We introduced matrix-vector multiplication by taking a weighted sum of vectors. Then we changed our perspective to view a matrix as a function that eats a vector and outputs another vector. Viewing through this lens allowed us to say things like "a matrix maps point A to point B" or "a matrix transforms a circle into an ellipse".
We will follow a similar approach here - we will look at a way to multiply two matrices. Then we will look at matrix-matrix multiplication through an entirely different lens which will enable us to see some insightful (and pretty!) visuals.
We will use an upper case letter like to denote a matrix, and a lower case letter like to denote a vector. If we are dealing with more than one matrices or vectors, we will either use different names like or use subscripts and
The matrix-vector product of and will be written as .
Remember that is a vector, not a matrix. Similarly is also a vector - it is the product of the matrix and the vector .
It follows the two rules of linearity:
Let's write down some functions to perform matrix-vector multiplication:
Vector = [float]
Matrix = [[float]]
def scale_vector(scale:float, vector:Vector) -> Vector:
return [i*scale for i in vector]
def add_vectors(array_of_vectors:Matrix) -> Vector:
dimensionality = len(array_of_vectors[0])
output = [0]*dimensionality
for vector in array_of_vectors:
output = [i+j for (i,j) in zip(output, vector)]
return output
def get_matrix_vector_product(matrix:Matrix, vector:Vector) -> Vector:
scaled_vectors = [scale_vector(scale, vector)
for (scale, vector) in zip(vector, matrix)]
return add_vectors(scaled_vectors)
Let be a matrix and be two vectors. Note that the matrix-vector products and are also vectors. Let us invent a new format to show these two matrix-vector multiplications in a single expression:
To make it clearer that are column vectors, we can modify the expression as shown below (the vertical lines denote that each entry is a column):
But a matrix is just an array or an ordered collection of vectors. This means that both and are matrices. Let's name them X and Y. This gives us: . There we have it, a matrix-matrix multiplication!
Once you have the function to calculate a matrix-vector product, get_matrix_vector_product, it is really easy to implement a matrix-matrix product using it:
def get_matrix_matrix_product(A:Matrix, B:Matrix) -> Matrix:
return [get_matrix_vector_product(A,v) for v in B]
Note that this function is meant to illustrate what matrix-matrix multiplication is - it is not an efficient implementation. You should use the native functions to multiply two matrices (especially if they are large matrices).
We can use the same format to show matrix-vector multiplications in a single expression:
Thus a matrix-matrix multiplication is just a collection of many matrix-vector multiplications, and the matrix-matrix product is just a collection of many matrix-vector products. Now some observations which should be obvious by now:
M.n in the example above).That is, . Note that are matrices whereas are vectors.
Let and let , with each colored to match the column of it scales. Then we have
This can easily be seen from the fact that a matrix-matrix product is a "collection" of multiple matrix-vector products. And we have seen earlier that matrix-vector multiplication is linear. You can try to find a rigorous (and proper) proof yourself.
Just like scaling a vector, scaling a matrix means multiplying each entry inside the matrix by the scale.
You can check this by looking at the output of the matrix function AB and the matrix function BA for the same input x. As an example, we will use 2-by-2 matrices:
We can see that
They are not equal. In the sections below we will visualize this difference in greater detail.
We have already seen some visual examples of how 2-by-2 matrices transform the input space (points, lines, circles etc). Note that if A and B are 2-by-2 matrices, their products AB and BA are also 2-by-2 matrices. Thus we can visualize how these matrices AB and BA transform the input space.
Since AB(x) = A(Bx), we can see how an input vector x is mapped to Bx first and then how the vector Bx is mapped to ABx. This allows us to decompose the transformation by the matrix AB into two transformations: B then A.
In the product , the matrix acts on the input first, and then acts on the result - the product is read right-to-left.
For the visualizations we use
This gives us
The canvas shows , which starts as the identity matrix (so the grid is undistorted). is the first column of the current matrix - the image of the vector [1,0] - and is the second column, the image of [0,1]. The current matrix itself is shown in the bottom-left corner.
Let's decompose the transformation by AB into two steps:
Now let's apply the two matrices in the opposite order:
The end result is different from AB - a visual confirmation that . You can flip between and to compare them directly.
In many cases, decomposing the transformation of a matrix M into two or more transformations A, B, C, ... makes it easier to understand how M transforms the input space. This is especially the case if the matrix transformations A, B, C, ... are simpler to understand. We can simply compose multiple simple transformations to get a complicated transformation.
In other words, for a given matrix M we want to find matrices A, B, C, ... such that M = ABC.... Let's look at some decompositions of the matrix
This is the same matrix as BA from the previous section, but let's call it M for the sake of simplicity.
Note that
Let and . In other words, .
Also note that itself can be factorized:
Let and . In other words, .
Each of these factors is a very simple transformation: stretches the plane vertically by 2, flips it horizontally, and is a vertical shear (it slides each point vertically in proportion to its x-coordinate). Let's compose them on the canvas and watch the complicated transformation M emerge from simple steps:
And with the finer decomposition , applied right-to-left in three steps:
2.A key takeaway from this section is that decomposition of a matrix into other matrices is a useful tool. We will explore this tool in greater detail later when we cover a particular decomposition: Singular Value Decomposition.
is equal to a square matrix whose diagonal elements are 1 and non-diagonal elements are 0. This is also called the Identity Matrix. If A is a 2-by-2 matrix then
This can be understood by noticing that just does the opposite of . So if maps a vector a to b, will map b to a. Thus the net effect of is that the output vector is the same as the input vector. The same logic can be applied to .
If two matrices X and C are equal, we can multiply both sides by another matrix:
Now let's say and . This gives us:
Thus, if , we have . Using a similar argument, we have .
We will use these equations later on.
In the next chapter, we will take a closer look at what makes a transformation linear.
โ 5. Barycentric coordinates ยท 7. Linear transformations โ