Почему lm исчерпывает память, а матричное умножение отлично работает для коэффициентов?

Я пытаюсь сделать линейную регрессию с фиксированными эффектами с R. Мои данные выглядят как

dte   yr   id   v1   v2
  .    .    .    .    .
  .    .    .    .    .
  .    .    .    .    .

Тогда я решил просто сделать это, сделав yr фактором и используя lm:

lm(v1 ~ factor(yr) + v2 - 1, data = df)

Однако, похоже, у него не хватает памяти. У меня 20 уровней в моем коэффициенте, а df - 14 миллионов строк, которые занимают около 2 ГБ для хранения. Я запускаю это на машине с 22 ГБ, предназначенной для этого процесса.

Затем я решил попробовать вещи старомодным способом: создать фиктивные переменные для каждого из моих лет t1 до t20, выполнив:

df$t1 <- 1*(df$yr==1)
df$t2 <- 1*(df$yr==2)
df$t3 <- 1*(df$yr==3)
...

и просто вычислить:

solve(crossprod(x), crossprod(x,y))

Это работает без проблем и дает ответ почти сразу.

Мне особенно любопытно, что это за lm, из-за чего у него заканчивается память, когда я могу вычислить коэффициенты просто отлично? Спасибо.

Ответ 1

Ни один из ответов пока не указал на правильное направление.

Принятый ответ @idr вызывает путаницу между lm и summary.lm. lm не вычисляет никакой диагностической статистики вообще; вместо этого summary.lm делает. Поэтому он говорит о summary.lm.

@Jake является фактом о числовой стабильности QR-факторизации и LU/Choleksy факторизации. Aravindakshan answer расширяет это, указав количество операций с плавающей запятой за обе операции (хотя, по его словам, он не учитывал затраты на вычисление матрицы cross продукт). Но, не путайте счета ФЛОП с расходами на память. Фактически оба метода имеют одинаковое использование памяти в LINPACK/LAPACK. В частности, его аргумент о том, что метод QR стоит больше ОЗУ для хранения коэффициента Q, является фиктивным. Сжатое хранилище, как описано в lm(): Что такое qraux, возвращаемое QR-декомпозицией в LINPACK/LAPACK, разъясняет, как вычисляется и сохраняется QR-факторизация. Скорость выпуска QR v.s. Чол подробно описан в моем ответе: Почему встроенная функция lm настолько медленная в R?, и мой ответ на быстрее lm предоставляет небольшую процедуру lm.chol с использованием метода Choleksy, который в 3 раза быстрее QR-метода.

@Greg ответ/предложение для biglm хорош, но он не отвечает на вопрос. Поскольку упоминается biglm, я хотел бы указать, что QR-разложение отличается в lm и biglm. biglm вычисляет отражение домовладельца, так что полученный фактор R имеет положительные диагонали. Подробнее см. Cholesky с помощью QR-факторизации. Причина, по которой biglm делает это, заключается в том, что результирующий R будет таким же, как фактор Cholesky, см. QR-декомпозицию и разложение Холески в R для информации. Кроме biglm, вы можете использовать mgcv. Прочтите мой ответ: biglm предсказывать не удается выделить вектор размера xx.x MB для более подробной информации.


После сводки, пришло время опубликовать мой ответ.

Чтобы соответствовать линейной модели, lm будет

  • генерирует модельный кадр;
  • создает матрицу модели;
  • вызов lm.fit для QR-факторизации;
  • возвращает результат QR-факторизации, а также модельный фрейм в lmObject.

Вы сказали, что ваш входной кадр данных с 5 столбцами стоит 2 ГБ для хранения. С 20 уровнями факторов результирующая модельная матрица имеет около 25 колонок, вмещающих 10 ГБ памяти. Теперь давайте посмотрим, как увеличивается использование памяти при вызове lm.

  • [глобальная среда] изначально у вас есть хранилище на 2 ГБ для фрейма данных;
  • [lm envrionment], затем он копируется в модельный кадр, стоимость которого составляет 2 ГБ;
  • [lm environment], тогда создается модельная матрица стоимостью 10 ГБ;
  • [lm.fit окружающая среда] копия матрицы модели производится, затем перезаписывается QR-факторизацией, стоимостью 10 ГБ;
  • [lm environment] возвращается результат lm.fit, стоимость которого составляет 10 ГБ;
  • [глобальная среда] результат lm.fit возвращается еще lm, стоимость которого составляет еще 10 ГБ;
  • [глобальная среда] модельный кадр возвращается lm, стоимостью 2 ГБ.

Таким образом, требуется в общей сложности 46 ГБ оперативной памяти, намного больше, чем у вашей доступной 22 ГБ ОЗУ.

Собственно, если lm.fit можно "вставить" в lm, мы сэкономим 20 ГБ. Но нет возможности включить функцию R в другую функцию R.

Может быть, мы можем взять небольшой пример, чтобы увидеть, что происходит вокруг lm.fit:

X <- matrix(rnorm(30), 10, 3)    # a `10 * 3` model matrix
y <- rnorm(10)    ## response vector

tracemem(X)
# [1] "<0xa5e5ed0>"

qrfit <- lm.fit(X, y)
# tracemem[0xa5e5ed0 -> 0xa1fba88]: lm.fit 

Таким образом, X копируется при передаче в lm.fit. Давайте посмотрим, что qrfit имеет

str(qrfit)
#List of 8
# $ coefficients : Named num [1:3] 0.164 0.716 -0.912
#  ..- attr(*, "names")= chr [1:3] "x1" "x2" "x3"
# $ residuals    : num [1:10] 0.4 -0.251 0.8 -0.966 -0.186 ...
# $ effects      : Named num [1:10] -1.172 0.169 1.421 -1.307 -0.432 ...
#  ..- attr(*, "names")= chr [1:10] "x1" "x2" "x3" "" ...
# $ rank         : int 3
# $ fitted.values: num [1:10] -0.466 -0.449 -0.262 -1.236 0.578 ...
# $ assign       : NULL
# $ qr           :List of 5
#  ..$ qr   : num [1:10, 1:3] -1.838 -0.23 0.204 -0.199 0.647 ...
#  ..$ qraux: num [1:3] 1.13 1.12 1.4
#  ..$ pivot: int [1:3] 1 2 3
#  ..$ tol  : num 1e-07
#  ..$ rank : int 3
#  ..- attr(*, "class")= chr "qr"
# $ df.residual  : int 7

Заметим, что компактная QR-матрица qrfit$qr$qr такая же, как модельная матрица X. Он создается внутри lm.fit, но при выходе из lm.fit он копируется. Итак, в общей сложности у нас будет 3 "копии" X:

  • оригинальный в глобальной среде;
  • тот, который скопирован в lm.fit, перезаписан QR-факторизацией;
  • тот, который возвращается lm.fit.

В вашем случае X составляет 10 ГБ, поэтому затраты памяти, связанные только с lm.fit, уже составляют 30 ГБ. Не говоря уже о других расходах, связанных с lm.


С другой стороны, взглянем на

solve(crossprod(X), crossprod(X,y))

X занимает 10 ГБ, но crossprod(X) - это только матрица 25 * 25, а crossprod(X,y) - это вектор длиной 25. Они настолько малы по сравнению с X, поэтому использование памяти не увеличивается вообще.

Возможно, вы обеспокоены тем, что локальная копия X будет сделана при вызове crossprod? Не за что! В отличие от lm.fit, который выполняет чтение и запись в X, crossprod только читает X, поэтому копия не производится. Мы можем проверить это с помощью нашей игрушечной матрицы X:

tracemem(X)
crossprod(X)

Вы не увидите копировального сообщения!


Если вы хотите короткую сводку для всех выше, вот она:

  • затраты памяти для lm.fit(X, y) (или даже .lm.fit(X, y)) в три раза больше, чем для solve(crossprod(X), crossprod(X,y));
  • В зависимости от того, насколько крупнее матрица модели, чем модель, затраты памяти для lm в 3 ~ 6 раз больше, чем для solve(crossprod(X), crossprod(X,y)). Нижняя граница 3 никогда не достигается, а верхняя граница 6 достигается, когда модельная матрица такая же, как и модельная. Это тот случай, когда нет факториальных переменных или "фактор-подобных" терминов, таких как bs() и poly() и т.д.

Ответ 2

В дополнение к тому, что сказал Идрис, также стоит отметить, что lm() не решает для параметров с использованием нормальных уравнений, как вы проиллюстрировали в своем вопросе, а скорее использует QR-декомпозицию, которая менее эффективна, но имеет тенденцию производить более точные цифровые решения.

Ответ 3

Возможно, вам стоит рассмотреть возможность использования пакета biglm. Он подходит для моделей lm, используя меньшие куски данных.

Ответ 4

lm делает гораздо больше, чем просто найти коэффициенты для ваших функций ввода. Например, он предоставляет диагностическую статистику, которая расскажет вам больше о коэффициентах ваших независимых переменных, включая стандартную ошибку и значение t каждой из ваших независимых переменных.

Я думаю, что понимание этих диагностических статистик важно при выполнении регрессий, чтобы понять, насколько эффективна ваша регрессия.

Эти дополнительные вычисления вызывают lm медленнее, чем простое решение матричных уравнений для регрессии.

Например, используя набор данных mtcars:

>data(mtcars)
>lm_cars <- lm(mpg~., data=mtcars)
>summary(lm_cars)

Call:                                                         
lm(formula = mpg ~ ., data = mtcars)                          

Residuals:                                                    
    Min      1Q  Median      3Q     Max                       
-3.4506 -1.6044 -0.1196  1.2193  4.6271                       

Coefficients:                                                 
            Estimate Std. Error t value Pr(>|t|)              
(Intercept) 12.30337   18.71788   0.657   0.5181              
cyl         -0.11144    1.04502  -0.107   0.9161              
disp         0.01334    0.01786   0.747   0.4635              
hp          -0.02148    0.02177  -0.987   0.3350              
drat         0.78711    1.63537   0.481   0.6353              
wt          -3.71530    1.89441  -1.961   0.0633 .            
qsec         0.82104    0.73084   1.123   0.2739              
vs           0.31776    2.10451   0.151   0.8814              
am           2.52023    2.05665   1.225   0.2340              
gear         0.65541    1.49326   0.439   0.6652              
carb        -0.19942    0.82875  -0.241   0.8122              
---                                                           
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 2.65 on 21 degrees of freedom        
Multiple R-squared: 0.869,      Adjusted R-squared: 0.8066    
F-statistic: 13.93 on 10 and 21 DF,  p-value: 3.793e-07       

Ответ 5

Разрабатывать точку Джейка. Скажем, регрессия пытается решить: y = Ax (A - матрица дизайна). С m наблюдениями и n независимыми переменными A является матрицей mxn. Тогда стоимость QR составляет ~ m*n^2. В вашем случае это выглядит как m = 14x10 ^ 6 и n = 20. Таким образом, m*n^2 = 14*10^6*400 является значительной стоимостью.

Однако с нормальными уравнениями вы пытаетесь инвертировать A'A ('указывает транспонирование), который является квадратным и размером nxn. Решение обычно выполняется с использованием LU, который стоит n^3 = 8000. Это намного меньше, чем вычислительная стоимость QR. Конечно, это не включает в себя стоимость матрицы умножить.

Кроме того, если программа QR пытается сохранить Q-матрицу размером mxm=14^2*10^12 (!), то вашей памяти будет недостаточно. Можно написать QR, чтобы эта проблема не возникала, Было бы интересно узнать, какая версия QR, фактически используется. И почему именно в вызове lm заканчивается память.