Shape2Force / experiments /losses.py
kaveh's picture
experiments: two-phase training protocol
90ed5f2
Raw History Blame Contribute Delete
2.07 kB
"""
Reconstruction loss used for the released single-cell model.
Per-image weighted force-magnitude relative error (WFM-RME), as in the manuscript and as in the
loss that trained the released checkpoint (Archive/src/utils/losses.WFMRelativeMagnitudeLoss):
the weight of each pixel is its ground-truth magnitude divided by the mean ground-truth magnitude
*of the same image* (eps = 1e-6). Note: utils.metrics.WFMRMELoss normalises by the mean over the
whole batch instead; the two coincide only for batch size 1.
"""
import torch
import torch.nn as nn
class PerImageWFMRMELoss(nn.Module):
def __init__(self, eps=1e-6):
super().__init__()
self.eps = eps
def forward(self, pred, target):
t, p = target[:, 0], pred[:, 0] # (B, H, W) magnitudes
rel = (p - t).abs() / (t + self.eps)
w = t / (t.mean(dim=(1, 2), keepdim=True) + self.eps)
return (rel * w).mean()
class WeightedMSEPlusDiceLoss(nn.Module):
"""Loss of the image-only training stage of the released model (Archive/src/utils/losses.py,
used in Archive/notebooks/train_pix2pix.ipynb with pos_weight=3, threshold=0.2, dice_weight=0.5).
Pixels above `threshold` get weight 1 + pos_weight in the MSE; the soft-Dice term rewards overlap of
the predicted and true high-force regions. Unlike L1 / WFM-RME it drives the network to localise
the force-generating regions when training starts from random weights."""
def __init__(self, pos_weight=3.0, threshold=0.2, dice_weight=0.5):
super().__init__()
self.pos_weight, self.threshold, self.dice_weight = pos_weight, threshold, dice_weight
def forward(self, pred, target):
w = 1.0 + self.pos_weight * (target > self.threshold).float()
mse = (w * (pred - target) ** 2).mean()
p, t = pred.flatten(1), target.flatten(1)
dice = (2 * (p * t).sum(1) + 1e-6) / (p.sum(1) + t.sum(1) + 1e-6)
return mse + self.dice_weight * (1 - dice.mean())
REC_LOSSES = {"rme": PerImageWFMRMELoss, "wmd": WeightedMSEPlusDiceLoss}