Autograd for weighted histogram

username: JonasHri
cooked: <p>Im trying to backpropagate through a weighted histogram inside my loss function.<br>
Currently i am manually computing the histogram as seen in “slow” but i would like to use histogrammdd as seen in “fast” since the computation time would be a lot shorter and the result is the same. The only problem is that histogramdd has no backward pass implemented.<br>
How would i implement the backward pass on histogramdd such that</p>
<pre><code class="lang-python">x = slow(theta, x, y)
x.mean().backward()
print(theta.grad)
</code></pre>
<p>and</p>
<pre><code class="lang-python">x = fast(theta, x, y)
x.mean().backward()
print(theta.grad)
</code></pre>
<p>have the same output?</p>
<p>This is my current code:</p>
<pre><code class="lang-python">batchsize = 512
bins = 30
neurons = 10

#dummydata
theta = torch.randn([neurons, batchsize], requires_grad=True)
x = torch.randint(0, bins, [neurons, batchsize]) # x coodinate per theta
y = torch.randint(0, bins, [neurons, batchsize]) # y coordinate per theta

def slow(theta, x, y):
    theta_xy = torch.zeros([neurons, bins, bins])
       
    for i in range(neurons):
        for t, x_bin, y_bin in zip(theta[i], x[i], y[i]):
            theta_xy[i, x_bin, y_bin] += t

    return theta_xy.double()

def fast(theta, x, y):
    neuron = torch.arange(neurons).repeat_interleave(batchsize).double()

    neuron_range = torch.arange(neurons + 1, dtype=float) - 0.1
    x_range = torch.arange(bins + 1, dtype=float) - 0.1
    y_range = torch.arange(bins + 1, dtype=float) - 0.1

    x = x.ravel()
    y = y.ravel()
    a = torch.vstack((neuron, x.clone(), y.clone()))

    return torch.histogramdd(a.T, bins=[neuron_range, x_range, y_range] , weight=theta.ravel().double())[0]

print(torch.allclose(slow(theta, x, y), fast(theta, x, y))) #same result

</code></pre>

username: KFrank
cooked: <p>Hi Jonas!</p>
<aside class="quote no-group" data-username="JonasHri" data-post="1" data-topic="151361" data-full="true">
<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/jonashri/48/49485_2.png" class="avatar"> JonasHri:</div>
<blockquote>
<p>Im trying to backpropagate through a weighted histogram inside my loss function.</p>
</blockquote>
</aside>
<p>I don’t really understand your use case, but as s a general rule, it doesn’t<br>
make sense to backpropagate through a histogram because the binning<br>
process is not usefully differentiable.</p>
<p>In the case of your weighted histogram, you can, technically, calculate<br>
gradients with respect to the weights as your <code>slow()</code> function demonstrates.<br>
But I can’t think of a use case for this that isn’t contrived.</p>
<aside class="quote no-group">
<blockquote>
<pre data-code-wrap="python"><code class="lang-python">x = slow(theta, x, y)
x.mean().backward()
print(theta.grad)
</code></pre>
<pre data-code-wrap="python"><code class="lang-python">bins = 30
neurons = 10

def slow(theta, x, y):
    theta_xy = torch.zeros([neurons, bins, bins])
    ...
    return theta_xy.double()
</code></pre>
</blockquote>
</aside>
<p><code>slow()</code> returns a tensor with shape <code>[neurons, bins, bins]</code>, which<br>
is <code>[10, 30, 30]</code> and therefore consists of 9000 elements.  When you<br>
calculate <code>x.mean()</code> you divide by this number of elements and doing so<br>
results in <code>theta.grad</code> having the same identical value for all of its elements,<br>
namely, <code>1 / 9000</code>.  So, sure, you’ve computed a gradient, but it isn’t very<br>
useful.</p>
<p>My assumption is that <code>histogramdd()</code> leaves <code>backward()</code> unimplemented<br>
because any resulting gradients wouldn’t be useful.</p>
<p>If you have a concrete use case in mind, could you describe it in more detail<br>
and explain which gradients you would use and how you would use them?</p>
<p>Best.</p>
<p>K. Frank</p>