Confusion matrix breaks the computation graph for custom loss function

burakydev: <p>Hi,<br>
I want to write a custom loss function which calculates a value from the confusion matrix. But getting confusion matrix in custom loss function breaks the computation graph. Is there a possible way to write a loss function without breaking the computation graph?<br>
Thanks a lot.</p>

AlphaBetaGamma96: <p>Hi <a class="mention" href="/u/burakydev">@burakydev</a>,</p>
<p>Can you share what you’ve done so far?</p>

burakydev: <p>My custom loss function is like given below:</p>
<pre><code class="lang-auto">#my loss function
from softargmax import softargmax1d
from numpy import dtype, require
from torchmetrics import ConfusionMatrix

def calc_my_loss(pred_y, real_y):
    ONES = torch.tensor(1, dtype=torch.float32, requires_grad=True)
    TWOS = torch.tensor(5, dtype=torch.float32, requires_grad=True)

    conf_matrix = ConfusionMatrix(num_classes=3)

    #print(f'softpred_ygradfn: {softargmax1d(pred_y).grad_fn}')
    conf_matrix = conf_matrix(softargmax1d(pred_y).type(torch.int32), real_y)


    my_metrics = torch.tensor(conf_matrix[0][1].item() * ONES + conf_matrix[0][2].item() * TWOS + conf_matrix[1][0].item() * ONES + conf_matrix[1][2].item() * ONES + conf_matrix[2][0].item() * TWOS + conf_matrix[2][1].item() * ONES, dtype=torch.float32)

    return my_metrics
</code></pre>
<p>and softargmax1d function like given below. I have used this function to get gradients for confusion matrix.</p>
<pre><code class="lang-auto">def softargmax1d(input, beta=100):
    *_, n = input.shape
    input = nn.functional.softmax(beta * input, dim=-1)
    indices = torch.linspace(0, 1, n)
    result = torch.sum((n - 1) * input * indices, dim=-1)
    return result
</code></pre>
<p>pred_y variable has the gradients just before the getting confusion matrix. When I calculate the confusion matrix, computation graph breaks and gradients are lost.</p>
<p>This is a multiclass classification and I want to penalize some metrics in confusion matrix with specified weights. How can I do this?</p>
<p>Thank you so much.</p>

AlphaBetaGamma96: <aside class="quote no-group" data-username="burakydev" data-post="3" data-topic="164279">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/b/82dd89/48.png" class="avatar"> burakydev:</div>
<blockquote>
<p><code>torch.tensor(</code></p>
</blockquote>
</aside>
<p>When you call <code>torch.tensor(x)</code>, you’re re-wrapping your input data and hence breaking your graph. Secondly, when you call the <code>.item()</code> method you are taking a <code>Tensor</code> and converting it to a <code>float</code>, which PyTorch can no longer track and will break your graph.</p>
<p>You need to re-write <code>calc_my_loss</code> without the use of <code>torch.tensor</code> and <code>.item()</code></p>

burakydev: <p>I’ve just corrected my loss function according to your comments and I checked it, my_metrics grad_fn is &lt;AddBackward0 object at 0x2a738bfa0&gt;. I think my custom loss function is differentiable now and has gradients but I trained my model again and after loss.backward() method checked the gradients of layer_1 weights like given below (for training phase). The result is None. Where can be the problem about this, do you have any idea?</p>
<pre><code class="lang-auto">for e in tqdm(range(1, EPOCHS+1)):
    print(f'Epoch: {e}')
    # TRAINING
    train_epoch_loss = 0
    train_epoch_acc = 0
    model.train()

    for X_train_batch, y_train_batch in train_loader:
        optimizer.zero_grad()
        
        y_train_pred = model(X_train_batch)
        
        train_loss = calc_my_loss(y_train_pred, y_train_batch)
        train_acc = multi_acc(y_train_pred, y_train_batch)
        
        train_loss.backward()
        print(f'layer1GRADIENTS: {model.layer_1.weight.grad}')
        
        optimizer.step()
        
        train_epoch_loss += train_loss.item()
        train_epoch_acc += train_acc.item()     
</code></pre>

KFrank: <p>Hi Burak!</p>
<aside class="quote no-group quote-modified" data-username="burakydev" data-post="3" data-topic="164279" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/b/82dd89/48.png" class="avatar"> burakydev:</div>
<blockquote>
<pre><code class="lang-auto">    conf_matrix = conf_matrix(softargmax1d(pred_y).type(torch.int32), real_y)
</code></pre>
<p>…<br>
pred_y variable has the gradients just before the getting confusion matrix. When I calculate the confusion matrix, computation graph breaks and gradients are lost.</p>
</blockquote>
</aside>
<p>When you convert your <code>softargmax1d()</code> results to “hard,” integer labels,<br>
your “break the graph.”  This is because integers, being discrete, can’t be<br>
differentiated, so you can’t backpropagate through them.</p>
<p>You will need to develop some sort of “soft” confusion matrix function<br>
that works (in a meaningful way) with soft, real-valued “labels.”</p>
<p>A further note:  Your use of <code>beta = 100</code> in <code>softargmax1d()</code> makes<br>
for a <em>very hard</em> version of <code>softmax()</code>.  Even though it is technically<br>
differentiable, its derivative will be almost exactly zero almost everywhere,<br>
so, although you will be able to backpropagate through it, you’ll just get<br>
zeros for the gradients and your training won’t actually train.</p>
<p>Best.</p>
<p>K. Frank</p>

burakydev: <p>Hi <a class="mention" href="/u/kfrank">@KFrank</a>,<br>
Thanks for your reply.</p>
<p>Now I’ll try to develop a soft confusion matrix function but now I don’t know how to do this. I think my labels must be continuos for this and the confusion matrix must be calculated according to these ranges, am i right? Or do you know any tutorials or other resources about this topic?</p>
<p>Best regards.</p>
<p>edit:</p>
<aside class="quote no-group" data-username="KFrank" data-post="6" data-topic="164279">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/k/ecb155/48.png" class="avatar"> KFrank:</div>
<blockquote>
<p>You will need to develop some sort of “soft” confusion matrix function<br>
that works (in a meaningful way) with soft, real-valued “labels.”</p>
</blockquote>
</aside>
<p>and also is it a problem to have integer labels like 1, 2, 3 in my condition for <code>y_train_batch</code> values?</p>

KFrank: <p>Hi Burak!</p>
<aside class="quote no-group" data-username="burakydev" data-post="7" data-topic="164279" data-full="true">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/b/82dd89/48.png" class="avatar"> burakydev:</div>
<blockquote>
<p>Now I’ll try to develop a soft confusion matrix function but now I don’t know how to do this.</p>
</blockquote>
</aside>
<p>I don’t understand your use case or how you want your confusion matrix<br>
to feed into your custom loss function.</p>
<p>But one approach for a “soft” confusion matrix would be the following:</p>
<p>Let <code>pred_i</code> be the predicted <em>probability</em> for your <code>i-th</code> class (so, typically<br>
the output of a <code>softmax()</code>) and <code>targ_i</code> be the ground-truth <em>probability</em><br>
of your <code>i-th</code> class – that is, your “soft” labels.</p>
<p>(It is okay if your labels are “hard,” that is, that <code>targ_i</code> is <code>1.0</code> for exactly<br>
one value of <code>i</code> and <code>0.0</code> for all the rest.)</p>
<p>The <code>i,j</code> element of your “soft” confusion matrix could then be chosen to<br>
be the sum over samples of <code>targ_i * pred_j</code>.  Note that as your predictions<br>
(and labels) become hard – that is, exactly one class has probability one<br>
and all the others zero – your soft confusion matrix will become the<br>
conventional confusion matrix.</p>
<p>Because the predictions used in your soft confusion matrix are now<br>
continuous probabilities, you will not have any issue backpropagating<br>
through the confusion matrix.</p>
<p>(But note my previous comment that if you harden your <code>softmax()</code> with<br>
a <code>beta</code> as large as <code>100</code>, you won’t be able to <em>usefully</em> backpropagate<br>
through that <code>softmax()</code>.)</p>
<p>Best.</p>
<p>K. Frank</p>

burakydev: <p>Hi <a class="mention" href="/u/kfrank">@KFrank</a>,<br>
I have multiclass (number_of_classes=3) problem. In my custom loss function, i want to penalize the false predictions according to the confusion matrix. For example, if the predicted class is 1 but actual class is 2, i will multiply the number of wrong predictions with 1. And if the predicted class is 1 but actual class is 3 i will multiply the number of false predictions with 5 and so on. After all, i will sum the weighted penalty scores.<br>
In my custom loss function, my aim is minimizing this weighted penalty score.</p>
<aside class="quote no-group" data-username="KFrank" data-post="8" data-topic="164279">
<div class="title">
<div class="quote-controls"></div>
<img loading="lazy" alt="" width="24" height="24" src="https://discuss.pytorch.org/letter_avatar_proxy/v4/letter/k/ecb155/48.png" class="avatar"> KFrank:</div>
<blockquote>
<p>The <code>i,j</code> element of your “soft” confusion matrix could then be chosen to<br>
be the sum over samples of <code>targ_i * pred_j</code>. Note that as your predictions<br>
(and labels) become hard – that is, exactly one class has probability one<br>
and all the others zero – your soft confusion matrix will become the<br>
conventional confusion matrix.</p>
</blockquote>
</aside>
<p>Is this approach and this soft confusion matrix convenient for my custom loss function? I will try to implement this soft confusion matrix.</p>
<p>Thank you so much.<br>
Best regards.</p>