共轭梯度法
共轭梯度法(conjugate gradient method,CG)是求解实对称正定线性系统的 Krylov 子空间迭代法。它沿相互 A 共轭的方向逐次修正,使二次能量在不断扩大的子空间内最小。每轮主要需要一次矩阵向量积,适合大型稀疏系统;非对称或不定矩阵不满足本方法的基本假设。
两步解出一个二阶系统
取 初始残差 ,首方向 。因为 ,步长为 。故 新残差与旧残差的内积为零。取 ,新方向为 。计算 ,所以 、。第二步得到 代回两行方程分别为一和二,残差为零。这里只用了两步,来自精确算术下二维空间的有限终止性质,不意味着所有大问题都只要少量步数。
二次能量与共轭方向
对称正定性使 有唯一极小点 ,因为 因此最小化能量等价于最小化 A 范数误差。方向 若满足 ,称为 A 共轭,它们通常不是普通欧氏正交。
沿方向 令 ,对 求导得到 。在 CG 的正交关系下 ,所以 SciPy 的接口明确要求矩阵为 Hermitian 正定,并以残差容限决定是否收敛。[1]
正交递推与有限终止的证明
可用归纳法证明:到第 步,方向 两两 A 共轭,残差 与此前方向正交。新残差满足 对更早的 ,新残差与其内积等于 。由于此前残差由此前方向线性组合而成,新残差也与此前残差正交。
要让新方向与当前方向共轭,应取 。利用 和残差正交,分子为 ,代入便得到上述平方残差比。对 ,,新残差与两项均正交,所以新方向也与所有早期方向共轭。归纳闭合。
每个非零共轭方向线性独立:若线性组合为零,与其中一个方向作 A 内积,正定性迫使它的系数为零。因此在 维精确算术下最多产生 个方向。此时残差与整个空间正交,只能为零。相同正交关系还说明 在 上最小化能量,其中 。
浮点运算会破坏精确正交和有限终止。条件数大时,实际步数与残差核验尤其重要。经典误差界 使用 ;特征值聚簇时实际表现还可能更好。预条件子正是通过改变有效几何来改善这些性质。[2]
可运行的稠密演示
代码输入实对称正定矩阵、右端、容限和最大步数,返回近似及迭代数。它检查对称性和迭代中出现的正曲率,但不以这些有限检查替代完整正定性证明。演示使用稠密乘法,每步 时间,工作向量为 ;稀疏存储时乘法可降为 。不修改输入。
import math
def cg(A, b, tol=1e-12, maxiter=None):
A = [list(map(float, row)) for row in A]
b = list(map(float, b)); n = len(b)
if not n or len(A) != n or any(len(row) != n for row in A):
raise ValueError("square matrix matching nonempty b required")
if not all(math.isfinite(v) for row in A for v in row) or not all(math.isfinite(v) for v in b):
raise ValueError("finite data required")
if any(A[i][j] != A[j][i] for i in range(n) for j in range(i)):
raise ValueError("symmetric matrix required")
if not math.isfinite(tol) or tol <= 0:
raise ValueError("positive finite tolerance required")
maxiter = n if maxiter is None else maxiter
if not isinstance(maxiter, int) or maxiter < 0:
raise ValueError("nonnegative integer maxiter required")
dot = lambda u, v: sum(a*c for a, c in zip(u, v))
mv = lambda p: [dot(row, p) for row in A]
x = [0.0]*n; r = b[:]; p = r[:]; rr = dot(r, r)
if not math.isfinite(rr):
raise OverflowError("initial residual square outside floating-point range")
target = tol*max(1.0, math.sqrt(rr))
if not math.isfinite(target):
raise OverflowError("residual threshold outside floating-point range")
for k in range(maxiter+1):
actual = [v-w for v, w in zip(b, mv(x))]
if math.hypot(*actual) <= target:
return x, k
if k == maxiter:
break
Ap = mv(p); denominator = dot(p, Ap)
if not math.isfinite(denominator) or denominator <= 0 or not math.isfinite(rr):
raise ArithmeticError("breakdown or nonpositive curvature")
alpha = rr/denominator
x = [v+alpha*d for v, d in zip(x, p)]
r = [v-alpha*d for v, d in zip(r, Ap)]
rr_new = dot(r, r)
if not math.isfinite(rr_new) or not all(math.isfinite(v) for v in x):
raise OverflowError("nonfinite iteration")
beta = rr_new/rr
p = [v+beta*d for v, d in zip(r, p)]; rr = rr_new
raise RuntimeError("true residual did not reach tolerance")
print(cg([[4, 1], [1, 3]], [1, 2])) # ([1/11, 7/11], 2), up to rounding有解变式取 、。初始残差已经是一个特征向量,首步 就到达 。若取不定矩阵 和 ,第一步分母为零,直接暴露正定假设的作用;这时应选与不定系统匹配的方法。
参考资料
- ↑ SciPy developers. scipy.sparse.linalg.cg。
- ↑ Richard Barrett et al. Templates for the Solution of Linear Systems,Conjugate Gradient。