Visualizing eigen vectors
Helper functions¶
import numpy as np
def random_symmetric_matrix(n):
A = np.random.rand(n, n)
return A + A.T
def random_positive_definite_matrix(n):
A = np.random.rand(n, n)
return A.T @ Aimport matplotlib.pyplot as plt
def plot_contour(ax, func):
x, y = np.mgrid[-2:2:21j,
-2:2:21j]
bfx = np.concatenate([x[..., None], y[..., None]], axis=-1)
f = func(bfx)
ctr = ax.contour(x, y, f, 20, cmap='Blues_r')
plt.clabel(ctr, ctr.levels, inline=True, fontsize=6)
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
return ax
def draw_arrows(ax, xs):
ax.arrow(0, 0, xs[0, 0], xs[0, 1], color='r', head_width=0.05)
ax.arrow(0, 0, xs[1, 0], xs[1, 1], color='y', head_width=0.05)
ax.axis('equal')
return axdef f(x, A=np.array([[2, -1], [-1, 3]])):
# x is m x m x n tensor
x_row_vec = x[..., None, :] # m x m x 1 x n
x_col_vec = x[..., :, None] # m x m x n x 1
res = x_row_vec @ A @ x_col_vec # m x m x 1 x 1
return res[..., 0, 0] # m x m
fig, ax = plt.subplots()
plot_contour(ax, f)
ax<Axes: xlabel='$x$', ylabel='$y$'>
Eigeven value decomposition¶
Recall that eigen values and eigen vectors are the solutions to the equation,
There are such solutions let for be such solutions.
You can arrange these equations in a matrix $$
=
$$
You can take common from the left hand side. On the right hand side, you will have to construct a diagonal matrix of for factorizing eigen vectors and eigen values.
Verify that the above matrix multiplication gives the same results.
is a matrix of eigen vectors arranged as columns. And is a diagonal matrix of eigen values.
If the matrix is invertible, then we can right multiply to both sides,
We have found a way to factorize (or decompose) a square matrix . This particular decomposition is called Eigen value decomposition. When such a decomposition exists, the matrix is called diagonalizable. A generalized form of such decomposition also exists, which is called generalized eigen value decomposition, where the middle matrix is a block diagonal matrix, not diagonal matrix.
Theorem: Symmeteric matrices have orthonormal Eigen vectors¶
When a square matrix is real and symmetric, then its Eigen value decomposition exists, is diagonalizable, and the eigen matrix is an orthogonal matrix.
If the matrix is symmetric,
where .
Proof:¶
Proof will take too much time out of the class. Interested readers are encouraged to read about Gram-Schmidt orthogonalization, Schur’s lemma and Spectral theorem.
Definition (Orthonormal)¶
A matrix is called orthonormal or orthogonal if equivalently, .
Example¶
Consider the quadratic form of a symmetric matrix ,
Because the matrix is symmetric, we can write
for eigen value decomposition of
A = np.array([[2, -1],
[-1, 3]])
Aarray([[ 2, -1],
[-1, 3]])np.linalg.eigh finds Eigen values of a Hermitian matrix.
lambdas, V = np.linalg.eigh(A)Varray([[-0.85065081, -0.52573111],
[-0.52573111, 0.85065081]])Definition (Conjugate transpose)¶
Conjugate transpose of a complex square matrix is defined as
where is the real part of the matrix and is the imaginary part of matrix and .
Conjugate transpose is the generalization of transpose to complex matrices.
Definition (Hermitian matrix)¶
A complex matrix is said to be Hermitian matrix if
Hermitian matrix is a generalization of symmetric matrices to complex matrices.
Theorem: Eigen values of an Hermitian matrices are real¶
For a complex vector , the dot product is real.
where denotes the complex conjugate of . If , then . Note that is real. Because each element is real, hence their sum is real.
Let be Hermitian then, its eigen vectors and eigen values satisify
Multiply both sides on the left by
or
Take complex conjugate on both sides,
We know the denominator is real. Numerator can be computed by taking the Hermitian transpose of the product,
Because is Hermitian, , which implies,
This implies that is real.
When is real
Theorem: Eigen vectors of a real symmetric matrices are real¶
Because real symmetric matrices are also hermitian matrices, their eigen values are real. If their eigen values are real, then eigen vectors that are non-zero solutions to the equation
are also real.
np.linalg.eigh finds Eigen values of a Hermitian matrix.
lambdas, V = np.linalg.eigh(A)lambdasarray([1.38196601, 3.61803399])Varray([[-0.85065081, -0.52573111],
[-0.52573111, 0.85065081]])You can verify that the eigen values and vectors are correct by checking if
V @ np.diag(lambdas) @ V.Tarray([[ 2., -1.],
[-1., 3.]])np.allclose(A, V @ np.diag(lambdas) @ V.T)TrueRecall that we were analyzing the function,
With this we can break down the computation of function in two steps,
followed by scaling,
If all eigen values are positive ( is positive definite), we can break down eigen values into their square roots and then
can be understood as element wise scaling.
Note that is equation of a circle in terms of for a contour where . We can also start from a circle plot for .
and then compute and plot
Note that the principle axes of the circle are scaled by eigen values.
Next we compute and plot from
import matplotlib as mpl
from functools import partial
def f_wrt_y(lambdas, y):
# lambdas is of shape 2
# y is of shape m x m x n
return (y * lambdas * y).sum(axis=-1)
def f_wrt_z(z):
# z is m x m x n
return (z * z).sum(axis=-1)
zs = np.array([
[1, 0],
[0, 1]])fig, axes = plt.subplots(1, 3, figsize=(18, 6))
ax = axes[0]
plot_contour(ax, f_wrt_z)
ax.set_title(r'$\mathbf{z}$')
ax.set_xlabel(r'$z_1$')
ax.set_ylabel(r'$z_2$')
draw_arrows(ax, zs)
ax = axes[1]
plot_contour(ax, partial(f_wrt_y, lambdas))
ax.set_title(r'$\mathbf{y} = \sqrt{\Lambda}^{-1} \mathbf{z}$')
ax.set_xlabel(r'$y_1$')
ax.set_ylabel(r'$y_2$')
ys = zs / np.sqrt(lambdas)
draw_arrows(ax, ys)
ax = axes[2]
plot_contour(ax, partial(f, A=A))
ax.set_title(r'$\mathbf{x} = V \sqrt{\Lambda}^{-1} \mathbf{z}$')
ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
xs = (V @ ys.T).T
draw_arrows(ax, xs)<Axes: title={'center': '$\\mathbf{x} = V \\sqrt{\\Lambda}^{-1} \\mathbf{z}$'}, xlabel='$x_1$', ylabel='$x_2$'>
Note that countours of are exactly alinged with the transformed circle from to
Eigen values of saddle point (Indefinite matrix)¶
A = np.array([[2, -1],
[-1, -3]])
Aarray([[ 2, -1],
[-1, -3]])lambdas, V = np.linalg.eigh(A)
lambdasarray([-3.1925824, 2.1925824])Note that the eigen values are neither all positive nor all negative. We cannot take the square root directly and we have to be respectful of the sign of the lambdas.
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
ax = axes[0]
plot_contour(ax, f_wrt_z)
ax.set_title(r'$\mathbf{z}$')
ax.set_xlabel(r'$z_1$')
ax.set_ylabel(r'$z_2$')
draw_arrows(ax, zs)
ax = axes[1]
plot_contour(ax, partial(f_wrt_y, lambdas))
ax.set_title(r'$\mathbf{y} = \text{sign}(\Lambda)\sqrt{|\Lambda|}^{-1} \mathbf{z}$')
ax.set_xlabel(r'$y_1$')
ax.set_ylabel(r'$y_2$')
ys = zs / np.sign(lambdas) * np.sqrt(np.abs(lambdas))
draw_arrows(ax, ys)
ax = axes[2]
plot_contour(ax, partial(f, A=A))
ax.set_title(r'$\mathbf{x} = V \text{sign}(\Lambda)\sqrt{|\Lambda|}^{-1} \mathbf{z}$')
ax.set_xlabel(r'$x_1$')
ax.set_ylabel(r'$x_2$')
xs = (V @ ys.T).T
draw_arrows(ax, xs)<Axes: title={'center': '$\\mathbf{x} = V \\text{sign}(\\Lambda)\\sqrt{|\\Lambda|}^{-1} \\mathbf{z}$'}, xlabel='$x_1$', ylabel='$x_2$'>