问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

直观解释矩阵的特征值与特征向量

创作时间:
作者:
@小白创作中心

直观解释矩阵的特征值与特征向量

引用
CSDN
1.
https://blog.csdn.net/weipf8/article/details/105892662

矩阵的特征值与特征向量是线性代数中非常重要的概念。为了帮助读者直观理解这一概念,本文将从一维情况开始解释,逐步过渡到二维情况,并通过具体的矩阵示例和Python代码生成的图像来说明特征值和特征向量的几何意义。

一维情况

首先考虑一阶矩阵,即一个标量 (a)。在这种情况下,线性方程为 (y = ax),其含义是将数轴上的数变为原来的 (a) 倍,有一种把数轴往两边拉长的感觉。在这个一维空间中,“矩阵” (a) 的特征值为 (a),数轴上每一个向量都是特征向量,因为数轴上任意一个“向量”经过线性变换后方向都没有改变。

二维情况

接下来考虑二维空间中的矩阵。以矩阵 (A) 为例:

[ A = \begin{pmatrix} 4 & -2 \ 1 & 1 \end{pmatrix} ]

经过计算,矩阵 (A) 的两个特征值分别为 (\lambda_1 = 3) 和 (\lambda_2 = 2),对应的特征向量分别为 (x_1 = (0.89, 0.45)^T) 和 (x_2 = (0.71, 0.71)^T)。

矩阵 (A) 可以看作是一个线性变换的算子,即 (y = Ax),对变量 (x) 施加一个线性变换得到新的变量 (y)。为了直观地理解这个线性变换的效果,我们可以通过可视化的方式观察其作用。

可视化解释

为了直观展示这个线性变换的效果,我们绘制了以下图形:

图中,绿线和蓝线分别表示特征向量 (x_1) 和 (x_2) 所在的方向。暗红色的点是从一个圆上采样的样本点 (x),绿色的点是经过矩阵 (A) 作用后得到的新向量,黄色的是二者的连接线。从图中可以看到,经过矩阵 (A) 作用后,圆上的向量被拉伸变形成了类似椭圆的形状。

为了进一步观察方向变化情况,我们从一个矩形区域内采样:

在这个图中,我们只关注变换后的方向,所有向量的模长都进行了归一化处理。可以观察到,在特征向量方向上的向量没有改变方向,而其他方向的向量都发生了方向的变化。

Python代码实现

下面是生成第一个图的Python代码:

from PIL import Image, ImageDraw
import numpy as np
from numpy.linalg import eig

img = Image.open("1500.png")
draw = ImageDraw.Draw(img) # 建立绘图对象
width, height = img.size    # 获取原始图像大小
A = np.array([[4, -2], 
              [1, 0.9]])
step = 80  # 每个多远采样一个点
p_range = 500  # 采样点范围
margin = width/2 
all_direction_norm = []
max_norm = 4050 # 提前计算好的最大长度

for i in range(-p_range, p_range, step):
    for j in range(-p_range, p_range, step):
        vector = np.array([i, j]) # 生成向量
        # 可选: 把vector变成单位向量, 再将模长放大50倍, 相当于在一个圆上采样
        vector = 50 * vector / (np.linalg.norm(vector) + 1e-3)
        conversion = np.matmul(A, vector) # 变换后的向量
        direction_norm = (np.linalg.norm(conversion - vector) + 1e-3)
        all_direction_norm.append(direction_norm)
        direction = (conversion - vector) / direction_norm # 计算移动方向
        direction *= direction_norm # 保证像素长度大, 能画出图来

        # 画出线, 按照 direction_norm 的大小, 赋予由深到浅的颜色, 需要提前确定好最大的 direction_norm
        draw.line( ((vector[0] + margin, vector[1] + margin),
                    (vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin))),   fill = (200, 200, 50)) #画一条直线,(0, 0)到(width-1, height-1),fill指线的颜色
        # 把线的起点描成红色
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin,    vector[1] + margin)),       fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin +1, vector[1] + margin)),       fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin+1,  vector[1] + margin+1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin,    vector[1] + margin+1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin-1,  vector[1] + margin)),       fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin-1,  vector[1] + margin+1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin-1,  vector[1] + margin-1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin,    vector[1] + margin-1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        draw.line( ((vector[0] + margin,    vector[1] + margin),
                    (vector[0] + margin+1,  vector[1] + margin-1)),     fill = (150, int(direction_norm / max_norm * 255), 0))
        # 把线的终点描成绿色
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin))),      fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin) +1, vector[1] + int(direction[1] + margin))),   fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin)+1, vector[1] + int(direction[1] + margin)+1)),  fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)+1)),    fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin)-1, vector[1] + int(direction[1] + margin))),    fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin)-1, vector[1] + int(direction[1] + margin)+1)),  fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin)-1, vector[1] + int(direction[1] + margin)-1)),  fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)-1)),    fill = (0, 255, 0))
        draw.line( ((vector[0] + int(direction[0] + margin), vector[1] + int(direction[1] + margin)),
                    (vector[0] + int(direction[0] + margin)+1, vector[1] + int(direction[1] + margin)-1)),  fill = (0, 255, 0))

print("最大长度: ", max(all_direction_norm))

# 画特征向量
vals,  vecs = eig(A) # 特征分解,每一列是特征向量
a1 = vecs[0, 0]
a2 = vecs[1, 0]
b1 = vecs[0, 1]
b2 = vecs[1, 1]

# 画出两个特征向量
draw.line( ((margin,            margin), 
            (margin + a1 * 500, margin + a2 * 500)),
           fill = (0, 10, 255)) #画一条直线,(0, 0)到(width-1, height-1),fill指线的颜色
draw.line( ((margin,            margin), 
            (margin + b1 * 500, margin + b2 * 500)),
           fill = (0, 255, 10)) #画一条直线,(0, 0)到(width-1, height-1),fill指线的颜色

# 画出紫色中心点
draw.line(((margin, margin), (margin, margin)), fill = (255, 0, 255))#画一条直线,(0, 0)到(width-1, height-1),fill指线的颜色
img.save("result.png")#保存新图像

print("特征值:", vals)
print("特征向量:", vecs)

通过上述代码,我们可以生成直观展示矩阵变换效果的图像,帮助读者更好地理解特征值和特征向量的概念。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号