程序设计与计算思维¶

Computer Programming and Computational Thinking

第 9 讲:主成分分析¶

2025—2026学年度春季学期

清华大学 韩文弢

问题引入¶

  • 目标:使用统计方法从数据(多个向量)中提取关键信息。
  • 核心问题:
    • 数据中哪些“方向”是最重要的?
    • 能否降低数据维度(减少有用变量的数量)?
  • 意义:发现数据底层结构,是通往机器学习的基础方法之一。

准备工作¶

In [ ]:
import numpy as np
import matplotlib.pyplot as plt
from scipy.linalg import expm
from ipywidgets import interact, FloatSlider

# 中文字体设置
plt.rcParams['font.sans-serif'] = ['Noto Sans CJK SC']


def format_axis(ax, xticks=None, yticks=None, fmt='.1f'):
    """统一的坐标轴格式化函数:隐藏右侧和顶部边框,将坐标轴移到原点"""
    ax.spines['right'].set_visible(False)
    ax.spines['top'].set_visible(False)
    ax.spines['bottom'].set_position(('data', 0))
    ax.spines['left'].set_position(('data', 0))
    ax.tick_params(direction='inout', length=8)
    ax.grid(True, alpha=0.3)
    
    # 设置刻度(隐藏原点标签)
    if xticks is not None:
        ax.set_xticks(xticks)
        ax.set_xticklabels([f'{x:{fmt}}' if x != 0 else '' for x in xticks])
    if yticks is not None:
        ax.set_yticks(yticks)
        ax.set_yticklabels([f'{y:{fmt}}' if y != 0 else '' for y in yticks])

矩阵的秩¶

在线性代数中,矩阵的秩(rank)是非常重要的概念。

外积与乘法表的可视化¶

  • 回顾:乘法表与向量的外积。
In [ ]:
np.outer(np.arange(1, 11), np.arange(1, 13))
  • 特征:每一列互为倍数,每一行也互为倍数。
  • 结构化矩阵:存储所需信息极少。
    • 一般 $(m \times n)$ 矩阵需 $m \times n$ 个存储点。
    • 外积构成的矩阵仅需 $m + n$ 个数字。
  • 现实中的例子:某些国旗的图案就是外积矩阵。
In [ ]:
flag = np.outer(np.array([1, 0.1, 2]), np.ones(6))
print(flag)
In [ ]:
flag2 = np.outer(np.array([1, 0.1, 2]), np.array([1, 1, 1, 3, 3, 3]))
print(flag2)

提示:肉眼未必能立刻看出外积关系,但能敏锐察觉到某种高度的结构化。

In [ ]:
def show_image(M, cmap='rainbow'):
    """显示矩阵为图像"""
    plt.figure(figsize=(6, 4))
    plt.imshow(M, cmap=cmap, aspect='auto')
    plt.axis('off')
    plt.show()

print("flag 的可视化:")
show_image(flag)

print("flag2 的可视化:")
show_image(flag2)

矩阵的秩与外积的关系¶

  • 秩-1矩阵:可表示为单个外积(乘法表)的矩阵。
  • 秩-k矩阵:可表示为 $k$ 个外积之和的矩阵。

演示:生成随机的秩-1矩阵:

In [ ]:
np.random.seed(42)
w = 300

# 创建随机秩-1矩阵
v = np.concatenate([[1, 0.4], np.random.rand(50)])
u = np.random.rand(w)
image = np.outer(v, u)

print("随机秩-1矩阵:")
show_image(image)

视觉特征:典型的棋盘或拼布图案。

演示:生成随机的秩-2矩阵:

In [ ]:
image2 = np.outer(np.concatenate([[1, 0.4], np.random.rand(50)]), np.random.rand(w)) + \
         np.outer(np.random.rand(52), np.random.rand(w))

print("随机秩-2矩阵:")
show_image(image2)

视觉特征:规则性显著降低。

噪声的影响¶

如果向完美的秩-1矩阵中加入随机噪声,会发生什么?

In [ ]:
noisy_image = image + 0.03 * np.random.randn(*image.shape)

print("原始秩-1矩阵:")
show_image(image)

print("带噪声的矩阵:")
show_image(noisy_image)
  • 变化:加噪后,矩阵的秩变得大于1。
  • 挑战:视觉上它仍“接近”秩-1矩阵,但我们如何通过计算和算法来自动发现这种底层简单结构?

视角转换:图像作为数据¶

  • 新视角:图像矩阵 = 数据矩阵。
  • 矩阵的每一列 = 一次数据观测的向量。
  • 可视化提取:取图像前两行,作为 $x$ 和 $y$ 坐标轴上的数据点。
In [ ]:
print("图像的前两行(前20列):")
show_image(image[:2, :20])
print(image[:2, :20])

从图像到数据的散点图¶

绘制 $(x_i, y_i)$ 数据云:

In [ ]:
xx = image[0, :]
yy = image[1, :]
xs = noisy_image[0, :]
ys = noisy_image[1, :]

fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(xs, ys, label='带噪声', alpha=0.3, s=16, marker='.')
ax.scatter(xx, yy, label='秩-1', alpha=0.3, s=9, marker='s', c='red')

ax.set_title('绘制秩-1矩阵会得到一条直线')
ax.set_xlim(-0.1, 1.1)
ax.set_ylim(-0.1, 0.65)
ax.legend(loc='upper right', facecolor='white', edgecolor='black', frameon=True)

format_axis(ax, xticks=[0.25, 0.50, 0.75, 1.00], yticks=[0.2, 0.4, 0.6])
plt.show()

几何意义:

  • 精确的秩-1数据:分布在穿过原点的直线 ($y_2/x_2 = y_1/x_1$) 上。
  • 近似秩-1数据(含噪):紧紧围绕直线分布。

目标:设计自动算法,检测数据是否靠近某条特定的直线。

测量数据云的“大小”¶

直觉:尝试测量这团数据云的整体宽度和高度。

先考虑一个方向:若要测量 $x$ 范围宽度,此时 $y$ 值的分布是无关的。

  • 问题:直接寻找最大/最小值极易受异常值干扰。
  • 解决方案:使用统计方法,反映数据主体的真实情况。

预处理步骤:中心化(去均值化),减去均值,将数据平移至 0 附近:

In [ ]:
xs_centered = xs - np.mean(xs)
ys_centered = ys - np.mean(ys)

fig, ax = plt.subplots(figsize=(6, 4), dpi=100)
ax.scatter(xs_centered, ys_centered, s=25, alpha=0.5)
ax.axis('equal')

format_axis(ax, xticks=[-0.4, -0.2, 0.2, 0.4], yticks=[-0.3, -0.2, -0.1, 0.1, 0.2, 0.3])
plt.show()

测量投影宽度¶

  • 投影:将数据垂直投射到单数轴(如 $x$ 轴)上。
  • 距离度量:直接平均中心化数据结果为0。需要衡量平均偏差的大小:
    • 度量一:平均绝对偏差
In [ ]:
fig, ax = plt.subplots(figsize=(6, 4), dpi=100)

# 绘制投影线
for i in range(len(xs_centered)):
    plt.plot([xs_centered[i], xs_centered[i]], [ys_centered[i], 0], 
             'k--', alpha=0.05)

ax.scatter(xs_centered, ys_centered, s=25, alpha=0.5)
ax.scatter(xs_centered, np.zeros_like(xs_centered), s=15, alpha=0.1, c='brown')
ax.axis('equal')

format_axis(ax, xticks=[-0.4, -0.2, 0.2, 0.4], yticks=[-0.3, -0.2, -0.1, 0.1, 0.2, 0.3])
plt.show()
In [ ]:
print(f"平均绝对偏差: {np.mean(np.abs(xs_centered))}")

虽然平均绝对偏差有效,但数学性质不够好。为什么?

均方根距离:标准差¶

  • 方差 (Variance):对中心化距离求平方后的平均值。
  • 标准差 (Standard Deviation):方差的平方根(将单位还原为真实可测距离)。
In [ ]:
sigma_x = np.sqrt(np.mean(xs_centered**2))
sigma_y = np.sqrt(np.mean(ys_centered**2))

print(f"σ_x = {sigma_x}")
print(f"σ_y = {sigma_y}")

结合标准差,可以画出数据云的边界框:

In [ ]:
fig, ax = plt.subplots(figsize=(6, 4), dpi=100)
ax.scatter(xs_centered, ys_centered, s=25, alpha=0.5)
ax.axis('equal')

# 绘制标准差边界
ax.axvline(x=2*sigma_x, color='green', linestyle='--', linewidth=2)
ax.axvline(x=-2*sigma_x, color='green', linestyle='--', linewidth=2)
ax.axhline(y=2*sigma_y, color='blue', linestyle='--', linewidth=2)
ax.axhline(y=-2*sigma_y, color='blue', linestyle='--', linewidth=2)

# 标注标准差值
ax.text(2*sigma_x - 0.02, 0.02, r'$2\sigma_x$', color='green', fontsize=16, ha='right', va='bottom')
ax.text(-2*sigma_x + 0.02, 0.02, r'$-2\sigma_x$', color='green', fontsize=16, ha='left', va='bottom')
ax.text(0.02, 2*sigma_y + 0.02, r'$2\sigma_y$', color='blue', fontsize=16, ha='left', va='bottom')
ax.text(0.02, -2*sigma_y - 0.02, r'$-2\sigma_y$', color='blue', fontsize=16, ha='left', va='top')

format_axis(ax, xticks=[-0.4, -0.2, 0.2, 0.4], yticks=[-0.3, -0.2, -0.1, 0.1, 0.2, 0.3])
plt.show()

经验法则:在正态分布(normal distribution)假设下,约 95% 的数据位于 $\mu \pm 2\sigma$ 区间内。

变量的相关性 (Correlation)¶

  • 局限性:正交的 $x$ 和 $y$ 轴并非描述此数据的“最佳方向”。
  • 相关性特征:
    • $x$ 与 $y$ 的变化同步关联:当 $x$ 为负大值时,$y$ 也为负大值。
    • 知道 $x$ 可以推测 $y$ 的大致范围。
  • 新目标:不局限于 xy 轴,而是从数据中寻找捕获主要分布宽度的“长轴方向”。

摒弃繁琐的公式,用计算思维直观地寻找这个方向。

旋转坐标系与投影¶

  • 方法:引入观察角度 $\theta$,评估数据沿 $\theta$ 方向的投影宽度。
  • 本质:将坐标轴沿直线定向旋转,进行坐标系变换。

投影过程:朝不同方向“观察”数据,将数据点进行垂直投影。

In [ ]:
M = np.vstack([xs_centered, ys_centered])
print(f"数据矩阵 M 的形状: {M.shape}")
print(M[:, :5])

交互式展示:旋转与投影¶

  • 左图:旋转观察轴(红色箭头)。
  • 右图:在新坐标方向视角下观察的数据分布(沿该轴的投影和标准差 $2\sigma$)。
In [ ]:
def rotation_matrix(theta):
    """创建旋转矩阵"""
    return np.array([
        [np.cos(theta), np.sin(theta)],
        [-np.sin(theta), np.cos(theta)]
    ])
In [ ]:
def plot_rotation_effects(degrees=28):
    """展示旋转与投影的效果"""
    theta = np.pi * degrees / 180  # 转换为弧度
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
    
    # 左图:原始数据和投影
    ax1.scatter(M[0, :], M[1, :], s=10, alpha=0.5)
    direction = np.array([[np.cos(theta), np.sin(theta)]])
    projections = (direction @ M) * direction.T
    ax1.scatter(projections[0, :], projections[1, :], s=5, alpha=0.1, c='green')
    
    # 绘制投影线
    for i in range(M.shape[1]):
        ax1.plot([M[0, i], projections[0, i]], [M[1, i], projections[1, i]], 
                 'k--', alpha=0.02)
    
    # 绘制方向箭头
    ax1.annotate('', xy=(0.5*np.cos(theta), 0.5*np.sin(theta)), 
                xytext=(-0.5*np.cos(theta), -0.5*np.sin(theta)),
                arrowprops=dict(arrowstyle='->', color='red', lw=2, alpha=0.3))
    
    ax1.axis('tight')
    ax1.set_aspect('equal')
    format_axis(ax1, xticks=[-0.50, -0.25, 0.25, 0.50], yticks=[-0.50, -0.25, 0.25, 0.50], fmt='.2f')
    
    # 右图:旋转后的数据
    M_rotated = rotation_matrix(theta) @ M
    sigma = np.std(M_rotated[0, :])
    
    ax2.scatter(M_rotated[0, :], M_rotated[1, :], s=10, alpha=0.3)
    ax2.scatter(M_rotated[0, :], np.zeros(M.shape[1]), s=8, alpha=0.1, c='green')
    
    # 绘制投影线和标准差边界
    for i in range(M.shape[1]):
        ax2.plot([M_rotated[0, i], M_rotated[0, i]], [M_rotated[1, i], 0], 
                 'k--', alpha=0.02)
    ax2.axvline(x=2*sigma, color='green', linestyle='--', linewidth=2)
    ax2.axvline(x=-2*sigma, color='green', linestyle='--', linewidth=2)
    ax2.text(2*sigma + 0.02, 0.02, r'$2\sigma$', color='green', fontsize=16, ha='left', va='bottom')
    ax2.text(-2*sigma - 0.02, 0.02, r'$-2\sigma$', color='green', fontsize=16, ha='right', va='bottom')
    
    ax2.set_title(f'σ = {sigma:.4f}', fontsize=20)
    ax2.axis('tight')
    ax2.set_aspect('equal')
    format_axis(ax2, xticks=[-0.50, -0.25, 0.25, 0.50], yticks=[-0.50, -0.25, 0.25, 0.50], fmt='.2f')
    
    plt.tight_layout()
    plt.show()

interact(plot_rotation_effects, 
         degrees=FloatSlider(min=0, max=360, step=1, value=28, description='角度'));

寻找方差最大的方向¶

  • 观察:数据范围随 $\theta$ 的变化而变化。
  • 图示:沿 $\theta$ 方向的方差变化曲线。
In [ ]:
def variance_in_direction(theta):
    """计算给定方向上的方差"""
    M_rotated = rotation_matrix(theta) @ M
    return np.var(M_rotated[0, :])

# 计算所有角度的方差
angles = np.linspace(0, 2*np.pi, 361)
variances = [variance_in_direction(a) for a in angles]

plt.figure(figsize=(6, 4))
plt.plot(np.degrees(angles), variances)
plt.xlabel('θ (度)')
plt.ylabel('θ 方向上的方差')
plt.grid(True, alpha=0.3)
plt.show()

主成分 (Principal Component)¶

  • 第一主成分:方差最大化的方向(信息最丰富、区分度最高的方向)。
  • 第一奇异向量:线性代数中对应的等价概念。
  • 秩-1 的判别:如果最小方差方向的宽度远小于主成分方向的宽度,则数据高度接近秩-1。

数值求解:扫描所有角度计算方差并提取极值。

In [ ]:
theta_values = np.arange(0, 2*np.pi, 0.0001)
f_values = [variance_in_direction(t) for t in theta_values]

theta_max = theta_values[np.argmax(f_values)]
theta_min = theta_values[np.argmin(f_values)]

f_max = variance_in_direction(theta_max)
f_min = variance_in_direction(theta_min)

print(f"最大方差方向: θ = {np.degrees(theta_max):.2f}°")
print(f"最小方差方向: θ = {np.degrees(theta_min):.2f}°")
print(f"最大方差: {f_max:.6f}")
print(f"最小方差: {f_min:.6f}")
In [ ]:
fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(xs_centered, ys_centered, s=25, alpha=0.3)

# 绘制主方向箭头
max_arrow = 2 * np.sqrt(f_max)
min_arrow = 2 * np.sqrt(f_min)
ax.annotate('', xy=(max_arrow*np.cos(theta_max), max_arrow*np.sin(theta_max)), 
            xytext=(0, 0),
            arrowprops=dict(arrowstyle='->', color='red', lw=3))
ax.annotate('', xy=(min_arrow*np.cos(theta_min), min_arrow*np.sin(theta_min)), 
            xytext=(0, 0),
            arrowprops=dict(arrowstyle='->', color='red', lw=3))

ax.axis('tight')
format_axis(ax, xticks=[-0.4, -0.2, 0.2, 0.4], yticks=[-0.3, -0.2, -0.1, 0.1, 0.2, 0.3])
plt.show()

拟合数据椭圆¶

  • 正交性:最大方差和最小方差方向必然互相垂直(SVD可严格证明)。
  • 几何视角:为数据拟合一个椭圆。主轴的长度体现了对应方向的重要程度。
  • 统计视角:拟合多元正态分布的最佳协方差矩阵。
In [ ]:
# 构建椭圆
theta_ellipse = np.linspace(0, 2*np.pi, 100)
circle = np.vstack([np.cos(theta_ellipse), np.sin(theta_ellipse)])
stretch = np.array([[2 * np.sqrt(f_max), 0], [0, 2 * np.sqrt(f_min)]])
ellipse = rotation_matrix(-theta_max) @ stretch @ circle

fig, ax = plt.subplots(figsize=(6, 4))
ax.scatter(xs_centered, ys_centered, s=25, alpha=0.3)
ax.fill(ellipse[0, :], ellipse[1, :], alpha=0.4, color='orange', label='拟合椭圆')

# 绘制主方向箭头
ax.annotate('', xy=(max_arrow*np.cos(theta_max), max_arrow*np.sin(theta_max)), 
            xytext=(0, 0),
            arrowprops=dict(arrowstyle='->', color='red', lw=3))
ax.annotate('', xy=(min_arrow*np.cos(theta_min), min_arrow*np.sin(theta_min)), 
            xytext=(0, 0),
            arrowprops=dict(arrowstyle='->', color='red', lw=3))

ax.axis('tight')
ax.legend()
format_axis(ax, xticks=[-0.4, -0.2, 0.2, 0.4], yticks=[-0.3, -0.2, -0.1, 0.1, 0.2, 0.3])
plt.show()

线性变换视角:数据椭圆是单位圆在线性变换下的像,寻找的是最佳的映射变换矩阵。

推广到高维空间¶

思想同样适用于更高维度。

  • 三维情况:
    • 秩-1矩阵 $\rightarrow$ 3D中的一条直线
    • 秩-2矩阵 $\rightarrow$ 3D中的一个平面
    • 秩-2 + 噪声 $\rightarrow$ 紧绕平面分布的点云。
  • 高维情况:寻找高维椭球体,根据轴的宽度判断维度重要性。
In [ ]:
# 三维数据
data_3d = noisy_image[:3, :]

fig = plt.figure(figsize=(6, 4))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(data_3d[0, :], data_3d[1, :], data_3d[2, :], alpha=0.5, s=10)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()

降维 (Dimensionality Reduction):

  • 使用 SVD 算法计算高维主轴。
  • 抛弃那些宽度(方差)极小的主轴,仅保留重要的方向以降低维度。

奇异值分解 (SVD)¶

  • 概念:用结构更简单的矩阵组合来表示任意矩阵。
  • 几何意义:任何线性变换 $T$ 等效于: $$T = \text{旋转}_2 \circ \text{拉伸} \circ \text{旋转}_1$$
  • 矩阵形式: $$M = U \Sigma V^T$$
    • $U, V$:正交矩阵 (旋转/反射变换,保持面积/体积不变)。
    • $\Sigma$:对角矩阵 (沿坐标轴进行的纯拉伸)。
  • Python调用:np.linalg.svd
In [ ]:
M_example = np.array([[2, 1], [1, 1]])
U, S, Vt = np.linalg.svd(M_example)

print("原始矩阵 M:")
print(M_example)
print("\nU (左奇异向量):")
print(U)
print("\n奇异值 Σ:")
print(S)
print("\nV^T (右奇异向量):")
print(Vt)

print("\n验证: U @ diag(Σ) @ Vt:")
print(U @ np.diag(S) @ Vt)

演示:通过生成单位圆盘内的随机点,观察SVD的拉伸与旋转作用。

In [ ]:
# 生成单位圆盘中的随机点
np.random.seed(42)
n_points = 2000
points = []
while len(points) < n_points:
    p = np.random.uniform(-1, 1, 2)
    if p[0]**2 + p[1]**2 < 1:
        points.append(p)
unit_disc = np.array(points).T

plt.figure(figsize=(6, 6))
plt.scatter(unit_disc[0, :], unit_disc[1, :], alpha=0.5, s=3)
plt.axis('equal')
plt.grid(True, alpha=0.3)
plt.show()
In [ ]:
def plot_svd_effect(t=0.5):
    """展示 SVD 的效果:旋转 + 拉伸"""
    M = np.array([[1 + t, t], [t, 1]])
    transformed = M @ unit_disc
    U, S, Vt = np.linalg.svd(M)
    stretched = np.diag(S) @ unit_disc
    
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
    
    # 左图:拉伸效果
    ax1.scatter(unit_disc[0, :], unit_disc[1, :], alpha=0.5, s=3, label='原始')
    ax1.scatter(stretched[0, :], stretched[1, :], alpha=0.2, s=3, label='拉伸后')
    ax1.set_xlim(-3, 3)
    ax1.set_ylim(-3, 3)
    ax1.set_title('拉伸效果')
    ax1.set_aspect('equal')
    ax1.legend()
    ax1.grid(True, alpha=0.3)
    
    # 右图:拉伸 + 旋转效果
    ax2.scatter(unit_disc[0, :], unit_disc[1, :], alpha=0.5, s=3, label='原始')
    ax2.scatter(transformed[0, :], transformed[1, :], alpha=0.2, s=3, label='拉伸+旋转后')
    ax2.set_xlim(-3, 3)
    ax2.set_ylim(-3, 3)
    ax2.set_title(f'拉伸 + 旋转效果 (t = {t:.2f})')
    ax2.set_aspect('equal')
    ax2.legend()
    ax2.grid(True, alpha=0.3)
    
    plt.tight_layout()
    plt.show()

interact(plot_svd_effect, 
         t=FloatSlider(min=0, max=1, step=0.01, value=0.5, description='t'));

高维旋转的反向视角¶

转置的本质:从“2维中的300个点” 转换为考量“300维中的2个点”。

  • SVD 中的 $V$ 代表在二维图像中看不到的“300维旋转”。
  • 高维旋转构造:
    1. 随机生成反对称矩阵 ($A = -A^T$)。
    2. 利用矩阵指数将其转换为高维正交矩阵。
In [ ]:
# 创建反对称矩阵
dim = M.shape[1]
A = np.random.randn(dim, dim)
A = A - A.T  # 反对称化

print(f"反对称矩阵 A 的形状: {A.shape}")
print(f"验证 A = -A^T: {np.allclose(A, -A.T)}")
In [ ]:
def plot_high_dim_rotation(t=0.0):
    """展示高维旋转的效果"""
    orthogonal_matrix = expm(t * A)  # 矩阵指数给出正交矩阵
    M_rotated = M @ orthogonal_matrix
    
    plt.figure(figsize=(6, 4))
    plt.scatter(M[0, :], M[1, :], alpha=0.5, s=10, label='原始')
    plt.scatter(M_rotated[0, :], M_rotated[1, :], alpha=0.5, s=10, label='旋转后')
    plt.xlim(-0.6, 0.6)
    plt.ylim(-0.3, 0.3)
    plt.legend()
    plt.grid(True, alpha=0.3)
    plt.show()

interact(plot_high_dim_rotation, 
         t=FloatSlider(min=0, max=1, step=0.0002, value=0.0, description='t'));

结果观察:无论数据在 300 维空间中如何旋转,其降维投影始终落在相同的椭圆范围内。

In [ ]:
# 计算完整的 SVD
U, S, Vt = np.linalg.svd(M, full_matrices=True)

print(f"奇异值: {S}")
print(f"\nU 的形状: {U.shape}")
print(f"奇异值数组 S 的形状: {S.shape}")
print(f"V^T 的形状: {Vt.shape}")
In [ ]:
# 应用 V 变换:将数据投影到主成分方向
M_transformed = M @ Vt.T

plt.figure(figsize=(6, 4))
plt.scatter(M_transformed[0, :], M_transformed[1, :], alpha=0.5, s=10)
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.title('PCA 变换后的数据')
plt.axis('equal')
plt.xlim(-5, 5)
plt.grid(True, alpha=0.3)
plt.show()

本讲小结¶

  • 矩阵秩:秩-1矩阵表现为外积和高度结构化的棋盘模式。
  • 数据统计:利用标准差测量数据散布范围。
  • 变量相关性:寻找数据的主要分布方向。
  • 主成分分析 (PCA):提取方差(变化)最大的方向,保留核心特征。
  • 奇异值分解 (SVD):任意矩阵 = 旋转 + 拉伸。

PCA 是一种强大的降维技术,也是现代机器学习和数据分析的核心基石。