Source code for pysiglib.sig_metrics

from typing import Union, Optional
import numpy as np
import torch

from .param_checks import check_type_multiple
from .static_kernels import StaticKernel
from .sig_kernel import sig_kernel_gram, _ensure_3d

[docs] def sig_score( sample : Union[np.ndarray, torch.tensor], y : Union[np.ndarray, torch.tensor], dyadic_order : Union[int, tuple], *, lam : float = 1., static_kernel : Optional[StaticKernel] = None, time_aug : bool = False, lead_lag : bool = False, end_time : float = 1., n_jobs : int = 1, max_batch : int = -1 ) -> Union[np.ndarray, torch.tensor]: """ Computes the (generalised) signature kernel score .. math:: \\phi_{\\text{sig}}(\\mu, y) := \\lambda \\mathbb{E}_{x,x' \\sim \\mu}[k(x,x')] - 2\\mathbb{E}_{x\\sim \\mu}[k(x,y)]. Given a batch of sample paths :math:`\\{x_i\\}_{i=1}^m \\sim \\mu` and a path :math:`y`, the score is computed using the consistent and unbiased estimator .. math:: \\widehat{\\phi}_{\\text{sig}}(\\mu, y) := \\frac{\\lambda }{m(m-1)} \\sum_{j \\neq i} k(x_i, x_j) - \\frac{2}{m} \\sum_i k(x_i, y). Optionally, a static kernel can be specified. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :param sample: The batch of sample paths drawn from :math:`\\mu`, of shape ``(*batch_shape, length_1, dimension)``. :type sample: numpy.ndarray | torch.tensor :param y: The path(s) :math:`y`, of shape ``(*batch_shape_y, length_2, dimension)``. ``batch_shape_y`` may be empty (single ``y``) or arbitrary; the score is computed independently for each ``y``. Independent of ``sample``'s batch shape. :type y: numpy.ndarray | torch.tensor :param dyadic_order: If set to a positive integer :math:`\\lambda`, will refine the paths by a factor of :math:`2^\\lambda`. If set to a tuple of positive integers :math:`(\\lambda_1, \\lambda_2)`, will refine the first path by :math:`2^{\\lambda_1}` and the second path by :math:`2^{\\lambda_2}`. :type dyadic_order: int | tuple :param lam: The parameter :math:`\\lambda` of the generalised signature kernel score (default = 1.0). :type lam: float :param static_kernel: Static kernel passed to the signature kernel computation. If ``None`` (default), the linear kernel will be used. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :type static_kernel: None | pysiglib.StaticKernel :param time_aug: If set to True, will compute the signature of the time-augmented path, :math:`\\hat{x}_t := (t, x_t)`, defined as the original path with an extra channel set to time, :math:`t`. This channel spans :math:`[0, t_L]`, where :math:`t_L` is given by the parameter ``end_time``. :type time_aug: bool :param lead_lag: If set to True, will compute the signature of the path after applying the lead-lag transformation. :type lead_lag: bool :param end_time: End time for time-augmentation, :math:`t_L`. :type end_time: float :param n_jobs: (Only applicable to CPU computation) Number of threads to run in parallel. If n_jobs = 1, the computation is run serially. If set to -1, all available threads are used. For n_jobs below -1, (max_threads + 1 + n_jobs) threads are used. For example if n_jobs = -2, all threads but one are used. :type n_jobs: int :param max_batch: Maximum batch size to run in parallel. If the computation is failing due to insufficient memory, this parameter should be decreased. If set to -1, the entire batch is computed in parallel. :type max_batch: int :return: Signature kernel score, of shape ``batch_shape_y`` (or ``(1,)`` if ``y`` is a single 2D path). :rtype: numpy.ndarray | torch.tensor Example: --------- .. code-block:: python import torch import pysiglib sample = torch.rand((20, 100, 5)) y = torch.rand((100, 5)) score = pysiglib.sig_score(sample, y, dyadic_order=2) print(score.shape) # (1,) .. code-block:: python # Using a static kernel with regularisation and time augmentation import torch import pysiglib sample = torch.rand((20, 100, 5)) y = torch.rand((100, 5)) rbf = pysiglib.RBFKernel(sigma=1.0) score = pysiglib.sig_score( sample, y, dyadic_order=2, lam=0.1, static_kernel=rbf, time_aug=True, max_batch=8, ) print(score) .. code-block:: python # Multi-dim sample batch and a batch of y paths # one score is returned per y import torch import pysiglib sample = torch.rand((4, 5, 100, 5)) # 4 * 5 = 20 sample paths total y = torch.rand((3, 100, 5)) # batch of 3 target paths score = pysiglib.sig_score(sample, y, dyadic_order=2) print(score.shape) # (3,) """ check_type_multiple(sample, "sample", (np.ndarray, torch.Tensor)) check_type_multiple(y, "y", (np.ndarray, torch.Tensor)) is_numpy = isinstance(sample, np.ndarray) sample = torch.as_tensor(sample) y = torch.as_tensor(y) batch_shape_y = tuple(y.shape[:-2]) sample = _ensure_3d(sample) y = _ensure_3d(y) B = sample.shape[0] if B < 2: raise ValueError("sig_score requires at least 2 sample paths (got {}).".format(B)) xx = sig_kernel_gram(sample, sample, dyadic_order, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch, return_grid=False) xy = sig_kernel_gram(sample, y, dyadic_order, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch, return_grid=False) xx_sum = (torch.sum(xx) - torch.trace(xx)) / (B * (B - 1.)) xy_sum = torch.sum(xy, dim=0) * (2. / B) res = lam * xx_sum - xy_sum if batch_shape_y: res = res.reshape(*batch_shape_y) if is_numpy: return res.numpy() return res
[docs] def expected_sig_score( sample1 : Union[np.ndarray, torch.tensor], sample2 : Union[np.ndarray, torch.tensor], dyadic_order : Union[int, tuple], *, lam : float = 1., static_kernel : Optional[StaticKernel] = None, time_aug : bool = False, lead_lag : bool = False, end_time : float = 1., n_jobs : int = 1, max_batch : int = -1 ) -> Union[np.ndarray, torch.tensor]: """ Computes the expected (generalised) signature kernel score .. math:: \\mathbb{E}_{y \\sim \\nu}[\\phi_{\\text{sig}}(\\mu, y)] := \\lambda \\mathbb{E}_{x,x' \\sim \\mu}[k(x,x')] - 2\\mathbb{E}_{x,y\\sim \\mu \\times \\nu}[k(x,y)]. Given a batch of sample paths :math:`\\{x_i\\}_{i=1}^m \\sim \\mu` and :math:`\\{y_j\\}_{j=1}^n \\sim \\nu`, the score is computed using the unbiased estimator .. math:: \\frac{\\lambda }{m(m-1)} \\sum_{j \\neq i} k(x_i, x_j) - \\frac{2}{mn} \\sum_{i,j} k(x_i, y_j). Optionally, a static kernel can be specified. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :param sample1: The batch of sample paths drawn from :math:`\\mu`, of shape ``(*batch_shape, length_1, dimension)``. :type sample1: numpy.ndarray | torch.tensor :param sample2: The batch of sample paths drawn from :math:`\\nu`, of shape ``(*batch_shape, length_2, dimension)``. Independent of ``sample1``'s batch shape. :type sample2: numpy.ndarray | torch.tensor :param dyadic_order: If set to a positive integer :math:`\\lambda`, will refine the paths by a factor of :math:`2^\\lambda`. If set to a tuple of positive integers :math:`(\\lambda_1, \\lambda_2)`, will refine the first path by :math:`2^{\\lambda_1}` and the second path by :math:`2^{\\lambda_2}`. :type dyadic_order: int | tuple :param lam: The parameter :math:`\\lambda` of the generalised signature kernel score (default = 1.0). :type lam: float :param static_kernel: Static kernel passed to the signature kernel computation. If ``None`` (default), the linear kernel will be used. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :type static_kernel: None | pysiglib.StaticKernel :param time_aug: If set to True, will compute the signature of the time-augmented path, :math:`\\hat{x}_t := (t, x_t)`, defined as the original path with an extra channel set to time, :math:`t`. This channel spans :math:`[0, t_L]`, where :math:`t_L` is given by the parameter ``end_time``. :type time_aug: bool :param lead_lag: If set to True, will compute the signature of the path after applying the lead-lag transformation. :type lead_lag: bool :param end_time: End time for time-augmentation, :math:`t_L`. :type end_time: float :param n_jobs: (Only applicable to CPU computation) Number of threads to run in parallel. If n_jobs = 1, the computation is run serially. If set to -1, all available threads are used. For n_jobs below -1, (max_threads + 1 + n_jobs) threads are used. For example if n_jobs = -2, all threads but one are used. :type n_jobs: int :param max_batch: Maximum batch size to run in parallel. If the computation is failing due to insufficient memory, this parameter should be decreased. If set to -1, the entire batch is computed in parallel. :type max_batch: int :return: Expected signature kernel score, of shape ``(1,)``. :rtype: numpy.ndarray | torch.tensor Example: --------- .. code-block:: python import torch import pysiglib sample1 = torch.rand((20, 100, 5)) sample2 = torch.rand((15, 100, 5)) score = pysiglib.expected_sig_score(sample1, sample2, dyadic_order=2) print(score) .. code-block:: python # Expected score with lead-lag and a static kernel import torch import pysiglib sample1 = torch.rand((20, 100, 5)) sample2 = torch.rand((15, 100, 5)) rbf = pysiglib.RBFKernel(sigma=1.0) score = pysiglib.expected_sig_score( sample1, sample2, dyadic_order=2, static_kernel=rbf, lead_lag=True, max_batch=8, ) print(score) """ res = sig_score(sample1, sample2, dyadic_order, lam=lam, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch) return res.mean().reshape(1)
[docs] def sig_mmd( sample1 : Union[np.ndarray, torch.tensor], sample2 : Union[np.ndarray, torch.tensor], dyadic_order : Union[int, tuple], *, static_kernel : Optional[StaticKernel] = None, time_aug : bool = False, lead_lag : bool = False, end_time : float = 1., n_jobs : int = 1, max_batch : int = -1 ) -> Union[np.ndarray, torch.tensor]: """ Computes the squared maximum mean discrepancy (MMD) .. math:: d(\\mu, \\nu)^2 := \\sup_f(\\mathbb{E}_{x \\sim \\mu}[f(x)] - \\mathbb{E}_{y \\sim \\nu}[f(y)]). .. math:: = \\mathbb{E}_{xx' \\sim \\mu}[k(x,x')] - 2\\mathbb{E}_{x,y \\sim \\mu \\times \\nu}[k(x,y)] + \\mathbb{E}_{y,y' \\sim \\nu}[k(y,y')]. Given a batch of sample paths :math:`\\{x_i\\}_{i=1}^m \\sim \\mu` and :math:`\\{y_j\\}_{j=1}^n \\sim \\nu`, the MMD is computed using the unbiased estimator .. math:: \\widehat{d}(\\mu, \\nu)^2 = \\frac{1}{m(m-1)}\\sum_{j \\neq i}k(x_i, x_j) - \\frac{2}{mn}\\sum_{i,j}k(x_i, x_j) + \\frac{1}{n(n-1)}\\sum_{j \\neq i} k(y_i, y_j). Optionally, a static kernel can be specified. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :param sample1: The batch of sample paths drawn from :math:`\\mu`, of shape ``(*batch_shape, length_1, dimension)``. All dimensions before ``length_1`` are batch dimensions and are flattened to a single batch :math:`m` of paths. :type sample1: numpy.ndarray | torch.tensor :param sample2: The batch of sample paths drawn from :math:`\\nu`, of shape ``(*batch_shape, length_2, dimension)``. All dimensions before ``length_2`` are flattened to a single batch :math:`n` of paths. Independent of ``sample1``'s batch shape. :type sample2: numpy.ndarray | torch.tensor :param dyadic_order: If set to a positive integer :math:`\\lambda`, will refine the paths by a factor of :math:`2^\\lambda`. If set to a tuple of positive integers :math:`(\\lambda_1, \\lambda_2)`, will refine the first path by :math:`2^{\\lambda_1}` and the second path by :math:`2^{\\lambda_2}`. :type dyadic_order: int | tuple :param static_kernel: Static kernel passed to the signature kernel computation. If ``None`` (default), the linear kernel will be used. For details, see the documentation on :doc:`static kernels </pages/signature_kernels/static_kernels>`. :type static_kernel: None | pysiglib.StaticKernel :param time_aug: If set to True, will compute the signature of the time-augmented path, :math:`\\hat{x}_t := (t, x_t)`, defined as the original path with an extra channel set to time, :math:`t`. This channel spans :math:`[0, t_L]`, where :math:`t_L` is given by the parameter ``end_time``. :type time_aug: bool :param lead_lag: If set to True, will compute the signature of the path after applying the lead-lag transformation. :type lead_lag: bool :param end_time: End time for time-augmentation, :math:`t_L`. :type end_time: float :param n_jobs: (Only applicable to CPU computation) Number of threads to run in parallel. If n_jobs = 1, the computation is run serially. If set to -1, all available threads are used. For n_jobs below -1, (max_threads + 1 + n_jobs) threads are used. For example if n_jobs = -2, all threads but one are used. :type n_jobs: int :param max_batch: Maximum batch size to run in parallel. If the computation is failing due to insufficient memory, this parameter should be decreased. If set to -1, the entire batch is computed in parallel. :type max_batch: int :return: Signature MMD :rtype: numpy.ndarray | torch.tensor Example: --------- .. code-block:: python import torch import pysiglib sample1 = torch.rand((20, 100, 5)) sample2 = torch.rand((15, 100, 5)) mmd = pysiglib.sig_mmd(sample1, sample2, dyadic_order=2) print(mmd) .. code-block:: python # MMD with a static kernel and time augmentation import torch import pysiglib sample1 = torch.rand((20, 100, 5)) sample2 = torch.rand((15, 100, 5)) rbf = pysiglib.RBFKernel(sigma=0.5) mmd = pysiglib.sig_mmd( sample1, sample2, dyadic_order=2, static_kernel=rbf, time_aug=True, max_batch=8, ) print(mmd) """ is_numpy = isinstance(sample1, np.ndarray) sample1 = torch.as_tensor(sample1) sample2 = torch.as_tensor(sample2) sample1 = _ensure_3d(sample1) sample2 = _ensure_3d(sample2) m = sample1.shape[0] n = sample2.shape[0] if m < 2: raise ValueError("sig_mmd requires at least 2 paths in sample1 (got {}).".format(m)) if n < 2: raise ValueError("sig_mmd requires at least 2 paths in sample2 (got {}).".format(n)) xx = sig_kernel_gram(sample1, sample1, dyadic_order, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch, return_grid=False) xy = sig_kernel_gram(sample1, sample2, dyadic_order, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch, return_grid=False) yy = sig_kernel_gram(sample2, sample2, dyadic_order, static_kernel=static_kernel, time_aug=time_aug, lead_lag=lead_lag, end_time=end_time, n_jobs=n_jobs, max_batch=max_batch, return_grid=False) xx_sum = (torch.sum(xx) - torch.trace(xx)) / (m * (m - 1)) xy_sum = 2. * torch.mean(xy) yy_sum = (torch.sum(yy) - torch.trace(yy)) / (n * (n - 1)) res = xx_sum - xy_sum + yy_sum if is_numpy: return res.numpy() return res