%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Copyright (C) 2020 Zaiwen Wen, Haoyang Liu, Jiang Hu
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program. If not, see .
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Change log:
%
% 2015.11 (Zaiwen Wen):
% First version
%
% 2020.4.18 (Jiang Hu):
% Add rules for trust-region radius update
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 信赖域方法解优化问题
% 考虑无约束光滑优化问题:
%
% $$\min_x \;\; f(x). $$
%
% 在第 $k$步迭代,信赖域方法对目标函数 $f(x)$ 在 $x=x^k$ 附近用如下二次函数逼近:
% $\displaystyle m_k(d)=f(x^k)+\nabla f(x^k)^\top d+\frac{1}{2}d^\top B^k
% d$,
% 其中 $B^k$ 是一个对称矩阵,可以是精确海瑟矩阵或者海瑟矩阵的近似。
% 同时,令 $\|d\|\le\Delta_k$ 限制区域大小以保证该二次函数的逼近质量。结合两者,信赖域子问题可以写为:
%
% $$d^k=\arg\min_{d}m_k(d),\quad \mathrm{s.t.}\quad \|d\|\le\Delta_k.$$
%
% 在得到 $d^k$ 之后,计算 $\hat{x}^{k+1} = x^k+d^k$ 和比值 $\rho_k$:
% $$ \displaystyle\rho_k=\frac{f(x^k)-f(\hat{x}^{k+1})}{m_k(0)-m_k(d^k)} $$
% 来确定是否更新当前迭代。如果 $\rho_k$ 较大,我们认为 $m_k$ 在 $x^k$附近逼近 $f(x)$的质量较好,更新 $x^{k+1} = \hat{x}^{k+1}$,
% 并增大信赖域半径 $\Delta_k$(如乘以一个大于1的常数)。否则,则拒绝更新,即令 $x^{k+1} = x^k$,并减小 $\Delta_k$(如乘以一个小于1的数)。
%
% 这里,对于信赖域子问题的求解,我们采用截断共轭梯度法,见 <../newton/tCG.html 截断共轭梯度法>。
%
%% 初始化和迭代准备
% 输入信息:初始迭代点 |x| ,目标函数值和梯度计算函数 |fun| ,海瑟矩阵 |hess| 以及算法求解相关参数的结构体
% |opts| 。
%
% 输出信息:迭代得到的解 |x| 和迭代信息结构体 |out| 。
%
% * |out.msg| :标记停机时算法的求解状态
% * |out.nrmG| :记录迭代过程的梯度范数
% * |out.nrmg| :记录迭代退出时的梯度范数
% * |out.iter| :记录迭代退出时的迭代步数
% * |out.f| :记录迭代退出时的函数值
% * |out.nfe| :调用原函数的次数
function [x, out] = fminTR(x,fun, hess, opts, varargin)
%%%
% 检查输入信息,MATLAB 以 |nargin| 表示函数输入的参数个数。当参数量不足 3 时报错,
% 等于 3 时认为 |opts| 为空结构体,即全部采用默认参数。
%
% 从输入的结构体中读取参数或采取默认参数。
%
% * |opts.ftol| :针对函数值的停机准则
% * |opts.gtol| :针对梯度范数的停机准则
% * |opts.eta1| : $\rho^k$ 的下界(当超出此界时意味着信赖域半径需要缩小并拒绝更新)
% * |opts.eta2| : $\rho^k$ 的上界(当超出此界时意味着信赖域半径需要增大并接受更新)
% * |opts.gamma1| :每次调整信赖域半径缩小的比例
% * |opts.gamma2| :每次调整信赖域半径增大的比例
% * |opts.maxit| :主循环的最大迭代次数
% * |opts.record| :是否需要打印迭代信息
% * |opts.verbose| :是否需要打印半径调整信息
% * |opts.itPrint| :每隔几步输出一次迭代信息
if (nargin < 3); error('[x, out] = fminTR(fun, x, opts)'); end
if (nargin < 4); opts = []; end
if ~isfield(opts,'gtol'); opts.gtol = 1e-6; end
if ~isfield(opts,'ftol'); opts.ftol = 1e-12; end
if ~isfield(opts,'eta1'); opts.eta1 = 1e-2; end
if ~isfield(opts,'eta2'); opts.eta2 = 0.9; end
if ~isfield(opts,'gamma1'); opts.gamma1 = .25; end
if ~isfield(opts,'gamma2'); opts.gamma2 = 10; end
if ~isfield(opts,'maxit'); opts.maxit = 200; end
if ~isfield(opts,'record'); opts.record = 0; end
if ~isfield(opts,'itPrint'); opts.itPrint = 1; end
if ~isfield(opts,'verbose'); opts.verbose = 0; end
%%%
% 从结构体中复制参数。
maxit = opts.maxit; record = opts.record; itPrint = opts.itPrint;
gtol = opts.gtol; ftol = opts.ftol;
eta1 = opts.eta1; eta2 = opts.eta2;
gamma1 = opts.gamma1; gamma2 = opts.gamma2;
%%%
% 迭代准备,计算初始点 $x$ 处的函数值和梯度。
out = struct();
[f,g] = fun(x);
out.nfe = 1;
nrmg = norm(g,2);
out.nrmG = nrmg;
fp = f; gp = g;
%%%
% 信赖域子问题利用截断共轭梯度法求解,利用结构体 |opts_tCG| 为其提供参数。
opts_tCG = struct();
%%%
% 初始化信赖域半径。初始值设定为提供的参数值或默认值 $\sqrt{\mathrm{len}(x)}/8$
% 并令 $\Delta$ 的上界 $\bar{\Delta}$ 为 $\sqrt{\mathrm{len}(x)}$。
Delta_bar = sqrt(length(x));
Delta0 = Delta_bar/8;
if isfield(opts,'Delta')
Delta = opts.Delta;
else
Delta = Delta0;
end
%%%
% 两个参数分别记录信赖域半径连续增大或减小的次数,以方便初始值的调整。
consecutive_TRplus = 0;
consecutive_TRminus = 0;
%%%
% 将 |tCG.m| 存放的目录加入工作目录。
addpath('../newton');
%%%
% 当需要详细输出时,设定输出格式。
if record >= 1
if ispc; str1 = ' %10s'; str2 = ' %8s';
else str1 = ' %10s'; str2 = ' %8s'; end
stra = ['%5s', str1, str2, str2, str2, str2, str2, str2, '\n'];
str_head = sprintf(stra, ...
'iter', 'F', 'fdiff', 'mdiff', 'redf', 'ratio', 'Delta', 'nrmg');
fprintf('%s', str_head);
str_num = ['%4d %+8.7e %+2.1e %+2.1e %+2.1e %+2.1e %2.1e %2.1e'];
end
%% 迭代主循环
% 以 |maxit| 为最大迭代次数。
for iter = 1:maxit
%%%
% 截断共轭梯度法中用于判断收敛的参数。
opts_tCG.kappa = 0.1;
opts_tCG.theta = 1;
%%%
% 调用 |tCG| 函数,利用截断共轭梯度法求解信赖域子问题,得到迭代方向 $d$。
% |stop_tCG| 表示截断共轭梯度法的退出原因。
hess_tCG = @(d) hess(x,d);
[d, Hd, out_tCG] = tCG(x, gp, hess_tCG, Delta, opts_tCG);
stop_tCG = out_tCG.stop_tCG;
%%%
% 计算比值
%
% $$ \displaystyle\rho_k=\frac{f(x^k)-f(x^k+d^k)}{m_k(0)-m_k(d^k)}, $$
%
% 以确定是否更新迭代和修正信赖域半径。
% 首先计算 $m_k(0)-m_k(d^k)=-\nabla f(x^k)^\top d^k + \frac{1}{2}(d^k)^\top B^k
% d^k$,为了保证数值稳定性,增加一个小常数 |rreg| 。
mdiff = g'*d + .5*d'*Hd;
rreg = 10*max(1, abs(fp)) * eps;
mdiff = -mdiff + rreg;
model_decreased = mdiff > 0;
%%%
% 构造试验点 $\hat{x}^{k+1}=x^k+d^k$,并计算该点处计算函数值和梯度。
xnew = x + d;
[f,g] = fun(xnew);
nrmg = norm(g,2);
out.nfe = out.nfe + 1;
%%%
% 计算 $f(x^k)-f(x^k+d^k)$,同样地增加一个小常数 |rreg|。
% 然后计算 $\rho^k$ 作为修正信赖域半径和判断是否更新的依据。
redf = fp - f + rreg;
ratio = redf/mdiff;
%%%
% 当 $\rho^k>\eta_1$ 时,接受此次更新,并记录上一步的函数值和梯度。
if ratio >= eta1
x = xnew; fp = f; gp = g;
out.nrmG = [out.nrmG; nrmg];
end
%%%
% 计算函数值相对变化。
fdiff = abs(redf/(abs(fp)+1));
%%%
% 停机准则:当满足(1)梯度范数小于阈值或(2)函数值的相对变化小于阈值且 $\rho^k>0$ 时,停止迭代。
cstop = nrmg <= gtol || (abs(fdiff) <= ftol && ratio > 0);
% 当需要详细输出时,在(1)开始迭代时(2)达到收敛时(3)达到最大迭代次数退出迭代时
% (4)每若干步时,打印详细结果。
if record>=1 && (cstop || ...
iter == 1 || iter==maxit || mod(iter,itPrint)==0)
if mod(iter,20*itPrint) == 0 && iter ~= maxit && ~cstop
fprintf('\n%s', str_head);
end
%%%
% |stop_tCG| 记录内层的截断共轭梯度法退出的原因,分别对应输出如下:
switch stop_tCG
case{1}
str_tCG = ' [negative curvature]\n';
case{2}
str_tCG = ' [exceeded trust region]\n';
case{3}
str_tCG = ' [linear convergence]\n';
case{4}
str_tCG = ' [superlinear convergence]\n';
case{5}
str_tCG = ' [maximal iteration number reached]\n';
case{6}
str_tCG = ' [model did not decrease]\n';
end
fprintf(strcat(str_num,str_tCG), ...
iter, f, fdiff, mdiff, redf, ratio, Delta, nrmg);
end
%%%
% 当满足停机准则时,记录达到最优值,退出循环。
if cstop
out.msg = 'optimal';
break;
end
%% 信赖域半径的调整
%%%
% 如果 $\rho^k<\eta_1$ 或者算法非降(或者当 $\rho^k$ 计算时出现分母约为 0 ,即
% ${m_k(0)-m_k(d^k)}\approx 0$)时,不接受当前步迭代。这种情况下,需要对信赖域半径进行缩减。
%
% 具体而言,令 $\Delta \leftarrow \gamma_1\Delta$,将信赖域半径的连续增大次数置零,
% 缩小次数加一。
if ratio < eta1 || ~model_decreased || isnan(ratio)
Delta = Delta*gamma1;
consecutive_TRplus = 0;
consecutive_TRminus = consecutive_TRminus + 1;
%%%
% 当信赖域半径连续 5 次减小时,认为当前的信赖域半径过大,并输出相应的提示信息。
if consecutive_TRminus >= 5 && opts.verbose >= 1
consecutive_TRminus = -inf;
fprintf(' +++ Detected many consecutive TR- (radius decreases).\n');
fprintf(' +++ Consider decreasing options.Delta_bar by an order of magnitude.\n');
fprintf(' +++ Current values: options.Delta_bar = %g and options.Delta0 = %g.\n', options.Delta_bar, options.Delta0);
end
%%%
% 当 $\rho_k>\eta_2$ 且 $\|d_k\|=\Delta$ (对应为截断共轭梯度法因遇到负曲率或者超出信赖域半径而终止),
% 增大信赖域半径。 $\Delta \leftarrow \min\{\gamma_2\Delta, \bar{\Delta}\}$
% ( $\bar{\Delta}$ 为信赖域半径的上界,设置为 $\sqrt{\mathrm{len}(x)}$ )。
% 将信赖域半径的连续减小次数置零,增大次数加一。
elseif ratio > eta2 && (stop_tCG == 1 || stop_tCG == 2)
Delta = min(gamma2*Delta, Delta_bar);
consecutive_TRminus = 0;
consecutive_TRplus = consecutive_TRplus + 1;
%%%
% 考虑当信赖域半径连续 5 次增大时,认为当前的信赖域半径过小,输出相应的提示信息。
if consecutive_TRplus >= 5 && opts.verbose >= 1
consecutive_TRplus = -inf;
fprintf(' +++ Detected many consecutive TR+ (radius increases).\n');
fprintf(' +++ Consider increasing options.Delta_bar by an order of magnitude.\n');
fprintf(' +++ Current values: options.Delta_bar = %g and options.Delta0 = %g.\n', Delta_bar, Delta0);
end
%%%
% 除了以上两种情况,不需要对信赖域半径进行调整,将其连续增大和减小次数都置零。
else
consecutive_TRplus = 0;
consecutive_TRminus = 0;
end
end
%%%
% 当从外层迭代退出时,记录退出信息。
out.iter = iter;
out.f = f;
out.nrmg = nrmg;
end
%% 参考页面
% 我们在页面
% 中展示了该算法的一个应用。另外,该算法调用截断共轭梯度法 |tCG|
% 求解信赖域子问题,关于截断共轭梯度法参考 <../newton/tCG.html 截断共轭梯度法>。
%
% 此页面的源代码请见: <../download_code/trust_region/fminTR.m fminTR.m>。
%% 版权声明
% 此页面为《最优化:建模、算法与理论》、《最优化计算方法》配套代码。
% 代码作者:文再文、刘浩洋、户将,代码整理与页面制作:杨昊桐。
%
% 著作权所有 (C) 2020 文再文、刘浩洋、户将