More Matrix Math in Python
Table of Contents
Beginning
This is another lab from Coursera's NLP Specialization. This time it's about using numpy to perform vector operations.
Imports
# python
from argparse import Namespace
from functools import partial
import math
# from pypi
import hvplot.pandas
import numpy
import pandas
# my stuff
from graeae import EmbedHoloviews
Set Up
Plotting
SLUG = "more-matrix-math-in-python"
Embed = partial(EmbedHoloviews, folder_path=f"files/posts/nlp/{SLUG}")
Plot = Namespace(
width=990,
height=780,
fontscale=2,
tan="#ddb377",
blue="#4687b7",
red="#ce7b6d",
)
Middle
Let's start with a simple matrix. We'll call it R
because when we do our machine translation we'll need a rotation matrix which is named R
.
R = numpy.array([[2, 0],
[0, -2]])
Now we'll create another matrix.
x = numpy.array([[1, 1]])
print(x.shape)
(1, 2)
Note the nested square brackets, this makes it a matrix and not a vector.
The Dot Product
y = numpy.dot(x, R)
print(y)
[[ 2 -2]]
The rotation matrix (R
) rotates and scales the matrix x
. To see the effect we can plot the original vector x
and the rotated version y
.
X = pandas.DataFrame(dict(X=[0, x[0][0]], Y=[0, x[0][1]]))
Y = pandas.DataFrame(dict(X=[0, y[0][0]], Y=[0, y[0][1]]))
x_plot = X.hvplot(x="X", y="Y", color=Plot.blue)
y_plot = Y.hvplot(x="X", y="Y", color=Plot.red)
plot = (x_plot * y_plot).opts(
title="Original and Rotated Vectors",
width=Plot.width,
height=Plot.height,
fontscale=Plot.fontscale,
xlim=(-2, 2),
ylim=(-2, 2)
)
outcome = Embed(plot=plot, file_name="original_and_rotate_vectors")()
print(outcome)
The blue segment is the original vector and the red is the rotated and scaled vector.
More Rotations
In the previous section we rotated the vector using integer values, but if we wanted to rotate the vector a specific number of degrees then the way to do that is to use a rotation matrix.
\[ Ro = \begin{bmatrix} cos \theta & -sin \theta \\ sin \theta & cos \theta \end{bmatrix} \]
Let's start with a vector and rotate it \(100^o\).
theta = math.radians(100)
Ro = pandas.DataFrame([[numpy.cos(theta), -numpy.sin(theta)],
[numpy.sin(theta), numpy.cos(theta)]])
x_2 = pandas.Series([2, 2])
y_2 = x_2.dot(Ro)
print("The Rotation Matrix")
print(Ro)
print("\nThe Rotated Vector")
print(y_2)
print(f'\n x2 norm {numpy.linalg.norm(x_2)}')
print(f'\n y2 norm {numpy.linalg.norm(y_2)}')
print(f'\n Rotation matrix norm {numpy.linalg.norm(Ro)}')
print(f" Square Root of 2: {2**0.5}")
The Rotation Matrix 0 1 0 -0.173648 -0.984808 1 0.984808 -0.173648 The Rotated Vector 0 1.622319 1 -2.316912 dtype: float64 x2 norm 2.8284271247461903 y2 norm 2.82842712474619 Rotation matrix norm 1.414213562373095 Square Root of 2: 1.4142135623730951
You can see that in this case our transformed vector (y2
) didn't change in length the way it did in the previous example. Let's plot it and see what it looks like.
origin = pandas.DataFrame([[0, 0]])
X = origin.append(x_2, ignore_index=True)
Y = origin.append(y_2, ignore_index=True)
COLUMNS = "X Y".split()
X.columns = COLUMNS
Y.columns = COLUMNS
x_plot = X.hvplot(x="X", y="Y", color=Plot.blue)
y_plot = Y.hvplot(x="X", y="Y", color=Plot.red)
plot = (x_plot * y_plot).opts(
title="100 Degree rotation",
width=Plot.width,
height=Plot.height,
fontscale=Plot.fontscale,
xlim=(-3, 3),
ylim=(-3, 3)
)
outcome = Embed(plot=plot, file_name="one_hundred_degree_rotation")()
print(outcome)
Rotation matrices rotate anti-clockwise, which makes that look like more than a 100 degree rotation. I'm going to have to figure that out.
The Frobenius Norm
\[ \| \vec a \| = \sqrt {{\vec a} \cdot {\vec a}} \]
For an \(R_2\) matrix, the Frobenius Norm looks like this:
\[ \| \mathrm{A} \|_{F} \equiv \sqrt{\sum_{i=1}^{m} \sum_{j=1}^{n}\left|a_{i j}\right|^{2}} \]
We can translate the second equation directly to numpy.
some_array = numpy.array([[2, 2],
[2, 2]])
frobenius_norm = numpy.sqrt(numpy.sum(numpy.square(some_array)))
print(f"The Frobenius Norm = {frobenius_norm}")
The Frobenius Norm = 4.0
So, you might be thinking, we've been using numpy.linalg.norm
all this time, what's the difference?
old_norm = numpy.linalg.norm(some_array)
print(old_norm)
assert old_norm == frobenius_norm
4.0
It turns out that the default for norm
is the Frobenius Norm so you can calculate it either way.