基于 SciPy 的矩阵运算与线性代数应用详解

基于 SciPy 的矩阵运算与线性代数应用详解

在科学计算中,矩阵与线性代数是非常核心的工具。SciPy 提供了功能强大且高效的 scipy.linalg 模块,它建立在优化后的 BLAS 与 LAPACK 库之上。如果 SciPy 在构建时使用了 ATLAS、LAPACK 和 BLAS 库,就能发挥极快的线性代数运算能力。相比于 NumPy,scipy.linalg 更加专业,函数覆盖面更广,几乎囊括了 numpy.linalg 的全部功能,并额外提供了更多高级函数。

本文将系统介绍 scipy.linalg 的核心功能,结合数学公式、代码示例和可视化案例,让你更直观地理解其应用场景。


1. 为什么使用 scipy.linalg

  • 更强大 :包含 numpy.linalg 的全部功能,并提供额外扩展。
  • 更高效:始终基于 BLAS/LAPACK 构建,速度通常更快,而 NumPy 并不保证一定包含。
  • 统一接口:直接面向矩阵与向量,方便科研计算。

建议 :除非有特殊理由避免引入 SciPy,一般优先使用 scipy.linalg,尤其是涉及大规模矩阵运算时。


2. 矩阵表示与基本区别

在 Python 中,矩阵主要由 NumPy 提供的数据结构表示:

  • numpy.ndarray:二维数组,推荐使用的标准方式。
  • numpy.matrix:矩阵类,虽然提供了一些便捷特性,但并不推荐,容易造成混淆。scipy.linalg 对这两种结构均兼容。

示例:

python 复制代码
import numpy as np
from scipy import linalg

A = np.array([[1, 2], [3, 4]])
print(linalg.inv(A))  # 使用 ndarray
txt 复制代码
[[-2.   1. ]
[ 1.5 -0.5]]

这里 linalg.inv 返回矩阵的逆矩阵,逆矩阵是一个重要概念,满足 A−1⋅A=IA^{-1} \cdot A = IA−1⋅A=I,即乘以原矩阵得到单位矩阵。


3. 矩阵求逆 (Inverse)

数学原理:
A−1⋅A=I A^{-1} \cdot A = I A−1⋅A=I

其中 III 是单位矩阵。

在 SciPy 中实现:

python 复制代码
import numpy as np
from scipy import linalg

# 定义矩阵
A = np.array([[1, 3, 5],
              [2, 5, 1],
              [2, 3, 8]])

# 计算逆矩阵和验证结果
A_inv = linalg.inv(A)
I_check = A.dot(A_inv)

# 打印原矩阵
print("原矩阵 A:")
print(A)

# 打印逆矩阵
print("\n逆矩阵 A^-1:")
print(A_inv)

# 验证 A·A^-1 是否接近单位矩阵
# 在计算机里用浮点数求逆时,A·A^-1 的结果往往不是完全精确的单位矩阵,而是与单位矩阵非常接近(存在微小的数值误差)
print("\nA·A^-1 的结果 (应接近单位矩阵 I):")
print(I_check)
txt 复制代码
原矩阵 A:
[[1 3 5]
[2 5 1]
[2 3 8]]

逆矩阵 A^-1:
[[-1.48  0.36  0.88]
[ 0.56  0.08 -0.36]
[ 0.16 -0.12  0.04]]

A·A^-1 的结果 (应接近单位矩阵 I):
[[ 1.00000000e+00 -1.11022302e-16 -5.55111512e-17]
[ 3.05311332e-16  1.00000000e+00  1.87350135e-16]
[ 2.22044605e-16 -1.11022302e-16  1.00000000e+00]]

注意:由于浮点数精度,A·A^-1 并非完全等于单位矩阵,但误差通常在 10−1610^{-16}10−16 级别,非常接近。

可视化说明 :通过 matshow 可以直观显示矩阵元素及其逆矩阵的关系(将 AAA 和 A−1A^{-1}A−1 相乘,结果应接近单位矩阵)。

python 复制代码
import matplotlib.pyplot as plt
import seaborn as sns

sns.set_theme(style="whitegrid", font="SimHei", rc={"axes.unicode_minus": False})

# 可视化函数
def plot_matrix(ax, mat, title):
    im = ax.imshow(mat, cmap="Blues")

    # 设置格子边框
    ax.set_xticks(np.arange(mat.shape[1]+1)-0.5, minor=True)
    ax.set_yticks(np.arange(mat.shape[0]+1)-0.5, minor=True)
    ax.grid(which="minor", linestyle='-', linewidth=1.5)
    ax.tick_params(which="minor", bottom=False, left=False)

    # 去掉主刻度
    ax.set_xticks([])
    ax.set_yticks([])

    # 标题
    ax.set_title(title, fontsize=12)

    # 获取归一化器
    norm = im.norm

    # 数值标注,根据背景深浅自动调整字体颜色
    for i in range(mat.shape[0]):
        for j in range(mat.shape[1]):
            value = mat[i, j]
            color = "white" if norm(value) > 0.5 else "black"
            ax.text(j, i, f"{value:.2f}",
                    ha="center", va="center", color=color, fontsize=10)

    # colorbar
    plt.colorbar(im, ax=ax, fraction=0.046)

# 绘制子图
fig, axes = plt.subplots(1, 3, figsize=(12, 4))
titles = ["矩阵 A", "逆矩阵 A^-1", "A·A^-1 ≈ I"]

for ax, mat, title in zip(axes, [A, A_inv, I_check], titles):
    plot_matrix(ax, mat, title)

plt.tight_layout()
plt.show()

应用场景:解线性方程组、系统控制问题、数学建模中的逆运算。


4. 求解线性方程组 (Solve Linear Systems)

线性方程组形式:
A⋅x=b A \cdot x = b A⋅x=b

SciPy 提供 linalg.solve,比直接求逆再乘向量更高效、稳定:

python 复制代码
# 数据
A = np.array([[1, 2],
              [3, 4]])
b = np.array([5, 6])
x = linalg.solve(A, b)

print("线性方程组的解 x 为:", x)
txt 复制代码
线性方程组的解 x 为:[-4.   4.5]

SciPy 提供 linalg.solve,比直接求逆再乘向量更高效、稳定:

结果显示两条直线相交于一点,这个交点就是方程组的唯一解。

python 复制代码
from matplotlib.patches import FancyArrow, Polygon

x_vals = np.linspace(-5, 5, 400)
y1 = (5 - x_vals) / 2
y2 = (6 - 3 * x_vals) / 4

fig, ax = plt.subplots(figsize=(6, 6))

# 美化颜色和线条
line_colors = {"y1": "#1f77b4", "y2": "#ff7f0e"}  # 蓝色和橙色,柔和饱和
point_color = "#d62728"  # 红色点
sns.lineplot(x=x_vals, y=y1, ax=ax, color=line_colors["y1"], zorder=4, linewidth=2)
sns.lineplot(x=x_vals, y=y2, ax=ax, color=line_colors["y2"], zorder=4, linewidth=2)
sns.scatterplot(x=[x[0]], y=[x[1]], color=point_color, s=80, ax=ax, zorder=6)

# 生成透明白色蒙版
mask_radius = 0.5  # 蒙版半径
mask = Polygon([[x[0] - mask_radius, x[1] - mask_radius],
                [x[0] + mask_radius, x[1] - mask_radius],
                [x[0] + mask_radius, x[1] + mask_radius],
                [x[0] - mask_radius, x[1] + mask_radius]],
                closed=True, color="white", alpha=0.5, zorder=2)
ax.add_patch(mask)

# 整数刻度范围
xticks = np.arange(-5, 6, 1)
yticks = np.arange(-5, 6, 1)
ax.set_xticks(xticks)
ax.set_yticks(yticks)
ax.set_xlim(xticks[0]-0.5, xticks[-1]+0.5)
ax.set_ylim(yticks[0]-0.5, yticks[-1]+0.5)

# 网格
ax.set_axisbelow(True)
ax.grid(which="major", color="gray", linestyle="--", linewidth=0.5, alpha=0.5, zorder=0)

# 隐藏默认刻度数字
ax.set_xticklabels([])
ax.set_yticklabels([])

# 手动画刻度线
tick_len = 0.15
fontsize = plt.rcParams["xtick.labelsize"]

for xt in xticks:
    if xt == 0:
        continue
    ax.plot([xt, xt], [0, tick_len], color="black", lw=1, zorder=3)
    ax.text(xt, tick_len-0.25, str(int(xt)), ha="center", va="top", fontsize=fontsize, zorder=4)

for yt in yticks:
    if yt == 0:
        continue
    ax.plot([0, tick_len], [yt, yt], color="black", lw=1, zorder=3)
    ax.text(tick_len-0.25, yt, str(int(yt)), ha="right", va="center", fontsize=fontsize, zorder=4)

# 原点 "0"
zero_x = tick_len - 0.3
zero_y = tick_len - 0.25
ax.text(zero_x, zero_y, "0", ha="right", va="top", fontsize=fontsize, zorder=4)

# 坐标轴穿过原点
for spine in ["left", "bottom"]:
    ax.spines[spine].set_position("zero")
    ax.spines[spine].set_color("black")
for spine in ["top", "right"]:
    ax.spines[spine].set_color("none")

# 箭头
arrow_style = dict(width=0.02, head_width=0.25, head_length=0.3,
                   length_includes_head=True, color="black", linewidth=0.5)
xlim, ylim = ax.get_xlim(), ax.get_ylim()
ax.add_patch(FancyArrow(xlim[0], 0, xlim[1]-xlim[0], 0, **arrow_style, zorder=5))
ax.add_patch(FancyArrow(0, ylim[0], 0, ylim[1]-ylim[0], **arrow_style, zorder=5))

# 在函数线旁边添加标签
ax.text(1, 2.2, "x + 2y = 5", color=line_colors["y1"], fontsize=14, weight="bold")
ax.text(1.2, -1.8, "3x + 4y = 6", color=line_colors["y2"], fontsize=14, weight="bold")

# 生成透明白色蒙版
mask = Polygon([[x[0], x[1] - 0.7],
                [x[0] + 0.9, x[1] - 0.7],
                [x[0] + 0.9, x[1] - 0.1],
                [x[0], x[1] - 0.1]],
                closed=True, color="white", alpha=0.6, zorder=5)
ax.add_patch(mask)

# 原点解标注
ax.text(x[0]-1.1, x[1]-0.5, f"({x[0]:.1f},{x[1]:.1f})", color=point_color, fontsize=12, weight="bold", zorder=10)

# 解点投影到 x 轴
ax.plot([x[0], x[0]], [0, x[1]], color="gray", linestyle="--", linewidth=1, zorder=2)
# 解点投影到 y 轴
ax.plot([0, x[0]], [x[1], x[1]], color="gray", linestyle="--", linewidth=1, zorder=2)

# 标题
ax.set_title("线性方程组的几何解", fontsize=14, weight="bold")

plt.show()

应用场景:物理建模、数据拟合、工程优化问题。


5. 行列式 (Determinant)

数学定义:
det⁡(A)=∑(−1)i+jaijMij \det(A) = \sum (-1)^{i+j} a_{ij} M_{ij} det(A)=∑(−1)i+jaijMij

行列式不仅用于判断矩阵是否可逆,还可用于几何解释,如二维矩阵作用下面积的缩放倍数。

示例:

python 复制代码
A = np.array([[1, 2], [3, 1]])
det_A = linalg.det(A)
print("矩阵 A 的行列式为:", det_A)
txt 复制代码
矩阵 A 的行列式为: -5.0

可视化面积缩放:

python 复制代码
# 定义单位正方形的顶点(顺时针或逆时针)
square = np.array([[0, 0],
                   [1, 0],
                   [1, 1],
                   [0, 1],
                   [0, 0]])  # 回到起点方便画图

# 矩阵作用到每个顶点上
transformed = square.dot(A.T)  # 注意 A.T 是因为我们是行向量表示顶点
print("单位正方形每个顶点经过矩阵 A 变换后的坐标:")
for i, point in enumerate(transformed):
    print(f"顶点 {i}: {point}")
txt 复制代码
单位正方形每个顶点经过矩阵 A 变换后的坐标:
顶点 0: [0 0]
顶点 1: [1 3]
顶点 2: [3 4]
顶点 3: [2 1]
顶点 4: [0 0]
python 复制代码
# 绘制原单位正方形
plt.plot(square[:, 0], square[:, 1], "b--", linewidth=2)
for i, point in enumerate(square[:-1]):  # 最后一个点重复,已连接
    if i == 0:  # 只为第一个点标记 A(A')
        plt.text(point[0], point[1], "A(A')", color="black", fontsize=12, ha='right', va='center')
    elif i == 1:  # 对第一个变换后的点进行特定调整
        plt.text(point[0] + 0.05, point[1] , "B", color="black", fontsize=12, ha='left', va='center')
    elif i == 2:  # 对第二个变换后的点进行调整
        plt.text(point[0] + 0.05, point[1], "C", color="black", fontsize=12, ha='left', va='center')
    elif i == 3:  # 对第三个变换后的点进行调整
        plt.text(point[0] - 0.05, point[1], "D", color="black", fontsize=12, ha='right', va='center')

# 绘制变换后的平行四边形arrow_style
plt.plot(transformed[:, 0], transformed[:, 1], "r-", linewidth=2)
plt.fill(transformed[:, 0], transformed[:, 1], alpha=0.2, color="red")
for i, point in enumerate(transformed[:-1]):  # 最后一个点重复,已连接
    if i == 0:  # 不为第一个点显示文本
        continue
    # 定制每个顶点的标记
    elif i == 1:  # 对第一个变换后的点进行特定调整
        plt.text(point[0], point[1], "B'", color="black", fontsize=12, ha='right', va='bottom')
    elif i == 2:  # 对第二个变换后的点进行调整
        plt.text(point[0] + 0.05, point[1] + 0.1, "C'", color="black", fontsize=12, ha='left', va='top')
    elif i == 3:  # 对第三个变换后的点进行调整
        plt.text(point[0] + 0.1, point[1] - 0.05, "D'", color="black", fontsize=12, ha='left')

# 设置标题
plt.title(f"矩阵 A 作用下单位正方形的变换 (det(A) = {det_A:.2f})")
plt.axis("equal")
plt.show()

应用场景:矩阵可逆性判定、线性相关性分析、几何面积缩放。


6. 矩阵范数 (Norms)

矩阵范数是将矩阵映射为非负实数的函数,用于衡量矩阵的整体"大小"。

常用范数:

  • Frobenius 范数:平方和开方,相当于二维向量的 L2 范数推广。
  • L1 范数:各列元素绝对值之和最大值。
  • L∞ 范数:各行元素绝对值之和最大值。
python 复制代码
print("矩阵范数示例:")
A = np.array([[1, 2], [3, 4]])
print("矩阵 A =\n", A)
print("Frobenius 范数 ||A||_F =", linalg.norm(A))         # 默认 Frobenius 范数
print("L1 范数 ||A||_1 =", linalg.norm(A, 1))            # 列和范数
print("L∞ 范数 ||A||_inf =", linalg.norm(A, np.inf))     # 行和最大值
txt 复制代码
矩阵范数示例:
矩阵 A =
[[1 2]
[3 4]]
Frobenius 范数 ||A||_F = 5.477225575051661
L1 范数 ||A||_1 = 6.0
L∞ 范数 ||A||_inf = 7.0

可视化二维向量在不同范数下的单位圆/菱形/方形:

python 复制代码
theta = np.linspace(0, 2*np.pi, 400)

# L2 范数单位圆
x2 = np.cos(theta)
y2 = np.sin(theta)

# L1 范数单位菱形
x1 = np.array([1, 0, -1, 0, 1])
y1 = np.array([0, 1, 0, -1, 0])

# L∞ 范数单位方形
x_inf = np.array([1, 1, -1, -1, 1])
y_inf = np.array([1, -1, -1, 1, 1])

plt.figure(figsize=(7,7))
plt.plot(x2, y2, label='Frobenius / L2', color='dodgerblue', linewidth=2)
# plt.fill(x2, y2, color='dodgerblue', alpha=0.1)

plt.plot(x1, y1, label='L1 范数', color='orange', linewidth=2)
# plt.fill(x1, y1, color='orange', alpha=0.1)
plt.scatter(x1[:-1], y1[:-1], color='orange')  # 顶点标记

plt.plot(x_inf, y_inf, label='L∞ 范数', color='green', linewidth=2)
# plt.fill(x_inf, y_inf, color='green', alpha=0.1)
plt.scatter(x_inf[:-1], y_inf[:-1], color='green')  # 顶点标记

plt.axis('equal')
plt.grid(True, linestyle='--', alpha=0.5)
plt.legend(fontsize=12)
plt.title("二维向量在不同范数下的单位球形状", fontsize=14)
plt.xlabel("x轴")
plt.ylabel("y轴")
plt.show()

应用场景:优化问题中的正则化、误差分析、数值稳定性检查。


7. 总结与建议

  • scipy.linalg 是科研计算中更推荐的线性代数库,相比 NumPy 更强大和高效。
  • 核心功能包括:矩阵求逆、线性方程组求解、行列式计算、范数计算等。
  • 建议配合 可视化 理解几何意义,例如交点、面积缩放、向量长度变化。

学习路径 :从 invsolve 入手,逐步扩展到 detnorm、特征值分解等高级方法。

相关推荐
伊织code3 小时前
Django - DRF
后端·python·django·drf
仰泳的熊猫3 小时前
LeetCode:239. 滑动窗口最大值
数据结构·c++·算法·leetcode
CoovallyAIHub3 小时前
版本号突袭!官方预览:YOLO26正式宣布,10月发布,CPU推理速度提升43%
深度学习·算法·计算机视觉
cwll20093 小时前
使用ffmpeg8.0的whisper模块语音识别
人工智能·whisper·语音识别
wanhengidc3 小时前
云手机:云计算的灵动化身
运维·服务器·人工智能·游戏·智能手机·云计算
蒋会全3 小时前
第2节 人工智能、机器人、编程之间的关系?
人工智能·机器人
学习路上_write3 小时前
新版Pycharm添加导入anaconda的python解释器
开发语言·python·pycharm
光影少年3 小时前
AI大模型开发语言排行
开发语言·人工智能
张较瘦_3 小时前
[论文阅读] 人工智能 + 软件工程 | 当传统调试遇上LLM:CodeHinter为新手程序员打造专属辅助工具
论文阅读·人工智能