strexp / captum /attr /_utils /approximation_methods.py
markytools's picture
added strexp
d61b9c7
raw
history blame
4.87 kB
#!/usr/bin/env python3
from enum import Enum
from typing import Callable, List, Tuple
import torch
class Riemann(Enum):
left = 1
right = 2
middle = 3
trapezoid = 4
SUPPORTED_RIEMANN_METHODS = [
"riemann_left",
"riemann_right",
"riemann_middle",
"riemann_trapezoid",
]
SUPPORTED_METHODS = SUPPORTED_RIEMANN_METHODS + ["gausslegendre"]
def approximation_parameters(
method: str,
) -> Tuple[Callable[[int], List[float]], Callable[[int], List[float]]]:
r"""Retrieves parameters for the input approximation `method`
Args:
method: The name of the approximation method. Currently only `riemann`
and gauss legendre are
"""
if method in SUPPORTED_RIEMANN_METHODS:
return riemann_builders(method=Riemann[method.split("_")[-1]])
if method == "gausslegendre":
return gauss_legendre_builders()
raise ValueError("Invalid integral approximation method name: {}".format(method))
def riemann_builders(
method: Riemann = Riemann.trapezoid,
) -> Tuple[Callable[[int], List[float]], Callable[[int], List[float]]]:
r"""Step sizes are identical and alphas are scaled in [0, 1]
Args:
n: The number of integration steps
method: `left`, `right`, `middle` and `trapezoid` riemann
Returns:
2-element tuple of **step_sizes**, **alphas**:
- **step_sizes** (*callable*):
`step_sizes` takes the number of steps as an
input argument and returns an array of steps sizes which
sum is smaller than or equal to one.
- **alphas** (*callable*):
`alphas` takes the number of steps as an input argument
and returns the multipliers/coefficients for the inputs
of integrand in the range of [0, 1]
"""
def step_sizes(n: int) -> List[float]:
assert n > 1, "The number of steps has to be larger than one"
deltas = [1 / n] * n
if method == Riemann.trapezoid:
deltas[0] /= 2
deltas[-1] /= 2
return deltas
def alphas(n: int) -> List[float]:
assert n > 1, "The number of steps has to be larger than one"
if method == Riemann.trapezoid:
return torch.linspace(0, 1, n).tolist()
elif method == Riemann.left:
return torch.linspace(0, 1 - 1 / n, n).tolist()
elif method == Riemann.middle:
return torch.linspace(1 / (2 * n), 1 - 1 / (2 * n), n).tolist()
elif method == Riemann.right:
return torch.linspace(1 / n, 1, n).tolist()
else:
raise AssertionError("Provided Reimann approximation method is not valid.")
# This is not a standard riemann method but in many cases it
# leades to faster approaximation. Test cases for small number of steps
# do not make sense but for larger number of steps the approximation is
# better therefore leaving this option available
# if method == 'riemann_include_endpoints':
# return [i / (n - 1) for i in range(n)]
return step_sizes, alphas
def gauss_legendre_builders() -> Tuple[
Callable[[int], List[float]], Callable[[int], List[float]]
]:
r"""Numpy's `np.polynomial.legendre` function helps to compute step sizes
and alpha coefficients using gauss-legendre quadrature rule.
Since numpy returns the integration parameters in different scales we need to
rescale them to adjust to the desired scale.
Gauss Legendre quadrature rule for approximating the integrals was originally
proposed by [Xue Feng and her intern Hauroun Habeeb]
(https://research.fb.com/people/feng-xue/).
Args:
n (int): The number of integration steps
Returns:
2-element tuple of **step_sizes**, **alphas**:
- **step_sizes** (*callable*):
`step_sizes` takes the number of steps as an
input argument and returns an array of steps sizes which
sum is smaller than or equal to one.
- **alphas** (*callable*):
`alphas` takes the number of steps as an input argument
and returns the multipliers/coefficients for the inputs
of integrand in the range of [0, 1]
"""
# allow using riemann even without np
import numpy as np
def step_sizes(n: int) -> List[float]:
assert n > 0, "The number of steps has to be larger than zero"
# Scaling from 2 to 1
return list(0.5 * np.polynomial.legendre.leggauss(n)[1])
def alphas(n: int) -> List[float]:
assert n > 0, "The number of steps has to be larger than zero"
# Scaling from [-1, 1] to [0, 1]
return list(0.5 * (1 + np.polynomial.legendre.leggauss(n)[0]))
return step_sizes, alphas