Macros 在Julia中使用@generated macro进行渐变的符号

Macros 在Julia中使用@generated macro进行渐变的符号,macros,julia,metaprogramming,Macros,Julia,Metaprogramming,出于性能方面的考虑,我需要梯度和Hessian函数,它们的执行速度与用户定义的函数一样快(例如,ForwardDiff库使我的代码速度大大降低)。然后,我尝试使用@生成的宏进行元编程,并使用一个简单的函数进行测试 using Calculus hand_defined_derivative(x) = 2x - sin(x) symbolic_primal = :( x^2 + cos(x) ) symbolic_derivative = differentiate(symbolic_prima

出于性能方面的考虑,我需要梯度和Hessian函数,它们的执行速度与用户定义的函数一样快(例如,ForwardDiff库使我的代码速度大大降低)。然后,我尝试使用
@生成的
宏进行元编程,并使用一个简单的函数进行测试

using Calculus
hand_defined_derivative(x) = 2x - sin(x)

symbolic_primal = :( x^2 + cos(x) )
symbolic_derivative = differentiate(symbolic_primal,:x)
@generated functional_derivative(x) = symbolic_derivative
这正是我想要的:

rand_x = rand(10000);
exact_values = hand_defined_derivative.(rand_x)
test_values = functional_derivative.(rand_x)

isequal(exact_values,test_values)        # >> true

@btime hand_defined_derivative.(rand_x); # >> 73.358 μs (5 allocations: 78.27 KiB)
@btime functional_derivative.(rand_x);   # >> 73.456 μs (5 allocations: 78.27 KiB)
我现在需要将其推广到具有更多参数的函数。显而易见的推断是:

symbolic_primal = :( x^2 + cos(x) + y^2  )
symbolic_gradient = differentiate(symbolic_primal,[:x,:y])
symbolic_gradient
的行为与预期的一样(与一维情况相同),但@generated宏不会像我认为的那样响应多个维度:

@generated functional_gradient(x,y) = symbolic_gradient
functional_gradient(1.0,1.0)

>> 2-element Array{Any,1}:
    :(2 * 1 * x ^ (2 - 1) + 1 * -(sin(x)))
    :(2 * 1 * y ^ (2 - 1))
也就是说,它不会将符号转换为生成的函数。有没有简单的方法来解决这个问题


旁白:我知道我可以将每个参数的导数定义为一维函数,并将它们捆绑成一个梯度(这就是我目前正在做的),但我确信一定有更好的方法。

首先,我认为你不需要在这里使用
@generated
:这是一个“简单”的代码生成案例,我认为使用
@eval
更简单,也不那么令人惊讶

因此1D案例可以改写如下:

julia>使用微积分
julia>symbolic_primal=:(x^2+cos(x))
:(x^2+cos(x))
julia>symbolic_导数=微分(symbolic_primal,:x)
:(2*1*x^(2-1)+1*-(sin(x)))
julia>手定义的导数(x)=2x-sin(x)
手动定义的导数(带1方法的通用函数)
#让我们首先检查要评估的代码
#(`quote`返回传递给它的未赋值表达式)
朱莉娅>引用
函数导数(x)=$符号导数
结束
引用
函数导数(x)=开始
2*1*x^(2-1)+1*-(sin(x))
结束
结束
#看起来不错=>让我们现在评估一下
#(因为“@eval”是宏,所以它的参数将不计算
#=>此处无“报价单”)
朱莉娅>@eval开始
函数导数(x)=$符号导数
结束
函数导数(带1方法的一般函数)
julia>rand_x=rand(10000);
julia>精确值=手动定义的导数(rand x);
julia>测试值=函数导数(rand x);
julia>@assert isequal(精确值、测试值)
#使用`基准测试工具'时,不要忘记插入数组参数`
julia>使用基准测试工具
朱莉娅>@b时间手定义的导数($rand\u x);
104.259μs(2次分配:78.20千磅)
朱莉娅>@b时间函数导数($rand\u x);
104.537μs(2次分配:78.20千磅)
现在,2D案例不起作用,因为
differention
的输出是一个表达式数组(每个组件一个表达式),您需要将其转换为一个表达式,以构建组件数组(或元组,以提高性能)。这是下例中的
符号\u梯度\u expr

julia>symbolic_primal=:(x^2+cos(x)+y^2)
:(x^2+cos(x)+y^2)
julia>手动定义的梯度(x,y)=(2x-sin(x,2y)
手动定义的梯度(带1方法的通用函数)
#这是一个表达式向量
julia>symbolic_gradient=微分(symbolic_primal,[:x,:y])
二元数组{Any,1}:
:(2*1*x^(2-1)+1*-(sin(x)))
:(2*1*y^(2-1))
#将渐变的所有组件的表达式包装到单个表达式中
#生成它们的元组:
julia>symbolic\u gradient\u expr=expr(:元组,symbolic\u gradient…)
:((2*1*x^(2-1)+1*-(sin(x)),2*1*y^(2-1)))
julia>@eval functional_gradient(x,y)=$symbolic_gradient_expr
函数梯度(带1方法的通用函数)
与1D的情况一样,这与手写版本的性能相同:

julia>rand_x=rand(10000);兰特=兰特(10000);
julia>精确值=手动定义的梯度;
julia>测试值=函数梯度;
julia>@assert isequal(精确值、测试值)
朱莉娅>@b时间指针定义的梯度。($rand\u x,$rand\u y);
113.182μs(2次分配:156.33千磅)
朱莉娅>@b时间函数梯度($rand\u x,$rand\u y);
112.283μs(2次分配:156.33千磅)

我也打算这么说。可能值得补充的是,
symbolic\u anti-derivative
在这里非常混乱,它是原始函数,如果需要术语,也可以称为“primal”。谢谢,我完全同意关于
symbolic\u anti-derivative
命名的混乱。我编辑后使用了
primal
,因为你明确地暗示了我在寻找什么。谢谢我也改成了
symbolic\u primal
来匹配你的答案,因为这显然是一个非常糟糕的名字。Julia还不太习惯的人提出的最后一个问题是:
quote
d段代码在您的第一个块中的用途是什么?你说你在检查什么,但我不明白。。。end或多或少是
:(…)
,它生成一个
Expr
,通常(如此处)插入其他表达式。您可以编写
eval(quote…
,将表达式传递给函数,而不是
@eval begin…
(为您引用块)。