Using the univariate spline from scipy inside pytorch

Dan3: <p>Hi,</p>
<p>I want to be able to use the univariate spline class from scipy with pytorch but I am not able to do this while maintaining autograd. I have been looking at <a href="https://pytorch.org/tutorials/advanced/numpy_extensions_tutorial.html" class="inline-onebox" rel="noopener nofollow ugc">Creating Extensions Using numpy and scipy — PyTorch Tutorials 1.8.0 documentation</a> to try and get the scipy code working in pytorch but I am unsure that the correct gradient is being returned. This is the code I have so far:</p>
<pre><code class="lang-auto">from torch.autograd import Function

class SPLFunction(Function):
   
    @staticmethod
    def forward(ctx, bin, x):
        ctx.save_for_backward(bin, x)

        x = x.detach().numpy()
        bin = bin.detach().numpy()
        
        bin_steps = np.arange(2, 22+20/63, 20/63)[:-1]
        spline_curve = UnivariateSpline(bin_steps, bin, ext=3)
        spline_curve.set_smoothing_factor(0.5)
        
        result = spline_curve(x)
        return torch.as_tensor(result)

    @staticmethod
    def backward(ctx, grad_output):
        bin, x = ctx.saved_tensors

        bin_steps = np.arange(2, 22+20/63, 20/63)[:-1]
        spline_curve = UnivariateSpline(bin_steps, bin, ext=3)
        spline_curve.set_smoothing_factor(0.5)
        
        df = spline_curve.derivative(x)
        df = torch.from_numpy(df*grad_output)

        return df

def apply_spline(bin, x):
    return SPLFunction.apply(bin, x)
</code></pre>
<p>Thank you for your help</p>

albanD: <p>Hi,</p>
<p>That looks mostly good.<br>
A few updates: the backward is not differentiable (as it used numpy) so you can mark it as such. The backward should return as many values as the forward’s inputs. If an input is not differentiable, it should return None for it. You can use the gradcheck tool we have to check gradient via finite difference (if your function is smooth and differentiable).<br>
That would look like:</p>
<pre><code class="lang-python">import torch

from torch.autograd import Function, gradcheck
from torch.autograd.function import once_differentiable

class SPLFunction(Function):
   
    @staticmethod
    def forward(ctx, bin, x):
        ctx.save_for_backward(bin, x)

        x = x.detach().numpy()
        bin = bin.detach().numpy()
        
        bin_steps = np.arange(2, 22+20/63, 20/63)[:-1]
        spline_curve = UnivariateSpline(bin_steps, bin, ext=3)
        spline_curve.set_smoothing_factor(0.5)
        
        result = spline_curve(x)
        return torch.as_tensor(result)

    @once_differentiable
    @staticmethod
    def backward(ctx, grad_output):
        bin, x = ctx.saved_tensors

        bin_steps = np.arange(2, 22+20/63, 20/63)[:-1]
        spline_curve = UnivariateSpline(bin_steps, bin, ext=3)
        spline_curve.set_smoothing_factor(0.5)
        
        df = spline_curve.derivative(x)
        df = torch.from_numpy(df*grad_output)

        return None, df # Bin is not differentiable?

def apply_spline(bin, x):
    return SPLFunction.apply(bin, x)

gradcheck(apply_spline, (example_bin, example_x))
</code></pre>

Dan3: <p>Thank you for the help,</p>
<p>I was thinking that the differentiable part would come from differentiating the spline curve and so I am a bit confused why you would return none</p>

albanD: <p>Ho, I added one <code>None</code> “randomly” just to make sure to return two things in the backward.<br>
But if the forward  takes <code>bin</code> and <code>x</code> as input, then the backward should return the gradients wrt bin and gradients wrt to x.</p>

googlebot: <aside class="quote no-group" data-username="albanD" data-post="2" data-topic="114451">
<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/alband/48/215_2.png" class="avatar"> albanD:</div>
<blockquote>
<p>A few updates: the backward is not differentiable (as it used numpy) so you can mark it as such.</p>
</blockquote>
</aside>
<p>are there any benefits from doing that, seems like this only detects unsupported double backprop?</p>

albanD: <p>The only benefit is that you are sure that you will get a nice error if you try to use double backward later. And you won’t get silently wrong result or weird error in the second backward.</p>

googlebot: <p>so, it is more appropriate for public / library code then; however I’d point out that <span class="mention">@once_differentiable</span> is undocumented, hence seriously underused…</p>

albanD: <p>Yes, there is a plan to rework the custom Function doc to include these tips.</p>