干净的方法来计算数组求和的jacobian

时间:2016-03-04 10:52:43

标签: arrays r derivative matrix-indexing

我在R中做了一些优化,并且与之相关,我需要编写一个返回jacobian的函数。这是一个非常简单的雅各比 - 只有零和一个 - 但我想快速干净地填充它。我目前的代码有效,但非常草率。

我有一个四维概率数组。按i, j, k, l索引维度。我的约束是,对于每个i, j, k,索引l上的概率总和必须等于1.

我像这样计算我的约束向量:

get_prob_array_from_vector <- function(prob_vector, array_dim) {
    return(array(prob_vector, array_dim))
}

constraint_function <- function(prob_vector, array_dim) {
    prob_array <- get_prob_array_from_vector(prob_vector, array_dim)
    prob_array_sums <- apply(prob_array, MARGIN=c(1, 2, 3), FUN=sum)
    return(as.vector(prob_array_sums) - 1)  # Should equal zero
}

我的问题是:{strong>干净,快速计算as.vector(apply(array(my_input_vector, array_dim), MARGIN=c(1, 2, 3), FUN=sum))的jacobian - 即上面代码中的constraint_function - 关于my_input_vector

这是我的草率解决方案(我检查了numDeriv包中jacobian函数的正确性):

library(numDeriv)

array_dim <- c(5, 4, 3, 3)

get_prob_array_from_vector <- function(prob_vector, array_dim) {
    return(array(prob_vector, array_dim))
}

constraint_function <- function(prob_vector, array_dim) {
    prob_array <- get_prob_array_from_vector(prob_vector, array_dim)
    prob_array_sums <- apply(prob_array, MARGIN=c(1, 2, 3), FUN=sum)
    return(as.vector(prob_array_sums) - 1)
}

constraint_function_jacobian <- function(prob_vector, array_dim) {
    prob_array <- get_prob_array_from_vector(prob_vector, array_dim)
    jacobian <- matrix(0, Reduce("*", dim(prob_array)[1:3]), length(prob_vector))
    ## Must be a faster, clearner way of populating jacobian
    for(i in seq_along(prob_vector)) {
        dummy_vector <- rep(0, length(prob_vector))
        dummy_vector[i] <- 1
        dummy_array <- get_prob_array_from_vector(dummy_vector, array_dim)
        dummy_array_sums <- apply(dummy_array, MARGIN=c(1, 2, 3), FUN=sum)
        jacobian_row_idx <- which(dummy_array_sums != 0, arr.ind=FALSE)
        stopifnot(length(jacobian_row_idx) == 1)
        jacobian[jacobian_row_idx, i] <- 1
    }  # Is there a fast, readable one-liner that does the same as this for loop?
    stopifnot(sum(jacobian) == length(prob_vector))
    stopifnot(all(jacobian == 0 | jacobian == 1))
    return(jacobian)
}

## Example of a probability array satisfying my constraint
my_prob_array <- array(0, array_dim)
for(i in seq_len(array_dim[1])) {
    for(j in seq_len(array_dim[2])) {
        my_prob_array[i, j, , ] <- diag(array_dim[3])
    }
}
my_prob_array[1, 1, , ] <- 1 / array_dim[3]
my_prob_array[2, 1, , ] <- 0.25 * (1 / array_dim[3]) + 0.75 * diag(array_dim[3])

my_prob_vector <- as.vector(my_prob_array)  # Flattened representation of my_prob_array
should_be_zero_vector <- constraint_function(my_prob_vector, array_dim)
is.vector(should_be_zero_vector)
all(should_be_zero_vector == 0)  # Constraint is satistied

## Check constraint_function_jacobian for correctness using numDeriv
jacobian_analytical <- constraint_function_jacobian(my_prob_vector, array_dim)
jacobian_numerical <- jacobian(constraint_function, my_prob_vector, array_dim=array_dim)
max(abs(jacobian_analytical - jacobian_numerical))  # Very small

我的函数将prob_vector作为输入 - 即我的概率数组的展平表示 - 因为优化函数需要向量参数。

1 个答案:

答案 0 :(得分:7)

花一些时间来了解您的目标,但这是一个替换您constraint_function_jacobian的建议:

enhanced <- function(prob_vector,array_dim) {
  firstdim <- Reduce("*", array_dim[1:3])
  seconddim <- length(prob_vector)
  jacobian <- matrix(0, firstdim, seconddim)
  idxs <- split(1:seconddim,cut(1:seconddim,array_dim[4],labels=F))
  for( i in seq_along(idxs)) {
    diag(jacobian[, idxs[[i]] ]) <- 1
  }
  stopifnot(sum(jacobian) == length(prob_vector))
  stopifnot(all(jacobian == 0 | jacobian == 1))
  jacobian
}

除非我错了,jacobian结构用1填充对角线,因为它不是方形矩阵,我们必须将它分割在array_dim[4]方阵上,以便用1填充对角线。

我确实摆脱了prob_vector到数组的转换然后得到它dim,因为它与array_dim相同,跳过这一步并不是一个巨大的进步但是它简化了代码IMO。

根据测试结果是可以的:

> identical(constraint_function_jacobian(my_prob_vector,array_dim),enhanced(my_prob_vector,array_dim))
[1] TRUE

根据基准测试,它提供了极大的加速:

> microbenchmark(constraint_function_jacobian(my_prob_vector,array_dim),enhanced(my_prob_vector,array_dim),times=100)

Unit: microseconds
                                                    expr       min        lq      mean     median         uq       max neval cld
 constraint_function_jacobian(my_prob_vector, array_dim) 16946.979 18466.491 20150.304 19066.7410 19671.4100 28148.035   100   b
                     enhanced(my_prob_vector, array_dim)   678.222   737.948   799.005   796.3905   834.5925  1141.773   100  a