考虑从 1 1 1到 N N N的所有时刻,并假设有 M M M个路标点。定义所有时刻的机器人位姿和路标点坐标为
x = { x 1 , … , x N } , y = { y 1 , … , y M } \boldsymbol{x}=\left\{\boldsymbol{x}_{1}, \ldots, \boldsymbol{x}_{N}\right\}, \quad \boldsymbol{y}=\left\{\boldsymbol{y}_{1}, \ldots, \boldsymbol{y}_{M}\right\} x={x1,…,xN},y={y1,…,yM}
同样地, 用不带下标的 u \boldsymbol{u} u 表示所有时刻的输人, z \boldsymbol{z} z 表示所有时刻的观测数据。 对机 器人状态的估计, 就是已知输人数据 u \boldsymbol{u} u 和观测数据 z \boldsymbol{z} z 的条件下, 求状态 x , y \boldsymbol{x}, \boldsymbol{y} x,y 的条件概率分布:
P ( x , y ∣ z , u ) (6.4) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})\tag{6.4} P(x,y∣z,u)(6.4)
特别地, 当我们不知道控制输人, 只有一张张的图像时, 即只考虑观测方程带来的数据时, 相当于估计 P ( x , y ∣ z ) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}) P(x,y∣z) 的条件概率分布, 此问题也称为 S f M \mathrm{SfM} SfM , 即如何从许多图像中重建三维空间结构。为了估计状态变量的条件分布, 利用贝叶斯法则, 有
P ( x , y ∣ z , u ) = P ( z , u ∣ x , y ) P ( x , y ) P ( z , u ) ∝ P ( z , u ∣ x , y ) ⏟ 似然 P ( x , y ) ⏟ 先验 . (6.5) P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})=\frac{P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) P(\boldsymbol{x}, \boldsymbol{y})}{P(\boldsymbol{z}, \boldsymbol{u})} \propto \underbrace{P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y})}_{\text {似然 }} \underbrace{P(\boldsymbol{x}, \boldsymbol{y})}_{\text {先验 }} .\tag{6.5} P(x,y∣z,u)=P(z,u)P(z,u∣x,y)P(x,y)∝似然 P(z,u∣x,y)先验 P(x,y).(6.5)
贝叶斯法则左侧称为后验概率,右侧的 P ( z ∣ x ) P(\boldsymbol{z} \mid \boldsymbol{x}) P(z∣x)称为似然 (Likehood),另一部分 P ( x ) P(\boldsymbol{x}) P(x)称为先验 (Prior)。
注:式(6.5)的一个重要意义是,当先验概率确定时,后验概率与似然成正比。
直接求后验分布是困难的,但是求一个状态最有估计,使得在该状态下后验概率最大化,则是可行的:
( x , y ) MAP ∗ = arg max P ( x , y ∣ z , u ) = arg max P ( z , u ∣ x , y ) P ( x , y ) (6.6) (\boldsymbol{x}, \boldsymbol{y})_{\text {MAP }}^{*}=\arg \max P(\boldsymbol{x}, \boldsymbol{y} \mid \boldsymbol{z}, \boldsymbol{u})=\arg \max P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) P(\boldsymbol{x}, \boldsymbol{y}) \tag{6.6} (x,y)MAP ∗=argmaxP(x,y∣z,u)=argmaxP(z,u∣x,y)P(x,y)(6.6)
贝叶斯法则的分母部分与待估计的状态 x , y \boldsymbol{x}, \boldsymbol{y} x,y无关,因而可以忽略。贝叶斯法则告诉我们,求解最大后验概率等价于最大化似然与先验的乘积。如果我们不知道机器人位姿或路标大概在在什么地方(预测),此时就没有了先验。那么,可以求解最大似然估计:
( x , y ) M L E ∗ = arg max P ( z , u ∣ x , y ) (6.7) (\boldsymbol{x}, \boldsymbol{y})_{\mathrm{MLE}}^{*}=\arg \max P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y}) \tag{6.7} (x,y)MLE∗=argmaxP(z,u∣x,y)(6.7)
通过贝叶斯法则,我们的求解目标由“基于现在观测到的数据,机器人最可能处于什么样的状态”转换成“在什么样的状态下,最可能产生现在观测到的数据”。
SLAM的观测模型:
z k , j = h ( y j , x k ) + v k , j \boldsymbol{z}_{k, j}=h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right)+\boldsymbol{v}_{k, j} zk,j=h(yj,xk)+vk,j
由于我们假设了噪声项 v k ∼ N ( 0 , Q k , j ) \boldsymbol{v}_{k} \sim \mathcal{N}\left(0, \boldsymbol{Q}_{k, j}\right) vk∼N(0,Qk,j) , 所以观测数据的条件概率为
P ( z j , k ∣ x k , y j ) = N ( h ( y j , x k ) , Q k , j ) P\left(\boldsymbol{z}_{j, k} \mid \boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)=N\left(h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right), \boldsymbol{Q}_{k, j}\right) P(zj,k∣xk,yj)=N(h(yj,xk),Qk,j)
它依然是一个高斯分布。考虑单次观测的最大似然估计, 可以使用最小化负对数来求一个高斯分布的最大似然。
我们知道高斯分布在负对数下有较好的数学形式。考虑任意高维高斯分布 x ∼ N ( μ , Σ ) x \sim \mathcal{N}(\mu, \Sigma) x∼N(μ,Σ) , 它 的概率密度函数展开形式为
P ( x ) = 1 ( 2 π ) N det ( Σ ) exp ( − 1 2 ( x − μ ) T Σ − 1 ( x − μ ) ) (6.8) P(\boldsymbol{x})=\frac{1}{\sqrt{(2 \pi)^{N} \operatorname{det}(\boldsymbol{\Sigma})}} \exp \left(-\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^{\mathrm{T}} \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\right)\tag{6.8} P(x)=(2π)Ndet(Σ)1exp(−21(x−μ)TΣ−1(x−μ))(6.8)
对其取负对数, 则变为
− ln ( P ( x ) ) = 1 2 ln ( ( 2 π ) N det ( Σ ) ) + 1 2 ( x − μ ) T Σ − 1 ( x − μ ) (6.9) -\ln (P(\boldsymbol{x}))=\frac{1}{2} \ln \left((2 \pi)^{N} \operatorname{det}(\boldsymbol{\Sigma})\right)+\frac{1}{2}(\boldsymbol{x}-\boldsymbol{\mu})^{\mathrm{T}} \boldsymbol{\Sigma}^{-1}(\boldsymbol{x}-\boldsymbol{\mu})\tag{6.9} −ln(P(x))=21ln((2π)Ndet(Σ))+21(x−μ)TΣ−1(x−μ)(6.9)
因为对数函数时单调递增的,所有对原函数求最大化相当于对负对数求最小化。在最小化上式的 x \boldsymbol{x} x时,第一项与 x \boldsymbol{x} x无关,可以略去。于是,只要最小化右侧的二次型项,就得到了对状态的最大似然估计。代入SLAM的观测模型,相当于在求:
( x k , y j ) ∗ = arg max N ( h ( y j , x k ) , Q k , j ) = arg min ( ( z k , j − h ( x k , y j ) ) T Q k , j − 1 ( z k , j − h ( x k , y j ) ) ) (6.10) \begin{aligned} \left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)^{*} &=\arg \max \mathcal{N}\left(h\left(\boldsymbol{y}_{j}, \boldsymbol{x}_{k}\right), \boldsymbol{Q}_{k, j}\right) \\ &=\arg \min \left(\left(\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\right)^{\mathrm{T}} \boldsymbol{Q}_{k, j}^{-1}\left(\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\right)\right) \end{aligned}\tag{6.10} (xk,yj)∗=argmaxN(h(yj,xk),Qk,j)=argmin((zk,j−h(xk,yj))TQk,j−1(zk,j−h(xk,yj)))(6.10)
该式等价于最小化噪声项(即误差)的马氏距离。
现在我们考虑批量时刻的数据。通常假设各个时刻的输入和观测是相互独立的,这意味着各个输入之间的独立的,各个观测之间是独立的,并且输入和观测也是独立的。于是我们可以对联合分布进行因式分解:
P ( z , u ∣ x , y ) = ∏ k P ( u k ∣ x k − 1 , x k ) ∏ k , j P ( z k , j ∣ x k , y j ) (6.11) P(\boldsymbol{z}, \boldsymbol{u} \mid \boldsymbol{x}, \boldsymbol{y})=\prod_{k} P\left(\boldsymbol{u}_{k} \mid \boldsymbol{x}_{k-1}, \boldsymbol{x}_{k}\right) \prod_{k, j} P\left(\boldsymbol{z}_{k, j} \mid \boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right)\tag{6.11} P(z,u∣x,y)=k∏P(uk∣xk−1,xk)k,j∏P(zk,j∣xk,yj)(6.11)
这说明我们可以独立地处理各时刻的运动和观测。定义各次输人和观测数据与模型之间的误差:
e u , k = x k − f ( x k − 1 , u k ) e z , j , k = z k , j − h ( x k , y j ) (6.12) \begin{aligned} e_{\boldsymbol{u}, k} &=\boldsymbol{x}_{k}-f\left(\boldsymbol{x}_{k-1}, \boldsymbol{u}_{k}\right) \\ e_{\boldsymbol{z}, j, k} &=\boldsymbol{z}_{k, j}-h\left(\boldsymbol{x}_{k}, \boldsymbol{y}_{j}\right) \end{aligned}\tag{6.12} eu,kez,j,k=xk−f(xk−1,uk)=zk,j−h(xk,yj)(6.12)
那么, 最小化所有时刻估计值与真实读数之间的马氏距离, 等价于求最大似然估计。负对数允许我们把乘积变成求和:
min J ( x , y ) = ∑ k e u , k T R k − 1 e u , k + ∑ k ∑ j e z , k , j T Q k , j − 1 e z , k , j (6.13) \min J(\boldsymbol{x}, \boldsymbol{y})=\sum_{k} e_{\boldsymbol{u}, k}^{\mathrm{T}} \boldsymbol{R}_{k}^{-1} \boldsymbol{e}_{\boldsymbol{u}, k}+\sum_{k} \sum_{j} e_{\boldsymbol{z}, k, j}^{\mathrm{T}} \boldsymbol{Q}_{k, j}^{-1} e_{\boldsymbol{z}, k, j}\tag{6.13} minJ(x,y)=k∑eu,kTRk−1eu,k+k∑j∑ez,k,jTQk,j−1ez,k,j(6.13)
这样就得到了一个最小二乘问题,它的解等价于状态的最大似然估计。
最小二乘的目标函数为
min ∑ k = 1 3 e u , k T Q k − 1 e u , k + ∑ k = 1 3 e z , k T R k − 1 e z , k \min \sum_{k=1}^{3} e_{\boldsymbol{u}, k}^{\mathrm{T}} \boldsymbol{Q}_{k}^{-1} e_{\boldsymbol{u}, k}+\sum_{k=1}^{3} e_{\boldsymbol{z}, k}^{\mathrm{T}} \boldsymbol{R}_{k}^{-1} \boldsymbol{e}_{z, k} mink=1∑3eu,kTQk−1eu,k+k=1∑3ez,kTRk−1ez,k
如果系统是线性系统,那么我们可以很容易地将它写成向量形式,定义向量 y = [ u , z ] T \mathrm{y}=[\mathrm{u},\mathrm{z}]^T y=[u,z]T,那么可以写出矩阵 H \mathrm{H} H,使得
y − H x = e ∼ N ( 0 , Σ ) y-H x=e \sim \mathcal{N}(0, \Sigma) y−Hx=e∼N(0,Σ)
那么:
H = [ 1 − 1 0 0 0 1 − 1 0 0 0 1 − 1 0 1 0 0 0 0 1 0 0 0 0 1 ] \boldsymbol{H}=\left[\begin{array}{cccc} 1 & -1 & 0 & 0 \\ 0 & 1 & -1 & 0 \\ 0 & 0 & 1 & -1 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{array}\right] H=⎣⎢⎢⎢⎢⎢⎢⎡100000−1101000−1101000−1001⎦⎥⎥⎥⎥⎥⎥⎤
且 Σ = diag ( Q 1 , Q 2 , Q 3 , R 1 , R 2 , R 3 ) \boldsymbol{\Sigma}=\operatorname{diag}\left(\boldsymbol{Q}_{1}, \boldsymbol{Q}_{2}, \boldsymbol{Q}_{3}, \boldsymbol{R}_{1}, \boldsymbol{R}_{2}, \boldsymbol{R}_{3}\right) Σ=diag(Q1,Q2,Q3,R1,R2,R3) 。整个问题可以写成
x m a p ∗ = arg min e T Σ − 1 e x_{\mathrm{map}}^{*}=\arg \min e^{\mathrm{T}} \Sigma^{-1} e xmap∗=argmineTΣ−1e
之后我们将看到, 这个问题有唯一的解:
x m a p ∗ = ( H T Σ − 1 H ) − 1 H T Σ − 1 y \boldsymbol{x}_{\mathrm{map}}^{*}=\left(\boldsymbol{H}^{\mathrm{T}} \boldsymbol{\Sigma}^{-1} \boldsymbol{H}\right)^{-1} \boldsymbol{H}^{\mathrm{T}} \boldsymbol{\Sigma}^{-1} \boldsymbol{y} xmap∗=(HTΣ−1H)−1HTΣ−1y
最小二乘问题的基本形式:
min x F ( x ) = 1 2 ∥ f ( x ) ∥ 2 2 (6.24) \min _{\boldsymbol{x}} F(\boldsymbol{x})=\frac{1}{2}\|f(\boldsymbol{x})\|_{2}^{2} \tag{6.24} xminF(x)=21∥f(x)∥22(6.24)
迭代求解过程:
将目标方程在 x k x_k xk附近进行泰勒展开:
F ( x k + Δ x k ) ≈ F ( x k ) + J ( x k ) T Δ x k + 1 2 Δ x k T H ( x k ) Δ x k (6.26) F\left(\boldsymbol{x}_{k}+\Delta \boldsymbol{x}_{k}\right) \approx F\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}+\frac{1}{2} \Delta \boldsymbol{x}_{k}^{\mathrm{T}} \boldsymbol{H}\left(\boldsymbol{x}_{k}\right) \Delta \boldsymbol{x}_{k}\tag{6.26} F(xk+Δxk)≈F(xk)+J(xk)TΔxk+21ΔxkTH(xk)Δxk(6.26)
其中 J ( x k ) \boldsymbol{J}\left(\boldsymbol{x}_{k}\right) J(xk) 是 F ( x ) F(\boldsymbol{x}) F(x) 关于 x \boldsymbol{x} x 的一阶导数 [也叫梯度、雅可比 ( Jacobian ) 矩阵], H \boldsymbol{H} H 则是二阶 导数 [海塞 (Hessian ) 矩阵],它们都在 x k x_{k} xk 处取值。
如果保留一阶梯度,那么取增量为反向的梯度,即可保证函数下降:
Δ x ∗ = − J ( x k ) (6.27) \Delta x^{*}=-J\left(x_{k}\right)\tag{6.27} Δx∗=−J(xk)(6.27)
当然这只是个方向,通常我们还要再指定一个步长 λ \lambda λ。
我们可以对式(6.26)保留二阶梯度信息,此时增量方程味:
Δ x ∗ = arg min ( F ( x ) + J ( x ) T Δ x + 1 2 Δ x T H Δ x ) (6.28) \Delta \boldsymbol{x}^{*}=\arg \min \left(F(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \Delta \boldsymbol{x}+\frac{1}{2} \Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{H} \Delta \boldsymbol{x}\right)\tag{6.28} Δx∗=argmin(F(x)+J(x)TΔx+21ΔxTHΔx)(6.28)
右侧只含 Δ x \Delta x Δx 的零次、一次和二次项。求右侧等式关于 Δ x \Delta x Δx 的导数并令它为零,得到
J + H Δ x = 0 ⇒ H Δ x = − J (6.29) J+H \Delta x=0 \Rightarrow H \Delta x=-J\tag{6.29} J+HΔx=0⇒HΔx=−J(6.29)
求解这个 Δ x = − H − 1 J \Delta x=-H^{-1}J Δx=−H−1J即可得到增量。
若 f ( X ) 和 g ( X ) f(\mathbf{X}) 和 g(\mathbf{X}) f(X)和g(X) 分别是矩阵 X \mathbf{X} X 的实值函数, c 1 , c 2 c_{1}, c_{2} c1,c2 为实常数, 则
以下设 a 、 A \mathbf{a} 、 \mathbf{A} a、A 为与变量 x \mathbf{x} x 无关的向量或者矩阵。则
∂ a ⊤ x ∂ x = ∂ x ⊤ a ∂ x = a \frac{\partial \mathbf{a}^{\top} \mathbf{x}}{\partial \mathbf{x}}=\frac{\partial \mathbf{x}^{\top} \mathbf{a}}{\partial \mathbf{x}}=\mathbf{a} ∂x∂a⊤x=∂x∂x⊤a=a
∂ x ⊤ A y ∂ x = A y , ∂ y ⊤ A x ∂ x = A ⊤ y \frac{\partial \mathbf{x}^{\top} \mathbf{A} \mathbf{y}}{\partial \mathbf{x}}=\mathbf{A} \mathbf{y}, \quad \frac{\partial \mathbf{y}^{\top} \mathbf{A x}}{\partial \mathbf{x}}=\mathbf{A}^{\top} \mathbf{y} ∂x∂x⊤Ay=Ay,∂x∂y⊤Ax=A⊤y
∂ x ⊤ A ∂ x = A , ∂ x ⊤ A x ∂ x = ( A + A ⊤ ) x \frac{\partial \mathbf{x}^{\top} \mathbf{A}}{\partial \mathbf{x}}=\mathbf{A}, \quad \frac{\partial \mathbf{x}^{\top} \mathbf{A} \mathbf{x}}{\partial \mathbf{x}}=\left(\mathbf{A}+\mathbf{A}^{\top}\right) \mathbf{x} ∂x∂x⊤A=A,∂x∂x⊤Ax=(A+A⊤)x
设 f = 1 2 x ⊤ Q x f=\frac{1}{2} \mathbf{x}^{\top} \mathbf{Q} \mathbf{x} f=21x⊤Qx, 其中 Q \mathbf{Q} Q 为对称矩阵。则 ∇ f = Q x , ∇ 2 f = Q \nabla f=\mathbf{Q} \mathbf{x}, \nabla^{2} f=\mathbf{Q} ∇f=Qx,∇2f=Q
它的思想是将 f ( x ) f(x) f(x)进行一阶的泰勒展开。注意这里不是目标函数 F ( x ) F(x) F(x),而是 f ( x ) f(x) f(x),否则就变成牛顿法了。
f ( x + Δ x ) ≈ f ( x ) + J ( x ) Δ x (6.30) f(\boldsymbol{x}+\Delta \boldsymbol{x}) \approx f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\tag{6.30} f(x+Δx)≈f(x)+J(x)Δx(6.30)
这里 J ( x ) T \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} J(x)T 为 f ( x ) f(\boldsymbol{x}) f(x) 关于 x \boldsymbol{x} x 的导数, 为 n × 1 n \times 1 n×1 的列向量。根据前面的框架, 当前的目标是寻找增 量 Δ x \Delta x Δx , 使得 ∥ f ( x + Δ x ) ∥ 2 \|f(x+\Delta x)\|^{2} ∥f(x+Δx)∥2 达到最小。为了求 Δ x \Delta x Δx , 我们需要解一个线性的最小二乘问题:
Δ x ∗ = arg min Δ x 1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 (6.31) \Delta x^{*}=\arg \min _{\Delta x} \frac{1}{2}\left\|f(x)+J(x) \Delta x\right\|^{2}\tag{6.31} Δx∗=argΔxmin21∥f(x)+J(x)Δx∥2(6.31)
注:必须说明的是,《视觉SLAM十四讲》里的式(6.30)泰勒展开有误,展开后的雅克比矩阵是不需要转置的
1 2 ∥ f ( x ) + J ( x ) Δ x ∥ 2 = 1 2 ( f ( x ) + J ( x ) Δ x ) T ( f ( x ) + J ( x ) Δ x ) = 1 2 ( f ( x ) T + Δ x T J ( x ) T ) ( f ( x ) + J ( x ) Δ x ) = 1 2 ( f ( x ) T f ( x ) + f ( x ) T J ( x ) Δ x + Δ x T J ( x ) T f ( x ) + Δ x T J ( x ) T J ( x ) Δ x ) (6.32) \begin{aligned} \frac{1}{2}\left\|f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})\Delta \boldsymbol{x}\right\|^{2} &=\frac{1}{2}\left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)^{\mathrm{T}}\left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})\Delta \boldsymbol{x}\right) \\ &= \frac{1}{2}\left(f(\boldsymbol{x})^{\mathrm{T}}+\Delta \boldsymbol{x}^{\mathrm{T}}\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \right) \left(f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)\\ &= \frac{1}{2}\left(f(\boldsymbol{x})^{\mathrm{T}} f(\boldsymbol{x})+ f(\boldsymbol{x})^\mathrm{T} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x} + \Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}}f(\boldsymbol{x})+\Delta \boldsymbol{x}^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}\right)\\ \end{aligned}\tag{6.32} 21∥f(x)+J(x)Δx∥2=21(f(x)+J(x)Δx)T(f(x)+J(x)Δx)=21(f(x)T+ΔxTJ(x)T)(f(x)+J(x)Δx)=21(f(x)Tf(x)+f(x)TJ(x)Δx+ΔxTJ(x)Tf(x)+ΔxTJ(x)TJ(x)Δx)(6.32)
求上式关于 Δ x \Delta x Δx 的导数, 并令其为零:
J ( x ) T f ( x ) + J ( x ) T J ( x ) Δ x = 0 (6.33) \boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} f(\boldsymbol{x})+\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) \Delta \boldsymbol{x}=\mathbf{0}\tag{6.33} J(x)Tf(x)+J(x)TJ(x)Δx=0(6.33)
可以得到如下方程组:
J T ( x ) J ⏟ H ( x ) ( x ) Δ x = − J T ( x ) f ( x ) ⏟ g ( x ) . (6.34) \underbrace{\boldsymbol{J}^{\mathrm{T}}(\boldsymbol{x}) \boldsymbol{J}}_{\boldsymbol{H}(\boldsymbol{x})}(\boldsymbol{x}) \Delta \boldsymbol{x}=\underbrace{-\boldsymbol{J}^{\mathrm{T}}(\boldsymbol{x}) f(\boldsymbol{x})}_{\boldsymbol{g}(\boldsymbol{x})} .\tag{6.34} H(x) JT(x)J(x)Δx=g(x) −JT(x)f(x).(6.34)
这个方程是关于变量 Δ x \Delta x Δx 的线性方程组, 我们称它为增量方程, 也可以称为高斯牛顿方程 ( Gauss-Newton equation) 或者正规方程 (Normal equation )。我们把左边的系数定义为 H \boldsymbol{H} H , 右边 定义为 g g g , 那么上式变为
H Δ x = g (6.35) \boldsymbol{H} \Delta \boldsymbol{x}=\boldsymbol{g}\tag{6.35} HΔx=g(6.35)
这里把左侧记作 H \boldsymbol{H} H是有意义的。对比牛顿法可见,高斯牛顿法用 J ( x ) J T \boldsymbol{J}(\boldsymbol{x}) \boldsymbol{J}^{\mathrm{T}} J(x)JT作为牛顿法中二阶海森矩阵的近似,从而省略了计算 H \boldsymbol{H} H的过程。
为了求解增量方程,我们需要求解 H − 1 \boldsymbol{H}^{-1} H−1,但实际数据中计算得到的 J ( x ) J T \boldsymbol{J}(\boldsymbol{x}) \boldsymbol{J}^{\mathrm{T}} J(x)JT只有半正定性。也就是说,求 H − 1 \boldsymbol{H}^{-1} H−1时可能无解,导致算法不收敛。直观地说,原函数在这个点的局部近似不像一个二次函数。因此,每次求逆都需要判断判断是否求逆成功。
高斯牛顿法中采用的近似二阶泰勒展开只能在展开点附近有较好的近似效果,所以我们应该给 Δ x \Delta \boldsymbol{x} Δx添加一个范围,称为信赖区域。这个范围定义了在什么情况下二阶近似是有效的。
那么,如何确定这个信赖区域的范围呢?一个比较好的方法是根据我们的近似模型跟实际函数之间的差异来确定:如果差异小,说明近似效果好,我们扩大近似的范围;反之,如果差异搭,就缩小近似的范围。我们定义一个指标 ρ \rho ρ来刻画近似的好坏程度:
ρ = f ( x + Δ x ) − f ( x ) J ( x ) T Δ x (6.34) \rho=\frac{f(\boldsymbol{x}+\Delta \boldsymbol{x})-f(\boldsymbol{x})}{\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \Delta \boldsymbol{x}}\tag{6.34} ρ=J(x)TΔxf(x+Δx)−f(x)(6.34)
ρ \rho ρ的分子式实际函数下降的值,分母式近似模型下降的值。
算法流程:
- 给定初始值 x 0 \boldsymbol{x}_0 x0,以及初始优化半径 μ \mu μ。
- 对于第 k k k次迭代,在高斯牛顿法的基础上加上信赖区域,求解:
min Δ x k 1 2 ∥ f ( x k ) + J ( x k ) T Δ x k ∥ 2 , s.t. ∥ D Δ x k ∥ 2 ⩽ μ (6.35) \min _{\Delta \boldsymbol{x}_{k}} \frac{1}{2}\left\|f\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}\right\|^{2}, \quad \text { s.t. } \quad\left\|\boldsymbol{D} \Delta \boldsymbol{x}_{k}\right\|^{2} \leqslant \mu\tag{6.35} Δxkmin21∥∥∥f(xk)+J(xk)TΔxk∥∥∥2, s.t. ∥DΔxk∥2⩽μ(6.35)
其中, μ \mu μ是信赖区域的半径, D \boldsymbol{D} D为系数矩阵- 按照式(6.34)计算 ρ \rho ρ。
- 若 ρ > 3 4 \rho > \frac{3}{4} ρ>43,则设置 μ = 2 μ \mu=2\mu μ=2μ
- 若 ρ < 1 4 \rho < \frac{1}{4} ρ<41,则设置 μ = 0.5 μ \mu=0.5\mu μ=0.5μ
- 如果 ρ \rho ρ大于某阈值,则认为近似可行。令 x k + 1 = x k + Δ x k \boldsymbol{x}_{k+1}=\boldsymbol{x}_k+\Delta\boldsymbol{x}_k xk+1=xk+Δxk
- 判断算法是否收敛。如不收敛则返回第2步,否则结束。
这里近似范围扩大的倍数和阈值都是经验值,可以替换成别的数值。在式(6.35)中,我们把增量限定于一个半径为 μ \mu μ的球中,认为只在这个球内才是有效的。带上 D \boldsymbol{D} D之后,这个球可以看成一个椭球。在列文伯格提出的优化方法中,把 D \boldsymbol{D} D取成单位阵 I \boldsymbol{I} I,相当于直接把 Δ x k \Delta \boldsymbol{x}_k Δxk约束在一个球中。随后,马夸尔特提出将 D \boldsymbol{D} D取成非负数对角阵——实际中通常用 J T J \boldsymbol{J}^\boldsymbol{T}\boldsymbol{J} JTJ的对角元素平方根,使得在梯度小的维度上的维度上约束范围更大一些。
这个子问题是带不等式约束的优化问题,我们用拉格朗日乘子把约束项放到目标函数中,构成拉格朗日函数:
L ( Δ x k , λ ) = 1 2 ∥ f ( x k ) + J ( x k ) T Δ x k ∥ 2 + λ 2 ( ∥ D Δ x k ∥ 2 − μ ) (6.36) \mathcal{L}\left(\Delta \boldsymbol{x}_{k}, \lambda\right)=\frac{1}{2}\left\|f\left(\boldsymbol{x}_{k}\right)+\boldsymbol{J}\left(\boldsymbol{x}_{k}\right)^{\mathrm{T}} \Delta \boldsymbol{x}_{k}\right\|^{2}+\frac{\lambda}{2}\left(\left\|\boldsymbol{D} \Delta \boldsymbol{x}_{k}\right\|^{2}-\mu\right)\tag{6.36} L(Δxk,λ)=21∥∥∥f(xk)+J(xk)TΔxk∥∥∥2+2λ(∥DΔxk∥2−μ)(6.36)
这里 λ \lambda λ 为拉格朗日乘子。类似于高斯牛顿法中的做法, 令该拉格朗日函数关于 Δ x \Delta x Δx 的导数为零, 它的核心仍是计算增量的线性方程:
( H + λ D T D ) Δ x k = g (6.37) \left(\boldsymbol{H}+\lambda \boldsymbol{D}^{\mathrm{T}} \boldsymbol{D}\right) \Delta \boldsymbol{x}_{k}=\boldsymbol{g}\tag{6.37} (H+λDTD)Δxk=g(6.37)
可以看到, 相比于高斯牛顿法, 增量方程多了一项 λ D T D \lambda \boldsymbol{D}^{T} \boldsymbol{D} λDTD 。如果考虑它的简化形式(列文伯格提出), 即 D = I \boldsymbol{D}=\boldsymbol{I} D=I , 那么相当于求解 :
( H + λ I ) Δ x k = g (6.37-1) (\boldsymbol{H}+\lambda \boldsymbol{I}) \Delta \boldsymbol{x}_{k}=\boldsymbol{g}\tag{6.37-1} (H+λI)Δxk=g(6.37-1)
进一步地,马夸尔特提出了通过对角线元素增加比例系数来提供更快的收敛速度:
( J ( x ) T J ( x ) + λ diag ( J ( x ) J ( x ) T ) ) Δ x k = − J ( x ) f ( x ) (6.37-2) \left(\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \boldsymbol{J}(\boldsymbol{x}) + \lambda \text{diag}\left(\boldsymbol{J}(\boldsymbol{x})\boldsymbol{J}(\boldsymbol{x})^{\mathrm{T}} \right)\right)\Delta \boldsymbol{x}_{k}=-\boldsymbol{J}(\boldsymbol{x}) f(\boldsymbol{x})\tag{6.37-2} (J(x)TJ(x)+λdiag(J(x)J(x)T))Δxk=−J(x)f(x)(6.37-2)
式(6.37-1)和式(6.37-2)对正规方程 H Δ x = g \boldsymbol{H} \Delta \boldsymbol{x}=\boldsymbol{g} HΔx=g的修改都可以从贝叶斯的角度理解为给系统增加了一个零均值的先验信息。
高斯牛顿法和列文伯格-马夸尔特算法之间最关键的区别在于,后者会拒绝可能导致更大的残差平方和的更新,而前者不会。一个更新被拒绝,说明非线性函数在局部附近表现不好,所以需要更小的更新步长。缩小更新步长可以通过启发式地增大 λ \lambda λ的值来实现,例如,可以将现有的 λ \lambda λ值乘以因子10,并且求解修改后的正规方程。另一方面,如果一个步长导致了残差平方和的减小,这个步长就会被接受,并且状态估计值也会相应地被更新。在这种情况下,减小 λ \lambda λ(通过将 λ \lambda λ除以10),算法在一个新的线性化点处继续迭代,直到收敛为止。
待续。。。
考虑一条满足以下方程的曲线:
y = exp ( a x 2 + b x + c ) + w , y=\exp \left(a x^{2}+b x+c\right)+w, y=exp(ax2+bx+c)+w,
其中, a , b , c a, b, c a,b,c 为曲线的参数, w w w 为高斯噪声, 满足 w ∼ ( 0 , σ 2 ) w \sim\left(0, \sigma^{2}\right) w∼(0,σ2) 。现在, 假设我们有 N N N 个关于 x , y x, y x,y 的观测数据点, 想根据这些数据点求出曲线的参数。那么, 可以求解下面的最小二乘问题以估计曲线参数:
min a , b , c 1 2 ∑ i = 1 N ∥ y i − exp ( a x i 2 + b x i + c ) ∥ 2 \min _{a, b, c} \frac{1}{2} \sum_{i=1}^{N}\left\|y_{i}-\exp \left(a x_{i}^{2}+b x_{i}+c\right)\right\|^{2} a,b,cmin21i=1∑N∥∥yi−exp(axi2+bxi+c)∥∥2
请注意, 在这个问题中, 待估计的变量是 a , b , c a, b, c a,b,c , 而不是 x x x 。我们的程序里先根据模型生成 x , y x, y x,y 的真值, 然后在真值中添加高斯分布的噪声。随后, 使用高斯牛顿法从带噪声的数据拟合参数模型。定义误差为
e i = y i − exp ( a x i 2 + b x i + c ) , e_{i}=y_{i}-\exp \left(a x_{i}^{2}+b x_{i}+c\right), ei=yi−exp(axi2+bxi+c),
那么, 可以求出每个误差项对于状态变量的导数:
∂ e i ∂ a = − x i 2 exp ( a x i 2 + b x i + c ) ∂ e i ∂ b = − x i exp ( a x i 2 + b x i + c ) ∂ e i ∂ c = − exp ( a x i 2 + b x i + c ) \begin{array}{l} \frac{\partial e_{i}}{\partial a}=-x_{i}^{2} \exp \left(a x_{i}^{2}+b x_{i}+c\right) \\ \frac{\partial e_{i}}{\partial b}=-x_{i} \exp \left(a x_{i}^{2}+b x_{i}+c\right) \\ \frac{\partial e_{i}}{\partial c}=-\exp \left(a x_{i}^{2}+b x_{i}+c\right) \end{array} ∂a∂ei=−xi2exp(axi2+bxi+c)∂b∂ei=−xiexp(axi2+bxi+c)∂c∂ei=−exp(axi2+bxi+c)
于是 J i = [ ∂ e i ∂ a , ∂ e i ∂ b , ∂ e i ∂ c ] T \boldsymbol{J}_{i}=\left[\frac{\partial e_{i}}{\partial a}, \frac{\partial e_{i}}{\partial b}, \frac{\partial e_{i}}{\partial c}\right]^{\mathrm{T}} Ji=[∂a∂ei,∂b∂ei,∂c∂ei]T , 高斯牛顿法的增量方程为
( ∑ i = 1 100 J i ( σ 2 ) − 1 J i ⊤ ) Δ x k = ∑ i = 1 100 − J i ( σ 2 ) − 1 e i , \left(\sum_{i=1}^{100} J_{i}\left(\sigma^{2}\right)^{-1} J_{i}{ }^{\top}\right) \Delta x_{k}=\sum_{i=1}^{100}-J_{i}\left(\sigma^{2}\right)^{-1} e_{i,} (i=1∑100Ji(σ2)−1Ji⊤)Δxk=i=1∑100−Ji(σ2)−1ei,
为了让求解过程更加清晰,我们选择把所有的 J i \boldsymbol{J}_{i} Ji排成一列,将这个方程写成矩阵形式。不过这样会消耗较多的内存。
#include
#include
#include
#include
#include
using namespace Eigen;
int main(int argc, char** argv) {
// 配置真实值
double ar = 1.0, br = 2.0, cr = 1.0;
// 配置噪声值
double w_sigma = 1.0;
double inv_w_sigma = 1.0/w_sigma;
// 生成数据点
int N = 100;
// OpenCV随机数生成器
cv::RNG rng;
// 雅克比矩阵
Eigen::Matrix<double, 100, 3> J;
J.setZero();
// 正规矩阵H = J^T*J
Eigen::Matrix<double, 3, 3> H;
H.setZero();
// J_n
Eigen::Matrix<double, 1, 3> Jn;
Jn.setZero();
// f(x)
Eigen::Matrix<double, 100, 1> f;
f.setZero();
// g(x)
Eigen::Matrix<double, 3, 1> g;
g.setZero();
// /Delta x
Eigen::Matrix<double, 3, 1> Delta_x;
Delta_x.setZero();
std::vector<double> x_data, y_data;
for(size_t i=0; i<N; i++)
{
double x = i/100.0;
x_data.push_back(x);
y_data.push_back(std::exp(ar*x*x+br*x+cr)+rng.gaussian(w_sigma*w_sigma));
}
// 设置初始值
double ae = 2.0, be = -1.0, ce = 5.0;
// 开始最速下降法迭代
// 设置迭代次数
// 设置结束条件
size_t numIter = 100;
double cost = 0.0;
double last_cost = 0.0;
size_t count = 0;
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
for(size_t i=0; i<numIter; i++)
{
for(size_t j=0; j<N; j++)
{
Jn(0, 0) = x_data[j]*x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
Jn(0, 1) = x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
Jn(0, 2) = 1*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
J.block(j, 0, 1, 3) = inv_w_sigma*Jn; // 记得配置上噪声权值
f(j, 0) = inv_w_sigma*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce) - y_data[j]; // 记得配置上噪声权值
}
cost = f.norm();
H = J.transpose()*J;
g = -J.transpose()*f;
// 直接求逆
// Delta_x = H.inverse()*g;
// 使用cholesky分解来解方程
// Delta_x = H.ldlt().solve(g);
// 使用QR分解来解方程,速度比较快
Delta_x = H.colPivHouseholderQr().solve(g);
if(isnan(Delta_x[0]))
{
std::cout<<"result is nan!"<<std::endl;
break;
}
if(cost>=last_cost && i>0)
{
std::cout<<"cost: "<<cost<<" >= last cost: "<<last_cost<<", break."<<std::endl;
break;
}
last_cost = cost;
ae = ae + Delta_x(0, 0);
be = be + Delta_x(1, 0);
ce = ce + Delta_x(2, 0);
count = i;
}
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
std::cout << "solve time cost = " << time_used.count() << " seconds. " << std::endl;
std::cout<<"estimated abc = "<<ae<<", "<<be<<", "<<ce<<std::endl;
std::cout<<"number of iterations = "<<count<<std::endl;
return 0;
}
#include
#include
#include
#include
#include
using namespace Eigen;
int main(int argc, char** argv) {
// 配置真实值
double ar = 1.0, br = 2.0, cr = 1.0;
// 配置噪声值
double w_sigma = 1.0;
double inv_w_sigma = 1.0/w_sigma;
// 生成数据点
int N = 100;
// OpenCV随机数生成器
cv::RNG rng;
double lambda = 1e-4;
// 雅克比矩阵
Eigen::Matrix<double, 100, 3> J;
J.setZero();
// 正规矩阵H = J^T*J
Eigen::Matrix<double, 3, 3> H;
H.setZero();
// J_n
Eigen::Matrix<double, 1, 3> Jn;
Jn.setZero();
// f(x)
Eigen::Matrix<double, 100, 1> f;
f.setZero();
// g(x)
Eigen::Matrix<double, 3, 1> g;
g.setZero();
// /Delta x
Eigen::Matrix<double, 3, 1> Delta_x;
Delta_x.setZero();
Eigen::Matrix3d DeltaMatrix;
DeltaMatrix.setZero();
std::vector<double> x_data, y_data;
for(size_t i=0; i<N; i++)
{
double x = i/100.0;
x_data.push_back(x);
y_data.push_back(std::exp(ar*x*x+br*x+cr)+rng.gaussian(w_sigma*w_sigma));
}
// 设置初始值
double ae = 2.0, be = -1.0, ce = 5.0;
// 开始最速下降法迭代
// 设置迭代次数
// 设置结束条件
size_t numIter = 100;
double cost = 0.0;
double last_cost = 0.0;
size_t count = 0;
std::chrono::steady_clock::time_point t1 = std::chrono::steady_clock::now();
for(size_t i=0; i<numIter; i++)
{
for(size_t j=0; j<N; j++)
{
Jn(0, 0) = x_data[j]*x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
Jn(0, 1) = x_data[j]*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
Jn(0, 2) = 1*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce);
J.block(j, 0, 1, 3) = inv_w_sigma*Jn; // 记得配置上噪声权值
f(j, 0) = inv_w_sigma*std::exp(ae*x_data[j]*x_data[j]+be*x_data[j]+ce) - y_data[j]; // 记得配置上噪声权值
}
cost = f.norm();
H = J.transpose()*J;
Eigen::Matrix3d diagMatrix = Eigen::Matrix3d(H.diagonal().asDiagonal());
DeltaMatrix = H + lambda*diagMatrix;
g = -J.transpose()*f;
// 直接求逆
// Delta_x = DeltaMatrix.inverse()*g;
// 使用cholesky分解来解方程
// Delta_x = DeltaMatrix.ldlt().solve(g);
// 使用QR分解来解方程,速度比较快
Delta_x = DeltaMatrix.colPivHouseholderQr().solve(g);
if(isnan(Delta_x[0]))
{
std::cout<<"result is nan!"<<std::endl;
break;
}
if(Delta_x.norm()<0.001)
{
std::cout<<"result is converge!"<<std::endl;
break;
}
// 接受更新
if((cost<last_cost && i>0) || i==0)
{
ae = ae + Delta_x(0, 0);
be = be + Delta_x(1, 0);
ce = ce + Delta_x(2, 0);
if(i>0) lambda*=0.1;
}
// 发散了,拒绝更新
else
{
lambda*=10.0;
}
last_cost = cost;
count = i;
}
std::chrono::steady_clock::time_point t2 = std::chrono::steady_clock::now();
std::chrono::duration<double> time_used = std::chrono::duration_cast<std::chrono::duration<double>>(t2 - t1);
std::cout << "solve time cost = " << time_used.count() << " seconds. " << std::endl;
std::cout<<"estimated abc = "<<ae<<", "<<be<<", "<<ce<<std::endl;
std::cout<<"number of iterations = "<<count<<std::endl;
return 0;
}
学习Ceres-solver最全的文档:Ceres Solver
代码以后再补…
在图优化库里,g2o和GTSAM的功能类似,而我自己更喜欢用GTSAM,因为它集成了ISAM2。因此这里暂时搁置g2o。
学习GTSAM最全的文档:GTSAM
代码以后再补…