【问题标题】:Package Optim.jl very slow包 Optim.jl 很慢
【发布时间】:2018-06-08 01:12:11
【问题描述】:

我用 RJulia 语言构建了两个几乎相同的程序。

我事先知道有一种方法可以提高 Julia 代码的性能,因为我没有声明类型,而且通常非向量化代码往往更有效。但是,Julia 代码和 R 代码位于同一地形,因此可以进行比较。

注意:我开始学习 Julia 一周,所以你可以看出我对 Julia 的编程不是很好。

我注意到 Optim 包提供了optimize() 函数。与与 R 一起默认安装的 stats 包的 R 语言的 optim() 函数相比,它非常慢语言。

以下分别是代码RJulia

R 代码:

rm(list=ls(all=TRUE))

Gexp <- function(par,x){
  lambda <- par[1]
  pexp(q = x, rate = lambda, lower.tail = TRUE, log.p = FALSE)
}

gexp <- function(par,x){
  lambda <- par[1]
  dexp(x = x, rate = lambda, log = FALSE)
}

QGexp <- function(p,...){
  qexp(p,...)
}

Gweibull <- function(par,x){
  alpha <- par[1]
  beta <- par[2]
  pweibull(q = x, shape = alpha, scale = beta, lower.tail = TRUE, log.p = FALSE)
}

QGweibull <- function(p,...){
  qweibull(p,...)
}

# Função de distribuição acumulada Exponentiated Kw-G class (EKw-G)
cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)

  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

# cdf_ekwg(cdf = G, par = c(0.2,0.4,0.21), x = 1, alpha = 1.1, beta = 1.2, lambda = 1)

# Função densidade de probabilidade Exponentiated Kw-G class (EKw-G)
pdf_ekwg <- function(cdf, pdf, par, x, ...){

  a <- par[1]
  b <- par[2]
  c <- par[3]
  #cdf_ekwg_locale <- function(x){
  #  cdf_ekwg(cdf = cdf, par = par, x, ...)
  #}

  npar <- c(...)

  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)

  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)

  #numDeriv::grad(func = cdf_ekwg_locale, x = x, method = "simple")
}

#integrate(f = pdf_ekwg, par = c(1,1,1.5), lower = 0, upper = Inf, cdf = Gexp, pdf = gexp,
#           lambda = 1.5)

# Será fixado os parâmetros de G. Serão estimados os parâmetros a, b e c do
# modelo EKwG.

sample_ekwg <- function(QG, n, par, ...){

  a <- par[1]
  b <- par[2]
  c <- par[3]

  u <- runif(n = n, min = 0, max = 1)
  p <- (1 - (1 - u^(1/c))^(1/b))^(1/a)

  QG(p = p, ...)

}

# Função de distribuição acumulada Exponentiated Kw-G class (EKw-G)
cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)

  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

# cdf_ekwg(cdf = G, par = c(0.2,0.4,0.21), x = 1, alpha = 1.1, beta = 1.2, lambda = 1)

# Função densidade de probabilidade Exponentiated Kw-G class (EKw-G)
pdf_ekwg <- function(cdf, pdf, par, x, ...){

  a <- par[1]
  b <- par[2]
  c <- par[3]
  #cdf_ekwg_locale <- function(x){
  #  cdf_ekwg(cdf = cdf, par = par, x, ...)
  #}

  npar <- c(...)

  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)

  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)

  #numDeriv::grad(func = cdf_ekwg_locale, x = x, method = "simple")
}

# integrate(f = pdf_ekwg, par = c(1.4,1.3,0.5), lower = 0, upper = Inf, cdf = G,  alpha = 1.1, beta = 1.2)

loglikelihood <- function(cdf, pdf, par, x, ...){
  -sum(log(pdf_ekwg(cdf = cdf, pdf = pdf, par = par, x = x, ...)))
}

myoptim <- function(...) tryCatch(optim(...), error = function(e) NA)

G = Gexp
g = gexp
data = sample_ekwg(QG = QGexp, n = 550, par = c(1,1,1), rate = 1.5)
starts = c(1,1,1)

set.seed(0)
start = Sys.time()
for(i in 1:5){
  result <- myoptim(par = starts, fn = loglikelihood, x = data, cdf = G,
                    pdf = g, method = "Nelder-Mead",rate = 1.5)
}
Sys.time() - start

Julia 代码

using Distributions
#using Cubature # Calculo de integrais numéricas.
#using Plots
using Optim
#using JuMP
#using NLopt

function gexp(x,par)
    λ = par[1]
    λ * exp(-λ * x)
end

# valor = hquadrature(x -> gexp(x,1), 0, 100)[1]

function Gexp(x,par)
    λ = par[1]
    1- exp(-λ * x)
end

function QGexp(x,par)
    λ = par[1]
    # A função Exponential no pacote Distributions é reparametrizada
    # como 1/lambda. Dessa forma, para trabalhar com densidade na forma
    # λ * exp(-λ*x) é preciso tomar 1/λ.
    quantile.(Exponential(1/λ),x)
end

function sample_ekwg(QG, n, par0, par1...)
    a = par0[1]
    b = par0[2]
    c = par0[3]

    u = rand(n)

    p = (1 - (1 - u.^(1/c)).^(1/b)).^(1/a)

    QG(p, par1...)
end

# Função de distribuição acumulada Exponentiated Kw-G class (EKw-G)
function cdf_ekwg(cdf, x, par0, par1...)
    a = par0[1]
    b = par0[2]
    c = par0[3]

    (1 - (1 - cdf.(x,par1...).^a).^b).^c
end

# Função densidade de probabilidade Exponentiated Kw-G class (EKw-G)
function pdf_ekwg(cdf, pdf, x, par0, par1...)
    a = par0[1]
    b = par0[2]
    c = par0[3]

    g = pdf(x, par1...)
    G = cdf(x, par1...)

    a * b * c * g * G.^(a-1) * (1-G.^a).^(b-1) * (1 - (1-G.^a).^b).^(c-1)

end

# valor = hquadrature(x ->
# pdf_ekwg(Gexp,gexp, x, [1,1,1], 1), 0, 100)[1]

function loglike(cdf, pdf, x, par0, par1...)
  n = length(x)
  soma = 0
  for i = 1:n
      soma += log(pdf_ekwg(cdf, pdf, x[i], par0, par1...))
  end
  return -soma # Queremos minimizar loglike.
end

 G = Gexp
g = gexp
data = sample_ekwg(QGexp, 550, [1,1,1],1.5)
starts = [1,1,1]
par0 = [1,1,1]
par1 = [1.5]

srand(0) # set seed.

@time for i = 1:5
     optimize(par0 -> loglike(G, g, data, par0, par1), [1.3,1.2,2.1])
end

执行这两个代码的机器配置如下:

注意:这些代码不适用于相同的样本。但是,我相信这不能证明计算时间的巨大差异是合理的。

在我的硬件中,R 代码花费 0.6508956 秒,Julia 代码花费 27.180257 秒。

有人知道如何让 Julia 的代码比 R 代码运行得更快吗? 我想要一个简单的解决方案,因为它承诺在 Julia 中具有出色的计算性能,而无需对编程有太多了解。看,在 R 中,并没有做太多的事情来证明 Julia 代码中的主要修复是合理的。

最好的问候。

【问题讨论】:

  • R 代码是开源的。大概(或者我应该假设)Julia 语言有一个到 C 的接口?
  • 别吐这么多。这对数组的性能真的很糟糕,因为数组的长度不是编译时间信息。只需传递数组就可以了。
  • 慢的不是 Optim.jl,而是你的函数。您的 loglike 被调用了大约 8500 次,优化在我的计算机上需要 16 秒,这与运行 loglike 8500 次所需的持续时间相同。
  • 谢谢。我会尝试将考虑的迭代次数放在R的优化算法中。
  • 使用 BenchmarkTools 中的一些宏来获得更高的精度,而不是 @time

标签: r julia


【解决方案1】:

总结上面的讨论,这里有几个问题:

  1. 如前所述,喷溅速度很慢
  2. optimize 的调用未包含在函数中,这也会减慢计算速度
  3. loglike 的类型不稳定,因为您将 soma 定义为整数
  4. 两个优化例程都可以调用 loglike 不同的次数(由于它们的配置不同) - 所以最好对给定的调用次数进行基准测试 loglike - 我在下面选择了 1000 个

下面我发布了一个清理过的 Julia 代码和 R 代码,它们应该可以完成相同的工作,而 Julia 的速度要快 2 倍。在 Julia 中预编译后的时间是:

julia> experiment(Gexp, gexp, data, par0, par1)
  0.112414 seconds (2.75 M allocations: 41.992 MiB, 3.38% gc time)

在 R 中是

> start = Sys.time()
> for(i in 1:1000){
+   loglikelihood(G, g, starts, data, 1.5)
+ }
> Sys.time() - start
Time difference of 0.2812479 secs

这是清理后的代码(我希望我在删除不必要的部分时没有弄错:) - 所以请检查我是否在某个地方搞砸了)。

朱莉娅

using Distributions

gexp(x,λ) = λ * exp(-λ * x)
Gexp(x,λ) = 1.0 - exp(-λ * x)
QGexp(x,λ) = quantile.(Exponential(1/λ), x)

function sample_ekwg(QG, n, par0, par1)
    a = par0[1]
    b = par0[2]
    c = par0[3]
    u = rand(n)
    p = (1 - (1 - u.^(1/c)).^(1/b)).^(1/a)
    QG(p, par1)
end

function pdf_ekwg(cdf, pdf, x, par0, par1)
    a = par0[1]
    b = par0[2]
    c = par0[3]
    g = pdf(x, par1)
    G = cdf(x, par1)
    a*b*c*g*G^(a-1)*(1-G^a)^(b-1)*(1-(1-G^a)^b)^(c-1)
end

function loglike(cdf, pdf, x, par0, par1)
  soma = 0.0
  for v in x
      soma += log(pdf_ekwg(cdf, pdf, v, par0, par1))
  end
  return -soma
end

par0 = [1.0,1.0,1.0]
par1 = 1.5
data = sample_ekwg(QGexp, 550, par0,par1)

function experiment(G, g, data, par0, par1)
  @time for i = 1:1000
       loglike(G, g, data, par0, par1)
  end
end

experiment(Gexp, gexp, data, par0, par1)

R

Gexp <- function(par,x){
  lambda <- par[1]
  pexp(q = x, rate = lambda, lower.tail = TRUE, log.p = FALSE)
}

gexp <- function(par,x){
  lambda <- par[1]
  dexp(x = x, rate = lambda, log = FALSE)
}

QGexp <- function(p,...){
  qexp(p,...)
}

cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)

  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

pdf_ekwg <- function(cdf, pdf, par, x, ...){

  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)
  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)
}

sample_ekwg <- function(QG, n, par, ...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  u <- runif(n = n, min = 0, max = 1)
  p <- (1 - (1 - u^(1/c))^(1/b))^(1/a)
  QG(p = p, ...)
}

cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

pdf_ekwg <- function(cdf, pdf, par, x, ...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)
  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)
}

loglikelihood <- function(cdf, pdf, par, x, ...){
  -sum(log(pdf_ekwg(cdf = cdf, pdf = pdf, par = par, x = x, ...)))
}

G = Gexp
g = gexp
data = sample_ekwg(QG = QGexp, n = 550, par = c(1,1,1), rate = 1.5)
starts = c(1,1,1)

start = Sys.time()
for(i in 1:1000){
  loglikelihood(G, g, starts, data, 1.5)
}
Sys.time() - start

编辑 - 进一步优化 Julia 代码

我决定不传递Gexpgexp,而是直接调用它们:

function pdf_ekwg(x, par0, par1)
    a = par0[1]
    b = par0[2]
    c = par0[3]
    g = gexp(x, par1)
    G = Gexp(x, par1)
    a*b*c*g*G^(a-1)*(1-G^a)^(b-1)*(1-(1-G^a)^b)^(c-1)
end

那么时机要好 2 倍:

julia> experiment(data, par0, par1)
  0.061860 seconds

【讨论】:

  • 感谢您的帮助。缺少考虑 optimize 函数。
  • 我遇到了为估计器的偏差校正制作引导程序的问题。在每次迭代中,我都必须对目标函数进行优化。我认为它会表现得非常好(即使它没有 Julia 中的最佳编程实践),因为 R 代码也没有考虑最佳实践。
  • 当然,为了公平起见,我将不得不考虑 optimize 函数的其他函数选项。我会这样做,但我怀疑最多我会有等价物。我会尝试返回结果。非常感谢。
  • 附带说明,如果您对引导程序感兴趣,请查看github.com/juliangehring/Bootstrap.jl
【解决方案2】:

请注意,R 和 Julia 计算迭代次数的方式不同。这意味着在相同数量的迭代中,Julia 执行了更多的函数调用,但也达到了更好的近似解。所以你报告的差异并不奇怪。

这是一个显示这一点的最小示例:

朱莉娅

julia> using Optim

julia> function f(x)
           println(x)
           sum(x.^2)
       end
f (generic function with 1 method)

julia> optimize(f, [10.0, 10.0, -10.0], Optim.Options(iterations = 10))
[10.0, 10.0, -10.0]
[15.025, 10.0, -10.0]
[10.0, 15.025, -10.0]
[10.0, 10.0, -14.975]
[13.35, 4.975, -13.3167]
[7.20833, 6.65, -15.5278]
[10.3722, 4.41667, -10.9213]
[10.4963, 2.55556, -9.57006]
[5.11975, 7.8287, -10.0819]
[2.37634, 8.77994, -9.00364]
[8.04009, 7.57366, -3.52135]
[8.31734, 7.88155, 0.480788]
[4.12665, 2.81136, -2.06194]
[2.16887, 0.41515, 0.584081]
[-1.92127, 8.82887, 4.27755]
[3.33362, 2.63711, 12.5652]
[2.57577, 7.50018, -4.51012]
[-6.43509, 3.28125, -0.246445]
[0.794297, -1.36448, -7.05921]
[-1.15731, 0.777307, -2.24052]
Results of Optimization Algorithm
 * Algorithm: Nelder-Mead
 * Starting Point: [10.0,10.0,-10.0]
 * Minimizer: [2.1688665345729614,0.4151501295534299, ...]
 * Minimum: 5.217482e+00
 * Iterations: 10
 * Convergence: false
   *  √(Σ(yᵢ-ȳ)²)/n < 1.0e-08: false
   * Reached Maximum Number of Iterations: true
 * Objective Calls: 20

R

> f <- function(x) {
+     print(x)
+     sum(x^2)
+ }
> 
> optim(c(10, 10, -10), f, control=list(maxit=10))
[1]  10  10 -10
[1]  11  10 -10
[1]  10  11 -10
[1] 10 10 -9
[1]  9.000000 10.666667 -9.333333
[1]  9.5 10.5 -9.5
[1]  9.333333  9.444444 -8.888889
[1]  9.000000  8.666667 -8.333333
[1]  8.666667  9.555556 -7.777778
[1]  9.000000  9.666667 -8.333333
[1]  9.444444  8.148148 -7.407407
[1]  9.666667  6.888889 -6.444444
$`par`
[1]  9.000000  8.666667 -8.333333

$value
[1] 225.5556

$counts
function gradient 
      12       NA 

$convergence
[1] 1

$message
NULL

【讨论】:

  • 有趣。似乎在我发布的代码中,我可以使 Julia 代码比 R 代码更高效,而无需花费一些时间,因为我可以通过使用 ``optimize`` 函数来放松算法的收敛标准。在这个例子中,我坚持不要花一点时间改进 Julia 代码,因为没有太多时间花在实现 R 代码上。代码非常接近。非常感谢您的提示。
【解决方案3】:

为了公平比较 R 语言的 optim 函数和 Julia 的 optimize 函数 Optim 包,我考虑了 Nelder-Mead 方法1e^-8 中的最大 500 次迭代和收敛容差。这是标准的 R optim 函数。

朱莉娅

using Distributions
using Optim

gexp(x,λ) = λ * exp(-λ * x)
Gexp(x,λ) = 1.0 - exp(-λ * x)
QGexp(x,λ) = quantile.(Exponential(1/λ), x)

function sample_ekwg(QG, n, par0, par1)
    a = par0[1]
    b = par0[2]
    c = par0[3]
    u = rand(n)
    p = (1 - (1 - u.^(1/c)).^(1/b)).^(1/a)
    QG(p, par1)
end

function pdf_ekwg(cdf, pdf, x, par0, par1)
    a = par0[1]
    b = par0[2]
    c = par0[3]
    g = pdf(x, par1)
    G = cdf(x, par1)
    a*b*c*g*G^(a-1)*(1-G^a)^(b-1)*(1-(1-G^a)^b)^(c-1)
end

function loglike(cdf, pdf, x, par0, par1)
  soma = 0.0
  for v in x
      soma += log(pdf_ekwg(cdf, pdf, v, par0, par1))
  end
  return -soma
end

par0 = [1.0,1.0,1.0]
par1 = 1.5
n = 20
srand(0)
data = sample_ekwg(QGexp, 5000, par0,par1)

function experiment(G, g, data, par0, par1, n)
  result = Vector(length(par0)*n)
  @time for i = 1:n
       result[3i - 2 : 3i] = optimize(par0 -> loglike(G, g, data, par0, par1),
                                      par0, Optim.Options(iterations = 500, g_tol = 1e-8)).minimizer
       loglike(G, g, data, par0, par1)
  end
  return reshape(result,length(par0),n)'
end

experiment(Gexp, gexp, data, par0, par1, n)

R

Gexp <- function(par,x){
  lambda <- par[1]
  pexp(q = x, rate = lambda, lower.tail = TRUE, log.p = FALSE)
}

gexp <- function(par,x){
  lambda <- par[1]
  dexp(x = x, rate = lambda, log = FALSE)
}

QGexp <- function(p,...){
  qexp(p,...)
}

cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)

  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

pdf_ekwg <- function(cdf, pdf, par, x, ...){

  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)
  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)
}

sample_ekwg <- function(QG, n, par, ...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  u <- runif(n = n, min = 0, max = 1)
  p <- (1 - (1 - u^(1/c))^(1/b))^(1/a)
  QG(p = p, ...)
}

cdf_ekwg <- function(cdf,par,x,...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  (1 - (1 - cdf(par = npar, x = x)^a)^b)^c

}

pdf_ekwg <- function(cdf, pdf, par, x, ...){
  a <- par[1]
  b <- par[2]
  c <- par[3]
  npar <- c(...)
  g = pdf(par = npar, x = x)
  G = cdf(par = npar, x = x)
  a * b * c * g * G^(a-1) * (1-G^a)^(b-1) * (1 - (1-G^a)^b)^(c-1)
}

loglikelihood <- function(cdf, pdf, par, x, ...){
  -sum(log(pdf_ekwg(cdf = cdf, pdf = pdf, par = par, x = x, ...)))
}

G = Gexp
g = gexp
set.seed(0)
data = sample_ekwg(QG = QGexp, n = 5e3, par = c(1,1,1), rate = 1.5)
starts = c(1,1,1)

start = Sys.time()
for(i in 1:20){
  result <- optim(par = starts, fn = loglikelihood, x = data, cdf = G,
                    pdf = g, method = "Nelder-Mead",rate = 1.5)
}
Sys.time() - start

我认为现在的比较比我第一次做的比较公平一点。 Julia 也考虑了我在 R 中采用的相同编程实践。例如,在 R 中,我不必考虑优化代码,我只是实现它。

输出 Julia

experiment(Gexp, gexp, data, par0, par1, n)
23.764565 seconds (289.59 M allocations: 4.316 GiB, 2.09% gc time)

输出 R

start = Sys.time()
for(i in 1:20){
  result <- optim(par = starts, fn = loglikelihood, x = data, cdf = G,
                    pdf = g, method = "Nelder-Mead",rate = 1.5)
}
Sys.time() - start
Time difference of 12.50351 secs

【讨论】:

  • 谢谢。我在几种设置下对其进行了调查,似乎 Optim.jl 对优化函数的调用比 R 中的优化多 2 倍(我已经计算了调用次数)。
  • 你不能只显示时间,你必须证明它们都收敛到局部最小值或至少达到了可比较的客观值。对不起,我不知道任何 R(我什至没有安装它),但是 2 倍的函数评估让我有点担心。可能是因为我们使用了不同的参数,所以我们遇到了不同的分支,但我有点担心我们正在做一个我们应该做的函数评估。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-04-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-22
  • 2011-03-08
  • 2013-02-23
相关资源
最近更新 更多