Understanding different Metrics implementations (IoU)

Hey!
I am working on an evaluation script for my semantic segmentation model and was looking for some IoU implementations.

The first one I found was this one:

EPS = 1e-6
#slightly modified
def get_IoU(outputs, labels):
    outputs = outputs.int()
    labels = labels.int()
    # Taken from: https://www.kaggle.com/iezepov/fast-iou-scoring-metric-in-pytorch-and-numpy
    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 zero if both are 0

    iou = (intersection + EPS) / (union + EPS)  # 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.mean()  # Or thresholded.mean() if you are interested in average across the batch
    return iou.mean()

The second one was found here (Jaccard Index):

# computes confusion matrix
def _fast_hist(true, pred, num_classes):
    mask = (true >= 0) & (true < num_classes)
    hist = torch.bincount(
        num_classes * true[mask] + pred[mask],
        minlength=num_classes ** 2,
    ).reshape(num_classes, num_classes).float()
    return hist

# computes IoU based on confusion matrix
def jaccard_index(hist):
    """Computes the Jaccard index, a.k.a the Intersection over Union (IoU).
    Args:
        hist: confusion matrix.
    Returns:
        avg_jacc: the average per-class jaccard index.
    """
    A_inter_B = torch.diag(hist)
    A = hist.sum(dim=1)
    B = hist.sum(dim=0)
    jaccard = A_inter_B / (A + B - A_inter_B + EPS)
    avg_jacc = nanmean(jaccard) #the mean of jaccard without NaNs
    return avg_jacc, jaccard

I wanted to cross check that both compute the same think:

true = torch.tensor([[
    [1, 0, 0],
    [1, 0, 0],
    [1, 0, 0],
]])
pred = torch.tensor([[
    [1, 0, 0],
    [1, 1, 0],
    [1, 0, 0],
]])
print(true.shape, pred.shape)
print(get_IoU(pred, true))
hist = _fast_hist(pred, true, num_classes=2)
print(jaccard_index(hist))

result is:

torch.Size([1, 3, 3]) torch.Size([1, 3, 3])
tensor(0.7500)
(tensor(0.7917), tensor([0.8333, 0.7500]))

I have 2 Questions:

  1. What is the correct IoU result? 0.75 / 0.83 / 0.79?
  2. Can someone explain to me what the purpose of the EPS (I assume epsilone?) value is and why we need it?

Thanks for any help!

Hi @mayool,

I think that the answer is: it depends (as usual).

  • The first code assumes you have one class: β€œ1”. If you calculate the IoU score manually you have: 3 "1"s in the right position and 4 "1"s in the union of both matrices: 3/4 = 0.7500.
  • If you consider that you have two classes: β€œ1” and β€œ0”. We know already that β€œ1” has an IoU score of 0.7500. Considering only "0"s, you have 5 "0"s correctly predicted, and 6 β€œ0s” in the union: 5/6 = 0.8333.

Also, your code seems to have mistaken the input order of _fast_hist, it should be hist = _fast_hist(true, pred, num_classes=2) (notice pred and true are not in the right order), but it gave the right answer anyway.

So, for question 1: 0.7500 is the IoU for class β€œ1” and 0.8333 is the IoU for class β€œ0”. 0.7971 is just the average of both.

The purpouse of EPS (yes, epsilon) is to avoid division by zero. I think that case is possible only if your class is not present in any of the matrices, but is not bad to have it. Also, the epsilon value is so small that it should not modify your values in a significant way.

5 Likes

Hi @Antonio_Ossa,

Thanks for this nice explanation.

I have a slightly different but still relevant question. Is there some standard practice in the research community for publishing results in journals/conferences?

Let’s consider a two class problem (a.k.a. binary segmentation) where c=0 for background (healthy tissues) and c=1 for foreground (diseased tissues).

What should be reported
a. IoU(c=1)
b. mean IoU = [IoU(c=0) + IoU(c=1)] / 2

Hi @stark ,

In that case, considering the context, I think it should be relevant to present both IoU (IoU(c=0) and IoU(c=1)). I’m assuming that there may be some class imbalance and also that both values have different importance. I’m not sure if there’s any standard practice but you should always try to provide the relevant insight considering the context. Personally, I would report the overall IoU in a table but in the analysis I would report both to confirm that the mean is (or not) hidding any insights.