Source code for alibi.utils.approximation_methods

# This module is copied from the captum library at
# https://github.com/pytorch/captum/blob/master/captum/attr/_utils/approximation_methods.py

from typing import List, Callable, Tuple
import numpy as np
from enum import Enum


[docs] class Riemann(Enum): left = 1 right = 2 middle = 3 trapezoid = 4
SUPPORTED_RIEMANN_METHODS = [ "riemann_left", "riemann_right", "riemann_middle", "riemann_trapezoid", ] """ Riemann integration methods. """ SUPPORTED_METHODS = SUPPORTED_RIEMANN_METHODS + ["gausslegendre"]
[docs] def approximation_parameters( method: str, ) -> Tuple[Callable[[int], List[float]], Callable[[int], List[float]]]: """ Retrieves parameters for the input approximation `method`. Parameters ---------- method The name of the approximation method. Currently supported only: ``'riemann_*'`` and ``'gausslegendre``'. Check :py:data:`alibi.utils.approximation_methods.SUPPORTED_RIEMANN_METHODS` for all ``'riemann_*'`` possible values. """ 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))
[docs] def riemann_builders( method: Riemann = Riemann.trapezoid, ) -> Tuple[Callable[[int], List[float]], Callable[[int], List[float]]]: """ Step sizes are identical and alphas are scaled in [0, 1]. Parameters ---------- n The number of integration steps. method Riemann method: ``Riemann.left`` | ``Riemann.right`` | ``Riemann.middle`` | ``Riemann.trapezoid``. Returns ------- 2-element tuple consisting of - `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 list(np.linspace(0, 1, n)) elif method == Riemann.left: return list(np.linspace(0, 1 - 1 / n, n)) elif method == Riemann.middle: return list(np.linspace(1 / (2 * n), 1 - 1 / (2 * n), n)) elif method == Riemann.right: return list(np.linspace(1 / n, 1, n)) else: raise AssertionError("Provided Reimann approximation method is not valid.") # This is not a standard riemann method but in many cases it # leads to faster approximation. 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
[docs] def gauss_legendre_builders() -> Tuple[ Callable[[int], List[float]], Callable[[int], List[float]] ]: """ `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/). Parameters ---------- n The number of integration steps. Returns ------- 2-element tuple consisting of - `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 > 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