接前面的矩阵知识小记,稍微整理一下相关的计算程序或库(不考虑商业版)。
对比
涉及矩阵的各种程序和库有很多,暂时关注一下如下4个:
- Maxima 是一个计算机代数系统,主要用于符号计算。
- NumPy 是 Python 中的科学计算库,广泛用于数值计算。
- OpenCV 是 C++ 用于计算机视觉的库,支持一些矩阵操作,但主要用于图像处理。他有python绑定,但python绑定的矩阵操作主要使用NumPy。
- Eigen 是 C++ 的矩阵和线性代数库,广泛用于数值计算。
矩阵常规操作
矩阵的创建,矩阵的运算,矩阵的分解...
| 操作 | Maxima | NumPy | OpenCV (C++) | Eigen | 
|---|---|---|---|---|
| 矩阵创建 | matrix([a,b], [c,d]) | np.array([[a, b], [c, d]]) | cv::Mat A = (cv::Mat_<double>(2,2) << a, b, c, d); | Eigen::MatrixXd A(2, 2); A << a, b, c, d; | 
| 矩阵加法 | A + B | A + B | cv::add(A, B, C); | A + B | 
| 矩阵乘法 | A . B | np.dot(A, B)或A @ B | C = A * B;或cv::gemm(A, B, 1.0, cv::Mat(), 0.0, C); | A * B | 
| 矩阵转置 | transpose(A) | A.T | cv::transpose(A, B); | A.transpose() | 
| 矩阵行列式 | determinant(A) | np.linalg.det(A) | cv::determinant(A); | A.determinant() | 
| 矩阵求逆 | invert(A) | np.linalg.inv(A) | cv::invert(A, B); | A.inverse() | 
| 矩阵求迹 | trace(A) | np.trace(A) | cv::trace(A); | A.trace() | 
| 矩阵的LU分解 | lu_factor(A) | scipy.linalg.lu(A) | cv::LU(A, B);(部分功能) | Eigen::FullPivLU | 
| 矩阵的QR分解 | N/A | np.linalg.qr(A) | N/A | Eigen::HouseholderQR | 
| 矩阵的特征值分解 | eigenvalues(A) | np.linalg.eig(A) | cv::eigenNonSymmetric() | Eigen::EigenSolver | 
| 矩阵的SVD分解 | N/A | np.linalg.svd(A) | cv::SVDecomp(A, W, U, Vt); | Eigen::JacobiSVD | 
| 矩阵与标量相乘 | k * A | k * A | A = A * k; | k * A | 
| 矩阵拼接 | augment(A, B)(水平),stackmatrix(A, B)(竖直) | np.hstack((A, B))(水平),np.vstack((A, B))(竖直) | cv::hconcat(A, B, C);(水平),cv::vconcat(A, B, C);(竖直) | Eigen::MatrixXd::hstack(A, B),Eigen::MatrixXd::vstack(A, B) | 
| 矩阵求范数 | N/A | np.linalg.norm(A) | cv::norm(A); | A.norm() | 
| 单位矩阵 | ident(n) | np.eye(n) | cv::Mat::eye(n, n, CV_64F); | Eigen::MatrixXd::Identity(n, n) | 
| 零矩阵 | zeromatrix(m, n) | np.zeros((m, n)) | cv::Mat::zeros(m, n, CV_64F); | Eigen::MatrixXd::Zero(m, n) | 
| 矩阵元素逐项相乘 | A .* B | A * B | cv::multiply(A, B, C); | A.cwiseProduct(B) | 
| 矩阵元素逐项取平方 | N/A | np.square(A) | cv::pow(A, 2, B); | A.array().square() | 
| 矩阵对角线元素 | diagmatrix(A) | np.diag(A) | cv::Mat diag = cv::Mat::diag(A); | A.diagonal() | 
存储结构
计算机内存是线性的,即内存地址是一个连续的线性空间,但矩阵是二维(或多维)的数据结构。为了将这些多维数据存储在线性内存中,就需要决定如何对数据进行线性化存储,即按行还是按列的顺序存储。
- 行主序(Row-major order) 是大多数编程语言(如 C、C++、Python(NumPy 默认))的默认存储方式。这种存储方式更符合线性代数中常见的运算模式,例如矩阵乘法中的行向量和列向量操作。
- 列主序(Column-major order) 是 Fortran 和一些数学工具(如 MATLAB 和 Eigen(默认))的选择。这是因为 Fortran 早期广泛用于科学计算,使用列主序可以更好地优化向量化操作和循环性能。
比如:对于 2x2 矩阵:
按 行主序 存储的内存排列为:[1, 2, 3, 4];按 列主序 存储的内存排列则为:[1, 3, 2, 4]。
| 库/工具 | 默认存储顺序 | 如何选择行/列主序 | 
|---|---|---|
| Maxima | 无特别存储顺序 | 无法选择,列表的列表 | 
| NumPy | 行主序(可选列主序) | 使用 order='C'选择行主序,<br>使用order='F'选择列主序 | 
| OpenCV | 行主序 | 无法选择,默认使用行主序 | 
| Eigen | 列主序(可选行主序) | 使用 Eigen::RowMajor选择行主序,<br>默认列主序 | 
例子-特征值计算
以如下矩阵为例,计算其特征值
maxima执行的是符号运算,我们可以使用它模拟手动计算过程:
“手动”计算
- 使用charpoly获取特征多项式,也就是\mathbf{A}-t\mathbf{I}的行列式值
- 使用factor对特征多项进行分解,得出 -(t-5)*(t-3)^2,可以肉眼看出特征值的结果
- 使用solve解方程,得出特征值
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |  | 
如何求解特征向量呢??
比如,接上面,我们要继续求解特征值 3 的特征向量,需要解如下方程 (A - 3I) \mathbf{v} = 0
| 1 2 |  | 
解出来的解是参数形式。据此,将参数用(1,0)和(0,1)带入,可以构造出两个特征向量
| 1 |  | 
同样,对于特征值5,需要解 (A - 5I) \mathbf{v} = 0
| 1 2 |  | 
接出来的解依然是参数形式,将参数用1带入,可以构造出特征向量
| 1 |  | 
maxima自动计算
让 maxima 直接计算特征值也很简单:
| 1 2 3 4 5 6 7 8 |  | 
真正的特征值是放在结果的第一个list 当中,也就是5 和3。那第二个list 代表什么呢?代表的就是每个特征值的几何重数, 也就是每个特征值对应的特征向量空间之维度。
如果同时要求出特征向量,可以直接用eigenvectors函数:
| 1 2 |  | 
numpy
Python下功能都是现成的:
| 1 2 3 4 5 6 7 8 9 10 |  | 
如果要同时求出特征向量,可以直接用
| 1 2 3 4 5 6 7 8 9 10 |  | 
特征向量要竖着看(列向量)。
Eigen
Eigen下使用 EigenSolver 计算矩阵 A 的特征值和特征向量
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |  | 
输出结果
| 1 2 3 4 5 6 |  | 
特征向量要竖着看(列向量)。
OpenCV
在OpenCV中,使用 cv::eigen 或 cv::eigenNonSymmetric 计算特征值和特征向量
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |  | 
输出结果
| 1 2 3 4 5 6 |  | 
特征向量要竖着看(列向量)。
注意,上面用的cv::eigenNonSymmetric函数,尽管cv::eigen也能用于特征值求解,但是它只适用于对称矩阵。 如果不小心用错了函数,大致会得到下面这种错误的结果:
| 1 2 3 4 5 6 |  | 
Octave
Octave:Octave 是一个开源的高级编程语言,主要用于数值计算,语法和功能与 MATLAB 高度兼容。有不少在线网站可以用于练习。
在一定程度上,Octave和Python的Numpy可以类比:
| 特性 | Octave | Python (NumPy + SciPy) | 
|---|---|---|
| 矩阵支持 | 原生支持矩阵类型,默认情况下一切操作都是基于矩阵 | 通过 NumPy 提供矩阵和数组支持,默认是数组(ndarray) | 
| 矩阵操作的简洁性 | 矩阵运算非常简洁,默认处理为二维矩阵,类似 MATLAB | NumPy 的数组操作非常灵活,但 Python 本身没有内置矩阵支持 | 
| 矩阵索引 | 1 基索引(从 1 开始) | 0 基索引(从 0 开始),符合大多数编程语言的习惯 | 
| 线性代数库 | 内置矩阵运算与线性代数函数,如 inv、eig、det等 | NumPy 和 SciPy 提供了丰富的线性代数函数,如 inv、eig、det等 | 
| 稀疏矩阵支持 | 支持稀疏矩阵,通过 sparse函数进行稀疏矩阵运算 | SciPy 提供了强大的稀疏矩阵支持,适合处理大规模稀疏矩阵 | 
| 广播机制 | 无自动广播机制,矩阵的维度必须匹配 | NumPy 支持广播机制,可在不同维度的数组之间进行操作 | 
| 矩阵乘法 | *表示矩阵乘法,.*表示元素逐个相乘 | @表示矩阵乘法,*表示元素逐个相乘(从 Python 3.5+ 开始) | 
| 向量化计算 | 默认支持向量化计算,所有操作都是矩阵或向量的 | NumPy 默认支持向量化计算,避免显式的循环 | 
| 性能 | 针对矩阵运算进行了优化,矩阵操作性能较好 | NumPy 和 SciPy 的矩阵运算经过高度优化,性能非常好,部分操作可以与 Octave 相媲美 | 
| 矩阵转置 | 使用 '符号进行矩阵转置 | 使用 T属性进行矩阵转置(如A.T) | 
| 高级矩阵分解 | 内置支持常见的矩阵分解如 QR、LU、SVD | SciPy 提供了丰富的矩阵分解功能,如 QR、LU、SVD、Cholesky 等 | 
| 矩阵求解 | 使用 A\b语法求解线性方程组 | 使用 np.linalg.solve(A, b)进行线性方程组求解 | 
| 性能优化 | 支持与 BLAS/LAPACK 库集成,提升大规模矩阵运算性能 | NumPy 和 SciPy 也可以与 BLAS/LAPACK 集成,提供类似的性能优化 | 
| GPU 支持 | 无原生支持,需要额外配置(如借助 OpenCL) | 可通过 CuPy 等库轻松扩展到 GPU 支持 | 
其他
- Armadillo:Armadillo 是一个 C++ 线性代数库,提供类似 MATLAB 的简洁语法,适合科学计算和与 BLAS/LAPACK 集成的高效运算。
- Blaze:Blaze 是一个高度优化的 C++ 线性代数库,专注于性能,通过自动向量化和并行化提供极致的矩阵运算效率。
- CuPy 提供了与 NumPy 类似的接口,适合 Python 用户进行 GPU 加速。
- MAGMA 支持混合 CPU 和 GPU 的矩阵运算,适合需要在多核 CPU 和 GPU 上并行执行的高性能任务。
- TensorFlow 和 PyTorch 虽然是机器学习框架,但也可以用于 GPU 上的高效矩阵运算。
- ...
参考
- https://maxima.sourceforge.io
- https://numpy.org/
- https://eigen.tuxfamily.org
- https://docs.opencv.org
- https://en.wikipedia.org/wiki/Eigenvalues_and_eigenvectors
- https://octave.org/
- https://arma.sourceforge.net/
- https://bitbucket.org/blaze-lib/blaze
- https://octave-online.net/
- https://www.mycompiler.io/new/octave
- https://docs.cupy.dev/en/stable/
- https://icl.utk.edu/magma/