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法)。