从零发明数值最优化 5:共轭梯度法

在之前的梯度下降法中,我们提到,即使在精确线搜索下,梯度下降法也大概率走出一条全是直角拐弯的优化路径。牛顿法虽然解决了这个问题,然而它的存储开销和计算开销实在太大了。我们最希望的当然是既只用一阶梯度,又能快速下降。

在通常的函数中这可能比较难以做到,那让我们退而求其次,来考察一下严格正定的二次型函数:

f(x)=12x⊤Ax−b⊤x,A∈Rn×n,A≻0,A⊤=Af(x) = \frac{1}{2} x^\top A x - b^\top x, \quad A \in \mathbb{R}^{n \times n}, \quad A \succ 0, \quad A^\top = A

求这个函数的最小值当然就是求 ∇f(x)=Ax−b=0\nabla f(x) = Ax - b = 0。

其实,共轭梯度法最初在 1952 年就是为了求出 Ax=bAx = b 而设计的。不过这里我们为了逻辑上的通畅,选择先有函数再算梯度的逻辑。

直观感受

圆形等高线:从任一等高线上出发,一步精确线搜索即到达圆心

在一个圆上进行下降,不管从什么地方出发都是一步的事。此时,A=μIA = \mu I,x⋆=1μbx^\star = \frac{1}{\mu} b。

长椭圆上最速下降 + 精确线搜索的直角折线轨迹

在一个椭圆上就不然。椭圆上的下降曲线要不断拐弯,逼近得很慢。而且即使每步都用精确线搜索优化到了最佳,下一步又会破坏已知的最优。

那我们的想法当然是,能不能第一步最优,第二步在把不破坏第一步最优的基础上也最优,第三步在不破坏前两步最优的基础上也最优……

同一个长椭圆、同一个起点:共轭梯度法的两步收敛路径

如何下降

那么,应该怎么走呢?

比较直观的想法是,矩阵是一个 n×nn \times n 的东西,而梯度是一个 n×1n \times 1 的东西,所以大概 nn 次就能从任意点收敛到最优值。而且,因为要走 nn 步,每步都走“尽” Rn\mathbb{R}^n 的一个维度,所以每走一步都要走到当前步最优的位置,而且肯定不能损害前一步的最优性。

我们经常算便宜的梯度 ∇f(xk)=Axk−b\nabla f(x_k) = Ax_k - b。另外,我们还知道 ∇f(x⋆)=Ax⋆−b=0\nabla f(x^\star) = A x^\star - b = 0。我们希望最终 xk=x⋆x_k = x^\star,即误差 ek=xk−x⋆=0e_k = x_k - x^\star = 0,所以会想把上面这两个式子做个差 ∇f(xk)−∇f(x⋆)=∇f(xk)=A(xk−x⋆)=Aek\nabla f(x_k) - \nabla f(x^\star) = \nabla f(x_k) = A(x_k - x^\star) = A e_k,得到 ∇f(xk)=Aek\nabla f(x_k) = A e_k。这个式子看上去很有意思,我们先记下来。

把我们沿着走的方向记为 p1,p2,…,pnp_1, p_2, \dots, p_n。我们希望最后满足 −e1=∑i=1nαipi-e_1 = \sum_{i=1}^n \alpha_i p_i。

那么当走了 kk 步时,我们应该怎么知道第 k+1k+1 步往哪儿走,走多远呢?

走多远

假设上帝已经给了我们一组能够铺满全空间的搜索方向 p1,p2,…,pnp_1, p_2, \dots, p_n。

在走完前 k−1k-1 步后,当前的累积位置是 xk=x1+∑i=1k−1αipix_k = x_1 + \sum_{i=1}^{k-1} \alpha_i p_i,当前的剩余误差是 ek=xk−x⋆e_k = x_k - x^\star。从全局来看,我们还剩下的总差距是:

−ek=x⋆−xk=∑i=knαipi-e_k = x^\star - x_k = \sum_{i=k}^n \alpha_i p_i

我们希望在第 kk 步就把这个式子中的 αk\alpha_k 解出来,并且不用知道未来还要走多远,怎么走。总的来说,我们希望 pip_i 和 pjp_j 之间满足某种联系,使得它们能进行顺滑的消消乐。

最常规的联系就是两两垂直,即 pi⊤pj=0(i≠j)p_i^\top p_j = 0 \quad (i \neq j)。然而,即使满足这个条件,把上式两边都左乘一个 pkp_k,我们也只能得到

−pk⊤ek=αk∥pk∥2+∑i=k+1nαi(pk⊤pi)=αk∥pk∥2-p_k^\top e_k = \alpha_k \Vert p_k \Vert^2 + \sum_{i=k+1}^n \alpha_i (p_k^\top p_i) = \alpha_k \Vert p_k \Vert^2

问题是很明显的:我们不知道终末状态,所以 eke_k 也无从得知。

然后你突然想起来上面那个神秘的式子:∇f(xk)=Aek\nabla f(x_k) = A e_k。如果我们左乘一个 pk⊤Ap_k^\top A,又会得到什么呢?

−pk⊤Aek=αk(pk⊤Apk)+∑i=k+1nαi(pk⊤Api)- p_k^\top A e_k = \alpha_k (p_k^\top A p_k) + \sum_{i = k+1}^n \alpha_i (p_k^\top A p_i)

化简,

−pk⊤∇f(xk)=αk(pk⊤Apk)+∑i=k+1nαi(pk⊤Api)- p_k^\top \nabla f(x_k) = \alpha_k (p_k^\top A p_k) + \sum_{i = k+1}^n \alpha_i (p_k^\top A p_i)

刚刚上帝给了你 p1p_1 到 pnp_n,所以 pkp_k 是已知的。AA 和 ∇f(xk)\nabla f(x_k) 当然也是已知的。这么看来,你要求 αk\alpha_k,最方便的办法当然就是让后面那一大串求和等于零,也就是

pi⊤Apj=0,i≠jp_i^\top A p_j = 0, \quad i \neq j

这就是大名鼎鼎的共轭条件。而在这个条件下,每步的步长就是

αk=−pk⊤∇f(xk)pk⊤Apk\alpha_k = \frac{- p_k^\top \nabla f(x_k)}{p_k^\top A p_k}

往哪儿走

现在我们知道走多远了,那么问题就转化为往哪儿走。很遗憾,现实生活中可没有上帝,我们还是得解决已知 p1,…,pk−1p_1, \dots, p_{k-1},如何求 pkp_k 的问题。

幸好,在上面的推理中,我们还顺便得到了 pi⊤Apj=0,i≠jp_i^\top A p_j = 0, \quad i \neq j 的共轭关系。

最简单的想法当然是用施密特正交化。

我们拿手头上现成的向量 ∇f(xk)\nabla f(x_k) 来做每步的施密特正交化。定义残差 rk=Ax⋆−Axk=b−Axk=−∇f(xk)r_k = A x^\star - A x_k = b - A x_k = - \nabla f(x_k)。那么理论上,下一个方向就是

pk=rk−∑i=1k−1pi⊤Arkpi⊤Apipip_k = r_k - \sum_{i=1}^{k-1} \frac{p_i^\top A r_k}{p_i^\top A p_i} p_i

不过,施密特正交化是 O(n3)O(n^3) 的算法,和矩阵求逆一样麻烦。我们看看能不能找到一点好的性质避免这么高的时间复杂度。

我们在第 kk 步所处的位置 xkx_k,实际上是在前 k−1k-1 个共轭方向张成的子空间内达到了全局最优。这意味着当前的梯度与已有的所有搜索方向都正交,也就是说:

rk⊤pi=0,∀i<kr_k^\top p_i = 0, \quad \forall i < k

第一个搜索方向没有前面的信息,我们干脆定义 p1=r1p_1 = r_1,而每次 pkp_k 都是用之前的 p1,…,pk−1p_1, \dots, p_{k-1} 和 rkr_k 来更新的。所以我们显然有 span{p1,…,pk}=span{r1,…,rk}\mathrm{span}\{p_1, \dots, p_k\} = \mathrm{span}\{r_1, \dots, r_k\}。

在第 kk 个点处,我们有 rk⊤pi=0,∀i<kr_k^\top p_i = 0, \quad \forall i < k 和 span{p1,…,pk−1}=span{r1,…,rk−1}\mathrm{span}\{p_1, \dots, p_{k-1}\} = \mathrm{span}\{r_1, \dots, r_{k-1}\}。所以,我们得到:

rk⊤ri=0,∀i<kr_k^\top r_i = 0, \quad \forall i < k

回顾残差的更新方式:

ri+1=b−Axi+1=b−A(xi+αipi)=ri−αiApir_{i+1} = b - A x_{i+1} = b - A (x_i + \alpha_i p_i) = r_i - \alpha_i A p_i

Api=1αi(ri−ri+1)A p_i = \frac{1}{\alpha_i} (r_i - r_{i+1})

代入到施密特正交化的分子上:

pi⊤Ark=rk⊤Api=1αirk⊤(ri−ri+1)p_i^\top A r_k = r_k^\top A p_i = \frac{1}{\alpha_i} r_k^\top (r_i - r_{i+1})

又根据前面得到的 rk⊤ri=0,∀i<kr_k^\top r_i = 0, \quad \forall i < k,我们知道

pi⊤Ark=1αirk⊤(ri−ri+1)={0i<k−1−1αk−1∥rk∥2i=k−1p_i^\top A r_k = \frac{1}{\alpha_i} r_k^\top (r_i - r_{i+1}) = \begin{cases} 0 & i < k-1 \\ - \frac{1}{\alpha_{k-1}} \Vert r_k \Vert^2 & i = k-1 \end{cases}

因此,

pk=rk−−1αk−1∥rk∥2pk−1⊤Apk−1pk−1p_k = r_k - \frac{- \frac{1}{\alpha_{k-1}} \Vert r_k \Vert ^2}{p_{k-1}^\top A p_{k-1}} p_{k-1}

其实这个式子的分母还可以用 Apk−1=1αk−1(rk−1−rk)A p_{k-1} = \frac{1}{\alpha_{k-1}} (r_{k-1} - r_k)、pk−1⊤rk−1=rk−1⊤rk−1p_{k-1}^\top r_{k-1} = r_{k-1}^\top r_{k-1} 和 pk−1⊤rk=0p_{k-1}^\top r_k = 0 继续化简,得到

pk=rk+∥rk∥2∥rk−1∥2pk−1p_k = r_k + \frac{\Vert r_k \Vert ^2}{\Vert r_{k-1} \Vert ^2} p_{k-1}

这样一来,站在当前点的下降方向只需要用当前点的梯度、上一个点的梯度和上一个点的下降方向就可以得出。这大大节省了存储空间。

总览

初步的共轭梯度法先设定 r1=b−Ax1r_1 = b - A x_1,p1=r1p_1 = r_1,接下来进行迭代:

αk=pk⊤rkpk⊤Apkxk+1=xk+αkpkrk+1=b−Axk+1βk+1=−pk⊤Ark+1pk⊤Apkpk+1=rk+1+βk+1pk\begin{aligned} \alpha_k &= \frac{p_k^\top r_k}{p_k^\top A p_k} \\ x_{k+1} &= x_k + \alpha_k p_k \\ r_{k+1} &= b - A x_{k+1} \\ \beta_{k+1} &= - \frac{p_k^\top A r_{k+1}}{p_k^\top A p_k} \\ p_{k+1} &= r_{k+1} + \beta_{k+1} p_k \end{aligned}

而更进一步化简后的共轭梯度法同样先设定 r1=b−Ax1r_1 = b - A x_1,p1=r1p_1 = r_1,不过接下来则是:

αk=∥rk∥2pk⊤Apkxk+1=xk+αkpkrk+1=rk−αkApkβk+1=∥rk+1∥2∥rk∥2pk+1=rk+1+βk+1pk\begin{aligned} \alpha_k &= \frac{\Vert{}r_k\Vert{}^2}{p_k^\top A p_k} \\ x_{k+1} &= x_k + \alpha_k p_k \\ r_{k+1} &= r_k - \alpha_k A p_k \\ \beta_{k+1} &= \frac{\Vert{}r_{k+1}\Vert{}^2}{\Vert{}r_k\Vert{}^2} \\ p_{k+1} &= r_{k+1} + \beta_{k+1} p_k \end{aligned}

化简后,矩阵-向量乘法的次数从每轮 2-3 次减少为严格 1 次,向量点积每轮迭代仅计算两次,且保证了 O(n)O(n) 的时间复杂度,是实际场景下的高效实现。