【问题标题】:How to use metaprogramming with function args?如何使用带有函数 args 的元编程?
【发布时间】:2017-03-09 08:15:52
【问题描述】:

这是我与 Julia 一起学习和实验的第二天。尽管我仔细阅读了有关元编程的文档(但可能不够仔细)和几个类似的线程。我仍然不知道如何在函数中使用它。 我试图使以下功能更灵活地模拟一些数据:

using Distributions
function gendata(N,NLATENT,NITEMS)
  latent = repeat(rand(Normal(6,2),N,NLATENT), inner=(1,NITEMS))
  errors = rand(Normal(0,1),N,NLATENT*NITEMS)
  x = latent+errors
end

通过这样做:

using Distributions
function gendata(N,NLATENT,NITEMS,LATENT_DIST="Normal(0,1)",ERRORS_DIST="Normal(0,1)")
  to_eval_latent = parse("latent = repeat(rand($LATENT_DIST,N,NLATENT), inner=(1,NITEMS))")
  eval(to_eval_latent)
  to_eval_errors = parse("error = rand($ERRORS_DIST,N,NLATENT*NITEMS)")
  eval(to_eval_errors)
  x = latent+errors
end

但是由于 eval 在本地范围内不起作用,所以它不起作用。我能做些什么来解决这个问题?

也是原来的功能,好像没那么快,是不是我在性能方面犯了什么大错?

我真的很欣赏任何建议。 提前致谢。

【问题讨论】:

  • 将分布作为参数传递有什么问题?使用eval 似乎过于复杂。
  • @phg 看起来并不过分复杂,确实如此。代码最初来自 R 脚本,这或多或少是必要的。你知道为什么重复这么慢吗?它是一个 BLAS,所以它应该像闪电一样快,它是功能中最慢的部分。

标签: scope metaprogramming julia


【解决方案1】:

那里不需要使用 eval,您可以通过将分布类型作为关键字 args(或具有默认值的命名 args)传递来保持相同的灵活性。解析和评估“字符串类型”参数通常会破坏优化,应该避免。

function gendata(N,NLATENT,NITEMS;  LATENT_DIST=Normal(0,1),ERRORS_DIST=Normal(0,1))
         latent = repeat(rand(LATENT_DIST,N,NLATENT), inner=(1,NITEMS))
         errors = rand(ERRORS_DIST,N,NLATENT*NITEMS)
         x = latent+errors
end


julia> gendata(10,2,3, LATENT_DIST=Pareto(.3))
...


julia> gendata(10,2,3, ERRORS_DIST=Gamma(.6))
...

等等

【讨论】:

    【解决方案2】:

    您不应该在这里使用eval(速度较慢,不会产生类型信息,会干扰编译等),但如果您想了解哪里出了问题,请按照以下方法操作它:

    要么将其与其余代码分开:

    function gendata(N,NLATENT,NITEMS,LDIST_EX="Normal(0,1)",EDIST_EX="Normal(0,1)")
    
      # Eval your expressions separately
    
      LATENT_DIST = eval(parse(LDIST_EX))
      ERRORS_DIST = eval(parse(EDIST_EX))
    
      # Do your thing
    
      latent = repeat(rand(LATENT_DIST,N,NLATENT), inner=(1,NITEMS))
      errors = rand(ERROR_DIST,N,NLATENT*NITEMS)
      x = latent+errors      
    end
    

    或者使用带引号的表达式的插值:

    function gendata(N,NLATENT,NITEMS,LDIST_EX="Normal(0,1)",EDIST_EX="Normal(0,1)")
    
      # Obtain expression objects
    
      LATENT_DIST = parse(LDIST_EX)
      ERRORS_DIST = parse(EDIST_EX)
    
      # Eval but interpolate in everything that's local to the function
      # And you can't introduce local variables with eval so keep them
      # out of it.
    
      latent = eval( :(repeat(rand($LATENT_DIST,$N,$NLATENT), inner=(1,$NITEMS))) )
      errors = eval( :(rand($ERRORS_DIST, $N, $NLATENT*$NITEMS)) )
      x = latent+errors
    end
    

    您还可以使用带有 let 块的单个 eval 来引入自包含范围:

    function gendata(N,NLATENT,NITEMS,LDIST_EX="Normal(0,1)",EDIST_EX="Normal(0,1)")
    
      LATENT_DIST = parse(LDIST_EX)
      ERRORS_DIST = parse(EDIST_EX)
      x = 
      @eval let
        latent = repeat(rand($LATENT_DIST,$N,$NLATENT), inner=(1,$NITEMS))
        errors = (rand($ERRORS_DIST, $N, $NLATENT*$NITEMS))
        latent+errors
      end
    end
    

    ((@eval x) == eval(:(x)))

    嗯,希望你能更好地理解eval 的事情。第二天,我的意思是,你应该尝试一下;)

    【讨论】:

    • 非常感谢。对更好地理解“eval”很有帮助。但是我选择了以前答案中的解决方案。这是我的一些速度优化的解决方案:使用分布函数 gendata(N,NLATENT,NITEMS;LATENT_DIST=Normal(0,1),ERRORS_DIST=Normal(0,1)) latent = repmat(rand(LATENT_DIST,N,NLATENT ), 1,NITEMS) for i in eachindex(latent) latent[i] = latent[i] + rand(ERRORS_DIST) end latent end
    • 但是有些东西我真的不明白,在尝试优化性能时,结果发现使用 repmat 而不是重复该函数快十倍,这是怎么回事?两者都是 BLAS,来自文档中的线性代数部分。
    • 感谢您的接受,但如果其他解决方案更好,您应该接受它,这更多是为了补充。 (虽然我是直接解决问题的人,但我认为在这种情况下提出替代方案更好)
    • 我认为前段时间有一个关于重复速度慢的讨论,并且有一个问题,最近有一个 PR 解决了这个问题(22 天前)。在最新的master上再试一次,看看有什么不同,否则你可能应该问另一个问题。
    • 参见issue #20495 以及其中引用的链接。
    猜你喜欢
    • 2016-09-10
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-26
    • 1970-01-01
    相关资源
    最近更新 更多