附录 B — 矩阵运算
There’s probably some examples, but there are some examples of people using
solve(t(X) %*% W %*% X) %*% W %*% Y
to compute regression coefficients, too.— Thomas Lumley 1
本文主要介绍 Base R 提供的矩阵运算,包括加、减、乘等基础矩阵运算和常用的矩阵分解方法,总结 Base R 、Matrix 包和 Eigen 库对应的矩阵运算函数,分别对应基础、进阶和高阶的读者。最后,介绍矩阵运算在线性回归中的应用。
B.1 基础运算
约定符号
B.1.1 加、减、乘
矩阵
[,1] [,2]
[1,] 1.0 1.2
[2,] 1.2 3.0
[,1] [,2]
[1,] 1 3
[2,] 2 4
B.1.2 对数、指数与幂
矩阵
矩阵
expm 包可以计算矩阵的指数、开方、对数等。
或者使用奇异值分解
$d
[1] 3.5620499 0.4379501
$u
[,1] [,2]
[1,] -0.4241554 -0.9055894
[2,] -0.9055894 0.4241554
$v
[,1] [,2]
[1,] -0.4241554 -0.9055894
[2,] -0.9055894 0.4241554
[,1] [,2]
[1,] 7.60987 12.93908
[2,] 12.93908 29.17501
矩阵
计算
B.1.3 迹、秩、条件数
矩阵
B.1.4 求逆与广义逆
Moore-Penrose Generalized Inverse 摩尔广义逆
如果 A 可逆,则广义逆就是逆。
B.1.5 行列式与伴随
矩阵必须是方阵
伴随矩阵
-
A 的 n 次伴随是?
B.1.6 外积、直积与交叉积
通常的矩阵乘法也叫矩阵内积
外积
, , 1, 1
[,1] [,2]
[1,] 1.0 1.2
[2,] 1.2 3.0
, , 2, 1
[,1] [,2]
[1,] 2.0 2.4
[2,] 2.4 6.0
, , 1, 2
[,1] [,2]
[1,] 3.0 3.6
[2,] 3.6 9.0
, , 2, 2
[,1] [,2]
[1,] 4.0 4.8
[2,] 4.8 12.0
直积/克罗内克积
[,1] [,2] [,3] [,4]
[1,] 1.0 3.0 1.2 3.6
[2,] 2.0 4.0 2.4 4.8
[3,] 1.2 3.6 3.0 9.0
[4,] 2.4 4.8 6.0 12.0
交叉积
B.1.7 Hadamard 积
Hadamard 积(法国数学家 Jacques Hadamard)也叫 Schur 积(德国数学家 Issai Schur )或 entrywise 积是两个维数相同的矩阵对应元素相乘,特别地,
[,1] [,2]
[1,] 1.00 1.44
[2,] 1.44 9.00
[,1] [,2]
[1,] 1.000000 1.244565
[2,] 1.244565 27.000000
[,1] [,2]
[1,] 2.000000 2.297397
[2,] 2.297397 8.000000
[,1] [,2]
[1,] 2.718282 3.320117
[2,] 3.320117 20.085537
B.1.8 矩阵范数
矩阵的范数,包括 1,2,无穷范数
-
-范数 -
列和绝对值最大的
-
- 范数 -
又称谱范数,矩阵最大的奇异值,如果是方阵,就是最大的特征值
-
- 范数 -
行和绝对值最大的
- Frobenius - 范数
-
Euclidean 范数
-
- 范数 -
矩阵里模最大的元素,矩阵里面的元素可能含有复数,所以取模最大
B.1.9 转置与旋转
矩阵
B.1.10 正交与投影
矩阵
B.1.11 Givens 变换(*)
- Givens 旋转
- 帽子矩阵在统计中的应用,回归与方差分析 (Hoaglin 和 Welsch 1978)
B.1.12 Householder 变换(*)
Householder 变换是平面反射的一般情况: 要计算
其中
并且
让
它是
或者
其中
举个例子
借助 Householder 变换做 QR 分解的优势:
- 更快、数值更稳定比直接构造 Q,特别当 N 大于 P 的时候
- 相比于存储矩阵 Q 的
个元素,Householder 变换只存储 P 个向量 - QR 分解的真实实现,比如在 LINPACK 中,定义
的时候, 式 B.1 或 式 B.2 的选择基于 的第一个坐标的符号。如果坐标是负的,使用 式 B.1 ,如果是正的,使用 式 B.2 , 这个做法可以使得数值计算更加稳定。
用 Householder 变换做 QR 分解 (Bates 和 Watts 1988) 及其 R 语言、Eigen 实现。
B.1.13 单位矩阵
矩阵对角线上全是1,其余位置都是0
而全1矩阵是所有元素都是1的矩阵,可以借助外积运算构造,如3阶全1矩阵
B.1.14 对角矩阵
B.1.15 稀疏矩阵
稀疏矩阵的典型构造方式是通过三元组。
8 x 10 sparse Matrix of class "dgCMatrix"
[1,] . 7 . . . . . . . .
[2,] . . . . . . . . . .
[3,] . . . . . . . . 14 .
[4,] . . . . . 21 . . . .
[5,] . . . . . . 28 . . .
[6,] . . . . . . . 35 . .
[7,] . . . . . . . . 42 .
[8,] . . . . . . . . . 49
B.1.16 上、下三角矩阵
[,1] [,2]
[1,] 1.0 1.2
[2,] 1.2 3.0
[,1] [,2]
[1,] FALSE TRUE
[2,] FALSE FALSE
[1] 1.2
[,1] [,2]
[1,] 1 1.2
[2,] 0 3.0
矩阵 A 的下三角矩阵
B.2 矩阵分解
B.2.1 LU 分解
矩阵
B.2.2 Schur 分解
矩阵
$Q
[,1] [,2]
[1,] -0.9055894 -0.4241554
[2,] 0.4241554 -0.9055894
$T
[,1] [,2]
[1,] 0.4379501 0.00000
[2,] 0.0000000 3.56205
$EValues
[1] 0.4379501 3.5620499
其中
B.2.3 QR 分解
矩阵
$qr
[,1] [,2]
[1,] -1.5620499 -3.0728851
[2,] 0.7682213 0.9986877
$rank
[1] 2
$qraux
[1] 1.6401844 0.9986877
$pivot
[1] 1 2
attr(,"class")
[1] "qr"
QR 分解结果中的 Q
QR 分解结果中的 R
恢复矩阵
B.2.4 Cholesky 分解
矩阵
B.2.5 特征值分解
特征值分解(Eigenvalues Decomposition)也叫谱分解(Spectral Decomposition)
矩阵
eigen() decomposition
$values
[1] 3.5620499 0.4379501
$vectors
[,1] [,2]
[1,] 0.4241554 -0.9055894
[2,] 0.9055894 0.4241554
返回值列表中的元素 vectors 就是
计算特征值,即求解如下一元
B.2.6 SVD 分解
矩阵
当矩阵是对称矩阵时,SVD 分解和特征值分解结果是一样的。
$d
[1] 3.5620499 0.4379501
$u
[,1] [,2]
[1,] -0.4241554 -0.9055894
[2,] -0.9055894 0.4241554
$v
[,1] [,2]
[1,] -0.4241554 -0.9055894
[2,] -0.9055894 0.4241554
[,1] [,2]
[1,] 1.0 1.2
[2,] 1.2 3.0
[,1] [,2]
[1,] 3.562050e+00 -3.276838e-16
[2,] 3.602566e-17 4.379501e-01
[,1] [,2]
[1,] 1.000000e+00 -1.647255e-17
[2,] -1.647255e-17 1.000000e+00
[,1] [,2]
[1,] 1.000000e+00 -7.722454e-17
[2,] -7.722454e-17 1.000000e+00
B.3 Eigen 库
Eigen 是一个高性能的线性代数计算库,基于 C++ 编写,有 R 语言接口 RcppEigen 包。示例来自 RcppEigen 包,本文增加了特征向量,下面介绍如何借助 RcppEigen 包调用 Eigen 库做 SVD 矩阵分解。
#include <RcppEigen.h>
// [[Rcpp::depends(RcppEigen)]]
using Eigen::Map; // 'maps' rather than copies
using Eigen::MatrixXd; // variable size matrix, double precision
using Eigen::VectorXd; // variable size vector, double precision
using Eigen::SelfAdjointEigenSolver; // one of the eigenvalue solvers
// [[Rcpp::export]]
VectorXd getEigenValues(Map<MatrixXd> M) {
SelfAdjointEigenSolver<MatrixXd> es(M);
return es.eigenvalues();
}
// [[Rcpp::export]]
MatrixXd getEigenVectors(Map<MatrixXd> M) {
SelfAdjointEigenSolver<MatrixXd> es(M);
return es.eigenvectors();
}
对上面的代码做几点说明:
-
// [[Rcpp::depends(RcppEigen)]]
可以看作一种标记,表示依赖 RcppEigen 包提供的 C++ 头文件,并导入到 C++ 命名空间中。// [[Rcpp::export]]
也可以看作一种标记,表示下面的函数需要导出到 R 语言环境中,这样 C++ 中定义的函数可以在 R 语言环境中使用。 -
MatrixXd
和VectorXd
分别是 Eigen 库中定义的可变大小的双精度矩阵、向量类型。 -
SelfAdjointEigenSolver
是 Eigen 库中关于特征值分解方法中的一个求解器,特征值分解的结果有两个部分:一个是由特征值构成的向量,一个是特征向量构成的矩阵。求解器SelfAdjointEigenSolver
名称中SelfAdjoint
是伴随的意思,它是做矩阵 的伴随矩阵 的特征值分解。 -
getEigenValues
和getEigenVectors
是用户自定义的两个函数名称,分别计算特征值和特征向量。
伴随矩阵的特征值分解和原矩阵的特征值分解有何关系?为什么不直接求原矩阵的特征值分解呢?
- 伴随矩阵的特征值与原矩阵是一样的。
- 伴随矩阵的特征向量有一个符号差异。
RcppEigen 包封装了 Eigen 库,它在 RcppEigen 包的源码路径为
RcppEigen/inst/include/Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h
在 Eigen 库的源码路径如下:
Eigen/src/Eigenvalues/SelfAdjointEigenSolver.h
。
如何使用 RcppEigen 包加速计算?还是要看 Eigen 库的文档和源码,通过阅读源码,可以知道有哪些求解器,比如名称 SelfAdjointEigenSolver
,以及求解器包含的方法,比如 eigenvalues()
和 eigenvectors()
,还有参数和返回值类型等。以特征值分解器 SelfAdjointEigenSolver
为例,编译上面的 C++ 代码,获得在 R 语言环境中可直接使用的函数 getEigenValues()
。
然后,函数 getEigenValues()
计算特征值,返回一个向量。
返回一个矩阵,列是特征向量。
根据上述分解结果计算矩阵 A 的伴随矩阵
B.4 应用
以线性模型为例讲述一些初步的计算性能提升办法。回顾一下线性回归的矩阵表示。
模型中
在获得参数的估计后,响应变量
下面不同的方法来计算预测值
矩阵乘向量比矩阵乘矩阵快。虽然矩阵乘法没有交换律,但是有结合律。先向量计算,然后矩阵计算。
解线性方程组比求逆快。
QR 分解。
其中,函数 qr.qy(decomp, y)
表示 Q %*% y
,函数 qr.qty(decomp, y)
表示 t(Q) %*% y
。实际上,Base R 提供的线性回归拟合函数 lm()
就采用 QR 分解。
Cholesky 分解。记
函数 backsolve()
求解上三角线性方程组。