Skip to content

数组和矩阵操作

在科学计算中,数组和矩阵操作是最基础也是最重要的技能。SciPy 在 NumPy 的基础上,通过 scipy.linalg 模块提供了更加丰富和高效的线性代数功能。本章将详细介绍这些功能的使用方法。

scipy.linalg 模块概述

scipy.linalg 模块包含了所有在 numpy.linalg 中的函数,并且提供了更多高级功能:

  • 更完整的线性代数函数集合
  • 更好的性能优化
  • 更多的矩阵分解方法
  • 特殊矩阵的处理
  • 矩阵函数计算
python
import numpy as np
from scipy import linalg
import matplotlib.pyplot as plt

# 查看 scipy.linalg 的主要功能
print("scipy.linalg 主要功能:")
functions = [attr for attr in dir(linalg) if not attr.startswith('_')]
print(f"总共 {len(functions)} 个函数和类")
print("主要函数:", functions[:10])

基本矩阵操作

1. 矩阵创建和基本属性

python
# 创建各种类型的矩阵
n = 4

# 随机矩阵
A = np.random.random((n, n))
print(f"随机矩阵 A:\n{A}")

# 对称矩阵
S = A + A.T
print(f"\n对称矩阵 S:\n{S}")

# 正定矩阵
P = A @ A.T
print(f"\n正定矩阵 P:\n{P}")

# 单位矩阵
I = np.eye(n)
print(f"\n单位矩阵 I:\n{I}")

# 零矩阵
Z = np.zeros((n, n))
print(f"\n零矩阵 Z:\n{Z}")

# 对角矩阵
D = np.diag([1, 2, 3, 4])
print(f"\n对角矩阵 D:\n{D}")

2. 矩阵基本运算

python
# 矩阵加法和减法
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])

print(f"矩阵 A:\n{A}")
print(f"矩阵 B:\n{B}")
print(f"A + B:\n{A + B}")
print(f"A - B:\n{A - B}")

# 矩阵乘法
print(f"\n矩阵乘法 A @ B:\n{A @ B}")
print(f"元素乘法 A * B:\n{A * B}")

# 矩阵幂
print(f"\n矩阵平方 A²:\n{linalg.matrix_power(A, 2)}")
print(f"矩阵立方 A³:\n{linalg.matrix_power(A, 3)}")

# 矩阵转置
print(f"\n矩阵转置 A.T:\n{A.T}")

# 矩阵迹(对角线元素之和)
print(f"\n矩阵的迹 tr(A): {np.trace(A)}")

3. 矩阵范数

python
# 不同类型的矩阵范数
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print(f"矩阵 A:\n{A}")
print(f"\n各种范数:")
print(f"Frobenius 范数: {linalg.norm(A, 'fro'):.4f}")
print(f"1-范数 (列和的最大值): {linalg.norm(A, 1):.4f}")
print(f"∞-范数 (行和的最大值): {linalg.norm(A, np.inf):.4f}")
print(f"2-范数 (最大奇异值): {linalg.norm(A, 2):.4f}")
print(f"核范数 (奇异值之和): {linalg.norm(A, 'nuc'):.4f}")

# 向量范数
v = np.array([3, 4, 5])
print(f"\n向量 v: {v}")
print(f"L1 范数: {linalg.norm(v, 1):.4f}")
print(f"L2 范数: {linalg.norm(v, 2):.4f}")
print(f"L∞ 范数: {linalg.norm(v, np.inf):.4f}")

线性方程组求解

1. 基本线性方程组

python
# 求解线性方程组 Ax = b
A = np.array([[3, 2, -1], [2, -2, 4], [-1, 0.5, -1]])
b = np.array([1, -2, 0])

print(f"系数矩阵 A:\n{A}")
print(f"常数向量 b: {b}")

# 使用 scipy.linalg.solve
x = linalg.solve(A, b)
print(f"\n解向量 x: {x}")

# 验证解的正确性
residual = A @ x - b
print(f"残差 ||Ax - b||: {linalg.norm(residual):.2e}")

# 检查矩阵条件数
cond_num = linalg.cond(A)
print(f"条件数: {cond_num:.2f}")
if cond_num > 1e12:
    print("警告: 矩阵接近奇异,解可能不稳定")

2. 特殊类型的线性方程组

python
# 对称正定矩阵的求解(使用 Cholesky 分解)
n = 5
A_spd = np.random.random((n, n))
A_spd = A_spd @ A_spd.T  # 确保正定
b = np.random.random(n)

# 通用求解
start_time = time.time()
x1 = linalg.solve(A_spd, b)
general_time = time.time() - start_time

# 利用正定性质求解
start_time = time.time()
x2 = linalg.solve(A_spd, b, assume_a='pos')
cholesky_time = time.time() - start_time

print(f"通用求解耗时: {general_time:.6f} 秒")
print(f"Cholesky 求解耗时: {cholesky_time:.6f} 秒")
print(f"性能提升: {general_time / cholesky_time:.2f} 倍")
print(f"解的差异: {linalg.norm(x1 - x2):.2e}")

# 三对角矩阵求解
from scipy.linalg import solve_banded

# 创建三对角矩阵
n = 100
diag_main = 2 * np.ones(n)
diag_upper = -1 * np.ones(n-1)
diag_lower = -1 * np.ones(n-1)

# 构造带状矩阵格式
ab = np.zeros((3, n))
ab[0, 1:] = diag_upper  # 上对角线
ab[1, :] = diag_main    # 主对角线
ab[2, :-1] = diag_lower # 下对角线

b_tri = np.random.random(n)

# 使用带状矩阵求解器
x_banded = solve_banded((1, 1), ab, b_tri)
print(f"\n三对角矩阵求解完成,解向量长度: {len(x_banded)}")

3. 最小二乘问题

python
# 超定线性方程组的最小二乘解
m, n = 100, 50  # m > n,超定系统
A_over = np.random.random((m, n))
b_over = np.random.random(m)

# 最小二乘解
x_lstsq, residuals, rank, s = linalg.lstsq(A_over, b_over)

print(f"超定系统: {m} 个方程,{n} 个未知数")
print(f"矩阵秩: {rank}")
print(f"残差平方和: {residuals[0]:.4f}")
print(f"解向量范数: {linalg.norm(x_lstsq):.4f}")

# 正则化最小二乘(岭回归)
alpha = 0.1  # 正则化参数
A_reg = np.vstack([A_over, alpha * np.eye(n)])
b_reg = np.hstack([b_over, np.zeros(n)])
x_ridge = linalg.lstsq(A_reg, b_reg)[0]

print(f"\n正则化解向量范数: {linalg.norm(x_ridge):.4f}")
print(f"正则化效果: 范数减小了 {(1 - linalg.norm(x_ridge)/linalg.norm(x_lstsq))*100:.1f}%")

矩阵分解

1. LU 分解

python
# LU 分解:A = PLU
A = np.array([[2, 1, 1], [1, 3, 2], [1, 0, 0]])

P, L, U = linalg.lu(A)

print(f"原矩阵 A:\n{A}")
print(f"\n置换矩阵 P:\n{P}")
print(f"\n下三角矩阵 L:\n{L}")
print(f"\n上三角矩阵 U:\n{U}")

# 验证分解
reconstructed = P @ L @ U
print(f"\n重构矩阵 PLU:\n{reconstructed}")
print(f"重构误差: {linalg.norm(A - reconstructed):.2e}")

# 使用 LU 分解求解线性方程组
lu, piv = linalg.lu_factor(A)
b = np.array([4, 5, 6])
x = linalg.lu_solve((lu, piv), b)
print(f"\n使用 LU 分解求解 Ax = b:")
print(f"解: {x}")
print(f"验证: Ax = {A @ x}")

2. QR 分解

python
# QR 分解:A = QR
A = np.random.random((5, 3))

Q, R = linalg.qr(A)

print(f"原矩阵 A 形状: {A.shape}")
print(f"正交矩阵 Q 形状: {Q.shape}")
print(f"上三角矩阵 R 形状: {R.shape}")

# 验证正交性
print(f"\nQ 的正交性验证:")
print(f"Q.T @ Q:\n{Q.T @ Q}")
print(f"是否接近单位矩阵: {np.allclose(Q.T @ Q, np.eye(Q.shape[1]))}")

# 验证分解
reconstructed = Q @ R
print(f"\n重构误差: {linalg.norm(A - reconstructed):.2e}")

# QR 分解的应用:最小二乘问题
m, n = 10, 5
A_qr = np.random.random((m, n))
b_qr = np.random.random(m)

Q, R = linalg.qr(A_qr)
# 解 Rx = Q.T @ b
x_qr = linalg.solve_triangular(R, Q.T @ b_qr)

print(f"\n使用 QR 分解求解最小二乘问题:")
print(f"解向量: {x_qr}")
print(f"残差: {linalg.norm(A_qr @ x_qr - b_qr):.4f}")

3. 奇异值分解 (SVD)

python
# SVD 分解:A = UΣV^T
A = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]])

U, s, Vt = linalg.svd(A)

print(f"原矩阵 A 形状: {A.shape}")
print(f"U 矩阵形状: {U.shape}")
print(f"奇异值向量 s: {s}")
print(f"V^T 矩阵形状: {Vt.shape}")

# 重构矩阵
S = np.zeros(A.shape)
S[:len(s), :len(s)] = np.diag(s)
reconstructed = U @ S @ Vt

print(f"\n重构误差: {linalg.norm(A - reconstructed):.2e}")

# SVD 的应用:矩阵的秩和条件数
rank = np.sum(s > 1e-10)
cond_num = s[0] / s[-1] if s[-1] > 1e-15 else np.inf

print(f"\n矩阵的数值秩: {rank}")
print(f"条件数: {cond_num:.2f}")

# 低秩近似
for k in [1, 2, 3]:
    A_k = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
    error = linalg.norm(A - A_k, 'fro')
    print(f"秩-{k} 近似误差: {error:.4f}")

4. Cholesky 分解

python
# Cholesky 分解:A = L @ L.T(仅适用于正定矩阵)
n = 4
A_random = np.random.random((n, n))
A_pd = A_random @ A_random.T  # 构造正定矩阵

try:
    L = linalg.cholesky(A_pd, lower=True)
    print(f"正定矩阵 A:\n{A_pd}")
    print(f"\nCholesky 因子 L:\n{L}")
    
    # 验证分解
    reconstructed = L @ L.T
    print(f"\n重构误差: {linalg.norm(A_pd - reconstructed):.2e}")
    
    # 使用 Cholesky 分解求解线性方程组
    b = np.random.random(n)
    
    # 两步求解:Ly = b, L^T x = y
    y = linalg.solve_triangular(L, b, lower=True)
    x = linalg.solve_triangular(L.T, y, lower=False)
    
    print(f"\n使用 Cholesky 分解求解:")
    print(f"解: {x}")
    print(f"验证: {linalg.norm(A_pd @ x - b):.2e}")
    
except linalg.LinAlgError:
    print("矩阵不是正定的,无法进行 Cholesky 分解")

特征值和特征向量

1. 一般矩阵的特征值分解

python
# 计算特征值和特征向量
A = np.array([[1, 2], [2, 1]])

eigenvalues, eigenvectors = linalg.eig(A)

print(f"矩阵 A:\n{A}")
print(f"\n特征值: {eigenvalues}")
print(f"特征向量:\n{eigenvectors}")

# 验证特征值方程 Av = λv
for i in range(len(eigenvalues)):
    lambda_i = eigenvalues[i]
    v_i = eigenvectors[:, i]
    
    Av = A @ v_i
    lambda_v = lambda_i * v_i
    
    print(f"\n特征值 {i+1}: λ = {lambda_i:.4f}")
    print(f"Av = {Av}")
    print(f"λv = {lambda_v}")
    print(f"误差: {linalg.norm(Av - lambda_v):.2e}")

# 矩阵对角化
D = np.diag(eigenvalues)
P = eigenvectors
A_reconstructed = P @ D @ linalg.inv(P)

print(f"\n对角化验证:")
print(f"重构矩阵:\n{A_reconstructed}")
print(f"重构误差: {linalg.norm(A - A_reconstructed):.2e}")

2. 对称矩阵的特征值分解

python
# 对称矩阵有实特征值和正交特征向量
A_sym = np.array([[4, 2, 1], [2, 3, 0], [1, 0, 2]])

# 使用专门的对称矩阵算法
eigenvalues_sym, eigenvectors_sym = linalg.eigh(A_sym)

print(f"对称矩阵 A:\n{A_sym}")
print(f"\n特征值(按升序排列): {eigenvalues_sym}")
print(f"特征向量矩阵:\n{eigenvectors_sym}")

# 验证正交性
print(f"\n特征向量的正交性:")
print(f"V^T @ V:\n{eigenvectors_sym.T @ eigenvectors_sym}")
print(f"是否正交: {np.allclose(eigenvectors_sym.T @ eigenvectors_sym, np.eye(3))}")

# 谱分解
spectral_decomp = eigenvectors_sym @ np.diag(eigenvalues_sym) @ eigenvectors_sym.T
print(f"\n谱分解重构误差: {linalg.norm(A_sym - spectral_decomp):.2e}")

3. 广义特征值问题

python
# 广义特征值问题:Av = λBv
A = np.array([[1, 2], [3, 4]])
B = np.array([[2, 1], [1, 2]])

eigenvalues_gen, eigenvectors_gen = linalg.eig(A, B)

print(f"矩阵 A:\n{A}")
print(f"矩阵 B:\n{B}")
print(f"\n广义特征值: {eigenvalues_gen}")
print(f"广义特征向量:\n{eigenvectors_gen}")

# 验证广义特征值方程
for i in range(len(eigenvalues_gen)):
    lambda_i = eigenvalues_gen[i]
    v_i = eigenvectors_gen[:, i]
    
    Av = A @ v_i
    lambda_Bv = lambda_i * (B @ v_i)
    
    print(f"\n广义特征值 {i+1}: λ = {lambda_i:.4f}")
    print(f"误差: {linalg.norm(Av - lambda_Bv):.2e}")

矩阵函数

1. 矩阵指数

python
# 矩阵指数 exp(A)
A = np.array([[1, 2], [0, 1]])

# 计算矩阵指数
exp_A = linalg.expm(A)

print(f"矩阵 A:\n{A}")
print(f"\n矩阵指数 exp(A):\n{exp_A}")

# 验证性质:exp(A + B) = exp(A) * exp(B)(当 AB = BA 时)
B = np.array([[0, 1], [0, 0]])
if np.allclose(A @ B, B @ A):
    exp_A_plus_B = linalg.expm(A + B)
    exp_A_exp_B = linalg.expm(A) @ linalg.expm(B)
    
    print(f"\nexp(A + B):\n{exp_A_plus_B}")
    print(f"exp(A) * exp(B):\n{exp_A_exp_B}")
    print(f"差异: {linalg.norm(exp_A_plus_B - exp_A_exp_B):.2e}")

# 矩阵指数的应用:求解线性微分方程组
# dx/dt = Ax, x(0) = x0
# 解为 x(t) = exp(At) * x0
t = 1.0
x0 = np.array([1, 0])
exp_At = linalg.expm(A * t)
x_t = exp_At @ x0

print(f"\n线性微分方程组的解:")
print(f"t = {t} 时的解: {x_t}")

2. 矩阵对数和幂函数

python
# 矩阵对数
A_pos = np.array([[2, 1], [0, 3]])  # 确保特征值为正

log_A = linalg.logm(A_pos)
print(f"矩阵 A:\n{A_pos}")
print(f"\n矩阵对数 log(A):\n{log_A}")

# 验证:exp(log(A)) = A
exp_log_A = linalg.expm(log_A)
print(f"\nexp(log(A)):\n{exp_log_A}")
print(f"重构误差: {linalg.norm(A_pos - exp_log_A):.2e}")

# 矩阵幂函数
for p in [0.5, 2, -1]:
    A_power = linalg.fractional_matrix_power(A_pos, p)
    print(f"\nA^{p}:\n{A_power}")
    
    if p == -1:
        # 验证逆矩阵
        A_inv_check = linalg.inv(A_pos)
        print(f"使用 inv() 计算的逆矩阵:\n{A_inv_check}")
        print(f"差异: {linalg.norm(A_power - A_inv_check):.2e}")

3. 矩阵平方根

python
# 矩阵平方根
A_sqrt = linalg.sqrtm(A_pos)
print(f"矩阵平方根 sqrt(A):\n{A_sqrt}")

# 验证:sqrt(A) * sqrt(A) = A
A_reconstructed = A_sqrt @ A_sqrt
print(f"\nsqrt(A) * sqrt(A):\n{A_reconstructed}")
print(f"重构误差: {linalg.norm(A_pos - A_reconstructed):.2e}")

# 对于对称正定矩阵,可以使用特征值分解计算平方根
A_spd = np.array([[4, 2], [2, 3]])
evals, evecs = linalg.eigh(A_spd)
A_sqrt_eig = evecs @ np.diag(np.sqrt(evals)) @ evecs.T

print(f"\n使用特征值分解计算的平方根:\n{A_sqrt_eig}")
print(f"验证: {linalg.norm(A_sqrt_eig @ A_sqrt_eig - A_spd):.2e}")

稀疏矩阵操作

1. 稀疏矩阵的创建和基本操作

python
from scipy import sparse

# 创建稀疏矩阵
n = 1000
# 创建三对角矩阵
diagonals = [np.ones(n-1), 2*np.ones(n), np.ones(n-1)]
offsets = [-1, 0, 1]
A_sparse = sparse.diags(diagonals, offsets, format='csr')

print(f"稀疏矩阵形状: {A_sparse.shape}")
print(f"非零元素数量: {A_sparse.nnz}")
print(f"稀疏度: {1 - A_sparse.nnz / (n * n):.4f}")

# 稀疏矩阵的线性方程组求解
from scipy.sparse.linalg import spsolve

b_sparse = np.random.random(n)
x_sparse = spsolve(A_sparse, b_sparse)

print(f"\n稀疏线性方程组求解完成")
print(f"解向量范数: {linalg.norm(x_sparse):.4f}")
print(f"残差: {linalg.norm(A_sparse @ x_sparse - b_sparse):.2e}")

# 稀疏矩阵的特征值计算
from scipy.sparse.linalg import eigsh

# 计算最大的几个特征值
k = 5  # 计算前 5 个特征值
eigenvalues_sparse, eigenvectors_sparse = eigsh(A_sparse, k=k, which='LA')

print(f"\n最大的 {k} 个特征值: {eigenvalues_sparse}")
print(f"对应特征向量形状: {eigenvectors_sparse.shape}")

实际应用示例

示例 1:图像压缩(SVD 应用)

python
# 使用 SVD 进行图像压缩
import matplotlib.pyplot as plt

# 创建一个简单的"图像"(二维数组)
np.random.seed(42)
original_image = np.random.random((50, 50))
# 添加一些结构
for i in range(10, 40):
    for j in range(10, 40):
        original_image[i, j] = 0.8

# SVD 分解
U, s, Vt = linalg.svd(original_image)

# 不同压缩比的重构
compression_ratios = [5, 10, 20, 30]

plt.figure(figsize=(15, 3))

plt.subplot(1, len(compression_ratios) + 1, 1)
plt.imshow(original_image, cmap='gray')
plt.title('原始图像')
plt.axis('off')

for i, k in enumerate(compression_ratios):
    # 使用前 k 个奇异值重构
    compressed_image = U[:, :k] @ np.diag(s[:k]) @ Vt[:k, :]
    
    # 计算压缩比和误差
    compression_ratio = k * (U.shape[0] + Vt.shape[1]) / (U.shape[0] * Vt.shape[1])
    mse = np.mean((original_image - compressed_image)**2)
    
    plt.subplot(1, len(compression_ratios) + 1, i + 2)
    plt.imshow(compressed_image, cmap='gray')
    plt.title(f'k={k}\n压缩比: {compression_ratio:.2f}\nMSE: {mse:.4f}')
    plt.axis('off')

plt.tight_layout()
plt.show()

# 奇异值的衰减
plt.figure(figsize=(10, 6))
plt.semilogy(s, 'bo-')
plt.xlabel('奇异值索引')
plt.ylabel('奇异值大小')
plt.title('奇异值衰减')
plt.grid(True, alpha=0.3)
plt.show()

示例 2:主成分分析 (PCA)

python
# 使用 SVD 实现 PCA
from sklearn.datasets import make_blobs

# 生成二维数据
n_samples = 300
X, _ = make_blobs(n_samples=n_samples, centers=2, n_features=2, 
                 random_state=42, cluster_std=1.5)

# 数据中心化
X_centered = X - np.mean(X, axis=0)

# 使用 SVD 进行 PCA
U, s, Vt = linalg.svd(X_centered, full_matrices=False)

# 主成分(特征向量)
components = Vt.T
print(f"主成分方向:")
print(f"第一主成分: {components[:, 0]}")
print(f"第二主成分: {components[:, 1]}")

# 解释方差比
explained_variance_ratio = (s**2) / np.sum(s**2)
print(f"\n解释方差比:")
print(f"第一主成分: {explained_variance_ratio[0]:.3f}")
print(f"第二主成分: {explained_variance_ratio[1]:.3f}")

# 数据投影到主成分空间
X_pca = X_centered @ components

# 可视化
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], alpha=0.6)
plt.title('原始数据')
plt.xlabel('特征 1')
plt.ylabel('特征 2')
plt.axis('equal')
plt.grid(True, alpha=0.3)

# 绘制主成分方向
mean_point = np.mean(X, axis=0)
for i in range(2):
    direction = components[:, i] * s[i] * 0.3
    plt.arrow(mean_point[0], mean_point[1], direction[0], direction[1],
              head_width=0.3, head_length=0.2, fc=f'C{i}', ec=f'C{i}',
              label=f'PC{i+1}')
plt.legend()

plt.subplot(1, 2, 2)
plt.scatter(X_pca[:, 0], X_pca[:, 1], alpha=0.6)
plt.title('PCA 变换后的数据')
plt.xlabel('第一主成分')
plt.ylabel('第二主成分')
plt.axis('equal')
plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

示例 3:线性回归的矩阵形式

python
# 使用矩阵运算实现线性回归
from sklearn.datasets import make_regression

# 生成回归数据
X, y = make_regression(n_samples=100, n_features=3, noise=10, random_state=42)

# 添加偏置项
X_with_bias = np.column_stack([np.ones(X.shape[0]), X])

print(f"设计矩阵形状: {X_with_bias.shape}")
print(f"目标向量形状: {y.shape}")

# 方法 1:正规方程 θ = (X^T X)^(-1) X^T y
XTX = X_with_bias.T @ X_with_bias
XTy = X_with_bias.T @ y
theta_normal = linalg.solve(XTX, XTy)

print(f"\n正规方程解: {theta_normal}")

# 方法 2:使用 QR 分解
Q, R = linalg.qr(X_with_bias)
theta_qr = linalg.solve_triangular(R, Q.T @ y)

print(f"QR 分解解: {theta_qr}")

# 方法 3:使用 SVD(最稳定)
U, s, Vt = linalg.svd(X_with_bias, full_matrices=False)
theta_svd = Vt.T @ (np.diag(1/s) @ (U.T @ y))

print(f"SVD 解: {theta_svd}")

# 比较解的差异
print(f"\n解的差异:")
print(f"正规方程 vs QR: {linalg.norm(theta_normal - theta_qr):.2e}")
print(f"正规方程 vs SVD: {linalg.norm(theta_normal - theta_svd):.2e}")
print(f"QR vs SVD: {linalg.norm(theta_qr - theta_svd):.2e}")

# 预测和评估
y_pred = X_with_bias @ theta_svd
mse = np.mean((y - y_pred)**2)
r2 = 1 - np.sum((y - y_pred)**2) / np.sum((y - np.mean(y))**2)

print(f"\n模型性能:")
print(f"均方误差: {mse:.2f}")
print(f"R² 分数: {r2:.3f}")

# 可视化(选择一个特征进行可视化)
plt.figure(figsize=(10, 4))

for i in range(3):
    plt.subplot(1, 3, i+1)
    plt.scatter(X[:, i], y, alpha=0.6)
    
    # 绘制回归线
    x_range = np.linspace(X[:, i].min(), X[:, i].max(), 100)
    X_range = np.column_stack([np.ones(100), 
                              np.zeros((100, 3))])
    X_range[:, i+1] = x_range
    y_range = X_range @ theta_svd
    
    plt.plot(x_range, y_range, 'r-', linewidth=2)
    plt.xlabel(f'特征 {i+1}')
    plt.ylabel('目标值')
    plt.title(f'特征 {i+1} vs 目标值')
    plt.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

性能优化技巧

1. 选择合适的算法

python
import time

# 比较不同算法的性能
n = 1000

# 创建不同类型的矩阵
A_general = np.random.random((n, n))
A_symmetric = A_general + A_general.T
A_positive_definite = A_general @ A_general.T
b = np.random.random(n)

# 通用求解
start = time.time()
x1 = linalg.solve(A_general, b)
general_time = time.time() - start

# 对称矩阵求解
start = time.time()
x2 = linalg.solve(A_symmetric, b, assume_a='sym')
symmetric_time = time.time() - start

# 正定矩阵求解
start = time.time()
x3 = linalg.solve(A_positive_definite, b, assume_a='pos')
positive_definite_time = time.time() - start

print(f"性能比较 (n={n}):")
print(f"通用算法: {general_time:.4f} 秒")
print(f"对称矩阵算法: {symmetric_time:.4f} 秒")
print(f"正定矩阵算法: {positive_definite_time:.4f} 秒")
print(f"\n性能提升:")
print(f"对称 vs 通用: {general_time / symmetric_time:.2f}x")
print(f"正定 vs 通用: {general_time / positive_definite_time:.2f}x")

2. 内存优化

python
# 就地操作和内存管理
n = 2000

# 避免不必要的拷贝
A = np.random.random((n, n))
B = np.random.random((n, n))

# 低效:创建新数组
start = time.time()
C1 = A + B
C1 = C1 @ A
copy_time = time.time() - start

# 高效:就地操作
start = time.time()
C2 = A.copy()  # 只拷贝一次
C2 += B        # 就地加法
C2 @= A        # 就地矩阵乘法(Python 3.5+)
inplace_time = time.time() - start

print(f"拷贝操作耗时: {copy_time:.4f} 秒")
print(f"就地操作耗时: {inplace_time:.4f} 秒")
print(f"性能提升: {copy_time / inplace_time:.2f}x")

# 使用合适的数据类型
A_float64 = np.random.random((n, n)).astype(np.float64)
A_float32 = A_float64.astype(np.float32)

print(f"\n内存使用:")
print(f"float64: {A_float64.nbytes / 1024**2:.1f} MB")
print(f"float32: {A_float32.nbytes / 1024**2:.1f} MB")
print(f"内存节省: {(1 - A_float32.nbytes / A_float64.nbytes) * 100:.1f}%")

小结

在本章中,我们深入学习了 SciPy 的数组和矩阵操作:

  1. 基本操作:矩阵创建、运算、范数计算
  2. 线性方程组:各种求解方法和特殊情况处理
  3. 矩阵分解:LU、QR、SVD、Cholesky 分解及其应用
  4. 特征值问题:一般和对称矩阵的特征值分解
  5. 矩阵函数:指数、对数、幂函数等
  6. 稀疏矩阵:大规模稀疏问题的高效处理
  7. 实际应用:图像压缩、PCA、线性回归等
  8. 性能优化:算法选择和内存管理

这些技能是科学计算的基础,为后续学习更高级的 SciPy 功能奠定了坚实基础。接下来,我们将在 统计分析 中学习 SciPy 强大的统计功能。

练习题

  1. 矩阵分解应用:实现一个函数,使用不同的矩阵分解方法求解线性方程组,并比较它们的性能
  2. 特征值应用:编写代码分析一个网络的连通性(使用图的邻接矩阵的特征值)
  3. SVD 应用:实现一个简单的推荐系统,使用 SVD 进行矩阵分解
  4. 性能优化:比较稠密矩阵和稀疏矩阵操作的性能差异
python
# 练习题参考答案

# 1. 矩阵分解应用
def solve_comparison(A, b):
    """
    使用不同方法求解线性方程组并比较性能
    """
    methods = {}
    
    # 直接求解
    start = time.time()
    x_direct = linalg.solve(A, b)
    methods['直接求解'] = (time.time() - start, x_direct)
    
    # LU 分解
    start = time.time()
    lu, piv = linalg.lu_factor(A)
    x_lu = linalg.lu_solve((lu, piv), b)
    methods['LU分解'] = (time.time() - start, x_lu)
    
    # QR 分解
    start = time.time()
    Q, R = linalg.qr(A)
    x_qr = linalg.solve_triangular(R, Q.T @ b)
    methods['QR分解'] = (time.time() - start, x_qr)
    
    return methods

# 测试
n = 500
A_test = np.random.random((n, n))
b_test = np.random.random(n)
results = solve_comparison(A_test, b_test)

for method, (time_taken, solution) in results.items():
    error = linalg.norm(A_test @ solution - b_test)
    print(f"{method}: {time_taken:.4f}s, 误差: {error:.2e}")

本站内容仅供学习和研究使用。