%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % 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: % % 2020.2.15 (Jiang Hu): % First version % % 2020.7.15 (Jiang Hu): % Parameter tuning % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% LASSO 问题的近似点算法 % 近似点算法(PPA)对于 LASSO 问题,考虑其等价形式 % % $$ \min_{x,y} f(x,y)=\mu\|x\|_1+\frac{1}{2}\|y\|_2^2, % \quad \mathrm{s.t.}\quad Ax-y-b=0.$$ % % 近似点算法的一个迭代步为 % % $$ (x^{k+1},y^{k+1})\approx\arg\min_{(x,y)\in\mathcal{D}} % \left\{f(x,y)+\frac{1}{2t_k}(\|x-x^k\|_2^2+\|y-y^k\|_2^2)\right\}, $$ % % 其中 $\mathcal{D}=\{(x,y)\big|Ax-y=b\}$。对于子问题考虑其对偶问题,引入拉格朗日乘子 $z$,并令 % % $$ \Phi_k(z)=\inf_x\left\{\mu\|x\|_1+z^\top Ax+\frac{1}{2t_k}\|x-x^k\|_2^2 % \right\} +\inf_y\left\{\frac{1}{2}\|y\|_2^2-z^\top y+\frac{1}{2t_k} % \|y-y^k\|_2^2\right\}-b^\top z, $$ % % 则其迭代格式满足 % % $$ z^{k+1}=\arg\max_z \Phi_k(z).$$ % % 以 $z^{k+1}$ 为逼近解, $\inf_x$ 和 $\inf_y$ 的子问题是半光滑的, % 因此利用半光滑牛顿法加速的梯度法进行求解。 % 再结合最优性条件,有 % % $$ \left\{\begin{array}{l}x^{k+1}=\mathrm{prox}_{\mu t_k\|\cdot\|_1} % (x^k-t_kA^\top z^{k+1})\\y^{k+1}=\frac{1}{t_k+1}(y^k+t_kz^{k+1})\end{array}\right. $$ % %% 初始化和迭代准备 % PPA不使用连续化策略,直接对原始的 LASSO 问题进行求解。 % % 输入信息: $A$, $b$, % $\mu$ ,迭代初始值 $x^0$ 以及提供各参数的结构体 |opts| 。 % % 输出信息: 迭代得到的解 $x$ 和结构体 |out| 。 % % * |out.fvec| :每一步迭代的 LASSO 目标函数值 % * |out.flag| :标记是否达到收敛 % * |out.fval| :迭代终止时的 LASSO 目标函数值 % * |out.tt| :运行时间 % * |out.itr| :外层迭代次数 function [x, out] = LASSO_ppa(x0, A, b, mu, opts) %%% % 从输入的结构体 |opts| 中读取参数或采取默认参数。 % % * |opts.maxit| :最大迭代次数 % * |opts.ftol| :针对函数值的停机准则,当相邻两次迭代函数值之差小于该值时认为该条件满足 % * |opts.gtol| :针对梯度的停机准则,当当前步梯度范数小于该值时认为该条件满足 % * |opts.verbose| : |>1| 时输出每一步信息, |0-1| 时输出外层迭代信息, |=0| 时不输出 % * |opts.t0| :初始步长 if ~isfield(opts, 'maxit'); opts.maxit = 500; end if ~isfield(opts, 'ftol'); opts.ftol = 1e-8; end if ~isfield(opts, 'gtol'); opts.gtol = 1e-6; end if ~isfield(opts, 'verbose'); opts.verbose = 0; end if ~isfield(opts, 't0'); opts.t0 = 1e3; end out = struct(); %%% % optsz 为 z 的子问题提供参数。 optsz = struct(); optsz.verbose = (opts.verbose > 1); %%% % 迭代初始化。 % % 以 x^0 为迭代初始点,在初始 x^0 处计算相关变量的值。 x = x0; fp = inf; tt = tic; r = A*x - b; f = .5*norm(r,2)^2 + mu*norm(x,1); out.fvec = f; %%% % t 为步长,初始值设定为 opts.t0。 t = opts.t0; %%% % 变量 y=Ax-b 和拉格朗日乘子 z。 z = zeros(size(b)); y = A*x - b; toldiff = 1; %% 迭代主循环 % 以 |maxit| 为最大迭代步数。每次迭代开始,记录上一次迭代的目标函数值。 for k = 1:opts.maxit fp = f; %%% % |toldiff| 为 $\|x^{k+1}-x^{k}\|_2^2+\|y^{k+1}-y^{k}\|_2^2$,这里为内层的 % $z$ 的子问题迭代确定停机准则: % $\|\nabla\Phi_k(z^{k+1})\|_2\le\sqrt{\alpha_k/t_k}\sigma_k\| % (x^{k+1},y^{k+1})-(x^k,y^k)\|_2$,并令其中的 $\delta_k=\frac{8}{k^2}$, % $\alpha_k=\frac{t_k}{t_k+1}$。 optsz.gtol = 8/k^2*min(1, toldiff); %%% % 内层循环的梯度法解拉格朗日乘子 $z$ 的子问题。然后对 $x$, $y$ 更新。 % % % $$ \begin{array}{rl} % x^{k+1}=&\hspace{-0.5em}\mathrm{prox}_{\mu t_k\|\cdot\|_1} % (x^k-t_kA^\top z^{k+1}),\\ % y^{k+1}=&\hspace{-0.5em}\frac{1}{t_k+1}(y^k+t_kz^{k+1}). % \end{array} $$ % [z,~] = zgbb(z,A,b,mu,t, x,y, optsz); xp = x; x = prox(x - t*A'*z, mu*t); yp = y; y = 1/(t+1)*(y + t*z); %%% % 一步 PPA 的外层迭代结束,计算目标函数值,并以一步近似点梯度法的 $x$ 估计梯度。 Axb = A*x - b; f = .5*norm(Axb,2)^2 + mu*norm(x,1); nrmG = norm(x - prox(x - A'*Axb, mu)); toldiff = norm(x-xp,2) + norm(y- yp,2); %%% % 记录一步迭代的目标函数值。 out.fvec = [out.fvec; f]; %%% % 当 opts.verbose 不为 0 时输出详细的迭代信息。 if opts.verbose fprintf('itr: %d\t t: %e\t fval: %e \t nrmG: %.1e \t feasi: %.1e \n', ... k, t, f, nrmG, norm(A*x -b - y,2)); end %%% % 外层迭代的停机准则,当函数值的变化或梯度(一步近似点梯度法估计)范数小于阈值时, % 认为收敛,退出循环。 if abs(f-fp) < opts.ftol || nrmG < opts.gtol break; end end %%% % 当收敛时,记录输出信息。以 |out.flag| 记录迭代结束原因, |out.flag=1| 表示满足梯度条件, % out.flag=0 表示满足函数值条件。 if abs(f - fp) < opts.ftol out.flag = 0; else out.flag = 1; end %%% % 记录输出信息。 out.fval = f; out.itr = k; out.tt = toc(tt); end %% 拉格朗日乘子 $z$ 子问题 %%% % 从输入的结构体中读取参数或采取默认参数。 % % * |optsz.maxit| :最大迭代次数 % * |optsz.gtol| :子问题的停机准则,当梯度范数小于该值时认为满足; % 注意到该值随着外层迭代而变化,具体的计算方法见上文 % * |optsz.verbose| :不为 0 时输出每步迭代信息,否则不输出 % * |optsz.tau0| :初始步长 function [z,out] = zgbb(z0,A,b,mu,t,x,y, optsz) if ~isfield(optsz, 'maxit'); optsz.maxit = 500; end if ~isfield(optsz, 'gtol'); optsz.gtol = 1e-6; end if ~isfield(optsz, 'verbose'); optsz.verbose = 0; end if ~isfield(optsz, 'tau0'); optsz.tau0 = 1e-2; end %%% % 初始化变量。 out = struct(); [m,n] = size(A); Im = eye(m); H = eye(m); z = z0; r = x - t*(A'*z); w = prox(r,mu*t); %%% % 初始步长设定为 optsz.tau0。 tau = optsz.tau0; %%% % 将子问题中的 $y$ 的部分解析地代入,得到其迭代的目标函数(去掉常数项) % % $$ -\Phi_k(z)=-\mu\Gamma_{\mu t_k}(x^k-t_kA^\top z)+\frac{1}{2t_k} % (\|x^k-t_kA^\top z\|_2^2)+\frac{t_k}{2(t_k+1)}\|z\|_2^2+\frac{1}{t_k+1}z^\top % y^k-\frac{1}{2(t_k+1)}\|y^k\|_2^2+b^\top z, $$ % % 其中 $\Gamma_{\mu t_k}(u)=\inf_x\left\{\|x\|_1+\frac{1}{2\mu % t_k}\|x-u\|_2^2\right\}$。 % % 令 $w=\mathrm{prox}_{\mu t_k\|\cdot\|_1}(x^k-t_kA^\top z)$, $\Gamma_{\mu % t_k}(x^k-t_kA^\top z)=\|w\|_1+\frac{1}{2\mu^2t_k}\|w-(x^k-t_kA^\top % z)\|_2^2$。 f = - mu*(norm(w,1) + 1/(2*mu*t)*norm(w-r,2)^2) + 1/(2*t)*norm(r,2)^2 ... + t/(2*(t+1))*(norm(z,2)^2) + 1/(t+1)*(y'*z) + b'*z; %%% % 初始化线搜索参数。 Q = 1; Cval = f; gamma = 0.85; rhols = 1e-6; %%% % 目标函数为连续可微函数,且其梯度为 $-Aw+\frac{t_k}{t_k+1}z+\frac{1}{t_k+1}y+b$。 g = - A*w + t/(t+1)*z + 1/(t+1)*y + b; nrmg = norm(g,2); %%% % 对 $z$ 的梯度法迭代主循环,最大迭代步 |maxit|。 maxit = optsz.maxit; for k = 1:maxit zp = z; gp = g; %%% % 进行线搜索。 |nls| 记录线搜索次数, $\eta=0.2$ 表示每次不满足线搜索时步长减小的比例。 % 每次循环以当前步长进行一步试探,下降方向 $d^k=-H g^k$,求出在新的 $z$ 处的变量和目标函数值。 % 当 $H=I$ 时,则为一般的带线搜索的 BB 步长梯度法,从 10 步后, $H$ % 为广义海瑟矩阵的逆,步长设定为 $\tau_k=1$,对应半光滑牛顿法。 nls = 1; deriv = rhols*nrmg^2; eta = 0.2; while 1 z = zp - tau*(H*g); r = x - t*(A'*z); w = prox(r, mu*t); f = - mu*(norm(w,1) + 1/(2*mu*t)*norm(w-r,2)^2) + 1/(2*t)*norm(r,2)^2 ... + t/(2*(t+1))*(norm(z,2)^2) + 1/(t+1)*(y'*z) + b'*z; %%% % 满足线搜索准则 (Zhang & Hager) $\displaystyle f(x^k+\tau d^k)\le C_k % +\rho\tau (g^k)^\top d^k$ 或进行超过 10 次步长衰减后退出线搜索,否则以 $\eta$ % 的比例对步长进行衰减。 if f < Cval - tau*deriv || nls == 10 break; end tau = eta*tau; nls = nls + 1; end %%% % 更新梯度。 g = - A*w + t/(t+1)*z + 1/(t+1)*y + b; nrmg = norm(g,2); %%% % 当 |optsz.verbose| 不为 0 时输出详细的迭代信息。 if optsz.verbose fprintf('iter: %d \t f: %.4e \t nrmG: %.1e \t nls: %d \n', k, f, nrmg, nls); end %%% % 停机准则(见上文),当满足停机准则时退出循环。 gtol = sqrt(1/(t+1))*optsz.gtol; if nrmg < gtol break; end %%% % BB 步长的计算,令 $s^k=z^{k+1}-z^k$, $y^k=g^{k+1}-g^k$,则两种 BB 步长 % $\displaystyle \tau_{BB1}=\frac{(s^k)^\top s^k}{(s^k)^\top y^k}$, % $\displaystyle \tau_{BB2}=\frac{(s^k)^\top y^k}{(y^k)^\top y^k}$。 % 这里我们交替使用两种 BB 步长并限定在 $[10^{-12},10^{12}]$ 中。 dz = z - zp; dg = g - gp; dzg = abs(dz'*dg); if dzg > 0 if mod(k,2) == 0 tau = norm(dz,2)^2 /dzg; else tau = dzg/(norm(dg,2)^2); end end tau = min(max(1e-12,tau),1e12); %%% % 线搜索参数的更新。 Qp = Q; Q = gamma*Qp + 1; Cval = (gamma*Qp*Cval + f)/Q; %%% % 从 10 步之后,开始使用半光滑牛顿法。 % 由优化的目标函数的形式 % % $$ \displaystyle -\Phi_k(z)=-\mu\Gamma_{\mu t_k}(x^k-t_kA^\top z) % +\frac{1}{2t_k}(\|x^k-t_kA^\top z\|_2^2)+\frac{t_k}{2(t_k+1)}\|z\|_2^2 % +\frac{1}{t_k+1}z^\top y^k-\frac{1}{2(t_k+1)}\|y^k\|_2^2+b^\top z, $$ % % 得到其广义海瑟矩阵 % % $$ \displaystyle -\nabla^2\Phi_k(z)=\frac{t_k}{t_k+1}I+t_k\bar{A}\bar{A}^\top, $$ % % 其中 $\bar{A}$ 为满足 $x^k-t_kA^\top z>\mu t_k$ 的下标对应的 $A$ 的列。再由 % Sherman-Morrison-Woodbury 公式可得 % % $$ \displaystyle H=\frac{t_k+1}{t_k}\left(I-\bar{A}(\frac{1}{t_k+1}I % +\bar{A}^\top\bar{A})^{-1}\bar{A}^\top\right) $$ % % 为广义海瑟矩阵的逆。在利用半光滑牛顿法的情况下,采用 $\tau=1$ 的固定步长和一般的线搜索准则 % (即 |Cval| 直接取当前点函数值)。 if k > 10 ind = find(abs(r) > mu*t); Aind = A(:,ind); tmp = 1/(t+1)*eye(length(ind)) + Aind'*Aind; H = (t+1)/t*(Im - Aind*(tmp\Aind')); tau = 1; Cval = f; end end %%% % 当从迭代中退出时,返回这一内重循环的迭代步。 out.iter = k; end %% 辅助函数 %%% % 函数 $h(x)=\mu\|x\|_1$ 对应的邻近算子 $\mathrm{sign}(x)\max\{|x|-\mu,0\}$。 function y = prox(x, mu) y = max(abs(x) - mu, 0); y = sign(x) .* y; end %% 参考页面 % 在页面 中我们构建一个 LASSO % 问题并展示该算法在其中的应用。 % % 此页面的源代码请见: <../download_code/lasso_ppa/LASSO_ppa.m LASSO_ppa.m>。 %% 版权声明 % 此页面为《最优化:建模、算法与理论》、《最优化计算方法》配套代码。 % 代码作者:文再文、刘浩洋、户将,代码整理与页面制作:杨昊桐。 % % 著作权所有 (C) 2020 文再文、刘浩洋、户将