How to calculate 2nd derivative of a likelihood function

Wei_Deng: <p>I want to calculate the diagonal of 2nd derivative of a function (likelihood function for example), but I didn’t find any corresponding documents supporting that?</p>
<p>Can anyone give me an example?</p>
<p>I really appreciate that.</p>
<p>Thanks a lot.</p>

Wei_Deng: <p>I know there are some basic tutorials like</p>
<aside class="onebox whitelistedgeneric">
  <header class="source">
      <img src="https://assets-cdn.github.com/favicon.ico" class="site-icon" width="16" height="16">
      <a href="https://github.com/pytorch/pytorch/releases/tag/v0.2.0" target="_blank" rel="nofollow noopener">GitHub</a>
  </header>
  <article class="onebox-body">
    <img src="https://avatars2.githubusercontent.com/u/21003710?s=400&amp;v=4" class="thumbnail onebox-avatar" width="60" height="60">

<h3><a href="https://github.com/pytorch/pytorch/releases/tag/v0.2.0" target="_blank" rel="nofollow noopener">pytorch/pytorch</a></h3>

<p>pytorch - Tensors and Dynamic neural networks in Python  with strong GPU acceleration</p>


  </article>
  <div class="onebox-metadata">
    
    
  </div>
  <div style="clear: both"></div>
</aside>

<p>However, I am afraid they still can’t solve my problem.</p>

Wei_Deng: <p>For example, I used his blog to try to get the 2nd derivative [<a href="https://discuss.pytorch.org/t/second-order-derivatives-and-inplace-gradient-zeroing/14211/3">Second order derivatives and inplace gradient "zeroing"</a> ], but it turns out that the grd.grad information is None. Can anyone give me some suggestions?</p>
<p>import torch<br>
from torch import Tensor<br>
from torch.autograd import Variable<br>
from torch.autograd import grad<br>
from torch import nn</p>
<p># some toy data<br>
x = Variable(Tensor([4., 2.]), requires_grad=False)<br>
y = Variable(Tensor([1.]), requires_grad=False)</p>
<p># linear model and squared difference loss<br>
model = nn.Linear(2, 1)<br>
loss = torch.sum((y - model(x))**2)</p>
<p>optimizer = torch.optim.Adam(model.parameters(), lr=1e-2)</p>
<p># instead of using loss.backward(), use torch.autograd.grad() to compute gradients<br>
loss_grads = grad(loss, model.parameters(), create_graph=True)</p>
<p>gn2 = sum([grd.norm()**2 for grd in loss_grads]) # 2nd derive<br>
print(‘loss %f grad norm %f’ % (loss.data, gn2.data))<br>
model.zero_grad()<br>
gn2.backward()<br>
optimizer.step()</p>
<p>for grd in loss_grads:<br>
print grd.grad</p>
<p>The answer is None.</p>

dpernes: <p>Hi <a class="mention" href="/u/wei_deng">@Wei_Deng</a>,</p>
<p>You are doing everything right, except that you’re not looking for the gradients in the right place.</p>
<p>After a backward pass, only the gradients of the loss with respect to model parameters will be kept. Thus, if your model, has some parameter theta, then you’ll find the gradient of the loss w.r.t. theta in the variable theta.grad.</p>
<p>Therefore, if you want to print gradients, change your final loop to something like:</p>
<pre><code class="lang-auto">for name, param in model.named_parameters():
  print(name, param.grad)
</code></pre>

Wei_Deng: <p>Thanks you <a class="mention" href="/u/dpernes">@dpernes</a> for you inspiring comments, that works for that problem.</p>
<p>But I found this still can not help me find the 2nd derivative of a likelihood function for each weight, do you know how to achieve that?</p>
<p>Or do you know if we can do it based on the current pytorch version?</p>

dpernes: <p>What do you mean exactly by second derivative? Do you want to find the full Hessian matrix or only the second order derivatives with respect to each parameter individually (i.e. the diagonal of the Hessian matrix)?</p>
<p>Btw, what is your final purpose?</p>

Wei_Deng: <p><a class="mention" href="/u/dpernes">@dpernes</a>, my final goal is to get the diagonal of the Hessian matrix to estimate the empirical Fisher information matrix.</p>

dpernes: <p>That’s a bit tricky, I think. But it’s doable, of course.</p>
<p>It is tricky because PyTorch only allows you to compute derivatives of scalars with respect to multidimensional Tensors. Thus, you have to iterate through every single scalar parameter in your model (i.e., every entry in every parameter matrix) and compute the derivative of its derivative with respect to itself.</p>
<p>Honestly, I don’t know if there is an easy way to iterate through every entry in an n-D Tensor for arbitrary n (in numpy, there is nditer for this job). If you can find one, then the task is easy.</p>
<p>Let me show some pseudo-code (which is “pseudo” because the function <code>iterator_over_tensor</code> is unspecified).</p>
<pre><code class="lang-auto">import torch
from torch import Tensor
from torch.autograd import Variable
from torch.autograd import grad
from torch import nn

# some toy data
x = Variable(Tensor([4., 2.]), requires_grad=False)
y = Variable(Tensor([1.]), requires_grad=False)

# linear model and squared difference loss
model = nn.Linear(2, 1)
loss = torch.sum((y - model(x))**2)

# instead of using loss.backward(), use torch.autograd.grad() to compute gradients
loss_grads = grad(loss, model.parameters(), create_graph=True)

# compute the second order derivative w.r.t. each parameter
d2loss = []
for param, grd in zip(model.parameters(), loss_grads):
  for idx in iterator_over_tensor(param)
    drv = grad(grd[idx], param[idx], create_graph=True)
    d2loss.append(drv)
    print(param, drv)
</code></pre>

Wei_Deng: <p>Thank you so much for helping me find a possible way of doing this. Hope someday PyTorch people could find develop some new version to handle this.</p>

nima_rafiee: <aside class="quote no-group" data-username="Wei_Deng" data-post="7" data-topic="15085">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/wei_deng/48/3370_2.png" class="avatar"> Wei_Deng:</div>
<blockquote>
<p>diagonal of the Hessian matrix</p>
</blockquote>
</aside>
<p>Simply try backward() function two times and you get the diagonal of Hessian matrix.</p>
<pre><code class="lang-auto">x = torch.ones(2, requires_grad=True, )
y = torch.pow(x,3)
out = torch.mean(y)
print(y)
print(out)
out.backward( retain_graph=True, create_graph=True)
print(x.grad)
print(out)
out.backward()
x.grad
</code></pre>

albanD: <p>Hi,</p>
<p>No this won’t give you second derivative.<br>
See the answer from <a class="mention" href="/u/dpernes">@dpernes</a> above for the right way to do this.</p>

Peter_Ham: <p>I want to do similar things, basically like in meta learning, you have a function y = f_{\theta}(x,t), and you update your variable t such that f(x,t) approximates the target y_target. The mathematics is like this: t’ = t-\nabla_t |f(x,t)-y_target|^2, then you update \theta using \theta = \theta-\nabla_{\theta}|f(x,t’)-y_target|^2. here you need to first update your t, and then once t is updated, you want t to still carry the gradient and then you update the theta variables. I don’t think the proposed solution can do this? But in tensorflow this is very easy and you can update t for multiple time, which means it can not only take the secondary derivative but also the third derivative and more. Is pytorch able to achieve this?</p>

albanD: <p>Hi,</p>
<p>Yes you can take any degree of derivatives by calling backward() or autograd.grad() on the output of such function.</p>
<p>That being said, in your example, I don’t see any second derivative. You update t, then evaluate f with this new t, then update theta right?</p>

Peter_Ham: <p>you update t, but the t’ carries the gradient, and then you update f with the new t, it’s like the t’ should bring the computation you’ve done previously. I think autograd.grad() is not a good approach to do this, as you have to manually call this for each variable involved, which is tedious?</p>

albanD: <p>Ok, then you can just do:</p>
<pre><code class="lang-auto"># This is pseudo code, you can have a list of params from `model.parameters()` for example
# Also the autograd.grad return tuples so some massaging and for loops for gradients update may be needed
t = torch.rand(xxx, requires_grad=True)
theta = torch.rand(xxx, requires_grad=True)

out = f(t, theta)
loss = F.mse(out, target)

gradt = autograd.grad(loss, t, retain_graph=True, create_graph=True)

new_t = t - gradt

new_out = f(new_t, theta)
new_loss = F.mse(new_out, target)

gradtheta = autograd.grad(new_loss, theta)

new_theta = theta - gradtheta
</code></pre>

D-X-Y: <p>Hi alban, do you know some efficient way to compute the second order derivate of f(x;w) instead of the for loop style?</p>

albanD: <p>Hi,</p>
<p>There is no for-loop here. What do you mean?</p>

D-X-Y: <aside class="quote no-group" data-username="dpernes" data-post="8" data-topic="15085">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/user_avatar/discuss.pytorch.org/dpernes/48/16250_2.png" class="avatar"> dpernes:</div>
<blockquote>
<p>create_graph=True)</p>
</blockquote>
</aside>
<p>Hi albanD, I hope to compute Hessian matrix, and I write the following codes:</p>
<pre><code class="lang-auto">class Net(torch.nn.Module):
  def __init__(self, iS):
    super(Net, self).__init__()
    self.layer = torch.nn.Linear(iS, 1)
  def forward(self, inputs):
    outputs = self.layer(inputs)
    return outputs.mean()
net = Net(10)
inputs = torch.rand(256, 10)
loss = net(inputs)
first_order_grads = torch.autograd.grad(loss, net.parameters(), retain_graph=True, create_graph=True)
# strange .... first_order_grads[0].requires_grad = False, should it be True?
second_order_grads = torch.autograd.grad(first_order_grads, net.parameters())
# the above line does not work
</code></pre>
<p>Do you have any suggestions? Thanks!</p>

albanD: <p>If requires_grad is False, it is because the gradient is independent of the parameters. And so the second order derivative would be 0.</p>

D-X-Y: <p>I see, thanks. I have revised the codes as following:</p>
<pre><code class="lang-auto">def test_auto_grad():
  class Net(torch.nn.Module):
    def __init__(self, iS):
      super(Net, self).__init__()
      self.layer = torch.nn.Linear(iS, 1)
    def forward(self, inputs):
      outputs = self.layer(inputs)
      outputs = torch.exp(outputs)
      return outputs.mean()
  net = Net(10)
  inputs = torch.rand(256, 10)
  loss = net(inputs)
  first_order_grads = torch.autograd.grad(loss, net.parameters(), retain_graph=True, create_graph=True)
  first_order_grads = torch.cat([x.view(-1) for x in first_order_grads])
  second_order_grads = []
  for grads in  first_order_grads:
    s_grads = torch.autograd.grad(grads, net.parameters())
    second_order_grads.append( s_grads )
</code></pre>
<p>As you can see, if I want to obtain the Hessian matrix, I have to loop for every grad in the first_order_grads. Is there an efficient way to calculate the second_order_grads?</p>