| 在数值代数的语境中,对于对称正定矩阵A,要求解 4 ~7 I) h( A' ~" W7 }
  9 |  G- m* \* t 就可以考虑共轭梯度法。共轭梯度法(与其切比雪夫加速变种)是求解对称正定线性系统的标准算法,但共轭梯度法也经常出现在非线性优化的语境里,这是由于上面线性系统的求解与下面的极小化问题等价:
 6 o8 [: y8 `) |
  7 t+ i3 R" M9 F* b3 C/ Z. n2 ] 从优化的角度看有诸多便利,比如可以求梯度:+ j4 f5 {/ ^$ u! F
 
  ; {$ F7 ?: O& Y( Z. \9 j1 |& |3 y! v用梯度下降可以求解线性方程,这个效率不太高,于是有共轭梯度法来救场。+ Q' R3 {. ?- H
 若干假设
 + E0 a  _8 J6 @8 M7 b0 B9 e4 _+ Q7 K0 x. t/ K; I6 i
 接下来将在以下几个假设下推导出共轭梯度法:! M. V, ^  B2 \5 A7 C, P
 
 共轭梯度法的性质3 z8 t( V6 {. |& N0 }) m3 Z8 [& F3 l! c2 N
 首先CG作为一种梯度法,其核心要素在于下降方向与步长:. d/ G4 Z, P* k( W8 }
  其中  是方向,  是步长。1 ]- E+ V0 o: X2 F+ C 
 构成矩阵A导出内积空间下的正交基,4 y. a5 f7 j3 V+ W7 W$ c 
  由此,  可以写成5 S! [  a8 [) o# C8 }! @# \( W* v 
  5 L8 o1 n3 Q, s" {$ Y 
对于迭代点 , 还希望其具有某种最优性,比如: {) f  M( p% I3 e" l2 `
  其中  ,随着  空间的扩张,  会逐渐逼近  .! j$ R8 J) @9 m/ B8 ^$ @+ b 这个最优性是共轭梯度法的本质,如果变更最优准则(比如二范数),还可以导出其他算法,不提。
 : Q+ Q( z- i, i
 也不能随意取,需要充分利用已知信息,取$ o: @3 z+ N$ k* d 
  其中  , 也挺合理,即为之前点所在位置的梯度所构成的子空间。9 e' F/ g$ d8 Y4 C5 M  y5 u 另外,给定初始值
  后,初始方向也有了,  9 E( P  X% {1 d- x9 _' z
 : `+ F9 P' |4 g/ a4 Y) d! K
 有了上面这些对于算法的假设或者限制,就可以得到更多关于算法的性质了:
 : ~0 F2 ?/ R* G' t$ U9 b
 0 H* D) `" d! J& H
 步长的实质与 的表示如。果没有假设3的话,  是可以随意取值的,然而有了性质3,就有:. T3 G- I8 {+ S7 p 
  6 B, z2 `$ |0 P% q* Y2 f由于正交性,立马得到
  .这说明了  ,即有限步一定收敛,在收敛性分析中,虽然有限步收敛听起来非常强,但通常N非常大,最后分析线性收敛的界更具有参考性。/ t# N& E2 _/ X, j) w( I" `3 t 
 1 G5 @. j& d' }& P5 [算例2 N$ s" Y1 P) T% k( j% N
 由假设4,由于 , 那么有  6 Z% N1 R" o/ C 
除了上面这条,还有一条非常类似的,由+ F( k0 f8 f* F) W$ ]6 f% f
  推出:. Z2 N! O, o) I! j, q( \% X1 W 
  当  时,由正交性,就有% f& ]$ j/ y; ?+ g5 `* W1 ~+ W 
  即  与  .! I2 z. \0 I  P' F* R 
共轭梯度法已经呼之欲出了,利用正交化,先求出方向:7 S6 M2 W  h* {! B1 c$ P7 Q/ p. c
   5 q* U& d2 t- Q
再考虑下具体步长,由于- d$ v8 u5 e" U7 k, p
  那么有 5 g7 D! h. S. t6 y. ^/ o
  也即 0 J* B' G* Y5 d" I( F 
 : V: K4 M6 n6 A4 X8 h
  " M  `& V4 }8 B6 g# W: A2 f( I% S! [0 M8 } 
此时,就得到共轭梯度法的第一个形式* F. W% z" Z0 L$ z. M: ]+ ?2 p 
 ! x( d0 ]: I9 {9 {
  当然,这个形式并不太像教科书上的共轭梯度法,但理论上已经一致了,接下来需要化简。/ ~- ^/ }$ L( K$ C+ T* {8 V 
由于 , 那么有  ,性质 4中的式子可以大幅简化,结合步长公式,变成:8 f8 k" k, F/ j* C 
  ; w0 Y/ N; k: Z" \4 M0 g7 s 
上面的推导需要计算内积 ,这个还可以被优化掉,注意到 ! K) X5 t0 u# T- \" k- x9 Q
  那么有  .' i3 h% l* h9 A  c: u' C 
最后的共轭梯度法:/ ^7 H' }4 I% u% ^7 S! }4 C
  ) o5 X* E5 W+ k5 d# G0 Y1 i 
 6 Z( @9 K1 `* g! g& E; f
 CG的实现非常简单,考虑求解一个Poisson边值问题,上次用Deepritz方法求解过:
 1 ]6 {/ F+ b& K$ L$ G6 s# l
  7 G. \+ p7 s8 s/ p9 h0 l 离散Poisson方程也讲过很多了,这里不讲如何进行方程的理算与构建了,可参考之前的文章, |. H  z4 ~8 a
 派大西:泊松方程求解与卫星组件热布局快速预测.py网格数量为
  , 那么对应的线性系统矩阵  ,初始化  :  l+ L: H9 l% z( _ import numpy as np) ^- i3 x& V/ o2 B8 `- ~
 import scipy as sp1 d. Y/ P% C' J% c- C% Y
 import matplotlib.pyplot as plt* y- p" @7 C) p5 K
 ) @& i! Y1 j1 {" W: S) d$ s
 Nx = 201( T3 ^6 I2 N1 \# Z2 D
 Ny = 201
 4 T( o& }, R% j2 E) mdx = 2 / (Nx - 1)
 # r5 N+ j& L8 ?; Edy = 2 / (Ny - 1)
 . R7 Z. U2 x9 V1 k) ]/ L/ s+ |3 kA = sp.sparse.lil_matrix((Nx * Ny, Nx * Ny))3 U- h7 O2 O! T( `/ c2 z+ v
 b = np.zeros(Nx * Ny)7 M) }  h& ^4 E" Z/ ^& A
 q = lambda x, y: 2 * np.pi ** 2 * np.sin(np.pi * x) * np.sin(np.pi * y)
 g1 @, S4 {: D, Q$ i) o- b5 Lfor i in range(Ny):
 " \: |. ?1 X% x$ Z4 m$ S0 V    for j in range(Nx):5 O% [* r: o0 s6 `4 y
 xp = j * 2 / (Nx - 1) - 1
 ( K* V/ w, U/ V        yp = i * 2 / (Ny - 1) - 1& ?: [9 ]' E# |' l
 if i == 0 or i == Ny - 1 or j == 0 or j == Ny - 1:5 T, @8 f' X* I' x- {
 A[i * Nx + j, i * Nx + j] = 1
 5 u6 Y! ]0 x/ c" L. A/ A, s            b[i * Nx + j] = 0.
 1 y! a! y9 g/ U$ N: ]' O5 V7 i            continue" M6 f' }7 `: h( f
 A[i * Nx + j, i * Nx + j] = 1 / dx + 1 / dy
 ! g- R$ G4 B9 g& i/ ?- h7 t- H5 l; L! U' l        A[i * Nx + j, i * Nx + j + 1] = -1 / 2 / dx! w6 s6 g" G7 C9 m4 z8 t  ~# \
 A[i * Nx + j, i * Nx + j - 1] = -1 / 2 / dx
 & z+ C9 k7 M6 W% E        A[i * Nx + j, (i - 1) * Nx + j] = -1 / 2 / dy
 ; M" B0 }2 k6 }+ I+ Y( ]        A[i * Nx + j, (i + 1) * Nx + j] = -1 / 2 / dy$ {6 r% u+ Y7 T( t! Z& [
 b[i * Nx + j] = q(xp, yp)
 - D; N7 m7 c: x# P( q: c9 z定义共轭梯度法:- R0 R% S5 f! J/ ~8 |
 def cg(A, b, x0=None, tor=1e-10):
 3 p; m% F2 q4 O* ]/ N/ G    N = len(b)
 [& q% B/ t: V. S' a8 _* D    if x0 is None:) u' W8 e* I8 _+ N
 x = np.random.randn(N)
 , j8 i8 c, X( X! a& V    else:: J/ G2 x5 ?9 W/ @
 x = x0.copy()
 2 }6 Z5 y* b2 o0 D. e: A    res = np.Inf
 % r$ f) s3 ]: G8 ]: g, t    r = b - A @ x
 2 E. q) X0 C6 g% ?" N3 X    d = r" I0 |5 ]( d5 C( H" Q" h
 r_inner_old = np.sum(r * r)
 0 s  ?1 S) {% J1 `" e2 G    iteration = 0
 8 Y' g0 `2 J* O4 `! g! f    while res > tor:
 : x4 U8 v+ A+ r& {        Ad = A @ d4 h' f- B- J+ ~# n+ _1 @
 alpha = r_inner_old / np.sum(Ad * d)
 ! P8 E* _. q- e; h( n  \        x = x + alpha * d
 , D  d: F$ m$ Y; @; ]7 T        if iteration % 50 != 0:! ?$ r& e/ }0 E: B
 r = r - alpha * Ad
 $ ?; l5 \7 [3 c% Q5 V3 Q& Y5 h  @        else:7 D6 ]: h+ J* D0 E/ u$ d, J8 G* y
 r = b - A @ x
 ' G: H2 ]+ ], `& t$ E4 f) A( f6 ^        r_inner_new = np.sum(r * r)
 $ ?/ s# R5 Q# n# A0 j( a        beta = r_inner_new / r_inner_old
 8 }9 [* _% D$ n" D2 T- g: a- }# k        d = r + beta * d
 9 q4 a; ^" _- R0 \/ I4 q        r_inner_old = r_inner_new" n. ~) w' n) G; T2 e; D
 res = np.linalg.norm(r_inner_new)
 9 A5 i) d' ]8 @1 Z! k        print(iteration, res)
 z) z+ i1 o* r! Z" s5 q+ w5 T' Z; V        iteration += 1
 , z! l+ J# m* a7 B- W4 Y    return x: o% W, G/ ]" W) v1 ~  N+ a( g
 . R5 u8 @5 \0 h/ Z) s! ]
 最后求解得到:
 " l. Q: L0 Q( d5 R
 * h1 F& a3 C- Q- q/ Y) Z
   3 m9 Q( `3 a) e8 @" z! V0 \看起来没啥问题,然后是时间与规模的关系:
 3 S1 A3 }7 a; Q. E, r
 M% a) C, U) V6 S2 V
   ( y7 u' k; w# r" o0 L时间的增长可能来源于矩阵向量乘法的耗时增加与迭代步数的增加,看起来会比迭代步数增长更快,如果追求更高的规模或精度,就需要引入一些预条件了,下次再说吧。
 |