import numpy as np
import seaborn as sns
import pandas as pd
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
from sympy import Matrix, MatrixSymbol, Eq, MatMul
sns.reset_defaults()="talk", font_scale=0.75)
sns.set_context(context%matplotlib inline
%config InlineBackend.figure_format='retina'
Goals
G1: To understand matrix vector multiplication as transformation of the vector
Multiplying a matrix A with a vector x transforms x
G2: Understanding low rank matrices as applying transformation on a vector resulting in a subspace of the original vector space
Transforming a vector via a low rank matrix in the shown examples leads to a line
We first study Goal 1. The interpretation of matrix vector product is borrowed from the excellent videos from the 3Blue1Brown channel. I’ll first set up the environment by importing a few relevant libraries.
Basic imports
= MatrixSymbol("A", 2, 2)
sympy_A = MatrixSymbol("x", 2, 1)
sympy_x = MatrixSymbol("y", 2, 1)
y
*sympy_x, evaluate=False) Eq(y, sympy_A
\(\displaystyle y = A x\)
Given a matrix A
and a vector x
, we are trying to get y=Ax
. Let us first see the values for a specific instance in the 2d space.
= np.array([[2, 1], [1, 4]])
A
= np.array([1, 1])
x = A @ x
Ax
=False) Eq(Matrix(Ax), MatMul(Matrix(A), Matrix(x)),evaluate
\(\displaystyle \left[\begin{matrix}3\\5\end{matrix}\right] = \left[\begin{matrix}2 & 1\\1 & 4\end{matrix}\right] \left[\begin{matrix}1\\1\end{matrix}\right]\)
Here, we have A = \(\left[\begin{matrix}2 & 1\\1 & 4\end{matrix}\right]\) and x = \({\text{[1 1]}}\)
Now some code to create arrows to represent arrows.
def plot_arrow(ax, x, color, label):
= x[0], x[1]
x_head, y_head = 0.0
x_tail = 0.0
y_tail = x_head - x_tail
dx = y_head - y_tail
dy
= mpatches.FancyArrowPatch(
arrow =10, color=color, label=label
(x_tail, y_tail), (x_head, y_head), mutation_scale
)
ax.add_patch(arrow)=(1.6, 1), borderaxespad=0) ax.legend(bbox_to_anchor
Now some code to plot the vector corresponding to Ax
def plot_transform(A, x):
= A @ x
Ax = plt.subplots()
fig, ax "k", f"Original (x) {x}")
plot_arrow(ax, x, "g", f"Transformed (Ax) {Ax}")
plot_arrow(ax, Ax, -5, 5))
plt.xlim((-5, 5))
plt.ylim((=0.1)
plt.grid(alpha"equal")
ax.set_aspect(f"A = {A}")
plt.title(=True, bottom=True)
sns.despine(left plt.tight_layout()
1.0, 1.0], [1.0, -1.0]]), [1.0, 2.0])
plot_transform(np.array([["Ax1.png", dpi=100) plt.savefig(
In the plot above, we can see that the vector [1, 2] is transformed to [3, -1] via the matrix A.
Let us now write some code to create the rotation matrix and apply it on our input x
def rot(angle):
= np.radians(angle)
theta = np.cos(theta), np.sin(theta)
c, s = np.array(((c, -s), (s, c)))
R return np.round(R, 2)
= np.array([1.0, 2.0])
x 90), x)
plot_transform(rot("Ax2", dpi=100) plt.savefig(
As we can see above, creating the 90 degree rotation matrix indeed transforms our vector anticlockwise 90 degrees.
Now let us talk about matrices A that are low rank. I am creating a simple low rank matrix where the second row is some constant times the first row.
def plot_lr(x, slope):
= np.array([1.0, 2.0])
low_rank = np.vstack((low_rank, slope * low_rank))
low_rank
plot_transform(low_rank, x)= np.linspace(-5, 5, 100)
x_lin = x_lin * slope
y =0.4, lw=5, label=f"y = {slope}x")
plt.plot(x_lin, y, alpha=(1.2, 1), borderaxespad=0) plt.legend(bbox_to_anchor
1.01)
plot_lr(x,
plt.tight_layout()"lr-1.png", bbox_inches="tight", dpi=100) plt.savefig(
1.0, -1.0], 1.01)
plot_lr([
plt.tight_layout()"lr-2.png", bbox_inches="tight", dpi=100) plt.savefig(
0.5, -0.7], 1.01)
plot_lr([
plt.tight_layout()"lr-3.png", bbox_inches="tight", dpi=100) plt.savefig(
-1.0, 0.0], 1.01)
plot_lr([
plt.tight_layout()"lr-4.png", bbox_inches="tight", dpi=100) plt.savefig(
To summarize
In the above plots we can see that changing our x to any vector in the 2d space leads to us to transformed vector not covering the whole 2d space, but on line in the 2d space. One can easily take this learning to higher dimensional matrices A.