| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| import numpy as np |
|
|
| class AdaptiveWingLoss(nn.Module): |
| def __init__(self, omega=14.0, theta=0.5, epsilon=1.0, alpha=2.1): |
| super(AdaptiveWingLoss, self).__init__() |
| self.omega = omega |
| self.theta = theta |
| self.epsilon = epsilon |
| self.alpha = alpha |
|
|
| def forward(self, y_pred, y_true): |
| """ |
| y_pred, y_true: (Batch, N) |
| """ |
| delta_y = (y_true - y_pred).abs() |
| device = y_pred.device |
| |
| |
| |
| |
| |
| mask = delta_y < self.theta |
| |
| |
| loss1 = self.omega * torch.log(1 + torch.pow(delta_y[mask] / self.epsilon, self.alpha)) |
| |
| |
| theta_eps = torch.tensor(self.theta / self.epsilon, device=device) |
| A = self.omega * (1.0 / (1.0 + torch.pow(theta_eps, self.alpha))) * \ |
| (self.alpha * torch.pow(theta_eps, self.alpha - 1.0) * (1.0 / self.epsilon)) |
| B = A * self.theta - self.omega * torch.log(1.0 + torch.pow(theta_eps, self.alpha)) |
| |
| loss2 = A * delta_y[~mask] - B |
| |
| return (loss1.sum() + loss2.sum()) / y_pred.numel() |
|
|
| class KDLoss(nn.Module): |
| def __init__(self, temperature=3.0): |
| super(KDLoss, self).__init__() |
| self.temperature = temperature |
| self.kl_div = nn.KLDivLoss(reduction='batchmean') |
|
|
| def forward(self, student_logits, teacher_logits): |
| """ |
| student_logits, teacher_logits: (Batch, NumBins) |
| """ |
| soft_log_probs = F.log_softmax(student_logits / self.temperature, dim=1) |
| soft_targets = F.softmax(teacher_logits / self.temperature, dim=1) |
| |
| return self.kl_div(soft_log_probs, soft_targets) * (self.temperature ** 2) |
|
|
| class GazeDistillationLoss(nn.Module): |
| def __init__(self, w_aw=1.0, w_kd=0.5, temperature=3.0, focal_tau=4.2, focal_gamma=1.5): |
| super(GazeDistillationLoss, self).__init__() |
| self.aw_loss = AdaptiveWingLoss() |
| self.kd_loss = KDLoss(temperature=temperature) |
| self.w_aw = w_aw |
| self.w_kd = w_kd |
| self.focal_tau = focal_tau |
| self.focal_gamma = focal_gamma |
|
|
| def forward(self, student_outputs, gt_gaze, teacher_logits=None): |
| """ |
| student_outputs: Tuple(pitch_logits, yaw_logits) or Tuple(pitch, yaw, domain) |
| gt_gaze: (Batch, 2) in Radians or Degrees (depending on training) |
| teacher_logits: Tuple(t_pitch_logits, t_yaw_logits) |
| """ |
| |
| if len(student_outputs) == 3: |
| s_pitch_logits, s_yaw_logits, _ = student_outputs |
| else: |
| s_pitch_logits, s_yaw_logits = student_outputs |
| |
| device = s_pitch_logits.device |
| |
| |
| idx = torch.arange(90).float().to(device) |
| |
| s_pitch_prob = F.softmax(s_pitch_logits, dim=1) |
| s_yaw_prob = F.softmax(s_yaw_logits, dim=1) |
| |
| s_pitch_idx = torch.sum(s_pitch_prob * idx, dim=1) |
| s_yaw_idx = torch.sum(s_yaw_prob * idx, dim=1) |
| |
| s_pitch_deg = s_pitch_idx * 2 - 90 |
| s_yaw_deg = s_yaw_idx * 2 - 90 |
| |
| s_gaze = torch.stack([s_pitch_deg, s_yaw_deg], dim=1) |
| |
| |
| gt_gaze_deg = gt_gaze * (180.0 / np.pi) |
| |
| |
| |
| with torch.no_grad(): |
| errors = torch.abs(s_gaze - gt_gaze_deg).mean(dim=1) |
| |
| weights = torch.where(errors >= self.focal_tau, |
| torch.tensor(self.focal_gamma, device=device), |
| torch.tensor(1.0, device=device)) |
| |
| |
| delta_y = (gt_gaze_deg - s_gaze).abs() |
| |
| |
| mask = delta_y < self.aw_loss.theta |
| loss_val = torch.zeros_like(delta_y) |
| |
| loss_val[mask] = self.aw_loss.omega * torch.log(1 + torch.pow(delta_y[mask] / self.aw_loss.epsilon, self.aw_loss.alpha)) |
| |
| |
| theta_eps = torch.tensor(self.aw_loss.theta / self.aw_loss.epsilon, device=device) |
| A = self.aw_loss.omega * (1.0 / (1.0 + torch.pow(theta_eps, self.aw_loss.alpha))) * \ |
| (self.aw_loss.alpha * torch.pow(theta_eps, self.aw_loss.alpha - 1.0) * (1.0 / self.aw_loss.epsilon)) |
| B = A * self.aw_loss.theta - self.aw_loss.omega * torch.log(1.0 + torch.pow(theta_eps, self.aw_loss.alpha)) |
| loss_val[~mask] = A * delta_y[~mask] - B |
| |
| |
| |
| loss_aw = (loss_val.mean(dim=1) * weights).mean() |
| |
| |
| loss_kd = 0 |
| if teacher_logits is not None: |
| t_pitch_logits, t_yaw_logits = teacher_logits |
| |
| |
| def weighted_kl(s_logits, t_logits, w): |
| soft_log_probs = F.log_softmax(s_logits / self.kd_loss.temperature, dim=1) |
| soft_targets = F.softmax(t_logits / self.kd_loss.temperature, dim=1) |
| |
| kl = F.kl_div(soft_log_probs, soft_targets, reduction='none').sum(dim=1) |
| return (kl * w).mean() * (self.kd_loss.temperature ** 2) |
|
|
| loss_kd_pitch = weighted_kl(s_pitch_logits, t_pitch_logits, weights) |
| loss_kd_yaw = weighted_kl(s_yaw_logits, t_yaw_logits, weights) |
| loss_kd = (loss_kd_pitch + loss_kd_yaw) / 2 |
| |
| total_loss = self.w_aw * loss_aw + self.w_kd * loss_kd |
| |
| return total_loss, loss_aw, loss_kd |
|
|
|
|