虽然OP已经一年多没有活跃了,但我还是决定发布一个答案。我会使用X
而不是S
,就像在统计学中一样,我们经常需要线性回归上下文中的投影矩阵,其中X
是模型矩阵,y
是响应向量,H = X(X'X)^{-1}X'
而是帽子/投影矩阵,以便Hy
给出预测值。
这个答案假设了普通最小二乘的背景。对于加权最小二乘,请参阅从 QR 分解获取帽子矩阵以进行加权最小二乘回归。
概述
solve
是基于一般方阵的 LU 分解。对于对称X'X
的(应该由crossprod(X)
而不是t(X) %*% X
在 R 中计算,请阅读?crossprod
更多内容),我们可以使用chol2inv
基于 Choleksy 分解的。
但是,三角分解不如QR
分解稳定。这不难理解。如果X
有条件数kappa
,X'X
就会有条件数kappa ^ 2
。这可能会导致很大的数值困难。您收到的错误消息:
# system is computationally singular: reciprocal condition number = 2.26005e-28
只是告诉这个。kappa ^ 2
大约e-28
,比机器精度要小很多左右e-16
。有了容差tol = .Machine$double.eps
,X'X
将被视为秩不足,因此 LU 和 Cholesky 因式分解将失效。
通常,在这种情况下,我们会切换到 SVD 或 QR,但枢轴Cholesky 分解是另一种选择。
- SVD 是最稳定的方法,但成本太高;
- QR 非常稳定,计算成本适中,并且在实践中普遍使用;
- Pivoted Cholesky 速度很快,具有可接受的稳定性。对于大型矩阵,这是首选。
在下文中,我将解释所有三种方法。
使用 QR 分解
请注意,投影矩阵是与置换无关的,即,我们是否执行带有或不带有枢轴的 QR 分解都无关紧要。
在 R 中,qr.default
可以调用 LINPACK 例程DQRDC
进行非旋转 QR 分解,并调用 LAPACK 例程DGEQP3
进行块旋转 QR 分解。让我们生成一个玩具矩阵并测试这两个选项:
set.seed(0); X <- matrix(rnorm(50), 10, 5)
qr_linpack <- qr.default(X)
qr_lapack <- qr.default(X, LAPACK = TRUE)
str(qr_linpack)
# List of 4
# $ qr : num [1:10, 1:5] -3.79 -0.0861 0.3509 0.3357 0.1094 ...
# $ rank : int 5
# $ qraux: num [1:5] 1.33 1.37 1.03 1.01 1.15
# $ pivot: int [1:5] 1 2 3 4 5
# - attr(*, "class")= chr "qr"
str(qr_lapack)
# List of 4
# $ qr : num [1:10, 1:5] -3.79 -0.0646 0.2632 0.2518 0.0821 ...
# $ rank : int 5
# $ qraux: num [1:5] 1.33 1.21 1.56 1.36 1.09
# $ pivot: int [1:5] 1 5 2 4 3
# - attr(*, "useLAPACK")= logi TRUE
# - attr(*, "class")= chr "qr"
请注意$pivot
,两个对象的不同。
现在,我们定义一个包装函数来计算QQ'
:
f <- function (QR) {
## thin Q-factor
Q <- qr.qy(QR, diag(1, nrow = nrow(QR$qr), ncol = QR$rank))
## QQ'
tcrossprod(Q)
}
我们将看到qr_linpack
并qr_lapack
给出相同的投影矩阵:
H1 <- f(qr_linpack)
H2 <- f(qr_lapack)
mean(abs(H1 - H2))
# [1] 9.530571e-17
使用奇异值分解
在 R 中,svd
计算奇异值分解。我们仍然使用上面的示例矩阵X
:
SVD <- svd(X)
str(SVD)
# List of 3
# $ d: num [1:5] 4.321 3.667 2.158 1.904 0.876
# $ u: num [1:10, 1:5] -0.4108 -0.0646 -0.2643 -0.1734 0.1007 ...
# $ v: num [1:5, 1:5] -0.766 0.164 0.176 0.383 -0.457 ...
H3 <- tcrossprod(SVD$u)
mean(abs(H1 - H3))
# [1] 1.311668e-16
同样,我们得到相同的投影矩阵。
使用 Pivoted Cholesky 分解
为了演示,我们仍然使用X
上面的示例。
## pivoted Chol for `X'X`; we want lower triangular factor `L = R'`:
## we also suppress possible rank-deficient warnings (no harm at all!)
L <- t(suppressWarnings(chol(crossprod(X), pivot = TRUE)))
str(L)
# num [1:5, 1:5] 3.79 0.552 -0.82 -1.179 -0.182 ...
# - attr(*, "pivot")= int [1:5] 1 5 2 4 3
# - attr(*, "rank")= int 5
## compute `Q'`
r <- attr(L, "rank")
piv <- attr(L, "pivot")
Qt <- forwardsolve(L, t(X[, piv]), r)
## P = QQ'
H4 <- crossprod(Qt)
## compare
mean(abs(H1 - H4))
# [1] 6.983997e-17
同样,我们得到相同的投影矩阵。