【问题标题】:How to compile a function that computes the Hessian?如何编译计算 Hessian 的函数?
【发布时间】:2011-11-20 21:15:47
【问题描述】:

我希望了解如何编译计算对数似然的 Hessian 函数的函数,以便它可以有效地与不同的参数集一起使用。

这是一个例子。

假设我们有一个计算 logit 模型的对数似然函数,其中 y 是向量,x 是矩阵。 beta 是参数向量。

 pLike[y_, x_, beta_] :=
 Module[
  {xbeta, logDen},
  xbeta = x.beta;
  logDen = Log[1.0 + Exp[xbeta]];
  Total[y*xbeta - logDen]
  ]

给定以下数据,我们可以如下使用它

In[1]:= beta = {0.5, -1.0, 1.0};

In[2]:= xmat = 
  Table[Flatten[{1, 
     RandomVariate[NormalDistribution[0.0, 1.0], {2}]}], {500}];

In[3]:= xbeta = xmat.beta;

In[4]:= prob = Exp[xbeta]/(1.0 + Exp[xbeta]);

In[5]:= y = Map[RandomVariate[BernoulliDistribution[#]] &, prob] ;

In[6]:= Tally[y]

Out[6]= {{1, 313}, {0, 187}}

In[9]:= pLike[y, xmat, beta]

Out[9]= -272.721

我们可以这样写它的hessian

 hessian[y_, x_, z_] :=
  Module[{},
   D[pLike[y, x, z], {z, 2}]
  ]


In[10]:= z = {z1, z2, z3}

Out[10]= {z1, z2, z3}

In[11]:= AbsoluteTiming[hess = hessian[y, xmat, z];]

Out[11]= {0.1248040, Null}

In[12]:= AbsoluteTiming[
 Table[hess /. {z1 -> 0.0, z2 -> -0.5, z3 -> 0.8}, {100}];]

Out[12]= {14.3524600, Null}

出于效率原因,我可以将原始似然函数编译如下

pLikeC = Compile[{{y, _Real, 1}, {x, _Real, 2}, {beta, _Real, 1}},
   Module[
    {xbeta, logDen},
    xbeta = x.beta;
    logDen = Log[1.0 + Exp[xbeta]];
    Total[y*xbeta - logDen]
    ],
   CompilationTarget -> "C", Parallelization -> True,  
   RuntimeAttributes -> {Listable}
   ];

产生与 pLike 相同的答案

In[10]:= pLikeC[y, xmat, beta]

Out[10]= -272.721

鉴于我有兴趣多次评估它,我正在寻找一种简单的方法来获得类似的 hessian 函数的编译版本。

【问题讨论】:

  • 正如您在我的回答中看到的那样,Hessian 不依赖于 y,因此您可以大大简化(编译)函数。

标签: wolfram-mathematica hessian-matrix


【解决方案1】:

Leonid 已经打败了我,但我还是会发表我的想法只是为了一笑而过。

这里的主要问题是编译适用于数值函数,而 D 需要符号。因此,诀窍是首先定义 pLike 函数,其变量数量与您打算使用的特定矩阵大小所需的变量数量相同,例如,

pLike[{y1, y2}, {{x1, x2, x3}, {x12, x22, x32}}, {z1, z2, z3}]

黑森州:

D[pLike[{y1, y2}, {{x1, x2, x3}, {x12, x22, x32}}, {z1, z2, z3}], {{z1, z2, z3}, 2}]

这个函数应该是可编译的,因为它只依赖于数值。

为了概括各种向量,可以构建如下内容:

Block[{ny = 2, nx = 3, z1, z2, z3},
   hessian[
      Table[ToExpression["y" <> ToString[i] <> "_"], {i, ny}], 
      Table[ToExpression["xr" <> ToString[i] <> "c" <> ToString[j] <> "_"], 
           {i, ny}, {j, nx}], {z1_, z2_, z3_}
   ] =
   D[
     pLike[
        Table[ToExpression["y" <> ToString[i]], {i, ny}], 
        Table[ToExpression["xr" <> ToString[i] <> "c" <> ToString[j]], 
             {i, ny}, {j, nx}], {z1, z2, z3}
        ], 
     {{z1, z2, z3}, 2}
   ]
 ]

这当然可以很容易地推广到变量 nx 和 ny。


现在是Compile 部分。这是一段丑陋的代码,由上述代码和一个编译器组成,适用于可变 y 大小。我比我更喜欢 ruebenko 的代码。

ClearAll[hessianCompiled];
Block[{z1, z2, z3},
 hessianCompiled[yd_] :=
  (hessian[
     Table[ToExpression["y" <> ToString[i] <> "_"], {i, yd}], 
     Table[ToExpression["xr" <> ToString[i]<>"c"<>ToString[j] <>"_"],{i,yd},{j,3}],
     {z1_, z2_, z3_}
     ] =
    D[
     pLike[
      Table[ToExpression["y" <> ToString[i]], {i, yd}],
      Table[ToExpression["xr" <> ToString[i] <> "c" <> ToString[j]], {i,yd},{j,3}],
      {z1, z2, z3}
     ], {{z1, z2, z3}, 2}
    ];
   Compile[{{y, _Real, 1}, {x, _Real, 2}, {z, _Real, 1}}, 
    hessian[Table[y[[i]], {i, yd}], Table[x[[i, j]], {i, yd}, {j, 3}],
      Table[z[[i]], {i, 3}]]]// Evaluate] // Quiet
   )
 ]

hessianCompiled[500][y, xmat, beta] // Timing 

{1.497, {{-90.19295669, -15.80180276, 6.448357845}, 
        {-15.80180276, -80.41058154, -26.33982586},
        {6.448357845, -26.33982586, -72.92978931}}}

ruebenko's version (including my edits):

(cf = mkCHessian[500, 3]; cf[y, xmat, beta]) // Timing

{1.029, {{-90.19295669, -15.80180276, 6.448357845}, 
         {-15.80180276, -80.41058154, -26.33982586}, 
         {6.448357845, -26.33982586, -72.92978931}}}

请注意,这两个测试都包括编译时间。自行运行计算:

h = hessianCompiled[500];
Do[h[y, xmat, beta], {100}]; // Timing
Do[cf[y, xmat, beta], {100}]; // Timing

(* timing for 100 hessians: 

   ==> {0.063, Null}

   ==> {0.062, Null}
*)

【讨论】:

  • 我不清楚hessian是在哪里编译的。
  • 好吧,我想使用类似于 Leonid 所做的东西,但既然答案已经消失,我必须自己提供一些东西。不幸的是,这必须等到今晚。
【解决方案2】:

这是基于上一篇文章的一个想法:我们将输入构造为符号编译。

mkCHessian[{y_, ys_Integer}, {x_, xs_Integer}, {beta_, bs_Integer}] :=
  With[{
   args = MapThread[{#1, _Real, #2} &, {{y, x, beta}, {1, 2, 1}}],
   yi = Quiet[Part[y, #] & /@ Range[ys]],
   xi = Quiet[Table[Part[x, i, j], {i, xs}, {j, xs}]],
   betai = Quiet[Part[beta, #] & /@ Range[bs]]
   },
  Print[args];
  Print[yi];
  Print[xi];
  Print[betai];
  Compile[Evaluate[args], 
   Evaluate[D[pLike[yi, xi, betai], {betai, 2}]]]
  ]

然后生成编译后的函数。

cf = mkCHessian[{y, 3}, {x, 3}, {beta, 3}];

然后调用该编译函数

cf[y, xmat, beta]

请确认我没有犯错;在 de Vries 的帖子中,y 的长度为 2。我的长度为 3。我确定什么是正确的。当然,印刷品是为了说明......


更新
稍微改进了尺寸处理并本地化变量的版本:

ClearAll[mkCHessian];
mkCHessian[ys_Integer, bs_Integer] :=
 Module[
   {beta, x, y, args, xi, yi, betai},
   args = MapThread[{#1, _Real, #2} &, {{y, x, beta}, {1, 2, 1}}];
   yi = Quiet[Part[y, #] & /@ Range[ys]];
   xi = Quiet[Table[Part[x, i, j], {i, ys}, {j, bs}]];
   betai = Quiet[Part[beta, #] & /@ Range[bs]];
   Compile[Evaluate[args], Evaluate[D[pLike[yi, xi, betai], {betai, 2}]]]
 ]

现在,在 In[1] 到 In[5] 中使用 asim 的定义:

cf = mkCHessian[500, 3];
cf[y, xmat, beta]

(* ==> {{-8.852446923, -1.003365612, 1.66653381}, 
       {-1.003365612, -5.799363241, -1.277665283},
       {1.66653381, -1.277665283, -7.676551252}}  *)

由于 y 是一个随机向量,结果会有所不同。

【讨论】:

  • 我以 3 为例。我相信 y 在 asim 的情况下的长度为 500。
  • 非常优雅,顺便说一句。今晚我会玩一下,但它看起来应该能很好地完成它的工作。
  • @Sjoerd,谢谢。如果你喜欢它,请寻找以下内容: 如果我们要编译 500 个变量,这可能不是最好的方法,尤其是在需要编译为“C”时。另一个要看的东西是表达式优化器: h = D[{Cos[x y], Sin[x y]}, {{x, y}, 2}]; Compile[{x, y}, Evaluate[h]] 将其与 Experimental`OptimizeExpression[h] 进行比较,因此您可以在没有编译器的情况下使用表达式优化器。我从来不需要这个,但它可能会派上用场。
  • Oliver,我一直在看你的代码,我注意到两个问题:首先你让矩阵 xi 是正方形的,而它的尺寸是由 y 和 z 的尺寸决定的(所以没有必要通过 x 维度)。全局变量也有问题。如果 y,x 和 beta 已经定义(例如,通过运行 asim 的 In[1] 到 In[5]),那么您的代码将不再正常工作。我已经对变量进行了本地化,并使代码适用于 y 和 z 的任何维度,并将其放在您的代码下方。你能看一下,然后按你喜欢的方式定稿吗?
  • 这看起来不错。我需要完成这个,还是你能做到?如果你愿意,请继续。
猜你喜欢
  • 2021-12-08
  • 2011-01-29
  • 1970-01-01
  • 2015-06-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-17
  • 1970-01-01
相关资源
最近更新 更多