search

Search

Ilya Ezepovmaster tiersilver medalFast IOU scoring metric in PyTorch and numpy
Python script using data from TGS Salt Identification Challenge · 17,976 views · 2y ago
49
This notebook uses a data source linked to a competition. Please sign in to enable copying.
9
Code
This Notebook has been released under the Apache 2.0 open source license.
Download Code
import torch
import numpy as np 


# PyTroch version

SMOOTH = 1e-6

def iou_pytorch(outputs: torch.Tensor, labels: torch.Tensor):
    # You can comment out this line if you are passing tensors of equal shape
    # But if you are passing output from UNet or something it will most probably
    # be with the BATCH x 1 x H x W shape
    outputs = outputs.squeeze(1)  # BATCH x 1 x H x W => BATCH x H x W
    
    intersection = (outputs & labels).float().sum((1, 2))  # Will be zero if Truth=0 or Prediction=0
    union = (outputs | labels).float().sum((1, 2))         # Will be zzero if both are 0
    
    iou = (intersection + SMOOTH) / (union + SMOOTH)  # We smooth our devision to avoid 0/0
    
    thresholded = torch.clamp(20 * (iou - 0.5), 0, 10).ceil() / 10  # This is equal to comparing with thresolds
    
    return thresholded  # Or thresholded.mean() if you are interested in average across the batch
    
    
# Numpy version
# Well, it's the same function, so I'm going to omit the comments

def iou_numpy(outputs: np.array, labels: np.array):
    outputs = outputs.squeeze(1)
    
    intersection = (outputs & labels).sum((1, 2))
    union = (outputs | labels).sum((1, 2))
    
    iou = (intersection + SMOOTH) / (union + SMOOTH)
    
    thresholded = np.ceil(np.clip(20 * (iou - 0.5), 0, 10)) / 10
    
    return thresholded  # Or thresholded.mean()
Did you find this Notebook useful?
Show your appreciation with an upvote
49
Biscuit
Andrey Vykhodtsev
Alexander Morgun
Soonhwan Kwon
Vadim Borisov
Ilya Ezepov
Vladimir Iglovikov
James Trotman
Strideradu
Anton Protopopov
quantumgeek
M Hendra Herviawan
Muhamad Iqbal
wh1te
FariborzGhavamian
Execution Info
Succeeded
True
Exit Code
0
Used All Space
False
Run Time
4.5 seconds
Timeout Exceeded
False
Output Size
0
Accelerator
None
Input
460.69 MB
folder

Data Sources

      arrow_drop_down

      TGS Salt Identification Challenge

      TGS Salt Identification Challenge
          drive_zip_outline

          competition_data.zip

          competition_data.zip
          calendar_view_week

          depths.csv

          depths.csv
          drive_zip_outline

          flamingo.zip

          flamingo.zip
          calendar_view_week

          sample_submission.csv

          sample_submission.csv
          drive_zip_outline

          test.zip

          test.zip
          calendar_view_week

          train.csv

          train.csv
          drive_zip_outline

          train.zip

          train.zip
competition_data.zip(216.61 MB)
get_app
Download
Competition Rules

To see this data you need to agree to the competition rules.

Comments (32)

Sort by

Hotness

arrow_drop_down

Andrei SpiridonovPosted on Version 8 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

Ilya, great work!
Thank you!

weakEnoshPosted on Version 6 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

in the 36 line 'thresholded = np.ceil(np.clip(20 * (ious - 0.5), 0, 10)) / 10', the variable 'ious' need be modified as 'iou'

Ilya EzepovTopic AuthorPosted on Version 6 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Indeed, thank you!

NarayanaSwamyPosted on Version 5 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

I had earlier written this on one of my kernel - this code doesnot require calculation of range of thresholds and comparing it. It will give you the same answer as your code.

intersect = (outputs*targets).sum(2).sum(1)
union = (outputs+targets).sum(2).sum(1)
iou = (intersect+0.001)/(union-intersect+0.001)

This simple logic here will give the correct result for precision
without going thru each threshold

iou_metric = ((iou-0.451)*2*10).floor()/10
iou_metric[iou_metric<0] = 0
return iou_metric # or iou_metric.mean()

Ilya EzepovTopic AuthorPosted on Version 6 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Thos are great suggestions, thank you!

Yet, I'm not sure about the second one. For example for iou == 0 (complete miss) it returns negative metric which is not right. So I corrected it a little to be torch.clamp(20 * (iou - 0.5), 0, 10).ceil() / 10

NarayanaSwamyPosted on Version 6 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Sorry, I left out one line of code that needs to come after the iou_metric calculation:

 iou_metric[iou_metric<0] = 0

Ilya EzepovTopic AuthorPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Even like that, I think it's not working correctly. Say for iou = 0.5509 your code will return 0.1, while the correct answer should be 0.2 (iou > 0.5 and >0.55, so it's 2/10). For 0.551 it would be correct though.

So, great news, you were sligtly underestimating your models all the time! It's great, it means that they are actually better than you thought :)

NarayanaSwamyPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

I think if I change the 0.451 to 0.450009, it will give the precision on this score until the 5th digit. IOU of 0.55001 will still yield the right result of 0.2

ZuppiPosted on Version 5 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

Hi Ilya, thank you for the code. I get the following error:

line 123, in iou_pytorch
    intersection = (outputs &amp; labels).sum((1, 2)).float()  # Will be zero if Truth=0 or Prediction=0
RuntimeError: cbitand is only supported for integer type tensors at /pytorch/aten/src/THC/generated/../generic/

Booth outputs and labels are float

Ilya EzepovTopic AuthorPosted on Version 5 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

2

Booth outputs and labels are float

That's exactly the reason, both should be byte or int. Just add this to the functuion body: labels = labels.byte()

And outputs in that case is already thresholded predicted mask. If your net outputs probablities just do output > 0.5 (you may want ot optimize this threshold).

StrideraduPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

2

If my output logits, how should I use your function, there was error about RuntimeError: cbitand is only supported for integer type tensors at /pytorch/aten/src/THC/generated/../generic/, and if I change labels to byte, there is another error said that RuntimeError: Expected object of type torch.cuda.FloatTensor but found torch.cuda.ByteTensor for argument #2 'other'

Muhamad IqbalPosted on Version 8 of 8 • 3 months agoOptionsReply

keyboard_arrow_up

0

Hi @iezepov, I have a question

What if the IoU i need to calculate is from bounding boxes coordinates?

VIGNESH VENKATARAMANPosted on Version 8 of 8 • 10 months agoOptionsReply

keyboard_arrow_up

0

could you add an example of input type

TommaoPosted on Version 8 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Hi, @Ilya Ezepov. Thank you for your sharing.
I got some confusion on this line: thresholded = torch.clamp(20 * (iou - 0.5), 0, 10).ceil() / 10
why do you multiple 20?
could you shed some lights on me?
thanks a lot

Ilya EzepovTopic AuthorPosted on Version 8 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

4

Sure thing! It looks tricky because we do few things at once here. Namely, we map the IOU values to a different scale (x => 2*x - 1) and do the rounding.

We need the mapping because IOU=0.5 means 0 on the leaderboard, and IOU=1.0 means 1.0. Thus the mapping we a looking for is x => 2 * x - 1. And that's why I have that 20 * (iou - 0.5). As we later divide by 10 you can think of it as of 20 * (iou - 0.5) / 10 = 2 * (iou - 0.5) = 2 * iou - 1. But that's not enough. If original IOU was from 0 to 1, than 2 * iou - 1 is from -1 to 1. Now to the second part.

Here we don't care about the values itself, only whether it exceeds specific thresholds. Therefore we need to round our maped values. How do you round numbers to 0.1, 0.2…? You multiply by 10, round to integers and devide by 10. That's why we have (10 * x).ceil() / 10. In reality we have 20 * x but you already know why.

And finnaly we have torch.clamp(..., 0, 10) to omit all IOUs below 0.5 (or mapped IOUs below 0).

CalebPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

CalebPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

CalebPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Not sure if this is an issue with Pytorch 0.4, but with 0.3,

intersection = (outputs &amp; labels).sum((1, 2)).float()

yielded different results than:

intersection = (outputs &amp; labels).float().sum(1).sum(1)

The sum of the byte tensor was different than the sum of the float tensor, with the sum of the float tensor yielding the correct result. Summing across multiple dimensions was apparently also implemented in 0.4.

Ilya EzepovTopic AuthorPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Thanks for that, I'll fix it.

(outputs &amp; labels).float().sum((1, 2))

Would be the best looking, I guesss.

Frank J. EbbersPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

def lb_metric(iou):   
    """Linear implementation for conversion from IoU to Kaggle IoU LB score"""   
    thresholds = np.linspace(.45, 1., 12)  
    try:   
          score = np.argwhere(thresholds < iou)[-1] * .1   
          score = score[0]   
    except:   
          score = 0.   
    return score  

**take care of indentation as not shown in markdown

Ilya EzepovTopic AuthorPosted on Version 8 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Hello! Thanks for your comment, but I think it's not the best one, most because try-except is quite a dangerous pattern. Especially if your except is catching all errors (like in your example). For example, image your function will receive None or some string as an input. It shouldn't, but all can happen. Now np.argwhere(thresholds < iou) will fail, but you wouldn't know about it because of except clause. It will just silently return 0.

ZuppiPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

So basically, if I want to choose the best treshold I have to iterate over all of them an pick the ones that give mes the MAXIMUM mean iou (so the output of your function right?). I have done some testing and if in my validation test I get, for example, an mean iou of 0.750, on the publisc score I get always 0.05/0.06 less, so (0.700). Can you confirm this?

Ilya EzepovTopic AuthorPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

A gap between a local validation and a public LB is a normal thing because and happens often, yet 0.05-0.06 is maybe to big for this competition. I observe 0.02 gap (not k-fold, just 20% hold out). Maybe you want to stratify your validation by depth and/or salt %. Otherwise, your hold-out can be quite different from the training/public/private data and you can get a big gap.

ZuppiPosted on Version 5 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

0

Thank you again for your help :)

So assuming I have the output of my network with shape

BATCH x 1 x H x W

For example, a network with sigmoid activation. At that point I cannot just convert to byte (of course!). But I should, for each threshold, calculate the iou. So, something like

for tr in THRESHOLDS
output[output > tr] = 1
output = output.byte() 
// calculate iou for this tr

How can I do this with your code?

Ilya EzepovTopic AuthorPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

You can do something like:

for tr in THRESHOLDS:
    predicted_mask = outputs > tr  # This will be a byte tensor
    iou_pytorch(predicted_mask, labels.byte())  # If your labels are BATCH x H x W
    # or
    # iou_pytorch(predicted_mask, labels.squeeze(1).byte())  # If your labels are BATCH x 1 x H x W

Ilya EzepovTopic AuthorPosted on Version 7 of 8 • 2 years agoOptionsReply

keyboard_arrow_up

1

Dor some reason markdown is not working :(

Posted on Version 8 of 8 • 5 months ago

This comment was deleted.

Posted on Version 7 of 8 • 2 years ago

This comment was deleted.

Posted on Version 7 of 8 • 2 years ago

This comment was deleted.

Posted on Version 7 of 8 • 2 years ago

This comment was deleted.

Posted on Version 7 of 8 • 2 years ago

This comment was deleted.

Posted on Version 7 of 8 • 2 years ago

This comment was deleted.

We use cookies on Kaggle to deliver our services, analyze web traffic, and improve your experience on the site. By using Kaggle, you agree to our use of cookies.