我一直认为lm
函数在R中非常快,但正如本例所示,使用solve
函数计算的闭合解更快。
data<-data.frame(y=rnorm(1000),x1=rnorm(1000),x2=rnorm(1000))
X = cbind(1,data$x1,data$x2)
library(microbenchmark)
microbenchmark(
solve(t(X) %*% X, t(X) %*% data$y),
lm(y ~ .,data=data))
如果这个玩具示例是一个不好的例子,或者lm
实际上是慢的话,有人可以解释一下吗?
lm
需要解决公式,所以比较是不公平的,所以最好使用lm.fit
来解决公式1}
microbenchmark(
solve(t(X) %*% X, t(X) %*% data$y),
lm.fit(X,data$y))
Unit: microseconds
expr min lq mean median uq max neval cld
solve(t(X) %*% X, t(X) %*% data$y) 99.083 108.754 125.1398 118.0305 131.2545 236.060 100 a
lm.fit(X, y) 125.136 136.978 151.4656 143.4915 156.7155 262.114 100 b
答案 0 :(得分:23)
你忽略了那个
solve()
仅返回您的参数lm()
返回一个(非常丰富的)许多组件的对象,用于后续分析,推理,绘图...... lm()
来电的主要费用不投影,但是需要构建模型矩阵的公式y ~ .
的分辨率。为了说明Rcpp,我们写了函数fastLm()
的一些变体,它们更多地执行了lm()
所做的事情(即比基数R略多于lm.fit()
)并对其进行了测量。参见例如this benchmark script清楚地表明较小数据集的主要成本是解析公式并构建模型矩阵。
简而言之,您正在使用基准测试来执行 Right Thing ,但在尝试比较大多数无法比拟的内容时,您所做的并非正确:具有更大任务的子集。
答案 1 :(得分:17)
您的基准测试有问题
令人惊讶的是,没有人观察到这一点!
您在t(X) %*% X
内使用了solve()
。您应该使用crossprod(X)
,因为X'X
是对称矩阵。 crossprod()
仅在复制其余部分时计算矩阵的一半。 %*%
强制计算所有。所以crossprod()
会快两倍。这解释了为什么在您的基准测试中,solve()
和lm.fit()
之间的时间大致相同。
在我的旧英特尔Nahalem(2008英特尔酷睿2双核处理器)上,我有:
X <- matrix(runif(1000*1000),1000)
system.time(t(X)%*%X)
# user system elapsed
# 2.320 0.000 2.079
system.time(crossprod(X))
# user system elapsed
# 1.22 0.00 0.94
如果您的计算机速度更快,请尝试改为使用X <- matrix(runif(2000*2000),2000)
。
在下文中,我将解释所有拟合方法中涉及的计算细节。
QR分解vs. Cholesky分解
lm()
/ lm.fit()
是基于QR的,而solve()
是基于Cholesky的。 QR分解的计算成本为2 * n * p^2
,而Cholesky方法为n * p^2 + p^3
(n * p^2
用于计算矩阵交叉积,p^3
用于Cholesky分解)。因此,您可以看到n
远大于p
时,Cholesky方法比QR方法快2倍。所以这里真的没有必要进行基准测试。 (如果您不知道,n
是数据的数量,p
是参数的数量。)
LINPACK QR v.s. LAPACK QR
通常,lm.fit()
使用(修改的)LINPACK
QR分解算法,而不是LAPACK
QR分解算法。也许你对BLAS/LINPACK/LAPACK
不太熟悉;这些是FORTRAN代码,提供内核科学矩阵计算。 LINPACK
调用level-1 BLAS,而LAPACK
使用块算法调用level-3 BLAS
。平均而言,LAPACK
QR比LINPACK
QR快1.6倍。 lm.fit()
不使用LAPACK
版本的关键原因是需要部分列旋转。 LAPACK
版本执行完整列旋转,使其更难summary.lm()
使用QR分解的R
矩阵因子来生成F统计量和ANOVA
测试。
旋转vs.没有转动
来自fastLm()
包的 RcppEigen
使用LAPACK
非旋转QR分解。同样,您可能不清楚QR分解算法和旋转问题。您只需要知道使用旋转的QR分解仅具有级别3 BLAS
的50%份额,而没有旋转的QR分解具有级别3 BLAS
的100%份额。在这方面,放弃旋转将加速QR分解过程。当然,最终结果是不同的,当模型矩阵排名不足时,没有旋转会产生危险的结果。
有一个很好的问题与fastLM
:Why does fastLm()
return results when I run a regression with one observation?的不同结果有关。 @BenBolker,@ DirkEddelbuettel和我在Ben的回答评论中进行了非常简短的讨论。
结论:您想要速度还是数值稳定性?
就数值稳定性而言,有:
LINPACK pivoted QR > LAPACK pivoted QR > pivoted Cholesky > LAPACK non-pivoted QR
就速度而言,有:
LINPACK pivoted QR < LAPACK pivoted QR < pivoted Cholesky < LAPACK non-pivoted QR
正如Dirk所说,
FWIW RcppEigen包在其
fastLm()
示例中具有更全面的分解集。但就像Ben如此雄辩地说:“这是你为速度付出代价的一部分。”我们给你足够的绳索来吊死自己。如果您想保护自己,请坚持使用lm()
或lm.fit()
,或创建混合“快速但安全”的版本。
快速稳定的版本