Eigen學習筆記(12)-線性代數與矩陣分解

原文:Eigen官網-Linear algebra and decompositions

本篇文章介紹了線性方程求解、矩陣分解,包括LU分解法,QR分解法,SVD(奇異值分解)、特徵值分解等。

The problem:對於一般形式如下的線性系統:
[ Ax : = : b ]

The solution:解決上述方程的方式一般是將矩陣A進行分解,你可以根據A的形式選擇不同的分解方法。當然最基本的方法是高斯消元法。
示例如下:

#include <iostream>
#include <Eigen/Dense>
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;
}

結果如下:

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
 1
 1

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 - - - - +++

如上所示的所有分解方法都提供了一個solve()方法,如上例中所示。

比如,假設有一個正定的矩陣,如上表中所示,最好的選擇是使用LLTLDLT分解方法。

#include <iostream>
#include <Eigen/Dense>
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;
}

結果如下:

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

2. 確認解是否真的存在

你可以計算誤差並確認得到的解是否是有效的,Eigen允許你自己來計算相關誤差。

#include <iostream>
#include <Eigen/Dense>
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:
2.31495e-14

3. 計算特徵值和特徵向量

首先要檢查矩陣是否是self-adjoint
SelfAdjointEigenSolver 可以通過EigenSolver或者ComplexEigenSolver方法適用於一般矩陣。

特徵值和特徵向量的計算不一定收斂,但這種不收斂的情況非常少見。調用info()是爲了檢查這種可能性。

#include <iostream>
#include <Eigen/Dense>
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) 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;
}

結果如下:

Here is the matrix A:
1 2
2 3
The eigenvalues of A are:
-0.236
  4.24
Here's a matrix whose columns are eigenvectors of A 
corresponding to these eigenvalues:
-0.851 -0.526
 0.526 -0.851

4. 計算逆矩陣和矩陣行列式

Eigen 也提供了求逆矩陣和求矩陣行列式的算法,但是這兩種算法對於大型矩陣來說都是非常不經濟的算法,當需要對大型矩陣做這種的操作時,需要自己判斷到底需不需這樣做。但是對於小型矩陣 則可以沒有顧慮地使用。

PartialPivLUFullPivLU提供了inverse()determinant()方法,你也可以直接對矩陣使用inverse()determinant()方法。如果矩陣是一個很小的固定尺寸矩陣(至少是4*4),允許Eigen阻止使用LU分解,在這種小矩陣上使用公式將會更有效。

#include <iostream>
#include <Eigen/Dense>
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

5. 最小二乘法求解

最小二乘問題最準確的方法是SVD分解。
Eigen也提供瞭解最小二乘問題的解法,並給出兩種實現,分別是BDCSVDJacobiSVDBDCSVD可以很好地處理大問題,而對於較小的問題則自動返回到JacobiSVD類。因此推薦使用的一種是BDCSVD。這兩個類的solve()方法都可以解決最小二乘問題。

#include <iostream>
#include <Eigen/Dense>
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;
}

結果如下:

Here is the matrix A:
  0.68  0.597
-0.211  0.823
 0.566 -0.605
Here is the right hand side b:
 -0.33
 0.536
-0.444
The least-squares solution is:
-0.67
0.314

6. 將計算與構造分離

在上面的例子中,分解是在構造分解對象的同時計算的。然而,有些情況下,您可能希望將這兩件事情分開,例如,如果您不知道,在構造時,您將要分解的矩陣;或者,如果您想重用現有的分解對象。

使這成爲可能的是:

  • 所有分解都有一個默認構造函數,
  • 所有分解都有一個compute(matrix)方法來執行計算,並且可以在已計算的分解上再次調用該方法,重新初始化它。
#include <iostream>
#include <Eigen/Dense>
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

7. 揭示秩的分解

一些分解是可以揭示秩的,也就是說可以計算矩陣的秩。
** Rank-revealing decompositions** 提供了rank()方法,isInvertible(),有一些還提供了計算kernel(null-space)和image(column-space)的方法,示例如下:

#include <iostream>
#include <Eigen/Dense>
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
}

結果如下:

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

當然,任何秩的計算都依賴於任意閾值的選擇,因爲實際上沒有浮點矩陣是秩虧的。特徵選擇一個合理的默認閾值,這取決於分解,但通常是對角線大小乘以機器epsilon。雖然這是我們可以選擇的最佳默認值,但只有您知道應用程序的正確閾值。可以通過在調用rank()或需要使用此類閾值的任何其他方法之前對分解對象調用setThreshold()來設置此值。分解本身,即compute()方法,與閾值無關。更改閾值後,不需要重新計算分解。

#include <iostream>
#include <Eigen/Dense>
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

參考:

“Eigen學習之簡單線性方程與矩陣分解”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章