在线性代数中,正交基有许多美丽的性质。例如,由正交列向量组成的矩阵(又称正交矩阵)可以通过矩阵的转置很容易地进行反转。此外,例如:在由彼此正交的向量张成的子空间上投影向量也更容易。Gram-Schmidt过程是一个重要的算法,它允许我们将任意基转换为生成同一子空间的正交基。在这篇文章中,我们将使用一个流行的开源库。manim在3D中实现和可视化这个算法
Gram-Schmidt过程是一种用于将一组线性无关的向量转化为一组正交(或正交归一化)的向量的算法。这个过程在数学和工程中广泛应用,特别是在计算机图形学、信号处理和统计分析中。
Gram-Schmidt 正交化的步骤
假设我们有一组线性无关的向量 ,Gram-Schmidt过程的步骤如下:
- 初始化:给定向量,定它为第一个正交向量 :
2.后续向量的正交化:对于每个,计算新的正交向量 :
其中,投影 是:
这里表示向量的点积。
3.归一化(可选):如果需要将向量归一化,使其单位长度,可以通过以下公式进行:
其中 是向量的模。
应用
- 正交基:Gram-Schmidt过程的输出结果是正交基,可以用于简化内积空间中的计算。
- 数值稳定性:在计算中使用正交向量能提高数值稳定性,特别是在进行矩阵分解(如QR分解)时。
- 计算简化:在许多应用中(如最小二乘法),正交向量可以简化计算,使得更容易处理高维数据。
用manim实现向量的表示
代码为了实现 Gram-Schmidt 正交化的可视化过程而构建,使用了 Manim 库。
from manim import * # 导入 Manim 库
import numpy as np # 导入 NumPy 库以进行数值计算
from enum import Enum # 导入枚举模块以创建动作类型
# 定义颜色
basis_i_color = GREEN # 基向量 i 的颜色
basis_j_color = RED # 基向量 j 的颜色
basis_k_color = GOLD # 基向量 k 的颜色
q_color = PURPLE # 向量 q 的颜色
q_shifted_color = PINK # 移动的向量 q 的颜色
projection_color = BLUE # 投影向量的颜色
# 定义动作枚举,便于管理不同的操作
class Action(Enum):
UPDATE_MATRIX_REMOVE_Q = 1 # 更新矩阵并移除 q 向量
ADD_PROJECTION = 2 # 添加一个投影向量
REMOVE_PROJECTIONS_SET_Q = 3 # 移除投影向量并设置新的 q 向量
NORMALIZE_Q = 4 # 规范化 q 向量
# 实现 Gram-Schmidt 过程
def gram_schmidt(A):
(n, m) = A.shape # 获取矩阵 A 的形状
for i in range(m):
q = A[:, i] # 选取矩阵 A 的第 i 列
for j in range(i):
# 计算并从 q 中减去之前列的投影
projection = np.dot(A[:, j], A[:, i]) * A[:, j]
q = q - projection
# 归一化 q 向量
q /= np.linalg.norm(q)
yield Action.NORMALIZE_Q, q # 返回规范化的 q 向量
# 创建一个新的场景,用于展示 Gram-Schmidt
class GramSchmidt(Scene):
def construct(self):
# 创建基础向量的颜色,并初始化向量和矩阵 M
M = np.random.rand(3, 3) # 随机初始化 3x3 矩阵 M
projection_vectors = [] # 初始化用于存储投影向量的列表
q = None # 初始化 q 向量为 None
# 初始化基向量箭头
i_vec = Vector(M[:, 0], color=basis_i_color) # i 向量
j_vec = Vector(M[:, 1], color=basis_j_color) # j 向量
k_vec = Vector(M[:, 2], color=basis_k_color) # k 向量
# 播放箭头和矩阵的动画
self.play(GrowArrow(i_vec), GrowArrow(j_vec), GrowArrow(k_vec))
self.wait()
# 遍历 Gram-Schmidt 的步骤
for (action, payload) in gram_schmidt(M):
if action == Action.UPDATE_MATRIX_REMOVE_Q:
# 此步骤用于更新矩阵,并移除旧的 q 向量
assert q is not None
M_rounded = np.round(M.copy(), 2) # 对矩阵 M 进行四舍五入
matrix = self.create_matrix(M_rounded) # 创建新的矩阵对象
self.remove(matrix) # 移除旧的矩阵
# 更新基向量
i_vec_new = Vector(M[:, 0], color=basis_i_color)
j_vec_new = Vector(M[:, 1], color=basis_j_color)
k_vec_new = Vector(M[:, 2], color=basis_k_color)
animation_time = 2.0 # 动画时间
# 播放更新基向量的动画
self.play(
FadeOut(q, run_time=animation_time * 0.75),
ReplacementTransform(i_vec, i_vec_new, run_time=animation_time),
ReplacementTransform(j_vec, j_vec_new, run_time=animation_time),
ReplacementTransform(k_vec, k_vec_new, run_time=animation_time)
)
self.wait() # 等待一段时间
# 更新现有向量引用
i_vec, j_vec, k_vec = i_vec_new, j_vec_new, k_vec_new
elif action == Action.ADD_PROJECTION:
# 添加新的投影向量
p = Vector(payload, color=projection_color)
projection_vectors.append(p) # 将投影向量添加到列表中
self.play(GrowArrow(p)) # 播放投影箭头的动画
self.wait() # 等待
if len(projection_vectors) == 2:
# 当有两个投影向量时,更新其显示效果
first_projection_end = projection_vectors[0].get_end()
p_shifted = Arrow(first_projection_end, first_projection_end + payload, buff=0, color=projection_color)
projection_vectors[1] = p_shifted
self.play(ReplacementTransform(p, p_shifted)) # 替换动画
self.wait() # 等待
elif action == Action.REMOVE_PROJECTIONS_SET_Q:
# 移除投影并设置新的 q 向量
if not projection_vectors:
q = Vector(payload, color=q_color)
self.play(GrowArrow(q)) # 播放 q 向量的动画
self.wait()
else:
last_projection_end = projection_vectors[-1].get_end()
q_shifted = Arrow(last_projection_end, last_projection_end + payload, buff=0, color=q_shifted_color)
self.play(GrowArrow(q_shifted))
self.wait()
q = Vector(payload, color=q_color) # 设置新的 q 向量
self.play(
ReplacementTransform(q_shifted, q),
*[FadeOut(p) for p in projection_vectors] # 移除所有投影
)
self.wait()
projection_vectors = [] # 清空投影向量列表
elif action == Action.NORMALIZE_Q:
# 规范化 q 向量
q_normalized = Vector(payload, color=q_color)
self.play(ReplacementTransform(q, q_normalized)) # 播放规范化动画
self.wait()
q = q_normalized # 更新 q 向量
else:
assert False # 确保没有未识别的动作
self.wait(1) # 在每轮操作间等待
# 验证结果
assert np.allclose(M.T @ M, np.identity(3)) # 确保 M 的转置乘以 M 为单位矩阵
self.wait(15) # 等待更长时间以查看最终结果
代码解释
- 依赖导入:导入了 Manim 和 NumPy 库,Manim 用于创建动画,NumPy 用于数值计算。
- 颜色和动作类型定义:定义了不同颜色用于表示基向量、投影、规范化向量等,并通过枚举类型管理不同的动画操作。
- 实现 Gram-Schmidt:定义了
gram_schmidt
函数,用于计算 Gram-Schmidt 正交化,逐列处理输入矩阵。 - 创建场景:在
GramSchmidt
类中,设置基向量和矩阵,并实现了多个动画步骤,以展示算法的每一步。 - 动画逻辑:根据不同的
Action
执行相应的动画,包括更新向量、添加和移除投影向量、规范化向量等。 - 验证结果:程序的最后一步确保生成的矩阵是正交的,即其转置乘以自身等于单位矩阵。
你可以根据自己的需求调整参数、颜色或动画,以实现所需的效果。
运行结果: