手把手图解:用Python把‘能量守恒’和‘勾股定理’画出来,理解机器学习降维不丢信息的本质

在机器学习领域,降维技术如PCA(主成分分析)和SVD(奇异值分解)常被用来处理高维数据。但你是否好奇过,为什么这些方法能在减少数据维度的同时,几乎不丢失关键信息?答案藏在数学中的 能量守恒 勾股定理 里。本文将带你用Python代码,从几何视角直观理解这一核心原理。

我们将使用NumPy和Matplotlib,从二维数据点出发,通过可视化展示正交变换如何保持数据的"能量"(方差)不变。这种动手实践的方式,不仅能加深你对PCA原理的理解,还能让你真正掌握降维技术的数学本质。

1. 准备工作与环境配置

在开始之前,确保你的Python环境已安装以下库:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

如果你尚未安装这些库,可以使用pip快速安装:

pip install numpy matplotlib

提示:建议使用Jupyter Notebook进行本教程的实践,它能实时显示图形输出,方便交互式学习。

2. 理解数据"能量"的数学定义

在信号处理和机器学习中,"能量"这一概念与物理学中的定义有所不同。这里,我们定义数据点的能量为其到原点距离的平方:

能量 = x₁² + x₂² + ... + xn²

这与勾股定理在多维空间的扩展形式完全一致。让我们用代码生成一些二维数据点并计算它们的能量:

# 生成随机二维数据点
np.random.seed(42)
data = np.random.randn(100, 2) * 5  # 100个点,标准差为5

# 计算每个点的能量
energies = np.sum(data**2, axis=1)
total_energy = np.sum(energies)
print(f"总能量: {total_energy:.2f}")

3. 可视化原始数据及其能量分布

为了更好地理解,我们先绘制这些数据点:

plt.figure(figsize=(10, 6))
plt.scatter(data[:, 0], data[:, 1], c=energies, cmap='viridis')
plt.colorbar(label='单个点能量')
plt.axhline(0, color='black', linestyle='--')
plt.axvline(0, color='black', linestyle='--')
plt.title("二维数据点及其能量分布")
plt.xlabel("X轴")
plt.ylabel("Y轴")
plt.grid(True)
plt.show()

你会看到点越远离原点,其颜色越亮(能量越高)。所有点的能量总和就是我们要关注的守恒量。

4. 正交变换与能量守恒

正交变换(如旋转)是PCA的核心。这类变换有一个重要特性:保持向量的长度(即能量)不变。让我们创建一个旋转矩阵并应用它:

# 创建旋转矩阵(30度)
theta = np.radians(30)
rotation_matrix = np.array([
    [np.cos(theta), -np.sin(theta)],
    [np.sin(theta), np.cos(theta)]
])

# 应用旋转
rotated_data = data @ rotation_matrix

# 计算旋转后能量
rotated_energies = np.sum(rotated_data**2, axis=1)
rotated_total_energy = np.sum(rotated_energies)
print(f"旋转后总能量: {rotated_total_energy:.2f}")

比较旋转前后的总能量,你会发现它们几乎相同(可能有微小差异来自浮点运算)。这就是能量守恒的体现。

5. 从二维到三维的扩展理解

为了更深入理解,我们扩展到三维空间。首先生成一些三维数据:

# 生成三维数据
np.random.seed(42)
data_3d = np.random.randn(100, 3) * 5

# 计算能量
energies_3d = np.sum(data_3d**2, axis=1)
total_energy_3d = np.sum(energies_3d)
print(f"三维数据总能量: {total_energy_3d:.2f}")

然后创建一个三维旋转矩阵并应用:

# 创建绕Z轴旋转45度的矩阵
theta_z = np.radians(45)
rotation_z = np.array([
    [np.cos(theta_z), -np.sin(theta_z), 0],
    [np.sin(theta_z), np.cos(theta_z), 0],
    [0, 0, 1]
])

# 应用旋转
rotated_3d = data_3d @ rotation_z

# 验证能量守恒
rotated_energies_3d = np.sum(rotated_3d**2, axis=1)
rotated_total_energy_3d = np.sum(rotated_energies_3d)
print(f"旋转后三维总能量: {rotated_total_energy_3d:.2f}")

6. 连接PCA:寻找最大能量方向

PCA的核心思想是找到数据中能量(方差)最大的方向。让我们用代码实现一个简化版PCA:

# 计算协方差矩阵
cov_matrix = np.cov(data.T)

# 计算特征值和特征向量
eigenvalues, eigenvectors = np.linalg.eig(cov_matrix)

# 按特征值大小排序
sorted_indices = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[sorted_indices]
eigenvectors = eigenvectors[:, sorted_indices]

print("特征值(各主成分的能量):", eigenvalues)
print("特征向量(主成分方向):\n", eigenvectors)

第一主成分方向就是能量最大的方向。我们可以将数据投影到这个方向上:

# 投影到第一主成分
principal_component = eigenvectors[:, 0]
projected_data = data @ principal_component

# 计算投影后能量
projected_energy = np.sum(projected_data**2)
print(f"第一主成分保留能量: {projected_energy:.2f}")
print(f"能量保留比例: {projected_energy/total_energy:.2%}")

7. 完整可视化:从原始数据到PCA降维

最后,让我们将所有步骤可视化:

plt.figure(figsize=(12, 6))

# 原始数据
plt.subplot(1, 2, 1)
plt.scatter(data[:, 0], data[:, 1], alpha=0.6)
plt.quiver(0, 0, eigenvectors[0, 0]*eigenvalues[0], eigenvectors[1, 0]*eigenvalues[0], 
           angles='xy', scale_units='xy', scale=1, color='r', label='PC1')
plt.quiver(0, 0, eigenvectors[0, 1]*eigenvalues[1], eigenvectors[1, 1]*eigenvalues[1], 
           angles='xy', scale_units='xy', scale=1, color='g', label='PC2')
plt.title("原始数据与主成分方向")
plt.legend()

# 投影后的数据
plt.subplot(1, 2, 2)
plt.scatter(projected_data, np.zeros_like(projected_data), alpha=0.6)
plt.title("数据在第一主成分上的投影")
plt.tight_layout()
plt.show()

这个可视化清晰地展示了PCA如何找到数据中能量最大的方向,并将数据投影到这个方向上,同时最大限度地保留了原始数据的能量(方差)。

更多推荐