Почему встроенная функция lm так медленно работает в R?
Я всегда думал, что 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
который не должен разрешать формулу
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 ответа
Вы пропускаете это
solve()
только возвращает ваши параметрыlm()
возвращает вам (очень богатый) объект со многими компонентами для последующего анализа, вывода, графиков, ...- основная стоимость вашего
lm()
вызов не проекция, а разрешение формулыy ~ .
из которой должна быть построена матрица модели.
Для иллюстрации Rcpp мы написали несколько вариантов функции fastLm()
делать больше того, что lm()
делает (т.е. немного больше, чем lm.fit()
от базы R) и измерил его. Посмотрите, например, этот эталонный скрипт, который ясно показывает, что доминирующая стоимость для небольших наборов данных заключается в анализе формулы и построении матрицы модели.
Короче говоря, вы делаете правильные вещи, используя бенчмаркинг, но вы делаете это не совсем правильно, пытаясь сравнить то, что в основном несопоставимо: подмножество с гораздо более крупной задачей.
Что-то не так с вашим тестом
Это действительно удивительно, что никто не заметил этого!
Вы использовали t(X) %*% X
внутри solve()
, Вы должны использовать crossprod(X)
, как X'X
симметричная матрица. crossprod()
копирует только половину матрицы при копировании остальных. %*%
заставляет вычислять все. Так crossprod()
будет в два раза быстрее. Это объясняет, почему в вашем бенчмаркинге у вас примерно одинаковое время solve()
а также lm.fit()
,
На моем старом Intel Nahalem (Intel Core 2 Duo 2008) у меня есть:
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-факторизация против холесского факторизации
lm()
/ lm.fit()
на основе QR, в то время как solve()
основан на Холецком. Вычислительные затраты на QR-разложение 2 * n * p^2
в то время как метод Холецкого n * p^2 + p^3
(n * p^2
для вычисления матрицы перекрестного произведения, p^3
для холесского разложения). Таким образом, вы можете увидеть, что когда n
намного намного больше, чем p
Метод Холецкого примерно в 2 раза быстрее, чем метод QR. Таким образом, здесь действительно нет нужды сравнивать. (если вы не знаете, n
это количество данных, p
количество параметров.)
LINPACK QR против LAPACK QR
Как правило, lm.fit()
использует (модифицированный) LINPACK
Алгоритм QR-факторизации, а не LAPACK
Алгоритм QR-факторизации. Может быть, вы не очень знакомы с BLAS/LINPACK/LAPACK
; это код на языке FORTRAN, обеспечивающий вычисления в научной матрице ядра. LINPACK
вызывает BLAS уровня 1, а LAPACK
звонки уровня 3 BLAS
используя блочные алгоритмы. В среднем, LAPACK
QR в 1,6 раза быстрее LINPACK
QR. Критическая причина того, что lm.fit()
не использует LAPACK
версия, это необходимость частичного поворота столбца. LAPACK
версия делает полный поворот столбца, что делает его более сложным для summary.lm()
использовать R
матричный фактор QR-разложения для получения F-статистики и ANOVA
тестовое задание.
поворот против не поворот
fastLm()
от RcppEigen
пакет использует LAPACK
неповоротная QR-факторизация. Опять же, вы можете быть неясны в отношении алгоритма QR-факторизации и проблем поворота. Вам нужно только знать, что QR-факторизация с поворотом имеет только 50% доли уровня 3 BLAS
в то время как QR-факторизация без поворота имеет 100% долю уровня 3 BLAS
, В связи с этим отказ от поворота ускорит процесс QR-факторизации. Конечно, конечный результат отличается, и когда матрица модели имеет недостаток ранга, никакие повороты не дают опасного результата.
Есть хороший вопрос, связанный с другим результатом, который вы получаете от fastLM
: Почему fastLm()
возвращать результаты, когда я запускаю регрессию с одним наблюдением?, @BenBolker, @DirkEddelbuettel и я провели очень краткое обсуждение в комментариях ответа Бена.
Вывод: хотите ли вы скорость или числовую стабильность?
С точки зрения численной устойчивости, есть:
LINPACK pivoted QR > LAPACK pivoted QR > pivoted Cholesky > LAPACK non-pivoted QR
С точки зрения скорости, есть:
LINPACK pivoted QR < LAPACK pivoted QR < pivoted Cholesky < LAPACK non-pivoted QR
Как сказал Дирк,
FWIW пакет RcppEigen имеет более полный набор разложений в
fastLm()
пример. Но вот как красноречиво заявил Бен: "это часть цены, которую вы платите за скорость". Мы дадим вам достаточно веревки, чтобы повеситься. Если вы хотите защитить себя от себя, придерживайтесьlm()
или жеlm.fit()
или создайте гибридную "быструю, но безопасную" версию.
Быстрая и стабильная версия
Проверьте мой ответ здесь.