这里介绍线性系统的解析,如何进行各种分解计算,如LU,QR,SVD,特征值分解等。
在一个线性系统,常如下表示,其中A,b
分别是一个矩阵,需要求x
:
A x = b Ax \:= \: b Ax=b
在Eigen中,我们可以根据需要的精确性要求,性能要求,从而从好几种方法中选择一种来求解这个线性系统。
下面的示例,可以看到一种求解方法。
//matrxi_decomp1.cpp
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix3f A;
Vector3f b;
A << 1,2,3, 4,5,6, 7,8,10;
b << 3, 3, 4;
cout << "Here is the matrix A:\n" << A << endl;
cout << "Here is the vector b:\n" << b << endl;
Vector3f x = A.colPivHouseholderQr().solve(b);
cout << "The solution is:\n" << x << endl;
}
执行:
$ g++ -I /usr/local/include/eigen3 matrxi_decomp1.cpp -o matrxi_decomp1
$
$ ./matrxi_decomp1
Here is the matrix A:
1 2 3
4 5 6
7 8 10
Here is the vector b:
3
3
4
The solution is:
-2
0.999997
1
这个示例中,使用矩阵的colPivHouseholderQr()
方法,其返回一个ColPivHouseholderQR
实例对象。因为调用对象是一个Matrix3f
类型,所以也可以如此设计
ColPivHouseholderQR<Matrix3f> dec(A);
Vector3f x = dec.solve(b);
正如名字所示,这里ColPivHouseholderQR
是一个采用列旋转方法的QR
分解。下面Eigen提供的表列出了你可以使用分解类型:
Decomposition | Method | Requirements on the matrix | Speed(small-to-medium) | Speed(large) | Accuracy |
---|---|---|---|---|---|
PartialPivLU | partialPivLu() | Invertible(可逆) | ++ | ++ | + |
FullPivLU | fullPivLu() | None | - | - - | +++ |
HouseholderQR | householderQr() | None | ++ | ++ | + |
ColPivHouseholderQR | colPivHouseholderQr() | None | + | - | +++ |
FullPivHouseholderQR | fullPivHouseholderQr() | None | - | - - | +++ |
CompleteOrthogonalDecomposition | completeOrthogonalDecomposition() | None | + | - | +++ |
LLT | llt() | Positive definite(正定) | +++ | +++ | + |
LDLT | ldlt() | Positive or negative semidefinite | +++ | + | ++ |
BDCSVD | bdcSvd() | None | - | - | +++ |
JacobiSVD | jacobiSvd() | None | - | - - - | +++ |
其中的部分分解公式:
FullPivLU:
A = P − 1 L U Q − 1 A = P^{-1} L U Q^{-1} A=P−1LUQ−1
ColPivHouseholderQR:
A P = Q R \mathbf{A} \, \mathbf{P} = \mathbf{Q} \, \mathbf{R} AP=QR
FullPivHouseholderQR:
P A P ′ = Q R \mathbf{P} \, \mathbf{A} \, \mathbf{P}' = \mathbf{Q} \, \mathbf{R} PAP′=QR
LLT:
L L T LL^T LLT Cholesky decomposition of a symmetric, positive definite matrix A such that
A = L L ∗ = U ∗ U A = LL^* = U^*U A=LL∗=U∗U , where L is lower triangular.
LDLT
L D L T LDL^T LDLT
Cholesky decomposition:
A = P T L D L ∗ P A = P^TLDL^*P A=PTLDL∗P
where P is a permutation matrix, L is lower triangular with a unit diagonal and D is a diagonal matrix.
Note: Eigen的测试benchmark
如示例所示,所有的分解计算提供的solve()
方法,此方法真正进行分解运算。
如果你的运算的矩阵是正定的,如上表中所展示的,更好的选择是LLT和LDLT分解方法。下面的示例演示了使用方法,其中右边使用一个普通的矩阵,而不是向量vector。
示例:
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix2f A, b;
A << 2, -1, -1, 3;
b << 1, 2, 3, 1;
cout << "Here is the matrix A:\n" << A << endl;
cout << "Here is the right hand side b:\n" << b << endl;
Matrix2f x = A.ldlt().solve(b);
cout << "The solution is:\n" << x << endl;
}
这里使用了A.ldlt().solve(b)
来进行计算,得到结果x
。
执行结果:
Here is the matrix A:
2 -1
-1 3
Here is the right hand side b:
1 2
3 1
The solution is:
1.2 1.4
1.4 0.8
计算存在误差,只有我们知道了一个计算的解的可允许的误差容限error margin
,我们才能判断一个解是否满足要求。在Eigen内,很容易做这种计算。
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
MatrixXd A = MatrixXd::Random(100,100);
MatrixXd b = MatrixXd::Random(100,50);
MatrixXd x = A.fullPivLu().solve(b);
double relative_error = (A*x - b).norm() / b.norm(); // norm() is L2 norm
cout << "The relative error is:\n" << relative_error << endl;
}
执行结果:
The relative error is:
3.75098e-13
在线性代数里计算特征值Eigen value和特征向量 EigenVector是非常常见的计算,计算前,要检查确保你的矩阵是自伴随self-adjoint
矩阵。Eigen里提供了SelfAdjointEigenSolver
,很容易地在矩阵对象上使用EigenSolver , ComplexEigenSolver
。
特征值和特征向量的计算未必收敛,这种非收敛的情况很罕见,但确实存在。Eigen中的 SelfAdjointEigenSolver.info()
函数可以用来检查。
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix2f A;
A << 1, 2, 2, 3;
cout << "Here is the matrix A:\n" << A << endl;
SelfAdjointEigenSolver<Matrix2f> eigensolver(A);
if (eigensolver.info() != Success) {
cout << "converge error!"<<endl;
abort();
}
cout << "The eigenvalues of A are:\n" << eigensolver.eigenvalues() << endl;
cout << "Here's a matrix whose columns are eigenvectors of A \n"
<< "corresponding to these eigenvalues:\n"
<< eigensolver.eigenvectors() << endl;
}
执行结果:
$ g++ -I /usr/local/include/eigen3 matrxi_decomp3.cpp -o matrxi_decomp3
$ ./matrxi_decomp3
Here is the matrix A:
1 2
2 3
The eigenvalues of A are:
-0.236068
4.23607
Here's a matrix whose columns are eigenvectors of A
corresponding to these eigenvalues:
-0.850651 -0.525731
0.525731 -0.850651
首先,逆矩阵和行列式在数学上是一个基本的概念,但计算逆矩阵、计算行列式并不是一个好主意,因为这在处理问题时,并不是必须的。如上面介绍的,各种solve()
更能满足各种要求。而且,也可以不必使用计算行列式来判断一个矩阵是否可逆。
但对一些小矩阵,计算逆矩阵或计算行列式并不是什么大问题。同时,一些分解算法也提供了inverse(), determinant()
方法,来进行计算。
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix3f A;
A << 1, 2, 1,
2, 1, 0,
-1, 1, 2;
cout << "Here is the matrix A:\n" << A << endl;
cout << "The determinant of A is " << A.determinant() << endl;
cout << "The inverse of A is:\n" << A.inverse() << endl;
}
执行结果:
Here is the matrix A:
1 2 1
2 1 0
-1 1 2
The determinant of A is -3
The inverse of A is:
-0.667 1 0.333
1.33 -1 -0.667
-1 1 1
Least squares solving
精度最高的最小二乘法求解是SVD
分解法,而且Eigen提供了2中实现。推荐给大家的是 BDCSVD
,它对大规模的求解问题能很好匹配,同时在处理小规模的问题时,自动回落到JacobiSVD
处理模式。在设计上是一致性的,它们的solve()
方法用于求解。
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
MatrixXf A = MatrixXf::Random(3, 2);
cout << "Here is the matrix A:\n" << A << endl;
VectorXf b = VectorXf::Random(3);
cout << "Here is the right hand side b:\n" << b << endl;
cout << "The least-squares solution is:\n"
<< A.bdcSvd(ComputeThinU | ComputeThinV).solve(b) << endl;
}
执行结果:
$ g++ -I /usr/local/include/eigen3 matrxi_decomp4.cpp -o matrxi_decomp4
$
$ ./matrxi_decomp4
Here is the matrix A:
-0.999984 -0.0826997
-0.736924 0.0655345
0.511211 -0.562082
Here is the right hand side b:
-0.905911
0.357729
0.358593
The least-squares solution is:
0.46358
0.0429898
还有其他的求解方法,比如Cholesky分解法,QR分解法
等,可以查看Eigen相关参考说明和文档。
在上面的示例中,矩阵分解/线性求解的源码,看上去在分解器对象的构造和计算同时在一个语句内。其实我们可以把它们分开,这样构造时,我们知道了需要分解的矩阵,而且可以对这个分解器对象重复使用。这样的好处是:
下面的示例,演示了’LLT’的线性求解,矩阵分解方法。重复使用了LLT
分解器对象。
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix2f A, b;
LLT<Matrix2f> llt;
A << 2, -1, -1, 3;
b << 1, 2, 3, 1;
cout << "Here is the matrix A:\n" << A << endl;
cout << "Here is the right hand side b:\n" << b << endl;
cout << "Computing LLT decomposition..." << endl;
llt.compute(A);
cout << "The solution is:\n" << llt.solve(b) << endl;
A(1,1)++;
cout << "The matrix A is now:\n" << A << endl;
cout << "Computing LLT decomposition..." << endl;
llt.compute(A);
cout << "The solution is now:\n" << llt.solve(b) << endl;
}
执行结果:
Here is the matrix A:
2 -1
-1 3
Here is the right hand side b:
1 2
3 1
Computing LLT decomposition...
The solution is:
1.2 1.4
1.4 0.8
The matrix A is now:
2 -1
-1 4
Computing LLT decomposition...
The solution is now:
1 1.29
1 0.571
而且,我们可以指定分解器的处理矩阵尺寸,让编译器编译时,预分配存储空间,这样在后面执行时,无需动态分配内存空间,从而提高执行速度和执行效率。
比如:
HouseholderQR<MatrixXf> qr(50,50);
MatrixXf A = MatrixXf::Random(50,50);
qr.compute(A); // no dynamic memory allocation
某些分解可以用来对矩阵求秩,可以称为秩分解
。代表性的是,奇异矩阵的非全秩矩阵的分解。在Catalogue of dense decompositions上可以看到Eigen的分解器是否支持Rank-Revealing。
具有矩阵秩分解的分解器,至少会提供rank()
函数。它们可能还会提供isInvertible()
;还有一些提供了计算空域的核kernel的方法,也有提供列空间的像image。比如FullPivLU
,如下示例:
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix3f A;
A << 1, 2, 5,
2, 1, 4,
3, 0, 3;
cout << "Here is the matrix A:\n" << A << endl;
FullPivLU<Matrix3f> lu_decomp(A);
cout << "The rank of A is " << lu_decomp.rank() << endl;
cout << "Here is a matrix whose columns form a basis of the null-space of A:\n"
<< lu_decomp.kernel() << endl;
cout << "Here is a matrix whose columns form a basis of the column-space of A:\n"
<< lu_decomp.image(A) << endl; // yes, have to pass the original A
}
执行:
$ g++ -I /usr/local/include/eigen3 matrxi_decomp5.cpp -o matrxi_decomp5
$
$
$ ./matrxi_decomp5
Here is the matrix A:
1 2 5
2 1 4
3 0 3
The rank of A is 2
Here is a matrix whose columns form a basis of the null-space of A:
0.5
1
-0.5
Here is a matrix whose columns form a basis of the column-space of A:
5 1
4 2
3 3
当然,任何矩阵的秩rank的计算依赖于一个选择的任意阈值, 实际上,非浮点数的矩阵是有秩缺陷的rank-deficient.
。
计算特征Eigen时,可以选择合理的默认阈值,这取决于分解算法,但通常的选择是对角线尺寸乘以ε。虽然这是我们可以选择的最好的缺省值。但针对您的应用程序,只有你才知道什么是正确的阈值。在分解计算对象调用rank()
或任何其他方法之前,你可以设置通过调用setThreshold()
,来设置一个合适的阈值。而分解计算方法自身,即compute()
方法,独立于阈值 – 你无需在修改了阈值后,再重新计算分解对象。
当然,任何矩阵秩的计算,有赖于一个阈值的选择。特别是因为计算机计算的问题,浮点类型的矩阵是无秩的。Eigen中的分解器会选择一个合理的阈值,通常是于机器精度的数倍于对角阵中的尺寸 – 这个说法很绕。如果你明确知道阈值,你可以使用setThreshold()
函数来设置阈值。
注意: 矩阵分解器的computer()
计算方法,并不依赖于这个阈值。
看一个示例:
#include
#include
using namespace std;
using namespace Eigen;
int main()
{
Matrix2d A;
A << 2, 1,
2, 0.9999999999;
FullPivLU<Matrix2d> lu(A);
cout << "By default, the rank of A is found to be " << lu.rank() << endl;
lu.setThreshold(1e-5);
cout << "With threshold 1e-5, the rank of A is found to be " << lu.rank() << endl;
}
执行结果:
By default, the rank of A is found to be 2
With threshold 1e-5, the rank of A is found to be 1