Create a f_score loss function

Sidoroff_Michael: <p>Hi, I’m trying to implement a <strong>f_score</strong> loss function. From [<a href="https://gist.github.com/SuperShinyEyes/dcc68a08ff8b615442e3bc6a9b55a354" rel="noopener nofollow ugc">https://gist.github.com/SuperShinyEyes/dcc68a08ff8b615442e3bc6a9b55a354</a>] GitHub page I took the implementation of the required function, and tweaked it a bit to suit my needs. But when i ran the code, I get that the gradients are None, even though I’ve set the <code>requires_grad</code> to True in the first variable, as mentioned in <a href="https://discuss.pytorch.org/t/runtimeerror-element-0-of-variables-does-not-require-grad-and-does-not-have-a-grad-fn/11074">https://discuss.pytorch.org/t/runtimeerror-element-0-of-variables-does-not-require-grad-and-does-not-have-a-grad-fn/11074</a>.<br>
Some specifics -</p>
<ol>
<li>I need the output from the network to be a binary vector, as it represents activities.</li>
<li>The quantity im measuring is prediction of actions, hence the need for the f score loss.</li>
</ol>
<p>Here is a basic example:</p>
<pre><code class="lang-auto">import torch

class FScoreLoss(torch.nn.Module):
    def __init__(self, eps=1e-7):
        super().__init__()
        self.eps = eps
        
    def forward(self, y_true, y_pred, beta, grad=True):
        print(f'y_true = {y_true}')
        print(f'y_pred = {y_pred}')

        tp = (y_true * y_pred).sum().to(torch.float32)
        fn = ((1 - y_true) * y_pred).sum().to(torch.float32)
        fp = (y_true * (1 - y_pred)).sum().to(torch.float32)

        print(f'tp = {tp}, fn = {fn}, fp = {fp}')

        precision = tp / (tp + fp + self.eps)
        recall = tp / (tp + fn + self.eps)

        f_score_loss = (1 + beta ** 2) * (precision * recall) / ((beta**2)*precision + recall + self.eps)

        print(f'precision = {precision}, recall = {recall}, f_score = {f_score_loss}')
    
        return f_score_loss
f_score_loss_func = FScoreLoss()

model = torch.nn.Linear(10, 10)

x = torch.randn(1, 10).requires_grad_(True)

y_true = (torch.randn(1, 10)[0] &gt; .5).float()

y_pred = (model(x)&gt;.5).float().requires_grad_(True)

loss = f_score_loss_func(y_true, y_pred, beta=2., grad=True)
loss.backward()

print(f'y_pred.grad = {y_pred.grad}')

print(f'model.weight.grad = {model.weight.grad}')
</code></pre>
<p>As can be seen from the output (i.e. last two lines), :</p>
<pre><code class="lang-auto">y_pred.grad = tensor([[-0.4082,  0.3061, -0.4082, -0.4082, -0.4082, -0.4082,  0.3061, -0.4082,
          0.3061, -0.4082]])
model.weight.grad = None
</code></pre>
<p>the gradients are being calculated, but not updated in the model.</p>
<p>Appreciate any help. Thanks</p>

suraj.pt: <p>Hi,<br>
The result of <code>(model(x)&gt;.5).float()</code> is a binary tensor. Gradients cannot exist for non-continuous values. You might want to use <code>tanh()</code> instead (see this thread for a similar issue <a href="https://discuss.pytorch.org/t/step-activation-function/71791/3" class="inline-onebox">Step Activation Function</a>)</p>

Sidoroff_Michael: <aside class="quote no-group" data-username="suraj.pt" data-post="2" data-topic="102279">
<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/suraj.pt/48/53186_2.png" class="avatar"> suraj.pt:</div>
<blockquote>
<p>Gradients cannot exist for non-continuous values. You might want to use <code>tanh()</code> instead (see this thread for a similar issue <a href="https://discuss.pytorch.org/t/step-activation-function/71791/3">Step Activation Function</a>)</p>
</blockquote>
</aside>
<p>Thanks for your reply. The thing is that I need the vector to be binary, because i want to calculate the true / false positives in the function. Is there a work around that you are aware of ?</p>

Sidoroff_Michael: <p>Also what consider the following example where I use binary vector <code>a</code> to produce another variable <code>b</code> and compute gradients on both:</p>
<pre><code class="lang-auto">import torch

a = (torch.randn(5) &gt; .5).float().requires_grad_(True)
print ("a", a)

b = a.abs().mean()*(torch.sign(a))
print ("b", b)

b.retain_grad()
b.sum().backward()

print ("a.grad", a.grad)
print ("b.grad", b.grad)
</code></pre>

suraj.pt: <p>In <code>a.grad</code> you will get the gradients of <code>b</code> wrt <code>a</code>. That is valid because <code>b</code> has continuous values. But if you try to differentiate <code>a</code> that is not possible because it is non-continuous.</p>
<p>What you are trying to do is more along the lines of :</p>
<pre><code class="lang-auto">r = torch.randn(5).requires_grad_(True) # ~ model.weight
print("r", r)

a = (r &gt; .5).float().requires_grad_(True) # ~ y_pred
print ("a", a)

b = a.abs().mean()*(torch.sign(a)) # ~ loss
print ("b", b)

b.retain_grad()
b.sum().backward()

print ("a.grad", a.grad)
print ("b.grad", b.grad)

print("r.grad", r.grad) # None, because a is not continuous
</code></pre>
<p>[/quote]</p>

Sidoroff_Michael: <aside class="quote no-group" data-username="suraj.pt" data-post="6" data-topic="102279">
<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/suraj.pt/48/53186_2.png" class="avatar"> suraj.pt:</div>
<blockquote>
<pre><code class="lang-auto">r = torch.randn(5).requires_grad_(True) # ~ model.weight
print("r", r)

a = (r &gt; .5).float().requires_grad_(True) # ~ y_pred
print ("a", a)

b = a.abs().mean()*(torch.sign(a)) # ~ loss
print ("b", b)

b.retain_grad()
b.sum().backward()

print ("a.grad", a.grad)
print ("b.grad", b.grad)

print("r.grad", r.grad) # None, because a is not continuous
</code></pre>
</blockquote>
</aside>
<p>So, what will be the way you can train a network on a F Score? I s it possible?</p>

Sidoroff_Michael: <p>Is it possible, with regards to the original post, to update the weights by :</p>
<pre><code class="lang-auto">loss.backward()
model.weight.grad = torch.zeros_like(model.weight) + y_pred.grad
optimizer.step()
</code></pre>
<p>and then to call the optimizer to manipulate the weights of the model?</p>

suraj.pt: <p>AFAIK f-score is ill-suited as a loss function for training a network. F-score is better suited to judge a classifier’s calibration, but does not hold enough information for the neural network to improve its predictions.</p>
<p><a href="https://pytorch.org/docs/stable/nn.html#loss-functions" rel="noopener nofollow ugc">Loss functions</a> are differentiable so that they can propagate gradients throughout the network using the chain rule (see “backpropagation”).</p>

Sidoroff_Michael: <aside class="quote no-group" data-username="suraj.pt" data-post="9" data-topic="102279">
<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/suraj.pt/48/53186_2.png" class="avatar"> suraj.pt:</div>
<blockquote>
<p>score is ill-suited as a loss function for training a network. F-score is better suited to judge a classifier’s calibration, but does not hold enough information for the neural network to improve its predictions.</p>
</blockquote>
</aside>
<p>Thanks for the explanation!</p>