【问题标题】:Rewriting loops with apply functions用应用函数重写循环
【发布时间】:2014-07-21 18:30:26
【问题描述】:

我有以下 3 个想要更快的函数,我认为应用函数是最好的方法,但我从未使用过应用函数,所以我不知道该怎么做。任何类型的提示、想法和代码 sn-ps 将不胜感激。

n, T, dt 是全局参数,par 是参数向量。

函数 1:创建一个 m+1,n 矩阵的函数,该矩阵包含具有指数分布跳跃大小的泊松分布跳跃。我的麻烦是因为我有 3 个循环,我不确定如何将 if 语句合并到内部循环中。我也不知道是否可以仅在循环的外层使用应用函数。

jump <- function(t=0,T=T,par){
  jump <- matrix(0,T/dt+1,n) # initializing output matrix
  U <- replicate(n,runif(100,t,T)) #matrix used to decide when the jumps will happen
  Y <-replicate(n,rexp(100,1/par[6])) #matrix with jump sizes
  for (l in 1:n){ 
    NT <- rpois(1,par[5]*T) #number of jumps
    k=0
    for (j in seq(t,T,dt)){
      k=k+1
      if (NT>0){
        temp=0
        for (i in 1:NT){
          u <- vector("numeric",NT)
          if (U[i,l]>j){ u[i]=0
          }else u[i]=1
          temp=temp+Y[i,l]*u[i]
        }
        jump[k,l]=temp
      }else jump[k,l]=0
    }
  }
  return(jump)
}

函数 2:根据布朗运动和函数 1 的跳跃计算默认强度。这里我的麻烦是当用于计算的变量是输出矩阵中上一行的值时如何使用应用函数如何从计算中使用的外部矩阵中获取正确的值(BMz_C & J)

lambda <- function(t=0,T=T,par,fit=0){
  lambda <- matrix(0,m+1,n) # matrix to hold intesity path output
  lambda[1,] <- par[4] #initializing start value of the intensity path.
  J <- jump(t,T,par) #matrix containing jumps
  for(i in 2:(m+1)){
    dlambda <- par[1]*(par[2]-max(lambda[i-1,],0))*dt+par[3]*sqrt(max(lambda[i-   1,],0))*BMz_C[i,]+(J[i,]-J[i-1,])
    lambda[i,] <- lambda[i-1,]+dlambda
  } 
  return(lambda)
} 

函数 3:根据函数 2 的强度计算生存概率。这里 a() 和 B() 是返回数值的函数。我的问题是 i 和 j 都被使用了,因为 i 并不总是一个整数,因此可以用来引用外部矩阵。我之前曾尝试使用 i/dt,但有时它会覆盖矩阵中的一行并跳过矩阵中的下一行,这很可能是由于舍入错误。

S <- function(t=0,T=T,par,plot=0, fit=0){
  S <- matrix(0,(T-t)/dt+1,n)
  if (fit > 0) S.fit <- matrix(0,1,length(mat)) else S.fit <- 0
  l=lambda(t,T,par,fit)
  j=0
  for (i in seq(t,T,dt)){
    j=j+1
    S[j,] <- a(i,T,par)*exp(B(i,T,par)*l[j,])
  }
  return(S)
}

很抱歉这篇长文,任何功能的帮助将不胜感激。

编辑: 首先感谢 digEmAll 的精彩回复。

我现在已经研究了矢量化函数 2。首先我尝试了

lambda <- function(t=0,T=T,par,fit=0){
  lambda <- matrix(0,m+1,n) # matrix to hold intesity path input
  J <- jump(t,T,par,fit)
    lambda[1,] <- par[4] 
    lambda[2:(m+1),] <- sapply(2:(m+1), function(i){
      lambda[i-1,]+par[1]*(par[2]-max(lambda[i-1,],0))*dt+par[3]*sqrt(max(lambda[i-1,],0))*BMz_C[i,]+(J[i,]-J[i-1,])
    })
  return(lambda)
}

但它只会产生第一列。所以我尝试了一个两步应用功能。

lambda <- function(t=0,T=T,par,fit=0){
  lambda <- matrix(0,m+1,n) # matrix to hold intesity path input
  J <- jump(t,T,par,fit)
  lambda[1,] <- par[4] 
  lambda[2:(m+1),] <- sapply(1:n, function(l){
    sapply(2:(m+1), function(i){
      lambda[i-1,l]+par[1]*(par[2]-max(lambda[i-1,l],0))*dt+par[3]*sqrt(max(lambda[i-1,l],0))*BMz_C[i,l]+(J[i,l]-J[i-1,l])
    })
  })
  return(lambda)
}

这似乎有效,但仅在第一行,之后的所有行都有相同的非零值,好像 lambda[i-1] 没有用于计算 lambda[i],有没有人有知道如何管理它?

【问题讨论】:

  • 旁注:你知道T 等于True 吗?您真的应该考虑更改该参数的名称以避免混淆......
  • 如果您从未使用过apply 系列函数,您可以先看看this question
  • 您应该发布一个调用函数的示例(带有有效参数和全局参数的示例)。通过这种方式,我们将有一个可重现的示例来处理......
  • @Marcus:同样,如果您希望我们能够帮助您,您需要发布输入数据的示例(或者可能是较小的子集)。我的意思是:m,n,dt,T,par,BMz_C 等等 ...
  • @Marcus:顺便说一句,我想补充一句。您使用我的代码获得的速度提升主要是由于在循环外提取了rpoisseq 调用。事实上,sapply 并没有比写得好的循环快 太多(“写得好”我的意思是一个你不附加到矩阵而不是预分配的循环,你不t 多次重新计算可以存储和重复使用的东西等)。另外,你的第二个乐趣。似乎需要上一次迭代的结果,这很难使用 sapply 进行矢量化,因此,如果性能可以接受,我会坚持使用你的循环;)

标签: r for-loop recursion apply performance


【解决方案1】:

我将逐步向您解释如何矢量化第一个函数(一种可能的矢量化方法,可能不是最适合您的情况)。
对于其他 2 个功能,您可以简单地应用相同的概念,并且您应该能够做到。

这里的关键概念是:从最里面的循环开始向量化


1) 首先,rpois 一次可以生成多个随机值,但您调用它 n 次询问一个随机值。所以,让我们把它从循环中取出来:

jump <- function(t=0,T=T,par){
  jump <- matrix(0,T/dt+1,n) 
  U <- replicate(n,runif(100,t,T)) 
  Y <-replicate(n,rexp(100,1/par[6])) 
  NTs <- rpois(n,par[5]*T) # note the change
  for (l in 1:n){ 
    NT <- NTs[l] # note the change
    k=0
    for (j in seq(t,T,dt)){
      k=k+1
      if (NT>0){
        temp=0
        for (i in 1:NT){
          u <- vector("numeric",NT)
          if (U[i,l]>j){ u[i]=0
          }else u[i]=1
          temp=temp+Y[i,l]*u[i]
        }
        jump[k,l]=temp
      }else jump[k,l]=0
    }
  }
  return(jump)
}

2) 同样,在循环中调用seq(t,T,dt) n 次是无用/低效的,因为它总是会生成相同的序列。所以,让我们把它从循环中取出并存储到一个向量中,得到这个:

jump <- function(t=0,T=T,par){
  jump <- matrix(0,T/dt+1,n) 
  U <- replicate(n,runif(100,t,T)) 
  Y <-replicate(n,rexp(100,1/par[6])) 
  NTs <- rpois(n,par[5]*T) 
  js <- seq(t,T,dt) # note the change
  for (l in 1:n){ 
    NT <- NTs[l] 
    k=0
    for (j in js){ # note the change
      k=k+1
      if (NT>0){
        temp=0
        for (i in 1:NT){
          u <- vector("numeric",NT)
          if (U[i,l]>j){ u[i]=0
          }else u[i]=1
          temp=temp+Y[i,l]*u[i]
        }
        jump[k,l]=temp
      }else jump[k,l]=0
    }
  }
  return(jump)
}

3) 现在,让我们看看最里面的循环:

for (i in 1:NT){
  u <- vector("numeric",NT)
  if (U[i,l]>j){ u[i]=0
  }else u[i]=1
  temp=temp+Y[i,l]*u[i]
}

这等于:

u <- as.integer(U[1:NT,l]<=j)
temp <- sum(Y[1:NT,l]*u)

或者,在一行中:

temp <- sum(Y[1:NT,l] * as.integer(U[1:NT,l] <= j))

因此,现在函数可以写成:

jump <- function(t=0,T=T,par){
  jump <- matrix(0,T/dt+1,n) 
  U <- replicate(n,runif(100,t,T)) 
  Y <-replicate(n,rexp(100,1/par[6])) 
  NTs <- rpois(n,par[5]*T) 
  js <- seq(t,T,dt) 
  for (l in 1:n){ 
    NT <- NTs[l] 
    k=0
    for (j in js){ 
      k=k+1
      if (NT>0){
        jump[k,l] <- sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) # note the change
      }else jump[k,l]=0
    }
  }
  return(jump)
}

4) 再次,让我们看看当前最内层的循环:

for (j in js){ 
  k=k+1
  if (NT>0){
      jump[k,l] <- sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) # note the change
  }else jump[k,l]=0
}

如你所见,NT 不依赖于这个循环的迭代,所以内部的if 可以移到外面,如下:

if (NT>0){
  for (j in js){ 
    k=k+1
    jump[k,l] <- sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) # note the change
  }
}else{
  for (j in js){ 
    k=k+1
    jump[k,l]=0
  }
}

这似乎比以前更糟了,是的,但是现在这两个条件可以变成单行(注意使用sapply¹):

if (NT>0){
  jump[1:length(js),l] <- sapply(js,function(j){ sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) })
}else{
  jump[1:length(js),l] <- 0
}

获取如下跳转函数:

jump <- function(t=0,T=T,par){
  jump <- matrix(0,T/dt+1,n) 
  U <- replicate(n,runif(100,t,T)) 
  Y <-replicate(n,rexp(100,1/par[6])) 
  NTs <- rpois(n,par[5]*T) 
  js <- seq(t,T,dt) 
  for (l in 1:n){ 
    NT <- NTs[l] 
    if (NT>0){
      jump[1:length(js),l] <- sapply(js,function(j){ sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) })
    }else{
      jump[1:length(js),l] <- 0
    }
  }
  return(jump)
}

5)最后我们可以摆脱最后一个循环,再次使用sapply¹函数,得到最终的jump函数:

jump <- function(t=0,T=T,par){
  U <- replicate(n,runif(100,t,T)) 
  Y <-replicate(n,rexp(100,1/par[6])) 
  js <- seq(t,T,dt)
  NTs <- rpois(n,par[5]*T)

  jump <- sapply(1:n,function(l){
    NT <- NTs[l] 
    if (NT>0){
      sapply(js,function(j){ sum(Y[1:NT,l]*as.integer(U[1:NT,l]<=j)) })
    }else {
      rep(0,length(js))
    }
  })
  return(jump)
}

(¹)

sapply 函数很容易使用。对于X 参数中传递的列表或向量的每个元素,它应用在FUN 参数中传递的函数,例如:

vect <- 1:3
sapply(X=vect,FUN=function(el){el+10}
# [1] 11 12 13

由于默认情况下simplify 参数为真,结果被强制转换为最简单的对象。因此,例如在前面的例子中,结果变成了一个向量,而在下面的例子中,结果变成了一个矩阵(因为对于每个元素,我们返回一个相同大小的向量):

vect <- 1:3
sapply(X=vect,FUN=function(el){rep(el,5)})

#      [,1] [,2] [,3]
# [1,]    1    2    3
# [2,]    1    2    3
# [3,]    1    2    3
# [4,]    1    2    3
# [5,]    1    2    3

基准测试:

以下基准测试只是让您了解速度增益,但实际性能可能会根据您的输入参数而有所不同。
可以想象,jump_old 对应于你原来的函数 1,而jump_new 是最终的矢量化版本。

# let's use some random parameters
n = 10
m = 3
T = 13
par = c(0.1, 0.2, 0.3, 0.4, 0.5, 0.6)
dt <- 3

set.seed(123)
system.time(for(i in 1:5000) old <- jump_old(T=T,par=par))
# user   system  elapsed 
# 12.39    0.00    12.41

set.seed(123)
system.time(for(i in 1:5000) new <- jump_new(T=T,par=par))
# user  system  elapsed 
# 4.49    0.00     4.53 

# check if last results of the 2 functions are the same:
isTRUE(all.equal(old,new))
# [1] TRUE

【讨论】:

  • 添加了关于 sapply 函数的信息。
  • +1!任何迹象,甚至是关于新功能与旧功能相比如何执行的基准(因为加速功能是 OP 的目标)?
  • @PaulHiemstra:我真的不知道输入参数的大小或值范围,所以我添加了一个简单的基准,但它可能并不重要;)
  • 太棒了!!!万分感谢。当我通过更新代码的方式工作时,我将发布对效率的影响。对跳转的影响从:用户系统经过 547.76 0.14 548.39 到:用户系统经过 83.26 14.35 97.61,n=1000 T=30 dt=1/360
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-28
  • 1970-01-01
  • 2011-07-16
  • 1970-01-01
  • 1970-01-01
  • 2018-07-23
  • 2020-11-20
相关资源
最近更新 更多