簡體   English   中英

矢量化依賴於先前元素的產品計算?

[英]Vectorize a product calculation which depends on previous elements?

我正在嘗試加速/矢量化時間序列中的一些計算。 我可以在for循環中矢量化計算,這可能取決於早期迭代的結果嗎? 例如:

z <- c(1,1,0,0,0,0)
zi <- 2:6
for  (i in zi) {z[i] <- ifelse (z[i-1]== 1, 1, 0) }

使用前面步驟中更新的z [i]值:

> z
[1] 1 1 1 1 1 1

在我的努力中矢量化這個

z <- c(1,1,0,0,0,0)
z[zi] <- ifelse( z[zi-1] == 1, 1, 0)

逐個元素的操作不使用操作中更新的結果:

> z
[1] 1 1 1 0 0 0

因此,這種向量化操作以“並行”而非迭代方式運行。 有沒有辦法我可以寫/向量化這個來獲得for循環的結果?

ifelse是矢量化的,如果你在for循環中一次在一個元素上使用ifelse會有一點點懲罰。 在您的示例中,使用if而不是ifelse可以獲得相當好的加速。

fun1 <- function(z) {
  for(i in 2:NROW(z)) {
    z[i] <- ifelse(z[i-1]==1, 1, 0)
  }
  z
}

fun2 <- function(z) {
  for(i in 2:NROW(z)) {
    z[i] <- if(z[i-1]==1) 1 else 0
  }
  z
}

z <- c(1,1,0,0,0,0)
identical(fun1(z),fun2(z))
# [1] TRUE
system.time(replicate(10000, fun1(z)))
#   user  system elapsed 
#   1.13    0.00    1.32
system.time(replicate(10000, fun2(z)))
#   user  system elapsed 
#   0.27    0.00    0.26 

通過編譯,你可以從fun2獲得額外的速度提升。

library(compiler)
cfun2 <- cmpfun(fun2)
system.time(replicate(10000, cfun2(z)))
#   user  system elapsed 
#   0.11    0.00    0.11

所以沒有矢量化就有10倍的加速。 正如其他人所說(有些人已經說明),有一些方法可以對你的例子進行矢量化,但這可能無法轉化為你的實際問題。 希望這足以適用。

如果您可以根據自回歸或移動平均過程弄清楚如何表達問題, filter功能對您也很有用。

這是一個很好的簡單例子, Rcpp可以發光。

那么讓我們首先重新編寫函數1和2以及它們編譯的對應函數:

library(inline)
library(rbenchmark)
library(compiler)

fun1 <- function(z) {
  for(i in 2:NROW(z)) {
    z[i] <- ifelse(z[i-1]==1, 1, 0)
  }
  z
}
fun1c <- cmpfun(fun1)


fun2 <- function(z) {
  for(i in 2:NROW(z)) {
    z[i] <- if(z[i-1]==1) 1 else 0
  }
  z
}
fun2c <- cmpfun(fun2)

我們非常容易地編寫Rcpp變體:

funRcpp <- cxxfunction(signature(zs="numeric"), plugin="Rcpp", body="
  Rcpp::NumericVector z = Rcpp::NumericVector(zs);
  int n = z.size();
  for (int i=1; i<n; i++) {
    z[i] = (z[i-1]==1.0 ? 1.0 : 0.0);
  }
  return(z);
")

這使用內聯包來動態編譯,加載和鏈接五線程。

現在我們可以定義我們的測試日期,我們比原來的更長一些(因為只運行原始的太少次導致不可測量的時間):

R> z <- rep(c(1,1,0,0,0,0), 100)
R> identical(fun1(z),fun2(z),fun1c(z),fun2c(z),funRcpp(z))
[1] TRUE
R> 

所有答案都被視為相同。

最后,我們可以測試:

R> res <- benchmark(fun1(z), fun2(z),
+                  fun1c(z), fun2c(z),
+                  funRcpp(z),
+                  columns=c("test", "replications", "elapsed", 
+                            "relative", "user.self", "sys.self"),
+                  order="relative",
+                  replications=1000)
R> print(res)
        test replications elapsed relative user.self sys.self
5 funRcpp(z)         1000   0.005      1.0      0.01        0
4   fun2c(z)         1000   0.466     93.2      0.46        0
2    fun2(z)         1000   1.918    383.6      1.92        0
3   fun1c(z)         1000  10.865   2173.0     10.86        0
1    fun1(z)         1000  12.480   2496.0     12.47        0

對於最好的R版本,編譯版本贏得了近400倍,而對於其字節編譯版本,編譯版本幾乎達到100。 對於函數1,字節編譯的重要性要小得多,並且這兩種變體都使C ++的性能遠遠超過兩千

編寫C ++版本花了大約一分鍾。 速度提升表明它花了一分鍾。

為了比較,這里是更經常調用的原始短向量的結果:

R> z <- c(1,1,0,0,0,0)
R> res2 <- benchmark(fun1(z), fun2(z),
+                  fun1c(z), fun2c(z),
+                  funRcpp(z),
+                  columns=c("test", "replications", 
+                            "elapsed", "relative", "user.self", "sys.self"),
+                  order="relative",
+                  replications=10000)
R> print(res2)
        test replications elapsed  relative user.self sys.self
5 funRcpp(z)        10000   0.046  1.000000      0.04        0
4   fun2c(z)        10000   0.132  2.869565      0.13        0
2    fun2(z)        10000   0.271  5.891304      0.27        0
3   fun1c(z)        10000   1.045 22.717391      1.05        0
1    fun1(z)        10000   1.202 26.130435      1.20        0

定性排名沒有變化: Rcpp版本占主導地位,功能2排名第二。 字節編譯版本的速度大約是普通R版本的兩倍,但仍然比C ++版本快三倍。 並且相對差異較小:相對而言,函數調用開銷較少,實際循環更重要:C ++在較長向量中的實際循環操作中獲得更大的優勢。 這是一個重要的結果,因為它表明更多真實大小的數據,編譯版本可以獲得更大的好處。

編輯糾正代碼示例中的兩個小疏忽。 並再次編輯,感謝Josh捕獲相對於fun2c的設置錯誤。

我認為這是作弊而不是一般化的,但是:根據你上面的規則,在向量中出現1將使所有后續元素1(通過遞歸:如果z[i-1] z[i]z[i]為1設置為1 z[i-1]等於1;因此如果z[i-2]等於1,則z[i]將被設置為1;依此類推)。 根據您真正想要做的事情,如果您仔細考慮, 可能會有這樣的遞歸解決方案......

z <- c(1,1,0,0,0,0)
first1 <- min(which(z==1))
z[seq_along(z)>first1] <- 1

編輯 :這是錯的,但是我不肯承認我的錯誤。 基於一點點的游戲(並且思路較少),我認為這種遞歸的實際解決方案更加對稱,甚至更簡單:

rep(z[1],length(z))

測試用例:

z <- c(1,1,0,0,0,0)
z <- c(0,1,1,0,0,0)
z <- c(0,0,1,0,0,0)

查看zoorollapply功能。

我不是很熟悉它,但我認為這樣做你想要的:

> c( 1, rollapply(z,2,function(x) x[1]) )
[1] 1 1 1 1 1 1

我通過使用2的窗口然后僅使用該窗口的第一個元素來克服它。

對於更復雜的示例,您可以對x [1]執行一些計算並返回它。

有一個函數可以執行此特定計算: cumprod (累積產品)

> cumprod(z[zi])
[1] 1 0 0 0 0

> cumprod(c(1,2,3,4,0,5))
[1]  1  2  6 24  0  0

否則,使用Rccp進行矢量化,如其他答案所示。

有時你只需要完全不同地思考它。 你正在做的是創建一個向量,其中每個項目與第一個項目相同,否則為1或0。

z <- c(1,1,0,0,0,0)
if (z[1] != 1) z[1] <- 0
z[2:length(z)] <- z[1]

使用原始矢量和矢量的滯后版本作為數據幀的組成列,也可以使用“apply”來完成此操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM