【深度学习基础】简单的特征向量,背后门道如此之多?

人工智能研究多了,难免要回炉重修本科的线代。其中特征值和特征向量总让人头疼:到底在“做什么”?

先给答案:最直观的理解是——矩阵是一种空间动作:拉伸、压缩、旋转、剪切……而特征向量就是在这次动作里方向不变的那些轴;特征值是沿这些轴的伸缩倍数。

一、线性代数角度(代数定义)

设有一个方阵,如果存在一个非零向量和一个标量 ,使得:

则称: 是矩阵 的特征值(eigenvalue) 是对应于的特征向量(eigenvector)。换句话说,矩阵 作用在向量 上时,只改变了其长度(伸缩倍数),而不改变方向。

二、实例矩阵计算(代数定义)

让我们用简单的2维方阵来做例子吧:

以此为例,求特征值与特征向量。 根据定义来进行代数解法,原本有

经过变换:

为使该方程有非零解,需满足行列式为零:

设下未知数计算,运用行列式公式::

我们可以把得到的两个带回到其中去进行计算,把mathbf{v}求出来(一个是3,一个是1)

得到

,

到这里,两个特征值\lambda和两个特征向量,就全部求出来了。

三、深入几何解释

矩阵本质上是一种复杂的线性的变化指令,这里特征值分解后,我们可以得到的信息有:

  1. 沿着方向的向量会被放大 3倍;因为

  2. 同理,沿着方向的向量的向量放大1倍,不变 因为

  3. 进一步地,如果其他任何向量,分解成这两个向量的线性组合后,和矩阵A相乘,是不是达到了同样的效果? 在几何上表述就是:其它方向的向量会被“分解”为这两个特征方向的组合,再做相应程度地拉伸。

四、代码演示-几何概念可视化

让我们用python来实现一下,python中有非常方便的矩阵求特征值函数

# ---------- 1. 定义矩阵 ----------
A = np.array([[2.0, 1.0],
              [1.0, 2.0]])
eigvals, V = np.linalg.eig(A)

eigvals 是长度为 n 的一维数组,存放1-n个特征值,是矩阵,第j列对应第j个特征值的向量。

根据这些写可视化代码,代码中,Λ是特征值构造的对角阵,V是特征向量从前到后构造的矩阵,A就是原始矩阵:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch

# ---------- 1. 定义矩阵 ----------
A = np.array([[2.0, 1.0],
              [1.0, 2.0]])

# ---------- 2. 特征分解 ----------
eigvals, V = np.linalg.eig(A)
V = V / np.linalg.norm(V, axis=0, keepdims=True)  # 列单位化
Λ = np.diag(eigvals)
AV = A @ V
VΛ = V @ Λ

# ---------- 3. 绘制函数 ----------
def draw_axes(ax, lim=6, step=1):
    ax.axhline(0, color='black', lw=1)
    ax.axvline(0, color='black', lw=1)
    for t in np.arange(-lim, lim+1, step):
        ax.plot([t, t], [-lim, lim], color='gray', lw=0.3)
        ax.plot([-lim, lim], [t, t], color='gray', lw=0.3)
    ax.set_aspect('equal')
    ax.set_xlim(-lim, lim)
    ax.set_ylim(-lim, lim)

def arrow(ax, v, color='C0', label=None):
    ax.add_patch(FancyArrowPatch((0, 0), (v[0], v[1]),
                                 arrowstyle='->', mutation_scale=12,
                                 lw=2, color=color))
    if label:
        ax.text(v[0]*1.05, v[1]*1.05, label, fontsize=10)

# ---------- 4. 绘图 ----------
fig, axes = plt.subplots(2, 2, figsize=(10, 10))

# (1) 矩阵 A 的几何含义:列向量
ax = axes[0, 0]
draw_axes(ax, lim=4, step=0.5)
e1, e2 = np.array([1., 0.]), np.array([0., 1.])
Ae1, Ae2 = A @ e1, A @ e2
arrow(ax, e1, color='C1', label='e1')
arrow(ax, e2, color='C2', label='e2')
arrow(ax, Ae1, color='C1', label='A·e1')
arrow(ax, Ae2, color='C2', label='A·e2')
ax.set_title("Matrix A as linear map (columns = images of e1, e2)")

# (2) 特征向量矩阵 V
ax = axes[0, 1]
draw_axes(ax)
for i in range(V.shape[1]):
    arrow(ax, V[:, i], label=f'v{i+1}')
ax.set_title("Eigenvectors V")

# (3) A·V:特征向量作用后的结果
ax = axes[1, 0]
draw_axes(ax)
for i in range(V.shape[1]):
    arrow(ax, AV[:, i], label=f'A·v{i+1}')
ax.set_title("A·V (transformed eigenvectors)")

# (4) V·Λ:按特征值缩放
ax = axes[1, 1]
draw_axes(ax)
for i in range(V.shape[1]):
    arrow(ax, VΛ[:, i], label=f'λ{i+1}·v{i+1}')
ax.set_title("V·Λ (scaled eigenvectors)")

plt.tight_layout()
plt.show()

# ---------- 5. 数值验证 ----------
print("Matrix A:\n", A)
print("\nEigenvalues λ:", eigvals)
print("Eigenvectors (columns of V):\n", V)
print("\nCheck: AV = VΛ ?")
print("Residual (AV - VΛ):\n", AV - VΛ)

得到如下输出,第一张图(左上角)是A矩阵中的两个向量,第二张图(右上角)是分解得到的两个特征向量,三、四图就是用A向量去分别乘以两个特征向量得到的结果,即为一个向量拉长三倍,一个向量拉长一倍

得到如下输出,第一张图(左上角)是A矩阵中的两个向量,第二张图(右上角)是分解得到的两个特征向量,三、四图就是用A向量去分别乘以两个特征向量得到的结果,即为一个向量拉长三倍,一个向量拉长一倍

五、和线性方程组的零空间有什么关系?

当我们计算特征值的时候:

是否会想起求解矩阵线性方程组的零空间?

实际上,这分别是求矩阵的零空间/核空间(Null Space)和特征空间(Eigen Space)的方程。

而他们各自的几何含义,也就是:

  1. A 把哪些方向压缩(映射)为 0

  2. A 把哪些方向仅仅“伸缩”而不改变方向 但是不同的是,零空间只有一种“零方向”;而特征空间可能有多个不同的特征方向,对应不同 λ。就像托尔斯泰说的幸福的人往往相似,不幸的人却各有不同

    所以说当一个矩阵变化被应用之后:

  • 特征分解是“保方向”的变换分析;

  • 零空间是“失方向”的变换分析。

再进一步,我们发现,如果在计算特征值的时候:

有一个为0,那么这里对应的向量计算的方程就坍缩为

也就是说: 零空间是特征空间中特征值为 0 的那一部分。

六、从特征方程到对角化:推导

1. 特征值与特征向量

设 。如果存在标量 与非零向量 ,使得

则称 为 的特征值, 为对应的特征向量。

2. 组装成矩阵形式

把全部特征向量按列拼成矩阵

并令对角矩阵

逐列写出 ,合并为

3. 右乘 (可逆性前提)

可逆(即 线性无关,有 个独立特征向量), 可在上式两边右乘 ,得到

这就是 的特征分解(对角化)形式。

4. 几何直觉

把坐标变到“特征坐标系”, 在该系中仅做逐轴伸缩, 再把结果变回原坐标系,这部分和刚才提到的是一样的。

5. 成立条件与特例
  • 可对角化充要条件: 拥有 个线性无关的特征向量(等价于各特征值的几何重数之和为 )。

  • 实对称矩阵:总能在正交基下对角化,且可写成

  • 不可对角化:当特征向量不足时,一般只能写成 Jordan 形式

为块矩阵

大家同样也可以用python来进行验证:

import numpy as np

A = np.array([[2, 1],
              [1, 2]])

eigvals, V = np.linalg.eig(A)
Λ = np.diag(eigvals)

# 验证 AV = VΛ
print("AV:\n", A @ V)
print("VΛ:\n", V @ Λ)
print("Residual:\n", A@V - V@Λ)

# 验证 A = VΛV^{-1}
A_rebuild = V @ Λ @ np.linalg.inv(V)
print("\n重建后的A:\n", A_rebuild)

可以发现:Residual 接近 0; A_rebuild 几乎等于原矩阵 A。

AV:
 [[ 2.12132034 -0.70710678]
 [ 2.12132034  0.70710678]]
VΛ:
 [[ 2.12132034 -0.70710678]
 [ 2.12132034  0.70710678]]
Residual:
 [[0. 0.]
 [0. 0.]]

重建后的A:
 [[2. 1.]
 [1. 2.]]

七、特征分解的实用体验:PCA

特征分解虽然看起来非常抽象,但是其应用确实多种多样:

应用领域含义
PCA(主成分分析)协方差矩阵的特征向量给出数据最大方差方向
量子力学哈密顿矩阵的特征值代表能量本征态
机械振动特征向量表示振动模式,特征值为固有频率
图论图的拉普拉斯矩阵的特征值描述图结构(例如连通性)
神经网络雅可比矩阵的特征值反映梯度传播稳定性

其中,我们常用的PCA原理就是通过协方差矩阵的特征向量给出数据最大方差方向。下面我们通过一波python代码来了解PCA的运算过程:

代码逻辑
  1. 生成一团相关的 2D 数据点

  2. 计算协方差矩阵与其特征值/特征向量

  3. 绘制散点 + 主成分方向(特征向量)

  4. 绘制投影到第一主成分后的 1D 分布

先来整一片随机数据点:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch

np.random.seed(0)

# --- Cell 2: generate data & raw scatter ---
mean = np.array([0.0, 0.0])
cov  = np.array([[3.0, 2.0],
                 [2.0, 2.0]])  # 非对角 => 变量相关
X = np.random.multivariate_normal(mean, cov, size=400)

plt.figure(figsize=(6, 6))
plt.scatter(X[:, 0], X[:, 1], s=10, alpha=0.5)
plt.gca().set_aspect('equal', adjustable='box')
plt.axhline(0, lw=1); plt.axvline(0, lw=1)
plt.title("Step 1: Raw scatter of correlated 2D data")
plt.show()

协方差与特征分解并绘制主成分箭头走起

# --- Cell 3: covariance, eigendecomposition, plot PCs ---
# 协方差矩阵(注意 rowvar=False 表示按列是变量)
C = np.cov(X, rowvar=False)

# 对称阵用 eigh 更稳;返回的特征值升序
eigvals, eigvecs = np.linalg.eigh(C)

# 按特征值从大到小排序(PC1 在前)
order = np.argsort(eigvals)[::-1]
eigvals = eigvals[order]
eigvecs = eigvecs[:, order]

# 数据均值(箭头从均值处发出更直观)
origin = X.mean(axis=0)

# 打印数值结果
print("Covariance matrix C =\n", C)
print("Eigenvalues (variances):", eigvals)
print("Explained variance ratio:", eigvals / eigvals.sum())
print("Eigenvectors (columns):\n", eigvecs)

# 可视化:散点 + 主成分方向(按标准差长度缩放)
plt.figure(figsize=(6, 6))
plt.scatter(X[:, 0], X[:, 1], s=10, alpha=0.5)
for i in range(2):
    vec = eigvecs[:, i] * np.sqrt(eigvals[i]) * 2.5  # 尺度与方差相关
    plt.gca().add_patch(FancyArrowPatch(origin, origin + vec,
                                        arrowstyle='->', mutation_scale=12, lw=2))
    plt.text(*(origin + vec), f"PC{i+1}", fontsize=10)
plt.gca().set_aspect('equal', adjustable='box')
plt.axhline(0, lw=1); plt.axvline(0, lw=1)
plt.title("Step 2: Data with principal directions (PC1, PC2)")
plt.show()
Intel MKL WARNING: Support of Intel(R) Streaming SIMD Extensions 4.2 (Intel(R) SSE4.2) enabled only processors has been deprecated. Intel oneAPI Math Kernel Library 2025.0 will require Intel(R) Advanced Vector Extensions (Intel(R) AVX) instructions.
Covariance matrix C =
 [[2.94262058 1.9528261 ]
 [1.9528261  1.96885033]]
Eigenvalues (variances): [4.46834245 0.44312847]
Explained variance ratio: [0.90977683 0.09022317]
Eigenvectors (columns):
 [[-0.7880094  0.6156632]
 [-0.6156632 -0.7880094]]

# --- Cell 4: project onto PC1 & histogram ---
pc1 = (X - origin) @ eigvecs[:, 0]

plt.figure(figsize=(6, 4))
plt.hist(pc1, bins=30)
plt.title("Step 3: Histogram of projection onto PC1")
plt.xlabel("PC1 coordinate"); plt.ylabel("Count")
plt.show()

所以PCA是什么? 是如何从协方差的特征分解中找出数据的“主轴”(PCs),并通过把数据投影到 PC1 来实现尽可能保留信息的降维与可视化。*

说到这,本文也该结束了,请大家记住几句尬上天际的话: 线代不是在算矩阵, 是在寻找不变的方向。 找到方向,剩下的只是伸缩。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值