/
losses.py
108 lines (89 loc) · 3.49 KB
/
losses.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import numpy as np
import torch
from torch import nn
from typing import Optional
def _reduction(loss: torch.Tensor, reduction: str) -> torch.Tensor:
"""
Reduce loss
Parameters
----------
loss : torch.Tensor, [batch_size, num_classes]
Batch losses.
reduction : str
Method for reducing the loss. Options include 'elementwise_mean',
'none', and 'sum'.
Returns
-------
loss : torch.Tensor
Reduced loss.
"""
if reduction == 'elementwise_mean':
return loss.mean()
elif reduction == 'none':
return loss
elif reduction == 'sum':
return loss.sum()
else:
raise ValueError(f'{reduction} is not a valid reduction')
def cumulative_link_loss(y_pred: torch.Tensor, y_true: torch.Tensor,
reduction: str = 'elementwise_mean',
class_weights: Optional[np.ndarray] = None
) -> torch.Tensor:
"""
Calculates the negative log likelihood using the logistic cumulative link
function.
See "On the consistency of ordinal regression methods", Pedregosa et. al.
for more details. While this paper is not the first to introduce this, it
is the only one that I could find that was easily readable outside of
paywalls.
Parameters
----------
y_pred : torch.Tensor, [batch_size, num_classes]
Predicted target class probabilities. float dtype.
y_true : torch.Tensor, [batch_size, 1]
True target classes. long dtype.
reduction : str
Method for reducing the loss. Options include 'elementwise_mean',
'none', and 'sum'.
class_weights : np.ndarray, [num_classes] optional (default=None)
An array of weights for each class. If included, then for each sample,
look up the true class and multiply that sample's loss by the weight in
this array.
Returns
-------
loss: torch.Tensor
"""
eps = 1e-15
likelihoods = torch.clamp(torch.gather(y_pred, 1, y_true), eps, 1 - eps)
neg_log_likelihood = -torch.log(likelihoods)
if class_weights is not None:
# Make sure it's on the same device as neg_log_likelihood
class_weights = torch.as_tensor(class_weights,
dtype=neg_log_likelihood.dtype,
device=neg_log_likelihood.device)
neg_log_likelihood *= class_weights[y_true]
loss = _reduction(neg_log_likelihood, reduction)
return loss
class CumulativeLinkLoss(nn.Module):
"""
Module form of cumulative_link_loss() loss function
Parameters
----------
reduction : str
Method for reducing the loss. Options include 'elementwise_mean',
'none', and 'sum'.
class_weights : np.ndarray, [num_classes] optional (default=None)
An array of weights for each class. If included, then for each sample,
look up the true class and multiply that sample's loss by the weight in
this array.
"""
def __init__(self, reduction: str = 'elementwise_mean',
class_weights: Optional[torch.Tensor] = None) -> None:
super().__init__()
self.class_weights = class_weights
self.reduction = reduction
def forward(self, y_pred: torch.Tensor,
y_true: torch.Tensor) -> torch.Tensor:
return cumulative_link_loss(y_pred, y_true,
reduction=self.reduction,
class_weights=self.class_weights)