import torch from torch import nn import torch.nn.functional as F import numpy as np class QuantileLoss(nn.Module): def __init__(self, config): super().__init__() self.register_buffer('q', torch.tensor(config.quantiles)) def forward(self, predictions, targets): diff = predictions - targets.unsqueeze(-1).expand(-1, -1, -1, predictions.shape[-1]) ql = (1 - self.q) * F.relu(diff) + self.q * F.relu(-diff) losses = ql.view(-1, ql.shape[-1]).mean(0) return losses def qrisk(pred, tgt, quantiles): diff = pred - tgt ql = (1 - quantiles) * np.clip(diff, 0, float('inf')) + quantiles * np.clip(-diff, 0, float('inf')) losses = ql.reshape(-1, ql.shape[-1]) normalizer = np.abs(tgt).mean() risk = 2 * losses / normalizer return risk.mean(0) def weighted_bce_loss_fn(): bce = nn.BCEWithLogitsLoss(reduction="none") # Use reduction="none" to get per-element loss use_class_weights = False time_weight_factor = 1 def _loss_fn(y_pred, y_true): loss_list = [] # To accumulate per-sample loss values for i in range(len(y_pred)): current_pred = y_pred[i] current_true = y_true[i] loss_raw = bce(current_pred, current_true) if use_class_weights: # Calculate class weights using torch operations try: factor = torch.max(current_true) / torch.mean(current_true) / 2 non_zero_indices = current_true != 0 class_weights = torch.ones_like(current_pred) class_weights[non_zero_indices] = factor except Exception: class_weights = torch.ones_like(current_pred) else: class_weights = torch.ones_like(current_pred) # Calculate time weights using torch operations try: # Find indices where the event occurs event_indices = torch.where(current_true == 1)[0] if len(event_indices) > 0: first_event_index = event_indices[0].item() time_weights = torch.ones_like(current_pred) # Weight the loss before the first event higher if needed time_weights[:first_event_index] = time_weight_factor else: time_weights = torch.ones_like(current_pred) except Exception: time_weights = torch.ones_like(current_pred) # Apply the weights to the raw loss loss = loss_raw * class_weights * time_weights # Optionally, take the mean over the time dimension loss_list.append(loss.mean()) # Aggregate the loss for the entire batch return torch.stack(loss_list).mean() return _loss_fn def weighted_mse_prob_loss_fn(): mse = nn.MSELoss(reduce=False) after_event_factor = 1 use_weighted = False def _loss_fn(y_pred, y_true): nonlocal mse, after_event_factor loss_list = list() for i in range(len(y_pred)): current_pred = y_pred[i] current_true = y_true[i] loss_raw = mse(current_pred, current_true) # calculate factor to use for weighting, only works with probability values if use_weighted: try: factor = torch.max(current_true) / torch.mean(current_true) / 2 non_zero_indices = current_true != 0 class_weights = np.ones(len(current_true)) class_weights[non_zero_indices] = factor except: # if there are no non-zero indices, set all weights to 1 class_weights = np.ones(len(current_true)) else: # if we don't want to weight the loss, set all weights to 1 class_weights = np.ones(len(current_true)) # calculate time weight, here, after the event, the loss should be higher try: last_event_index = np.where(current_true != 0)[-1] time_weights = np.zeros(len(current_true)) # here, we want to weight the loss after the 'fertility curve' higher, so that we don't get the signal too late time_weights[last_event_index:] = after_event_factor except: # if there are no non-zero indices, set all weights to 1 time_weights = np.ones(len(current_true)) loss = loss_raw * torch.tensor(class_weights) * torch.tensor(time_weights) loss_list.append(loss.mean()) return torch.stack(loss_list).mean() return _loss_fn