为什么内置的lm功能在R中如此之慢?

时间:2016-04-12 11:24:48

标签: r regression linear-regression lm

我一直认为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实际上是慢的话,有人可以解释一下吗?

编辑:正如Dirk Eddelbuettel所建议的那样,由于lm需要解决公式,所以比较是不公平的,所以最好使用lm.fit来解决公式
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

2 个答案:

答案 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^3n * 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分解过程。当然,最终结果是不同的,当模型矩阵排名不足时,没有旋转会产生危险的结果。

有一个很好的问题与fastLMWhy 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(),或创建混合“快速但安全”的版本。

快速稳定的版本

检查my answer Here