Conjugate Gradient Methods
1. Introduction
CG方法一開始發明在上個世紀(1952年,Hestenes and Stiefel),作爲直接求解的方法,一開始的動力是因爲那時代的電腦完全沒有我們現在的電腦強大。在更早的時候,甚至沒有電腦存在,那種情況下使用基礎的牛頓法求解一個幾百個參數的系統,幾乎是不可能的(因爲我們要對一個100x100的矩陣求逆!),而在那個時代,幾乎沒有人會使用牛頓法。於是,聰明的數學家就發明這種共軛梯度方法(CG),CG的特點就是:
- 不需要完整的將A矩陣的逆()表達出來。
- 而需要的只是:能夠衡量A和一個向量的點乘(evaluate A*x)。
現在的時代,我們的計算機已經非常非常強大了,100x100的系統對我們來說非常非常小,幾個毫秒就可以求解完。最初提出CG所想要解決的問題已經不存在了,不過CG可以解決我們遇到的其他問題:
計算機內存不足的問題(因爲不需要真正存儲整個A矩陣或者A的逆)
- 有一些情況下,A可能不能直接給到我們(比如處於公司保密等情況),但是他們能提供Ax的結果。這種情況下,CG也是很合適的。(當然,我們可以通過Ae,分別點乘所有的基單位向量恢復原本的A)
- A可能具有特殊結構的情況,比如我們下面將看到的例子(A是一個塊矩陣和一個稀疏矩陣的和)這種情況下我們有。我們不需要直接存儲完整的A矩陣。
更快速的算法(因爲CG是一個迭代的過程,也就是說我們可以提前停止)。之後我們會發現,A爲n乘n矩陣的情況下,n次迭代的CG和Newton法是可以得到相同結果的。但是我們可以根據我們對精度的要求提前結束迭代(這個特性會由A矩陣的譜分析結果決定)。舉個例子,假設有個變量,如果直接使用Newton法,需要的flop,大概爲.但是由於A的矩陣特性,可能使用CG進行10次迭代就可以得到的精度,每次CG的flop大約爲(進行A矩陣和一個向量的點乘),那10次CG一共需要量級的flop。相對於,這個速度的提升幾乎是不可想象的(當然如果我們知道A的稀疏特性,可以在求解Newton法的時候運用它,也可以得到類似的加速效果)。
2. Algorithms
- 從共軛向量序列出發,見《最優化導論》的第十章“共軛方向法”。
- 從Krylov 序列出發,見斯坦福公開課“convex optimization II”的第13節課
2.1 Krylov sequence
下面主要討論從Krylov sequence出發的理解。首先根據定義,定義一串Krylov sequence。
2.2 d和x序列
然後我們定義x和d的序列。
2.3 證明我們建立的d序列是一個共軛向量序列
之後的證明步驟略,見《最優化導論》10.3章,127頁。
2.4 建立更優的alpha和beta參數序列
2.5 譜分析
更多細節見“convex optimization II”課程
2.6 CG特性
3. Matlab Implementation
https://gitee.com/gggliuye/cg_pcg/tree/master
3.1 CG
function [ x_res, vec_rhos, sqrt_vec_rhos ] = mine_cg( A, b, stop_threshold, num_iter )
n = length(A);
x = zeros(n,1);
r = b;
stop_rho = stop_threshold * stop_threshold * (b'*b);
rho = r'*r;
vec_rhos = [];sqrt_vec_rhos = [];
vec_rhos = [vec_rhos, rho];
sqrt_vec_rhos = [sqrt_vec_rhos, sqrt(rho)];
p = 0;
for k = 1:num_iter
% whether to stop
if sqrt(rho) < sqrt(stop_rho)
break;
end
if k == 1
p = r;
else
p = r + (vec_rhos(k)/vec_rhos(k-1))*p;
end
w = A * p;
alpha = vec_rhos(k)/(p'*w);
x = x + alpha * p;
r = r - alpha * w;
rho = r'*r;
vec_rhos = [vec_rhos, rho];
sqrt_vec_rhos = [sqrt_vec_rhos, sqrt(rho)];
end
x_res = x;
end
3.2 PCG
function [ x_res, vec_rhos, sqrt_vec_rhos ] = mine_pcg( A, b, L, LT, stop_threshold, num_iter )
n = length(A);
x = zeros(n,1);
r = b;
stop_rho = stop_threshold * stop_threshold * (b' *b);
z = LT\(L\r);
rho = r'* z;
vec_rhos = [];sqrt_vec_rhos = [];
vec_rhos = [vec_rhos, rho];
sqrt_vec_rhos = [sqrt_vec_rhos, sqrt(r'*z)];
p = 0;
z = 0;
for k = 1:num_iter
% whether to stop
if sqrt(rho) < sqrt(stop_rho)
break;
end
if k == 1
p = LT\(L\r);
else
p = z + (vec_rhos(k)/vec_rhos(k-1))*p;
end
w = A * p;
alpha = vec_rhos(k)/(p'*w);
x = x + alpha * p;
r = r - alpha * w;
z = LT\(L\r);
rho = r'* z;
vec_rhos = [vec_rhos, rho];
sqrt_vec_rhos = [sqrt_vec_rhos, sqrt(r'*r)];
end
x_res = x;
end
3.3 Test Result
%block preconditioning solution
clear all;
ex_blockprecond;
fprintf('\nStarting CG ...\n');
time_start = cputime;
[x,flag,relres,iter,resvec] = pcg(A,b,1e-4,200);
time_end = cputime;
fprintf('CG done. Status: %d\nTime taken: %e\n', flag,...
time_end - time_start);
figure; semilogy(resvec/norm(b), '.--'); hold on;
set(gca,'FontSize', 16, 'FontName', 'Times');
xlabel('cgiter'); ylabel('relres');
fprintf('\nStarting Mine CG ...\n');
time_start = cputime;
[x,res,resvec] = mine_cg(A,b,1e-4,200);
time_end = cputime;
fprintf('Mine CG done. Status: %d\nTime taken: %e\n', flag,...
time_end - time_start);
semilogy(resvec/norm(b), '.--'); hold on;
set(gca,'FontSize', 16, 'FontName', 'Times');
xlabel('cgiter'); ylabel('relres');
time_start = cputime;
L = chol(A_blk)';
time_end = cputime;
tchol = time_end - time_start;
fprintf('\nCholesky factorization of A blk. Time taken: %e\n', tchol);
fprintf('\nStarting Mine PCG ...\n');
time_start = cputime;
[x,res,resvec] = mine_pcg(A,b,L, L', 1e-4,200);
time_end = cputime;
fprintf('Mine CG done. Status: %d\nTime taken: %e\n', flag,...
time_end - time_start);
semilogy(resvec/norm(b), '.--'); hold on;
set(gca,'FontSize', 16, 'FontName', 'Times');
xlabel('cgiter'); ylabel('relres');
3.4 Data 1
%example: block precondioned pcg
rand('state', 364);
m = 200; k = 10; n=m*k;
M = sparse(n, n);
for i = 1:k
Asub = rand(m); Asub = 10*Asub*Asub';
M(((i-1)*m+1):(i*m),((i-1)*m+1):(i*m)) = Asub;
end
density=5/(n);
A = -abs(sprandsym(n,density));
v = A*ones(n,1);
Sdiagonal = spdiags(v,0,n,n);
A = A - Sdiagonal + M;
%spy(A);
clear M Sdiagonal Asub v
A_blk = sparse(n,n);
for i = 1:k
A_blk(((i-1)*m+1):(i*m),((i-1)*m+1):(i*m)) = A(((i-1)*m+1):(i*m),((i-1)*m+1):(i*m));
end
b = 10*rand(n,1);
實現的效果和Matlab內置的CG算法完全一致。
3.5 Data 2 SLAM data
3.6 summary
我實現了CG和PCG算法。
- 在未知矩陣稀疏特性的情況下,使用CG和PCG可能可以得到更快更好的結果。(但是由於他們是heuristic,不能保證一定可以得到好的結果)
- 如果已知稀疏特性,矩陣又不是特別大的情況下,直接使用稀疏特性處理,使用Newton法可以得到更好的結果。
- 無論如何CG和PCG在處理非常非常大的問題的情況下,都有非常非常大的優勢(在這裏沒有比較,但是CG和PCG可以處理極其巨大的問題,巨大到現代計算機都無法存儲的Hessien矩陣,無法存儲也就無法使用Newton法)。