Skip to content

Commit

Permalink
Merge branch 'development' into estimator-verbosity
Browse files Browse the repository at this point in the history
  • Loading branch information
victormvy committed Mar 26, 2024
2 parents 2ba8acf + ebbc821 commit 61024fc
Show file tree
Hide file tree
Showing 46 changed files with 459 additions and 208 deletions.
14 changes: 10 additions & 4 deletions .github/workflows/run_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ on:
pull_request:
branches:
- main
- development
paths:
- "tutorials/**"
- "dlordinal/**"
- ".github/workflows/**"

jobs:
tests:
runs-on: ubuntu-22.04

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.8

Expand All @@ -38,10 +43,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v5
with:
python-version: 3.8

Expand All @@ -55,6 +60,7 @@ jobs:
- name: Run tests for codecov
run: |
pytest --cov=dlordinal --cov-report=xml
timeout-minutes: 20

- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
Expand Down
2 changes: 1 addition & 1 deletion dlordinal/datasets/tests/test_adience.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import pytest
from PIL import Image

from ..adience import Adience
from dlordinal.datasets import Adience

temp_dir = None

Expand Down
2 changes: 1 addition & 1 deletion dlordinal/datasets/tests/test_featuredataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest
import torch

from ..featuredataset import FeatureDataset
from dlordinal.datasets import FeatureDataset


@pytest.fixture
Expand Down
2 changes: 1 addition & 1 deletion dlordinal/datasets/tests/test_fgnet.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import pytest

from ..fgnet import FGNet
from dlordinal.datasets import FGNet

TMP_DIR = "./tmp_test_dir_fgnet"

Expand Down
24 changes: 14 additions & 10 deletions dlordinal/distributions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from .beta_distribution import get_beta_probabilities
from .binomial_distribution import get_binomial_probabilities
from .exponential_distribution import get_exponential_probabilities
from .general_triangular_distribution import get_general_triangular_probabilities
from .beta_distribution import get_beta_softlabels
from .binomial_distribution import get_binomial_softlabels
from .exponential_distribution import get_exponential_softlabels
from .general_triangular_distribution import (
get_general_triangular_params,
get_general_triangular_softlabels,
)
from .poisson_distribution import get_poisson_probabilities
from .triangular_distribution import get_triangular_probabilities
from .triangular_distribution import get_triangular_softlabels

__all__ = [
"get_beta_probabilities",
"get_exponential_probabilities",
"get_binomial_probabilities",
"get_beta_softlabels",
"get_exponential_softlabels",
"get_binomial_softlabels",
"get_poisson_probabilities",
"get_triangular_probabilities",
"get_general_triangular_probabilities",
"get_triangular_softlabels",
"get_general_triangular_params",
"get_general_triangular_softlabels",
]
49 changes: 41 additions & 8 deletions dlordinal/distributions/beta_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def beta_dist(x, p, q, a=1.0):
q : float
Second shape parameter (:math:`q > 0`).
a : float, default=1.0
Scaling parameter.
Scaling parameter (:math:`a > 0`).
Returns
-------
Expand All @@ -57,27 +57,60 @@ 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_probabilities(n, p, q, a=1.0):
"""Get probabilities from a beta distribution :math:`B(p,q,a)` for ``n`` splits.
def get_beta_softlabels(J, p, q, a=1.0):
"""Get probabilities 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
function in the upper limit of the interval and the value of the distribution
function in the lower limit of the interval. Thus, the probability for class ``j``
is computed as :math:`B(p,q,a)(j/J) - B(p,q,a)((j-1)/J)`.
Parameters
----------
n : int
Number of classes.
J : int
Number of classes or splits.
p : float
First shape parameter (:math:`p > 0`).
q : float
Second shape parameter (:math:`q > 0`).
a : float, default=1.0
Scaling parameter.
Scaling parameter (:math:`a > 0`).
Raises
------
ValueError
If ``J`` is not a positive integer, if ``p`` is not positive, if ``q`` is
not positive or if ``a`` is not positive.
Returns
-------
probs: list
List of probabilities.
List of ``J`` elements that represent the probability associated with each
class or split.
Example
-------
>>> from dlordinal.distributions import get_beta_probabilities
>>> get_beta_probabilities(3, 2, 3)
[0.4074074080000002, 0.48148148059259255, 0.11111111140740726]
>>> get_beta_probabilities(5, 5, 1, a=2)
[1.0240000307200007e-07, 0.00010475520052121611, 0.005941759979320316,
0.10132756401484902, 0.8926258084053067]
"""

intervals = get_intervals(n)
if J < 2 or not isinstance(J, int):
raise ValueError(f"{J=} must be a positive integer greater than 1")

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

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

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

intervals = get_intervals(J)
probs = []

# Compute probability for each interval (class) using the distribution function.
Expand Down
36 changes: 28 additions & 8 deletions dlordinal/distributions/binomial_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,39 @@
from scipy.stats import binom


def get_binomial_probabilities(n):
"""Get probabilities from binominal distribution for n classes.
def get_binomial_softlabels(J):
"""Get probabilities for the binomial distribution for ``J`` classes or splits
using the approach described in :footcite:t:`liu2020unimodal`.
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 binomial
probability function for the interval boundaries. The probability for the first
interval is computed as the value of the binomial probability function for the first
interval boundary.
The binomial distributions employed are denoted as :math:`\\text{b}(k, n-1, p)` where
:math:`k` is given by the order of the class for which the probability is computed,
and :math:`p` is given by :math:`0.1 + (0.9-0.1) / (n-1) * j` where :math:`j` is
is the order of the target class.
Parameters
----------
n : int
Number of classes.
J : int
Number of classes or splits.
Raises
------
ValueError
If ``J`` is not a positive integer greater than 1.
Returns
-------
probs : 2d array-like
probs : 2d array-like of shape (J, J)
Matrix of probabilities where each row represents the true class
and each column the probability for class n.
and each column the probability for class ``j``.
Example
-------
>>> from dlordinal.distributions import get_binominal_probabilities
>>> get_binominal_probabilities(5)
array([[6.561e-01, 2.916e-01, 4.860e-02, 3.600e-03, 1.000e-04],
[2.401e-01, 4.116e-01, 2.646e-01, 7.560e-02, 8.100e-03],
Expand All @@ -26,6 +43,9 @@ def get_binomial_probabilities(n):
[1.000e-04, 3.600e-03, 4.860e-02, 2.916e-01, 6.561e-01]])
"""

if J < 2 or not isinstance(J, int):
raise ValueError(f"{J=} must be a positive integer greater than 1")

params = {}

params["4"] = np.linspace(0.1, 0.9, 4)
Expand All @@ -39,7 +59,7 @@ def get_binomial_probabilities(n):

probs = []

for true_class in range(0, n):
probs.append(binom.pmf(np.arange(0, n), n - 1, params[str(n)][true_class]))
for true_class in range(0, J):
probs.append(binom.pmf(np.arange(0, J), J - 1, params[str(J)][true_class]))

return np.array(probs)
39 changes: 30 additions & 9 deletions dlordinal/distributions/exponential_distribution.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,44 @@
from scipy.special import softmax


def get_exponential_probabilities(n, p=1.0, tau=1.0):
"""Get probabilities from exponential distribution for n classes.
def get_exponential_softlabels(J, p=1.0, tau=1.0):
"""Get probabilities from exponential distribution for ``J`` classes or splits as
described in :footcite:t:`liu2020unimodal` and :footcite:t:`vargas2023exponential`.
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 exponential
function for the interval boundaries. The probability for the first interval is
computed as the value of the exponential function for the first interval boundary.
Then, a softmax function is applied to each row of the resulting matrix to obtain
valid probabilities.
The aforementioned exponential function is defined as follows:
:math:`f(x; k, p, \\tau) = \\frac{-|x - k|^p}{\\tau}` where :math:`k` is given by the order of the
true class for which the probability is computed, :math:`p` is the exponent
parameter and :math:`\\tau` is the scaling parameter.
Parameters
----------
n : int
J : int
Number of classes.
p : float, default=1.0
Exponent parameter.
Exponent parameter :math:`p`.
tau : float, default=1.0
Scaling parameter.
Scaling parameter :math:`\\tau`.
Raises
------
ValueError
If ``J`` is not a positive integer greater than 1.
Returns
-------
probs : 2d array-like
probs : 2d array-like of shape (J, J)
Matrix of probabilities where each row represents the true class
and each column the probability for class n.
and each column the probability for class j.
Example
-------
>>> from dlordinal.distributions import get_exponential_probabilities
>>> get_exponential_probabilities(5)
array([[0.63640865, 0.23412166, 0.08612854, 0.03168492, 0.01165623],
[0.19151597, 0.52059439, 0.19151597, 0.07045479, 0.02591887],
Expand All @@ -30,9 +48,12 @@ def get_exponential_probabilities(n, p=1.0, tau=1.0):
[0.01165623, 0.03168492, 0.08612854, 0.23412166, 0.63640865]])
"""

if J < 2 or not isinstance(J, int):
raise ValueError(f"{J=} must be a positive integer greater than 1")

probs = []

for true_class in range(0, n):
probs.append(-(np.abs(np.arange(0, n) - true_class) ** p) / tau)
for true_class in range(0, J):
probs.append(-(np.abs(np.arange(0, J) - true_class) ** p) / tau)

return softmax(np.array(probs), axis=1)
Loading

0 comments on commit 61024fc

Please sign in to comment.