Hello and thanks a lot for sharing your code!
I find this whole paper pretty awesome, however I am facing an issue when trying to use the SurfaceLoss in my architecture and after a few days of debugging I am completely desperate...
I implemented the losses in the way it is done in issue 6 - #6 and they do run well; Your GDL is giving me similar results as another implementation that I have so I pretty certain it works fine (and it gives me good confusion matrix and IoU and Dice score estimations). However, the moment I add the Surface loss (e.g. total_loss = (alpha*region_loss) + ((1-alpha) * surface_loss))
my confusion matrix is all zeroes (except for the background class, which I do not use); from thereon my IoU is 0 and the Dice score is 0 as well.. I call the GDL on all 8 classes of mine (0 to 7) and the Surface loss on classes 1 to 7.
`
def SurfaceLoss(probs, dist_maps, idc):
assert simplex(probs)
assert not one_hot(dist_maps)
pc = probs[:, idc, ...].type(torch.float32)
dc = dist_maps[:, idc, ...].type(torch.float32)
print("pc ", pc.shape)
print("dc ", dc.shape)
multipled = einsum("bcwh,bcwh->bcwh", pc, dc)
loss = multipled.mean()
return loss
def GeneralizedDice(probs, target, idc):
assert simplex(probs) and simplex(target)
pc = probs[:, idc, ...].type(torch.float32)
tc = target[:, idc, ...].type(torch.float32)
w = torch.Tensor()
intersection = torch.Tensor()
union = torch.Tensor()
divided = torch.Tensor()
w = 1 / ((einsum("bcwh->bc", tc).type(torch.float32) + 1e-10) ** 2)
intersection = w * einsum("bcwh,bcwh->bc", pc, tc)
union = w * (einsum("bcwh->bc", pc) + einsum("bcwh->bc", tc))
divided = 1 - 2 * (einsum("bc->b", intersection) + 1e-10) / (einsum("bc->b", union) + 1e-10)
loss = divided.mean()
return loss
class Combined(nn.Module):
def init(self, **kwargs):
super(Combined, self).init()
# Self.idc is used to filter out some classes of the target mask. Use fancy indexing
self.idc = kwargs["idc"]
def forward(self, probs: Tensor, target: Tensor, onehot_labels: Tensor, dist_maps: Tensor) -> Tensor:
outputs_softmaxes = F.softmax(probs, dim=1)
#with torch.no_grad():
onehot_labels = cuda(onehot_labels)
dist_maps = cuda(dist_maps)
region_loss = GeneralizedDice(probs=outputs_softmaxes, target=onehot_labels, idc=[0, 1, 2, 3, 4, 5, 6, 7])
surface_loss = SurfaceLoss(probs=outputs_softmaxes, dist_maps=dist_maps, idc=[1, 2, 3, 4, 5, 6, 7])
alpha = 0.80
total_loss = (alpha*region_loss) + ((1-alpha) * surface_loss)
return total_loss
`
My validation code (confusion matrix + IoU and Dice score estimations is basically this code here - https://github.com/ternaus/robot-surgery-segmentation/blob/master/validation.py
`Generalized DICE on 0 to 7 classes
confusion_one
[[ 1118977 361517 5204866 34883107 86448949 375518847 222144 40271]
[ 34672 10748 238810 9134 3131142 5310977 1017 3465]
[ 117454 6785 495962 6441 1472272 3218903 2485 2626]
[ 253567 133000 1007755 22668 12158524 21612781 6985 35023]
[ 0 0 0 0 0 0 0 0]
[ 45668 14682 268096 10774 3597826 10969731 888 6391]
[ 16966 16472 119121 9718 4741829 16902867 486 10611]
[ 0 0 0 0 0 0 0 0]]
confusion_two
[[ 10748 238810 9134 3131142 5310977 1017 3465]
[ 6785 495962 6441 1472272 3218903 2485 2626]
[ 133000 1007755 22668 12158524 21612781 6985 35023]
[ 0 0 0 0 0 0 0]
[ 14682 268096 10774 3597826 10969731 888 6391]
[ 16472 119121 9718 4741829 16902867 486 10611]
[ 0 0 0 0 0 0 0]]
iou {'iou_4': 0.0, 'iou_1': 0.0012108741637217233, 'iou_5': 0.17717714705689105, 'iou_2': 0.07251695213631425, 'iou_6': 2.228082374314263e-05, 'iou_7': 0.0, 'iou_3': 0.0006474203165053652}
dice {'dice_5': 0.3010203646916845, 'dice_6': 4.4560654638193385e-05, 'dice_7': 0.0, 'dice_1': 0.0024188194414750566, 'dice_4': 0.0, 'dice_3': 0.0012940028692635529, 'dice_2': 0.1352276101405575}
Valid loss: 1.0000, average IoU: 0.0359, average Dice: 0.0629
`
This is confusion matrix with the combined GDL + Surface losses; As you can see it is all zeroes
`confusion_one
[[503798678 0 0 0 0 0 0 0]
[ 8739965 0 0 0 0 0 0 0]
[ 5322928 0 0 0 0 0 0 0]
[ 35230303 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 14914056 0 0 0 0 0 0 0]
[ 21818070 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]]
confusion_two
[[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]
[0 0 0 0 0 0 0]]`
"confusion_one" is the confusion matrix before trimming the background class and "confusion_two" is afterwords. The trimming operation is done by line 56 in the validation.py file above ( confusion_matrix = confusion_matrix[1:, 1:] # exclude background).
Please, oh please, if you have any ideas why is this happening, do tell me.
P.S. it also happens when I run SurfaceLoss on all classes (including class 0, the background), so the number of classes is unrelated (and, yes, I am aware that surfaceloss should not be run on the background class)