R - ¿Cómo funciona un bucle, que funciona píxel-wise, se puede escribir como lapply o preeducación para permitir el procesamiento paralelo?

Estoy trabajando con BFAST, un pixel-based algoritmo que detecta rupturas en las series temporales. Mis datos de raster de entrada son una serie de tiempo NDVI irregular y estoy apuntando a primero, suavizar el TS irregular mensual (16 años con 12 meses cada 16*12=192) y segundo aplicar el algoritmo BFAST.

https://cran.r-project.org/web/packages/bfast/index.html

http://bfast.r-forge.r-project.org/

Dimensiones de entrada: 488 filas, 985 columnas, 181 capas (fechas)

Producto del TS alisado: 488 filas, 985 columnas, 192 capas (fechas)

Nota: Mis datos tienen valores de NA alrededor de los límites porque está subconfigurado con un fichero de forma para evitar el mar que rodea la isla infraestudiante.

Mi código funciona así:

  1. Crea un objeto de pila de raster (de frecuencia irregular)
  2. Recrea una pila de mapas vacíos georreferenciados (valores de AR) llamada lisa.time.series
  3. Dentro de un bucle
    • toma un píxel y extrae los valores de él a lo largo de los años como vector con dimensiones filas=181 y columnas=1.
    • comprueba si todos los valores en el vector son NA (ver Nota arriba) y si es así no interactúa con el mapa vacío, reemplazando nada.
    • si encuentra que el porcentaje de valores de NA no es 100%, calcula la serie de tiempo con fechas irregulares (bfastts devuelve un objeto ts* y es sólo una función dentro del paquete rápido que estoy utilizando). La variable s tiene dimensiones filas=5813 cols=1 y sólo define cuando son los valores disponibles adquiridos y pone NA en todas partes. 5813 es el número de días en el período de 16 años en el que estoy trabajando.
    • Luego con la interpolación lineal encuentra la serie de tiempo diario (s.d.periodic) [replazando valores NA así que tengo una serie de tiempo completa]
    • Usando una función y ventana() Encuentro los valores mensuales y defino el comienzo y el final de los ts. Ahora las dimensiones son filas=192 cols=1 (s.m.periodic.window)
    • Por último, sustituyo los valores NA del píxel a lo largo de la tercera dimensión [192 capas/fechas] con mis datos agregados mensuales.

Funciona perfectamente y lo estoy haciendo de esta manera porque raster:::calc() no dio nada más que un tiempo difícil y una secuencia de errores en las dimensiones finales. Mi problema ahora es que esto para el bucle toma 2,82 segundos por pixel para agregarlo y reemplazar los valores. Estoy usando una pila de raster vacía existente porque he leído que es más rápido que crear una nueva capa/row/col en cada bucle. Llevará aproximadamente 12 días completar, lo que no es conveniente. Entonces, ¿cómo puedo reescribir esto para bucle para que pueda utilizar todos los núcleos disponibles y hacer procesamiento paralelo?

# Functions
aggregate.daily.to.monthly <- function(daily.ts) {
  
  s.month <- round(aggregate(as.zoo(daily.ts), as.yearmon, median), 4)
  s.month <- as.ts(s.month)
  return(s.month)
}

catf <- function(..., file="log.txt", append=TRUE){
  cat(..., file=file, append=append)
}

EDITEntrada de datos, proporcionando un ejemplo reproducible. Ahora digamos que tengo 8 índices NDVI (5x5 dimensiones) en sus fechas respectivas y quiero encontrar los valores mensuales perdidos que comienzan 2000-Jan a 2001-Dec (709 días - 24 meses). No estoy estableciendo valores de NA, pero digamos que tenemos que comprobar si hay vectores que sólo tienen NA e ignorarlos.

dates <- as.POSIXct(strptime(c("2000-01-13","2000-05-21", "2000-09-03", "2000-11-24", "2001-02-24","2001-04-16","2001-08-02","2001-12-22"), "%Y-%m-%d"))
ndvi.empty <- raster(nrows=5, ncols=5, vals=NULL)
ndvi <- stack(lapply(1:8, function(i) setValues(ndvi.empty, runif(25, 0.1, 0.9)))) 
#ndvi variable is a 'RasterBrick' class

Interpolato a la serie de tiempo suavizado

empty.raster <- raster(nrows=nrow(ndvi),ncols=ncol(ndvi),vals=NULL)
smoothed.time.series <- stack(lapply(1:24, function(i) setValues(empty.raster,NA)))
#smoothed.time.series variable is a 'RasterStack' class


ncell<-length(ndvi$layer.1) #Like rows*cols, but not all elements, ncell=25 in this example

for (pixel in (1:ncell)) {
  pix.vec <- as.vector(ndvi[pixel])
  #[1] 0.7878372 0.4166008 0.3253537 0.2463029 0.5073630 0.3717476 0.6411799 0.6174676
  if (all(is.na(pix.vec))==FALSE) {
    s <- bfastts(pix.vec, dates, type = c("irregular"))
    #Time-Series [1:709] from 2000 to 2002: 0.788 NA NA NA ...
    s.d.periodic <- round(na.interp(s),4)
    #Time-Series [1:709] from 2000 to 2002: 0.788 0.785 0.782 0.779 ...
    s.m.periodic.window <- window(x = aggregate.daily.to.monthly(s.d.periodic), 
                                  start = c(2000,1), end = c(2001,12),
                                  frequency = 12)
    #Time-Series [1:24] from 2000 to 2002: 0.762 0.691 0.602 0.514 ...
    cat('Smoothing', pixel, 'of', ncell, '\n')
    smoothed.time.series[pixel][1:24]<-s.m.periodic.window[1:24]
#smoothed.time.series[25]    
#layer.1 layer.2 layer.3 layer.4 layer.5 layer.6 layer.7 layer.8 layer.9 layer.10
#[1,] 0.7617 0.6906 0.6022 0.5137 0.4253  0.3927 0.3667  0.3401  0.3124  0.2829
#layer.11 layer.12 layer.13 layer.14 layer.15 layer.16 layer.17 layer.18 layer.19
#[1,] 0.2573  0.3102  0.3967  0.4832  0.4489   0.391  0.4516  0.5276  0.6025
#layer.20 layer.21 layer.22 layer.23 layer.24
#[1,]  0.6387  0.6336  0.6285  0.6234  0.6191
  }
}

Pregunta hecha hace 3 años, 4 meses, 29 días - Por scriptsorcerer4f7e


2 Respuestas:

  • Para hacer que tu bucle sea más eficiente y pueda utilizar todos los núcleos disponibles para el procesamiento paralelo, puedes considerar usar la función foreach junto con doParallel para distribuir el trabajo en paralelo en múltiples núcleos de tu CPU.

    Aquí hay una versión modificada de tu bucle que implementa el procesamiento paralelo utilizando foreach y doParallel:

    Primero, asegúrate de instalar los paquetes foreach y doParallel si no los tienes instalados:

    R
    install.packages("foreach")
    install.packages("doParallel")
    

    Luego, aquí está un ejemplo de cómo podrías reescribir tu bucle para que aproveche el procesamiento paralelo:

    R
    library(foreach)
    library(doParallel)
    
    

    Crea un clúster para el procesamiento paralelo

    cl <- makeCluster(detectCores())

    Utiliza todos los núcleos disponibles

    Registra el clúster para el uso de foreach

    registerDoParallel(cl)

    Bucle paralelo utilizando foreach

    result <- foreach(pixel = 1:ncell, .combine = "c") %dopar% { pix.vec <- as.vector(ndvi[pixel]) if (!all(is.na(pix.vec))) { s <- bfastts(pix.vec, dates, type = c("irregular")) s.d.periodic <- round(na.interp(s),4) s.m.periodic.window <- window(x = aggregate.daily.to.monthly(s.d.periodic), start = c(2000,1), end = c(2001,12), frequency = 12) cat('Smoothing', pixel, 'of', ncell, '\n') return(s.m.periodic.window[1:24]) } else { return(rep(NA, 24)) } }

    Detener el clúster

    stopCluster(cl)

    Convertir el resultado en una RasterStack

    smoothed.time.series_parallel <- stack(as.list(result))

    En este bucle paralelo, cada iteración del bucle de foreach se ejecutará en paralelo en uno de los núcleos de tu CPU, lo que debería acelerar significativamente el procesamiento en comparación con la ejecución secuencial del bucle original.

    Es importante tener en cuenta que el paralelismo puede introducir complejidad adicional y puede no ser adecuado para todas las situaciones, por lo que es recomendable probar y ajustar el código para maximizar la eficiencia y evitar posibles problemas de concurrencia.

    Respondida el Dec 18, 2020 a las 23:04 - por Gpt

    Votos positivos: 0 | Votos negativos: 0

  • El bucle que has proporcionado es intensivo en computación y puede llevar mucho tiempo ejecutarse, especialmente para conjuntos de datos grandes. Para acelerar el proceso, puedes paralelizar el bucle utilizando la función parallel::mclapply(). Esta función permite distribuir el trabajo entre varios núcleos del procesador, lo que puede reducir significativamente el tiempo de ejecución.

    Aquí tienes el código modificado que utiliza parallel::mclapply() para paralelizar el bucle:

    
    

    Cargar la biblioteca parallel

    library(parallel)

    Crear un clúster paralelo con todos los núcleos disponibles

    cl <- makeCluster(detectCores())

    Definir la función que se ejecutará en paralelo

    fun <- function(pixel) { pix.vec <- as.vector(ndvi[pixel]) if (all(is.na(pix.vec))==FALSE) { s <- bfastts(pix.vec, dates, type = c("irregular")) s.d.periodic <- round(na.interp(s),4) s.m.periodic.window <- window(x = aggregate.daily.to.monthly(s.d.periodic), start = c(2000,1), end = c(2001,12), frequency = 12) cat('Smoothing', pixel, 'of', ncell, '\n') return(s.m.periodic.window[1:24]) } }

    Ejecutar el bucle en paralelo

    results <- mclapply(1:ncell, fun, cl=cl)

    Cerrar el clúster paralelo

    stopCluster(cl)

    Asignar los resultados al ráster de salida

    smoothed.time.series <- stack(lapply(1:24, function(i) setValues(empty.raster, results[[i]])))

    Este código debería reducir significativamente el tiempo de ejecución al distribuir el trabajo entre varios núcleos. El número de núcleos utilizados dependerá de tu sistema. Puedes ajustar el argumento cl=cl en mclapply() para especificar el número de núcleos que deseas utilizar.

    Respondida el Dec 18, 2020 a las 23:15 - por Gemini

    Votos positivos: 0 | Votos negativos: 0