Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[API] Changed get_beta_softlabels interface to match the others #60

Merged
merged 5 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 1 addition & 123 deletions dlordinal/losses/beta_loss.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,116 +6,6 @@
from ..soft_labelling import get_beta_soft_labels
from .custom_targets_loss import CustomTargetsCrossEntropyLoss

# Params [a,b] for beta distribution
_beta_params_sets = {
"standard": {
3: [[1, 4, 1], [4, 4, 1], [4, 1, 1]],
4: [[1, 6, 1], [6, 10, 1], [10, 6, 1], [6, 1, 1]],
5: [[1, 8, 1], [6, 14, 1], [12, 12, 1], [14, 6, 1], [8, 1, 1]],
6: [[1, 10, 1], [7, 20, 1], [15, 20, 1], [20, 15, 1], [20, 7, 1], [10, 1, 1]],
7: [
[1, 12, 1],
[7, 26, 1],
[16, 28, 1],
[24, 24, 1],
[28, 16, 1],
[26, 7, 1],
[12, 1, 1],
],
8: [
[1, 14, 1],
[7, 31, 1],
[17, 37, 1],
[27, 35, 1],
[35, 27, 1],
[37, 17, 1],
[31, 7, 1],
[14, 1, 1],
],
9: [
[1, 16, 1],
[8, 40, 1],
[18, 47, 1],
[30, 47, 1],
[40, 40, 1],
[47, 30, 1],
[47, 18, 1],
[40, 8, 1],
[16, 1, 1],
],
10: [
[1, 18, 1],
[8, 45, 1],
[19, 57, 1],
[32, 59, 1],
[45, 55, 1],
[55, 45, 1],
[59, 32, 1],
[57, 19, 1],
[45, 8, 1],
[18, 1, 1],
],
11: [
[1, 21, 1],
[8, 51, 1],
[20, 68, 1],
[34, 73, 1],
[48, 69, 1],
[60, 60, 1],
[69, 48, 1],
[73, 34, 1],
[68, 20, 1],
[51, 8, 1],
[21, 1, 1],
],
12: [
[1, 23, 1],
[8, 56, 1],
[20, 76, 1],
[35, 85, 1],
[51, 85, 1],
[65, 77, 1],
[77, 65, 1],
[85, 51, 1],
[85, 35, 1],
[76, 20, 1],
[56, 8, 1],
[23, 1, 1],
],
13: [
[1, 25, 1],
[8, 61, 1],
[20, 84, 1],
[36, 98, 1],
[53, 100, 1],
[70, 95, 1],
[84, 84, 1],
[95, 70, 1],
[100, 53, 1],
[98, 36, 1],
[84, 20, 1],
[61, 8, 1],
[25, 1, 1],
],
14: [
[1, 27, 1],
[2, 17, 1],
[5, 23, 1],
[9, 27, 1],
[13, 28, 1],
[18, 28, 1],
[23, 27, 1],
[27, 23, 1],
[28, 18, 1],
[28, 13, 1],
[27, 9, 1],
[23, 5, 1],
[17, 2, 1],
[27, 1, 1],
],
}
}


class BetaCrossEntropyLoss(CustomTargetsCrossEntropyLoss):
"""Beta unimodal regularised cross entropy loss from :footcite:t:`vargas2022unimodal`.
Expand Down Expand Up @@ -172,20 +62,8 @@ def __init__(
reduction: str = "mean",
label_smoothing: float = 0.0,
):
self.params = _beta_params_sets[params_set]

# Precompute class probabilities for each label
cls_probs = torch.tensor(
[
get_beta_soft_labels(
num_classes,
self.params[num_classes][i][0],
self.params[num_classes][i][1],
self.params[num_classes][i][2],
)
for i in range(num_classes)
]
).float()
cls_probs = torch.tensor(get_beta_soft_labels(num_classes, params_set)).float()

super().__init__(
cls_probs=cls_probs,
Expand Down
171 changes: 170 additions & 1 deletion dlordinal/soft_labelling/beta_distribution.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import numpy as np
from scipy.special import gamma, hyp2f1

from .utils import get_intervals
Expand Down Expand Up @@ -57,7 +58,7 @@ def beta_dist(x, p, q, a=1.0):
return (x ** (a * p)) / (p * beta_func(p, q)) * hyp2f1(p, 1 - q, p + 1, x**a)


def get_beta_soft_labels(J, p, q, a=1.0):
def _get_beta_soft_label(J, p, q, a=1.0):
"""Get soft labels from a beta distribution :math:`B(p,q,a)` for ``J`` splits.
The :math:`[0,1]` interval is split into ``J`` intervals and the probability for
each interval is computed as the difference between the value of the distribution
Expand Down Expand Up @@ -118,3 +119,171 @@ class or split.
probs.append(beta_dist(interval[1], p, q, a) - beta_dist(interval[0], p, q, a))

return probs


# Params [p,q,a] for beta distribution
_beta_params_sets = {
"standard": {
3: [[1, 4, 1], [4, 4, 1], [4, 1, 1]],
4: [[1, 6, 1], [6, 10, 1], [10, 6, 1], [6, 1, 1]],
5: [[1, 8, 1], [6, 14, 1], [12, 12, 1], [14, 6, 1], [8, 1, 1]],
6: [[1, 10, 1], [7, 20, 1], [15, 20, 1], [20, 15, 1], [20, 7, 1], [10, 1, 1]],
7: [
[1, 12, 1],
[7, 26, 1],
[16, 28, 1],
[24, 24, 1],
[28, 16, 1],
[26, 7, 1],
[12, 1, 1],
],
8: [
[1, 14, 1],
[7, 31, 1],
[17, 37, 1],
[27, 35, 1],
[35, 27, 1],
[37, 17, 1],
[31, 7, 1],
[14, 1, 1],
],
9: [
[1, 16, 1],
[8, 40, 1],
[18, 47, 1],
[30, 47, 1],
[40, 40, 1],
[47, 30, 1],
[47, 18, 1],
[40, 8, 1],
[16, 1, 1],
],
10: [
[1, 18, 1],
[8, 45, 1],
[19, 57, 1],
[32, 59, 1],
[45, 55, 1],
[55, 45, 1],
[59, 32, 1],
[57, 19, 1],
[45, 8, 1],
[18, 1, 1],
],
11: [
[1, 21, 1],
[8, 51, 1],
[20, 68, 1],
[34, 73, 1],
[48, 69, 1],
[60, 60, 1],
[69, 48, 1],
[73, 34, 1],
[68, 20, 1],
[51, 8, 1],
[21, 1, 1],
],
12: [
[1, 23, 1],
[8, 56, 1],
[20, 76, 1],
[35, 85, 1],
[51, 85, 1],
[65, 77, 1],
[77, 65, 1],
[85, 51, 1],
[85, 35, 1],
[76, 20, 1],
[56, 8, 1],
[23, 1, 1],
],
13: [
[1, 25, 1],
[8, 61, 1],
[20, 84, 1],
[36, 98, 1],
[53, 100, 1],
[70, 95, 1],
[84, 84, 1],
[95, 70, 1],
[100, 53, 1],
[98, 36, 1],
[84, 20, 1],
[61, 8, 1],
[25, 1, 1],
],
14: [
[1, 27, 1],
[2, 17, 1],
[5, 23, 1],
[9, 27, 1],
[13, 28, 1],
[18, 28, 1],
[23, 27, 1],
[27, 23, 1],
[28, 18, 1],
[28, 13, 1],
[27, 9, 1],
[23, 5, 1],
[17, 2, 1],
[27, 1, 1],
],
}
}


def get_beta_soft_labels(J, params_set="standard"):
"""Get soft labels for each of the ``J`` classes using a beta distributions and
the parameter defined in the ``params_set`` as described in :footcite:t:`vargas2022unimodal`.

Parameters
----------
J : int
Number of classes or splits.

params_set : str, default='standard'
The set of parameters of the beta distributions employed to generate the
soft labels. It has to be one of the keys in the ``_beta_params_sets``
dictionary.

Raises
------
ValueError
If ``J`` is not a positive integer or if ``params_set`` is not a valid key.

Returns
-------
probs: list
List of ``J`` elements where each elements is also a list of ``J`` elements.
Each inner list represents the soft label of class ``j`` for each of the
``J`` classes. For example: ``probs[j]`` is the soft label for class ``j``.
Then, ``probs[j][k]`` is the probability of assigning class ``k`` to the
instance that belongs to class ``j``.

Example
-------
>>> from dlordinal.soft_labelling import get_beta_softlabels
>>> get_beta_softlabels(3)
[[0.802469132197531, 0.18518518474074064, 0.01234567906172801],
[0.17329675405578432, 0.6534064918884313, 0.1732967540557846],
[0.012345679061728405, 0.1851851847407408, 0.8024691321975309]]
>>> get_beta_softlabels(5)
[[0.8322278330066323, 0.15097599903815717, 0.01614079995258888,
0.0006528000025611824, 2.5600000609360407e-06], [0.16306230596685573,
0.6740152119811103, 0.15985465217901373, 0.003067150177798128,
6.796952229937148e-07], [0.0005973937258183486, 0.16304604740785777,
0.6727131177326486, 0.16304604740785367, 0.000597393725820794],
[6.796952207410873e-07, 0.0030671501777993896, 0.15985465217901168,
0.6740152119811109, 0.16306230596685678], [2.560000061440001e-06,
0.0006528000025600005, 0.01614079995258879, 0.1509759990381568,
0.8322278330066332]]
"""

if J <= 0:
raise ValueError(f"{J=} must be a positive integer")

if params_set not in _beta_params_sets:
raise ValueError(f"Invalid params_set: {params_set}")

params = _beta_params_sets[params_set]
return np.array([_get_beta_soft_label(J, p, q, a) for (p, q, a) in params[J]])
Loading
Loading