Эффективно стандартизировать большую матрицу в R с помощью foreach/doParallel?

Мне нужно стандартизировать (вычесть среднее значение и разделить на стандартное отклонение) столбцы нескольких больших матриц в R (примерно 300 000 строк по 10 000–20 000 столбцов).

Процесс был очень медленным, поэтому я попытался ускорить его с помощью foreach/doParallel, но, похоже, это не помогло.

Несколько примеров кода с меньшей матрицей:

library('doParallel')
n.cores <- 7
clust <- makeCluster(n.cores,type='FORK')
registerDoParallel(cl = clust)

rows <- 300000
cols <- 1500
big_matrix <- matrix(runif (rows*cols),nrow=rows,ncol=cols,dimnames=list(paste0('r',1:rows),paste0('c',1:cols)))

# Attempt 1: No parallelization. Took 52.3 seconds.
system.time( stand1 <- scale(big_matrix,center=T,scale=T) )

# Attempt 2: Each column separately with foreach. Took 168.9 seconds.
system.time(
  stand2 <- foreach(mchunk=big_matrix,.combine='cbind') %dopar% { #
              (mchunk - mean(mchunk))/sd(mchunk)
            }
)

# Attempt 3: In chunks with foreach. Took 52.7 seconds.
stand_chunks <- function(mat,n.cores) {
  chunk_indices <- split(1:ncol(mat),cut(1:ncol(mat),n.cores)) # A list of column indices, splitting the matrix into n.cores chunks
  stand_mat <- foreach(ci=chunk_indices,.combine='cbind') %dopar% {
    scale(mat[,ci],center=T,scale=T)
  }
  return(stand_mat)
}
system.time( stand3 <- stand_chunks(big_matrix,n.cores) )

# Attempt 4: In chunks with foreach, without copying whole matrix to each worker. 23 seconds.
stand_chunks_v2 <- function(mat,n.cores) {
  chunk_indices <- split(1:ncol(mat),cut(1:ncol(mat),n.cores)) # A list of column indices, splitting the matrix into n.cores chunks
  mchunks <- lapply(chunk_indices,function(ci) { return(mat[,ci]) })
  stand_mat <- foreach(mc=mchunks,.combine='cbind') %dopar% {
    scale(mc,center=T,scale=T)
  }
  return(stand_mat)
}
system.time( stand4 <- stand_chunks_v2(big_matrix,n.cores) )


parallel::stopCluster(cl=clust)

# Attempt 5: Do matrix algebra and let BLAS handle it. 20.5 seconds.
RhpcBLASctl::blas_set_num_threads(7)
stand_matalg <- function(x) {
  means <- rep(1, nrow(x)) %*% t(colMeans(x))
  sds <- rep(1, nrow(x)) %*% t(apply(x,2,sd))
  return((x - means)/sds)
}
system.time( stand5 <- stand_matalg(big_matrix) )
summary(c(abs(stand1 - stand5))) # Still looks correct, plus or minus a bit of floating-point weirdness

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

Попытка 3 была очень неэффективна с использованием памяти - проблема описана здесь как «предоставление работникам большего количества данных, чем им нужно» - если я наблюдаю за потоками с помощью команды «top», я вижу, что использование памяти каждым потоком увеличивается как вся большая матрица копируется для каждого работника. К тому времени, когда я увеличиваю матрицу до 5000 столбцов, это копирование становится настолько расточительным, что ему не хватает памяти на вычислительном узле HPC с 80 ГБ ОЗУ. Это также было не быстрее, чем без распараллеливания, возможно, из-за накладных расходов на копирование большой матрицы для каждого исполнителя.

Попытка 4, наконец, оказалась быстрее. Это определенно более эффективно с точки зрения использования памяти, чем попытка 2 — она дублирует большую матрицу при составлении списка mchunks, но, по крайней мере, дублирование лучше, чем копирование ее на 7 рабочих процессов. Но, вероятно, еще можно улучшить это дальше.

Попытка 5 (адаптированная из этого ответа на старый вопрос) на данный момент является самой быстрой, но она по-прежнему требует создания двух больших матриц «средних» и «sds», которые соответствуют размерам исходной большой матрицы.

Какой более эффективный способ стандартизировать эти большие матрицы (с помощью foreach или другого метода)?

Дополнительная информация: я работаю над кластером HPC с большим количеством оперативной памяти, поэтому мне, вероятно, не нужно использовать матрицы bigstatsr/file-backed. Я предполагаю, что поскольку дисковый ввод-вывод всегда будет медленнее, чем ОЗУ, лучше избегать этого, если у меня достаточно ОЗУ, чтобы оно не требовалось?

bluemouse 26.07.2024 07:54

Если вы не делаете все по ссылке, вам не хватит памяти. Обратите внимание, что «почти» все в R передается по значению. Например, если бы вы передали матрицу в функцию масштабирования, необходимо было бы создать копию. Нужен еще один фрагмент памяти. Поэтому рассмотрите возможность использования ссылок. И как вы загружаете данные в R? Я бы рекомендовал использовать C/C++/Python. Обратите внимание, что 50 000 столбцов и 300 000 строк составляют ~ 120 ГБ! Не стоит иметь это в памяти. Рассмотрите возможность двух чтений файла. Сначала вычислите среднее значение и стандартное отклонение, затем стандартизируйте и запишите обратно в файл.

Onyambu 26.07.2024 08:25

Матрица моделируется в R, по ней будет проводиться анализ, далее нужно сохранять только результаты анализа (а не саму матрицу). Поэтому я надеюсь хранить все в оперативной памяти во время стандартизации и анализа, потому что, если я смогу, тогда матрицу вообще не нужно будет читать/записывать на диск. Похоже, что 300 000x20 000 — это 44 ГБ, я не ожидаю, что мне понадобится больший размер.

bluemouse 26.07.2024 08:36

Не рекомендуется включать ответы в вопрос.

Friede 26.07.2024 09:09

@Фриде, ОК, я забрал его обратно! Извини!

bluemouse 26.07.2024 09:18
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
5
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте пакет MatrixStats.

system.time( stand1 <- scale(big_matrix,center=T,scale=T) )
# user  system elapsed 
#25.10    4.07   29.28 

library(matrixStats)
scaleM <- \(M) t((t(M) - colMeans2(M)) / colSds(M))
system.time( standx <- scaleM(big_matrix) )
#user  system elapsed 
#6.85    0.36    7.22 

all.equal(stand1, standx, check.attributes = FALSE)
#[1] TRUE

Я не думаю, что вы можете добиться большего, не изменяя входную матрицу. Это можно сделать, например, с помощью RcppEigen.

Если вы используете arrayStats, используйте colMeans2. Также для повышения скорости можно использовать пакет Rfast. Не могли бы вы сравнить разницу?

Onyambu 26.07.2024 08:43

Очень хорошо! 12,7 секунды в системе, на которой я проводил сравнения.

bluemouse 26.07.2024 09:19

Другой вариант может быть RcppParallel.

jay.sf 27.07.2024 00:05
Ответ принят как подходящий

Попробуйте fscale() из {свернуть}:

collapse::fscale(big_matrix)

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

Отличный! 2,9 секунды в системе, на которой я проводил сравнения!

bluemouse 26.07.2024 09:20

Другие вопросы по теме