共轭梯度法的简单直观理解

共轭梯度法的简单直观理解共轭梯度法的简单直观理解共轭梯度法可以看作是梯度下降法 又称最速下降法 的一个改进

大家好,欢迎来到IT知识分享网。

参考资料

本文是参考以下内容,结合自己的理解做的笔记。请尽量直接访问下述网页。

  1. 矩阵与数值计算(11)——共轭梯度法
  2. 共轭梯度法(一):线性共轭梯度
  3. 无痛版共轭梯度法介绍(更新到第五章)
  4. 共轭梯度法通俗讲义
    第4个资料尤其清晰完备,很多都是参考它的。

What is: 什么是共轭梯度法?(简单直观理解)

共轭梯度法可以看作是梯度下降法(又称最速下降法)的一个改进。

梯度下降法x移动的方向正是函数f的负梯度方向,这代表了局部上f减小最快的方向。

但是局部上减小最快的方向并不代表全局上指向最终解的方向。所以梯度下降法会出现像醉汉下山一样走出zig-zag的路线。如下图
在这里插入图片描述

为什么会走出这一Z形线呢?因为梯度下降的方向恰好与f垂直,也就是说和等高线垂直。沿着垂直于等高线的方向,一定能让函数减小,也就是最快地下了一个台阶。但是最快下台阶并不意味着最快到达目标位置(即最优解),因为你最终的目标并不是直对着台阶的。

为了修正这一路线,采用另一个方向:即共轭向量的方向。

在推进下一步之前,我们来看看什么是向量共轭。

共轭向量

下面先简要介绍共轭向量

可见,共轭是正交的推广化,因为向量正交的定义为:
p i T p j = 0 p_i^Tp_j=0 piTpj=0
共轭比正交中间只多了个矩阵A,而矩阵的几何意义正是对一个向量进行线性变换(可见Gilber Strang的线代公开课)。因此共轭向量的意思就是一个向量经过线性变换(缩放剪切和旋转)之后与另一个向量正交。

共轭方向

言归正传,如何找到一个更好的方向呢?我们首先可以看看最完美的方向是什么样的。

可惜我们并不能找到误差向量e,因为我们不知道精确解。

那么退而求其次,我们就找误差向量的共轭向量。因为图中可以看出,误差向量是与方向向量垂直的,即正交。刚才说了,共轭就是正交的推广。一个向量乘以一个矩阵之后与另一个方向正交,就是共轭。

但是这个公式里面仍然含有e,我们必须想办法去掉它,换成一个我们可以计算的量。

在推进下一步之前,我们先来看看误差与残差这两个概念的区别。

误差与残差

前面写道:

误差error 即当前迭代所得到的解与精确解的差值:
e ⃗ i = x ⃗ i − x ⃗ ∗ \vec e_i=\vec x_i- \vec x^* e
i
=
x
i
x

但是这种定义显然是没法直接用的,因为我们不知道精确解 x ∗ x^* x

那么退而求其次,我们想到,当误差收敛为0的时候,必然满足方程Ax=b,那么由此就可以定义出残差residual

r ⃗ i = b ⃗ − A x ⃗ i \vec r_i=\vec b-A\vec x_i r
i
=
b
Ax
i

这个定义的精妙之处在于,它定义了Ax接近b的距离,当距离为0的时候,恰好就是精确解。但是又能避开精确解本身。

在实际的程序中,我们还常常定义相对残差,即上一步迭代和这一步迭代的残差的相对变化率,这里就不再赘述。

显然,误差和残差之间就差了一个矩阵A,他们两者的关系是这样的:

r ⃗ i = b ⃗ − A ( e ⃗ i + x ⃗ ∗ ) = b ⃗ − A x ⃗ ∗ − A e ⃗ i = − A e ⃗ i \vec r_i=\vec b – A(\vec e_i+\vec x^*)=\vec b – A \vec x^* -A\vec e_i = -A\vec e_i r
i
=
b
A(e
i
+
x
)=
b
Ax
Ae
i
=
Ae
i

可见除了A,还多了个负号。

搜索方向的确定

那么,这告诉我们:方向向量d,正是与残差向量正交的方向!

接下来我们只需要构建一个与残差正交的向量就可以了。这部分内容是由施密特正交化(更严谨一点的说法是共轭格莱姆-施密特过程)完成的。由于只是一个计算的方法,对概念的理解没有帮助,所以我们跳过,直接给出结论。

步长,或者说系数alpha

实际上,还有一点没有解决,就是系数 α \alpha α怎么算?

How to: 怎么用共轭梯度法?(完整算法)

  1. 设定初值
    d ⃗ 0 = r ⃗ 0 = b ⃗ − A x ⃗ 0 \vec d_0=\vec r_0 = \vec b – A \vec x_0 \\ d
    0
    =
    r
    0
    =
    b
    Ax
    0

  2. 计算系数alpha
    α i = r ⃗ i + 1 T r ⃗ i + 1 d ⃗ i T A d ⃗ i \alpha_i = \frac{ \vec r_{i+1}^T \vec r_{i+1} } {\vec d_{i}^T A\vec d_{i} } αi=d
    iT
    Ad
    i
    r
    i+1T
    r
    i+1

  3. 迭代一步(向下走一步)
    x ⃗ i + 1 = x ⃗ i − α i d ⃗ i \vec x_{i+1}=\vec x_i – \alpha_i \vec d_i x
    i+1
    =
    x
    i
    αid
    i

  4. 计算残差(此处已经被修改,原文没有被50整除那一个公式 2022-05-27
    如果迭代次数可以被50整除
    r ⃗ i + 1 = r ⃗ i − α i A x ⃗ \vec r_{i+1}=\vec r_i – \alpha_i A\vec x r
    i+1
    =
    r
    i
    αiAx

    否则
    r ⃗ i + 1 = r ⃗ i − α i A d \vec r_{i+1}=\vec r_i – \alpha_i A d r
    i+1
    =
    r
    i
    αiAd




  5. 计算系数beta
    β i + 1 = r ⃗ i + 1 T r ⃗ i + 1 r ⃗ i T r ⃗ i \beta_{i+1} = \frac{ \vec r_{i+1}^T \vec r_{i+1} } {\vec r_{i}^T \vec r_{i} } βi+1=r
    iT
    r
    i
    r
    i+1T
    r
    i+1

  6. 计算搜索方向 d ⃗ \vec d d

    d ⃗ i + 1 = r ⃗ i + 1 + β i + 1 d ⃗ i \vec d_{i+1} = \vec r_{i+1} +\beta_{i+1} \vec d_i d
    i+1
    =
    r
    i+1
    +
    βi+1d
    i

重复2~6,直到残差足够小

Python代码

更正2024-4-29: 按照wiki重写的代码已经可以正常运行

import numpy as np from scipy.sparse import csr_matrix from scipy.sparse.linalg import cg import scipy.sparse as sp from time import time # judge if A is positive definite # https://stackoverflow.com/a// # if A is symmetric and able to be Cholesky decomposed, then A is positive definite def is_pos_def(A): A=A.toarray() if np.array_equal(A, A.T): try: np.linalg.cholesky(A) print("A is positive definite") return True except np.linalg.LinAlgError: print("A is not positive definite") return False else: print("A is not positive definite") return False def generate_A_b_psd(n=1000): A = sp.random(n, n, density=0.01, format="csr") A = A.T @ A b = np.random.rand(A.shape[0]) print(f"Generated PSD A: { 
     A.shape}, b: { 
     b.shape}") A = sp.csr_matrix(A) assert is_pos_def(A) return A, b def main(): A,b = generate_A_b_psd() t=time() x_sp, exit_code = cg(A, b, atol=1e-5) print("scipy_cg time:", time()-t) t=time() x_my = my_cg(A, b) print("my_cg time:", time()-t) print("error:", np.linalg.norm(x_sp-x_my)) def my_cg(A, b, atol=1e-5): x=np.zeros(A.shape[0]) r0=b-A@x p=r0.copy() r=r0.copy() k=0 while True: Ap = A@p rTr = r.T@r alpha = rTr / (p.T@Ap) x1 = x + alpha * p r1 = r - alpha * Ap if np.linalg.norm(r1)<atol: break beta=r1.T@r1/(rTr) p1=r1+beta*p x=x1.copy() r=r1.copy() p=p1.copy() k+=1 return x1 if __name__ == "__main__": main() 

输出

Generated PSD A: (1000, 1000), b: (1000,) A is positive definite scipy_cg time: 0. my_cg time: 0.82129 error: 0.00032572 

来自wiki

https://en.wikipedia.org/wiki/Conjugate_gradient_method

在这里插入图片描述

PCG

def test_pcg(): A,b = generate_A_b_psd() P = sp.diags(1/A.diagonal()) t=time() x_sp, exit_code = cg(A, b, atol=1e-5, M=P) print("scipy_cg time:", time()-t) t=time() x_my = my_pcg(A, b, atol=1e-5, M=P) print("my_pcg time:", time()-t) print("error:", np.linalg.norm(x_sp-x_my, ord=np.inf)) print("x(first 5):\n", x_sp[:5],"\n", x_my[:5]) # preconditioned conjugate gradient # https://en.wikipedia.org/wiki/Conjugate_gradient_method#The_preconditioned_conjugate_gradient_method # https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.linalg.cg.html # Note: Based on the scipy(https://github.com/scipy/scipy/blob/7dcd8c923cde8e9126f5fc2e6b30b/scipy/sparse/linalg/_isolve/iterative.py#L406), parameter M is actually the inverse of M in the wiki's formula. We adopt the scipy's definition. def my_pcg(A, b, atol=1e-5, M=None): def solvez(r): z = M@r if M is not None else r return z x=np.zeros(A.shape[0]) r=b-A@x r=r.copy() z = solvez(r) p=z.copy() k=0 while True: Ap = A@p rTz = r.T@z alpha = r.T@z / (p.T@Ap) x1 = x + alpha * p r1 = r - alpha * Ap if np.linalg.norm(r1)<atol: break z1 = solvez(r1) beta=r1.T@z1/(rTz) p1=z1+beta*p x=x1.copy() r=r1.copy() p=p1.copy() z=z1.copy() k+=1 return x1 if __name__ == "__main__": test_pcg() 

输出

Generated PSD A: (1000, 1000), b: (1000,) A is positive definite scipy_cg time: 0. my_pcg time: 0. error: 4.59954e-05 x(first 5): [31666. 618.0 1318. -3403. 12217.] [31666. 618.0 1318. -3403. 12217.] 

在这里插入图片描述

Why: 为什么共轭梯度法能求解Ax=b?

说了这么多,其实有一个关键问题没有讲,那就是:为什么共轭梯度法能求解Ax=b?

按理说,共轭梯度法是函数最优化的方法,怎么就扯上了求解Ax=b了呢?

实际上使用共轭梯度法的两个条件

  1. A是对称的
  2. A是正定的

也和这个原理有关。

数学家求解问题的思路是:把不会的问题转化成会的问题,再套用会的问题的思路求解问题。

为了说明这一点,我们要从线性代数的二次型入手。我们可以先复习一下二次型,了解一下它是什么。

二次型

二次型就是关于向量的二次函数。

f ( x ) = x T A x + b x + c f(x) = x^T A x +bx+c f(x)=xTAx+bx+c

这就是二次型。

将Ax=b问题转化为最优化问题

这个问题被转化为了求某个函数的导数等于0的问题,即驻值问题。

我们设这个函数为g(x)。我们的问题即:

g ′ ( x ) = 0 x ∗ = a r g m i n x g ( x ) g'(x)=0\\ x^*=argmin_x g(x) g(x)=0x=argminxg(x)
argmin_x的意思就是我们求取最小值的时候的x,而不是最小值本身。

这个 x ∗ x^* x就是最终解。

那么怎么联系到Ax=b呢?

我们只要改造这个函数g,让它的导数恰好就是Ax-b=0就好了!!
而这个函数,恰好就是二次型函数!

于是求最小值得问题就能够被转化为求Ax=b的问题!

所以我们就要限定 A T = A A^T=A AT=A,即限定A是对称的。这就是第一个条件的由来!

拓展:改进——预处理的共轭梯度法

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/121892.html

(0)
上一篇 2025-10-20 21:00
下一篇 2025-10-20 21:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信