07_linearAlgebra.py - 高级线性代数完全指南
学习路径第 7 步 (共 10 步) | 难度:中高级
概述
NumPy 的 linalg 模块是机器学习的数学基础。本文件系统讲解特征值分解、SVD、最小二乘法、QR/Cholesky 分解,并手写实现 PCA。
学习目标
- 理解特征值与特征向量的几何意义
- 掌握 SVD 奇异值分解及其在降维中的应用
- 学会使用最小二乘法进行数据拟合
- 了解 QR 分解和 Cholesky 分解的实际用途
- 手写 PCA 主成分分析,理解其数学本质
核心内容 (7 个模块)
| 模块 | 核心知识点 |
|---|---|
| 1. 特征值分解 | eigenvalues / eigenvectors / 矩阵对角化 |
| 2. SVD 奇异值分解 | 完整/截断 SVD、矩阵秩、低秩近似 |
| 3. 最小二乘法拟合 | np.linalg.lstsq、超定方程组求解 |
| 4. 矩阵分解 | QR 分解、Cholesky 分解(对称正定矩阵) |
| 5. 矩阵性质 | 行列式、秩、条件数、迹(trace) |
| 6. 线性方程组 | 求解 Ax=b、逆矩阵 vs solve |
| 7. PCA 手写实现 | 基于 SVD 的逐步推导与代码实现 |
CODE
python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
=====================================
NumPy 高级线性代数完全指南 (Advanced Linear Algebra)
=====================================
本案例深入介绍 NumPy 的线性代数能力(基于 LAPACK/BLAS):
1. 特征值分解 (Eigenvalue Decomposition)
2. SVD 奇异值分解 (Singular Value Decomposition)
3. 最小二乘法 (Least Squares Fitting)
4. 矩阵分解: QR / Cholesky
5. 行列式、秩、条件数
6. 实战应用: 主成分分析 (PCA) 手写实现
7. 实战应用: 线性方程组求解
【前置知识】
线性代数是机器学习、信号处理、计算机图形学的数学基石。
numpy.linalg 是 Python 科学计算的线性代数引擎。
作者:bloxed
"""
import numpy as np
import time
def separator(title):
print(f"\n{'='*60}")
print(f" {title}")
print('='*60)
# ============================================================
# 第一部分:特征值与特征向量
# ============================================================
separator("一、特征值分解 (Eigenvalue Decomposition)")
print("""
【定义】对于方阵 A, 若存在非零向量 v 和标量 λ 使得:
Av = λv
则 λ 称为特征值, v 称为特征向量。
【几何意义】
特征向量是矩阵变换下方向不变的轴,
特征值表示该方向上的缩放因子。
【应用】
- 主成分分析 (PCA)
- PageRank 算法 (Google搜索)
- 振动模式分析
- 矩阵幂运算加速
""")
# 构造一个对称矩阵 (实对称矩阵的特征值和特征向量都是实数)
A = np.array([
[4, 2, 1],
[2, 5, 3],
[1, 3, 6]
], dtype=float)
print(f"矩阵 A (3x3 对称):\n{A}\n")
eigenvalues, eigenvectors = np.linalg.eig(A)
print(f"特征值 λ: {eigenvalues.round(4)}")
print(f"特征向量 (每列是一个特征向量):\n{eigenvectors.round(4)}")
# 验证 Av = λv
print(f"\n--- 验证 Av = λv ---")
for i in range(3):
lam = eigenvalues[i]
vec = eigenvectors[:, i]
av = A @ vec
lam_v = lam * vec
error = np.linalg.norm(av - lam_v)
print(f" λ{i}={lam:.4f}: ||Av - λv|| = {error:.2e} (应接近0)")
# ============================================================
# 第二部分:奇异值分解 SVD
# ============================================================
separator("二、奇异值分解 (SVD) ★★★")
print("""
【公式】m×n 矩阵 A 可以分解为:
A = U · Σ · V^T
U: m×m 正交矩阵 (左奇异向量)
Σ: m×n 对角矩阵 (奇异值, 非负递减)
V^T: n×n 正交矩阵 (右奇异向量)
【为什么 SVD 如此重要?】
[OK] 任何矩阵都能做 SVD (不需要方阵!)
[OK] 揭示矩阵的 "能量分布"
[OK] 用于降维 (保留主要奇异值)
[OK] 图像压缩、推荐系统、去噪
""")
# 构造非方矩阵
B = np.array([
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
], dtype=float)
U, s, Vt = np.linalg.svd(B, full_matrices=True)
print(f"原始矩阵 B: shape {B.shape}")
print(f"\nU (左奇异向量): shape {U.shape}")
print(f"Σ (奇异值): {s.round(4)}")
print(f"V^T (右奇异向量转置): shape {Vt.shape}")
# 用 SVD 重构矩阵
Sigma = np.zeros(B.shape)
Sigma[:len(s), :len(s)] = np.diag(s)
reconstructed = U @ Sigma @ Vt
reconstruction_error = np.linalg.norm(B - reconstructed)
print(f"\n重构误差 ||B - USV^T|| = {reconstruction_error:.2e} (应接近0)")
# SVD 降维演示 (图像压缩概念)
print(f"\n--- 低秩近似 (数据压缩) ---")
total_energy = np.sum(s**2)
cumulative_energy = np.cumsum(s**2) / total_energy
print(f"{'保留奇异值数':>12} {'累积能量%':>12} {'信息损失':>12}")
for k in [1, 2, 3, 4]:
energy_pct = cumulative_energy[k-1] * 100
loss = 100 - energy_pct
# 低秩重构
B_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
err = np.linalg.norm(B - B_k)
print(f" k={k:>2} {energy_pct:>11.1f}% 误差={err:.4f}")
# ============================================================
# 第三部分:最小二乘法
# ============================================================
separator("三、最小二乘法 (Least Squares)")
print("""
【问题】给定数据点 (xi, yi),找到最佳拟合直线 y = ax + b
使残差平方和最小: min Σ(yi - (axi+b))²
【矩阵形式】X·β = y 的最小二乘解:
β = (X^T X)^(-1) X^T y
或直接用 np.linalg.lstsq()
""")
# 生成带噪声的线性数据
rng = np.random.default_rng(123)
n_points = 50
true_slope = 2.5
true_intercept = 10.0
x_data = np.linspace(0, 10, n_points)
noise = rng.normal(0, 2.0, size=n_points)
y_data = true_slope * x_data + true_intercept + noise
print(f"真实参数: slope={true_slope}, intercept={true_intercept}")
print(f"数据点数: {n_points}")
# 方式1: lstsq (推荐)
X = np.column_stack([x_data, np.ones(n_points)]) # 设计矩阵
result, residuals, rank, sv = np.linalg.lstsq(X, y_data, rcond=None)
slope_lstsq, intercept_lstsq = result
residual_norm = np.sqrt(residuals[0]) if len(residuals) > 0 else 0
print(f"\n--- np.linalg.lstsq 结果 ---")
print(f" 斜率 a = {slope_lstsq:.4f} (真实值 {true_slope})")
print(f" 截距 b = {intercept_lstsq:.4f} (真实值 {true_intercept})")
print(f" 残差范数: {residual_norm:.4f}")
# 方式2: 多项式拟合 (二次)
coeffs_2nd = np.polyfit(x_data, y_data, deg=2)
print(f"\n--- np.polyfit 二次拟合 ---")
print(f" y = {coeffs_2nd[0]:.4f}x^2 + {coeffs_2nd[1]:.4f}x + {coeffs_2nd[2]:.4f}")
# 方式3: 手动正规方程 (教学目的)
beta_manual = np.linalg.inv(X.T @ X) @ (X.T @ y_data)
print(f"\n--- 手动正规方程 (X^T X)^{-1} X^T y ---")
print(f" beta = [{beta_manual[0]:.4f}, {beta_manual[1]:.4f}]")
# R² 判定系数
y_pred = X @ result
ss_res = np.sum((y_data - y_pred)**2)
ss_tot = np.sum((y_data - y_data.mean())**2)
r_squared = 1 - ss_res / ss_tot
print(f"\n R² 判定系数 = {r_squared:.4f} (越接近1拟合越好)")
# ============================================================
# 第四部分:QR 分解 与 Cholesky 分解
# ============================================================
separator("四、矩阵分解: QR 和 Cholesky")
# --- QR 分解 ---
print("--- QR 分解 ---")
C = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 7]
], dtype=float)
Q, R = np.linalg.qr(C)
print(f"原始矩阵 C:\n{C}")
print(f"\nQ (正交矩阵, Q^T Q = I):")
print(Q.round(4))
print(f"\nR (上三角矩阵):")
print(R.round(4))
qr_check = Q @ R
print(f"\n验证 Q·R = C? 误差: {np.linalg.norm(C - qr_check):.2e}")
orthogonality_check = Q.T @ Q
print(f"Q的正交性 Q^T·Q:\n{orthogonality_check.round(4)}")
print("\n[用途] QR分解用于求解线性方程组、特征值迭代算法等")
# --- Cholesky 分解 (仅适用于对称正定矩阵) ---
print("\n--- Cholesky 分解 ---")
D = np.array([
[4, 2, 1],
[2, 5, 3],
[1, 3, 6]
], dtype=float)
try:
L = np.linalg.cholesky(D)
print(f"对称正定矩阵 D:\n{D}")
print(f"\nL (下三角矩阵, D = L·L^T):")
print(L.round(4))
cholesky_check = L @ L.T
print(f"\n验证 L·L^T = D? 误差: {np.linalg.norm(D - cholesky_check):.2e}")
except np.linalg.LinAlgError as e:
print(f"Cholesky 失败: {e} (矩阵可能不是正定的)")
print("\n[用途] Cholesky用于快速求解正定线性系统, 速度快于一般LU分解")
# ============================================================
# 第五部分:行列式、秩、条件数
# ============================================================
separator("五、行列式、秩、条件数")
matrices = {
"满秩矩阵": np.array([[1, 2], [3, 4]], dtype=float),
"奇异矩阵(行相关)": np.array([[1, 2], [2, 4]], dtype=float),
"近奇异矩阵": np.array([[1, 2], [1, 2.001]], dtype=float),
}
print(f"{'名称':<18} {'行列式':>10} {'秩':>5} {'条件数':>12} {'是否奇异'}")
print("-" * 58)
for name, M in matrices.items():
det = np.linalg.det(M)
rank = np.linalg.matrix_rank(M)
cond = np.linalg.cond(M)
is_singular = "YES" if abs(det) < 1e-10 else "No"
print(f"{name:<18} {det:>10.4f} {rank:>5} {cond:>12.1f} {is_singular}")
print(f"""
[解读]
行列式 = 0 → 矩阵奇异 (不可逆)
条件数越大 → 矩阵越接近奇异, 数值求解越不稳定
一般 cond > 1e10 就认为数值上接近奇异
""")
# ============================================================
# 第六部分:实战 ------ 手写 PCA (主成分分析)
# ============================================================
separator("六、实战: 手写 PCA (主成分分析)")
print("""
PCA 步骤:
1. 数据标准化 (零均值, 单位方差)
2. 计算协方差矩阵
3. 特征值分解协方差矩阵
4. 取 top-k 特征向量作为主成分
5. 投影数据到低维空间
""")
# 模拟数据: 3个特征, 100个样本
rng = np.random.default_rng(42)
n_samples = 100
# 特征1和特征2有强相关性 (构造出来)
feature1 = rng.normal(0, 1, n_samples)
feature2 = feature1 * 0.8 + rng.normal(0, 0.3, n_samples) # 与f1高度相关
feature3 = rng.normal(0, 1, n_samples) # 与其他无关
data_raw = np.column_stack([feature1, feature2, feature3])
print(f"原始数据形状: {data_raw.shape} (100样本 × 3特征)")
# Step 1: 标准化
data_mean = data_raw.mean(axis=0)
data_std = data_raw.std(axis=0)
data_normalized = (data_raw - data_mean) / data_std
# Step 2: 协方差矩阵
cov_matrix = np.cov(data_normalized.T)
print(f"\n协方差矩阵:\n{cov_matrix.round(3)}")
# Step 3: 特征值分解
eigenvalues, eigenvectors = np.linalg.eigh(cov_matrix)
# eigh 返回升序排列, 反转为降序
idx_sorted = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[idx_sorted]
eigenvectors = eigenvectors[:, idx_sorted]
variance_explained = eigenvalues / eigenvalues.sum()
cumulative_variance = np.cumsum(variance_explained)
print(f"\n各主成分解释方差:")
for i in range(3):
print(f" PC{i+1}: 特征值={eigenvalues[i]:.3f}, "
f"解释方差={variance_explained[i]*100:.1f}%, "
f"累积={cumulative_variance[i]*100:.1f}%")
# Step 5: 降维投影 (降到 2 维)
k = 2
W = eigenvectors[:, :k] # 投影矩阵
data_projected = data_normalized @ W
print(f"\n降维后数据形状: {data_projected.shape}")
print(f"保留了 {cumulative_variance[k-1]*100:.1f}% 的信息")
print(f"(从 3 维降至 {k} 维)")
# ============================================================
# 第七部分:实战 ------ 线性方程组求解对比
# ============================================================
separator("七、实战: 线性方程组求解方法对比")
print("""
求解 Ax = b 的多种方法及其适用场景
""")
# 创建测试方程组: 5个未知数
rng_test = np.random.default_rng(99)
size = 5
A_test = rng_test.standard_normal((size, size))
# 确保 A 可逆 (条件数不太大)
A_test += size * np.eye(size) # 对角增强
x_true = rng_test.standard_normal(size)
b_test = A_test @ x_true
print(f"未知数个数: {size}")
print(f"真实解: x = {x_true.round(4)}\n")
methods_results = {}
# 方法1: inv(A) * b (最直观但不推荐)
start = time.perf_counter()
x_inv = np.linalg.inv(A_test) @ b_test
methods_results['inv(A)*b'] = (time.perf_counter() - start, x_inv)
# 方法2: solve (推荐用于单个方程组)
start = time.perf_counter()
x_solve = np.linalg.solve(A_test, b_test)
methods_results['solve()'] = (time.perf_counter() - start, x_solve)
# 方法3: lstsq (也适用于超定/欠定系统)
start = time.perf_counter()
x_lstsq = np.linalg.lstsq(A_test, b_test, rcond=None)[0]
methods_results['lstsq()'] = (time.perf_counter() - start, x_lstsq)
print(f"{'方法':<15} {'耗时(us)':>10} {'最大误差':>12}")
print("-" * 38)
for name, (elapsed, solution) in methods_results.items():
error = np.max(np.abs(solution - x_true))
print(f"{name:<15} {elapsed*1e6:>10.1f} {error:>12.2e}")
print(f"""
[建议]
solve(A, b) → 方阵唯一解 (最快最准)
lstsq(A, b) → 超定/欠定/不适定系统
inv(A) @ b → 仅教学用, 实际中避免显式求逆
(inv 运算量大且数值不稳定)
""")
# ============================================================
# 总结
# ============================================================
separator("总结: NumPy Linear Algebra 速查")
summary = """
+------------------------------------------------------------+
| numpy.linalg 核心函数速查 |
+------------------------------------------------------------+
| |
| [特征值/特征向量] |
| np.linalg.eig(A) → 一般方阵的特征分解 |
| np.linalg.eigh(A) → 对称/Hermite矩阵 (更快更稳定) |
| |
| [SVD] |
| np.linalg.svd(A) → 奇异值分解 |
| |
| [方程组求解] |
| np.linalg.solve(A, b) → Ax=b 的精确解 |
| np.linalg.lstsq(A, b) → 最小二乘解 |
| np.linalg.inv(A) → 逆矩阵 (慎用!) |
| np.linalg.pinv(A) → Moore-Penrose伪逆 |
| |
| [矩阵分解] |
| np.linalg.qr(A) → QR分解 |
| np.linalg.cholesky(A) → Cholesky (正定矩阵) |
| np.linalg.lu(A) → LU分解 (SciPy) |
| |
| [矩阵属性] |
| np.linalg.det(A) → 行列式 |
| np.linalg.matrix_rank(A)→ 秩 |
| np.linalg.cond(A) → 条件数 |
| np.linalg.norm(A) → 范数 |
| np.trace(A) → 迹 (对角线之和) |
| |
| [向量化外积/内积] |
| np.dot(a, b) / a@b → 矩阵乘法/内积 |
| np.outer(a, b) → 外积 |
| np.inner(a, b) → 内积 |
| np.vdot(a, b) → 向量化内积 |
+------------------------------------------------------------+
"""
print(summary)
print("\n运行完毕!")