diff --git a/alibi_detect/cd/_domain_clf.py b/alibi_detect/cd/_domain_clf.py index 84e540e7d..942ef43fe 100644 --- a/alibi_detect/cd/_domain_clf.py +++ b/alibi_detect/cd/_domain_clf.py @@ -1,5 +1,4 @@ from abc import ABC, abstractmethod -from typing import Callable import numpy as np from sklearn.svm import SVC from sklearn.calibration import CalibratedClassifierCV @@ -34,7 +33,6 @@ def predict(self, x: np.ndarray) -> np.ndarray: class _SVCDomainClf(_DomainClf): def __init__(self, - kernel: Callable, cal_method: str = 'sigmoid', clf_kwargs: dict = None): """ @@ -52,52 +50,51 @@ def __init__(self, clf_kwargs A dictionary of keyword arguments to be passed to the :py:class:`~sklearn.svm.SVC` classifier. """ - self.kernel = kernel self.cal_method = cal_method clf_kwargs = clf_kwargs or {} - self.clf = SVC(kernel=self.kernel, **clf_kwargs) + self.clf = SVC(kernel='precomputed', **clf_kwargs) - def fit(self, x: np.ndarray, y: np.ndarray): + def fit(self, K_x: np.ndarray, y: np.ndarray): """ Method to fit the classifier. Parameters ---------- - x - Array containing conditioning variables for each instance. + K_x + Kernel matrix on the conditioning variables. y Boolean array marking the domain each instance belongs to (`0` for reference, `1` for test). """ clf = self.clf - clf.fit(x, y) + clf.fit(K_x, y) self.clf = clf - def calibrate(self, x: np.ndarray, y: np.ndarray): + def calibrate(self, K_x: np.ndarray, y: np.ndarray): """ Method to calibrate the classifier's predicted probabilities. Parameters ---------- - x - Array containing conditioning variables for each instance. + K_x + Kernel matrix on the conditioning variables. y Boolean array marking the domain each instance belongs to (`0` for reference, `1` for test). """ clf = CalibratedClassifierCV(self.clf, method=self.cal_method, cv='prefit') - clf.fit(x, y) + clf.fit(K_x, y) self.clf = clf - def predict(self, x: np.ndarray) -> np.ndarray: + def predict(self, K_x: np.ndarray) -> np.ndarray: """ The classifier's predict method. Parameters ---------- - x - Array containing conditioning variables for each instance. + K_x + Kernel matrix on the conditioning variables. Returns ------- Propensity scores (the probability of being test instances). """ - return self.clf.predict_proba(x)[:, 1] + return self.clf.predict_proba(K_x)[:, 1] diff --git a/alibi_detect/cd/base.py b/alibi_detect/cd/base.py index ca050c462..b09754312 100644 --- a/alibi_detect/cd/base.py +++ b/alibi_detect/cd/base.py @@ -508,7 +508,6 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, configure_kernel_from_x_ref: bool = True, n_permutations: int = 100, input_shape: Optional[tuple] = None, @@ -536,9 +535,6 @@ def __init__( for reservoir sampling {'reservoir_sampling': n} is passed. preprocess_fn Function to preprocess the data before computing the data drift metrics. - sigma - Optionally set the Gaussian RBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. configure_kernel_from_x_ref Whether to already configure the kernel bandwidth from the reference data. n_permutations @@ -553,12 +549,7 @@ def __init__( if p_val is None: logger.warning('No p-value set for the drift threshold. Need to set it to detect data drift.') - self.infer_sigma = configure_kernel_from_x_ref - if configure_kernel_from_x_ref and isinstance(sigma, np.ndarray): - self.infer_sigma = False - logger.warning('`sigma` is specified for the kernel and `configure_kernel_from_x_ref` ' - 'is set to True. `sigma` argument takes priority over ' - '`configure_kernel_from_x_ref` (set to False).') + self.infer_parameter = configure_kernel_from_x_ref # x_ref preprocessing self.preprocess_at_init = preprocess_at_init @@ -668,7 +659,6 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, n_permutations: int = 100, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, @@ -731,7 +721,6 @@ def __init__( # Other attributes self.p_val = p_val - self.sigma = sigma self.update_x_ref = update_x_ref self.preprocess_fn = preprocess_fn self.n = len(x_ref) diff --git a/alibi_detect/cd/context_aware.py b/alibi_detect/cd/context_aware.py index bb02c2ad3..ff2e193c2 100644 --- a/alibi_detect/cd/context_aware.py +++ b/alibi_detect/cd/context_aware.py @@ -4,6 +4,8 @@ from alibi_detect.utils.frameworks import has_pytorch, has_tensorflow, BackendValidator, Framework from alibi_detect.utils.warnings import deprecated_alias from alibi_detect.base import DriftConfigMixin +from alibi_detect.utils.pytorch.kernels import BaseKernel as BaseKernel_pt +from alibi_detect.utils.tensorflow.kernels import BaseKernel as BaseKernel_tf if has_pytorch: from alibi_detect.cd.pytorch.context_aware import ContextMMDDriftTorch @@ -26,8 +28,8 @@ def __init__( preprocess_at_init: bool = True, update_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - x_kernel: Callable = None, - c_kernel: Callable = None, + x_kernel: Union[BaseKernel_pt, BaseKernel_tf] = None, + c_kernel: Union[BaseKernel_pt, BaseKernel_tf] = None, n_permutations: int = 1000, prop_c_held: float = 0.25, n_folds: int = 5, @@ -109,9 +111,9 @@ def __init__( else: from alibi_detect.utils.pytorch.kernels import GaussianRBF # type: ignore[no-redef] if x_kernel is None: - kwargs.update({'x_kernel': GaussianRBF}) + kwargs.update({'x_kernel': GaussianRBF()}) if c_kernel is None: - kwargs.update({'c_kernel': GaussianRBF}) + kwargs.update({'c_kernel': GaussianRBF()}) if backend == Framework.TENSORFLOW: kwargs.pop('device', None) diff --git a/alibi_detect/cd/keops/learned_kernel.py b/alibi_detect/cd/keops/learned_kernel.py index e3073713d..8a6b7d7c2 100644 --- a/alibi_detect/cd/keops/learned_kernel.py +++ b/alibi_detect/cd/keops/learned_kernel.py @@ -2,13 +2,12 @@ from functools import partial from tqdm import tqdm import numpy as np -from pykeops.torch import LazyTensor import torch import torch.nn as nn from torch.utils.data import DataLoader from typing import Callable, Dict, List, Optional, Union, Tuple from alibi_detect.cd.base import BaseLearnedKernelDrift -from alibi_detect.utils.pytorch import get_device, predict_batch +from alibi_detect.utils.pytorch import get_device from alibi_detect.utils.pytorch.data import TorchDataset from alibi_detect.utils.frameworks import Framework @@ -137,6 +136,7 @@ def __init__( self.device = get_device(device) self.original_kernel = kernel self.kernel = deepcopy(kernel) + self.kernel = self.kernel.to(self.device) # Check kernel format self.has_proj = hasattr(self.kernel, 'proj') and isinstance(self.kernel.proj, nn.Module) @@ -174,21 +174,10 @@ def __init__(self, kernel: nn.Module, var_reg: float, has_proj: bool, has_kernel def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: n = len(x) - if self.has_proj and isinstance(self.kernel.proj, nn.Module): - x_proj, y_proj = self.kernel.proj(x), self.kernel.proj(y) - else: - x_proj, y_proj = x, y - x2_proj, x_proj = LazyTensor(x_proj[None, :, :]), LazyTensor(x_proj[:, None, :]) - y2_proj, y_proj = LazyTensor(y_proj[None, :, :]), LazyTensor(y_proj[:, None, :]) - if self.has_kernel_b: - x2, x = LazyTensor(x[None, :, :]), LazyTensor(x[:, None, :]) - y2, y = LazyTensor(y[None, :, :]), LazyTensor(y[:, None, :]) - else: - x, x2, y, y2 = None, None, None, None - k_xy = self.kernel(x_proj, y2_proj, x, y2) - k_xx = self.kernel(x_proj, x2_proj, x, x2) - k_yy = self.kernel(y_proj, y2_proj, y, y2) + k_xy = self.kernel(x, y) + k_xx = self.kernel(x, x) + k_yy = self.kernel(y, y) h_mat = k_xx + k_yy - k_xy - k_xy.t() h_i = h_mat.sum(1).squeeze(-1) @@ -221,6 +210,7 @@ def score(self, x: Union[np.ndarray, list]) -> Tuple[float, float, float]: self.kernel = deepcopy(self.original_kernel) if self.retrain_from_scratch else self.kernel self.kernel = self.kernel.to(self.device) + train_args = [self.j_hat, (dl_ref_tr, dl_cur_tr), self.device] LearnedKernelDriftKeops.trainer(*train_args, **self.train_kwargs) # type: ignore @@ -263,42 +253,24 @@ def _mmd2(self, x_all: Union[list, torch.Tensor], perms: List[torch.Tensor], m: preprocess_batch_fn = self.train_kwargs['preprocess_fn'] if isinstance(preprocess_batch_fn, Callable): # type: ignore[arg-type] x_all = preprocess_batch_fn(x_all) # type: ignore[operator] - if self.has_proj: - x_all_proj = predict_batch(x_all, self.kernel.proj, device=self.device, batch_size=self.batch_size_predict, - dtype=x_all.dtype if isinstance(x_all, torch.Tensor) else torch.float32) - else: - x_all_proj = x_all - x, x2, y, y2 = None, None, None, None + x, y = None, None k_xx, k_yy, k_xy = [], [], [] for batch in range(self.n_batches): i, j = batch * self.batch_size_perms, (batch + 1) * self.batch_size_perms # Stack a batch of permuted reference and test tensors and their projections - x_proj = torch.cat([x_all_proj[perm[:m]][None, :, :] for perm in perms[i:j]], 0) - y_proj = torch.cat([x_all_proj[perm[m:]][None, :, :] for perm in perms[i:j]], 0) - if self.has_kernel_b: - x = torch.cat([x_all[perm[:m]][None, :, :] for perm in perms[i:j]], 0) - y = torch.cat([x_all[perm[m:]][None, :, :] for perm in perms[i:j]], 0) + x = torch.cat([x_all[perm[:m]][None, :, :] for perm in perms[i:j]], 0) + y = torch.cat([x_all[perm[m:]][None, :, :] for perm in perms[i:j]], 0) if batch == 0: - x_proj = torch.cat([x_all_proj[None, :m, :], x_proj], 0) - y_proj = torch.cat([x_all_proj[None, m:, :], y_proj], 0) - if self.has_kernel_b: - x = torch.cat([x_all[None, :m, :], x], 0) # type: ignore[call-overload] - y = torch.cat([x_all[None, m:, :], y], 0) # type: ignore[call-overload] - x_proj, y_proj = x_proj.to(self.device), y_proj.to(self.device) - if self.has_kernel_b: - x, y = x.to(self.device), y.to(self.device) + x = torch.cat([x_all[None, :m, :], x], 0) # type: ignore[call-overload] + y = torch.cat([x_all[None, m:, :], y], 0) # type: ignore[call-overload] + x, y = x.to(self.device), y.to(self.device) # Batch-wise kernel matrix computation over the permutations with torch.no_grad(): - x2_proj, x_proj = LazyTensor(x_proj[:, None, :, :]), LazyTensor(x_proj[:, :, None, :]) - y2_proj, y_proj = LazyTensor(y_proj[:, None, :, :]), LazyTensor(y_proj[:, :, None, :]) - if self.has_kernel_b: - x2, x = LazyTensor(x[:, None, :, :]), LazyTensor(x[:, :, None, :]) - y2, y = LazyTensor(y[:, None, :, :]), LazyTensor(y[:, :, None, :]) - k_xy.append(self.kernel(x_proj, y2_proj, x, y2).sum(1).sum(1).squeeze(-1)) - k_xx.append(self.kernel(x_proj, x2_proj, x, x2).sum(1).sum(1).squeeze(-1)) - k_yy.append(self.kernel(y_proj, y2_proj, y, y2).sum(1).sum(1).squeeze(-1)) + k_xy.append(self.kernel(x, y).sum(1).sum(1).squeeze(-1)) + k_xx.append(self.kernel(x, x).sum(1).sum(1).squeeze(-1)) + k_yy.append(self.kernel(y, y).sum(1).sum(1).squeeze(-1)) c_xx, c_yy, c_xy = 1 / (m * (m - 1)), 1 / (n * (n - 1)), 2. / (m * n) # Note that the MMD^2 estimates assume that the diagonal of the kernel matrix consists of 1's diff --git a/alibi_detect/cd/keops/mmd.py b/alibi_detect/cd/keops/mmd.py index 5b1a2fdc0..3b93d50fb 100644 --- a/alibi_detect/cd/keops/mmd.py +++ b/alibi_detect/cd/keops/mmd.py @@ -1,10 +1,9 @@ import logging import numpy as np -from pykeops.torch import LazyTensor import torch from typing import Callable, Dict, List, Optional, Tuple, Union from alibi_detect.cd.base import BaseMMDDrift -from alibi_detect.utils.keops.kernels import GaussianRBF +from alibi_detect.utils.keops.kernels import BaseKernel, GaussianRBF from alibi_detect.utils.pytorch import get_device from alibi_detect.utils.frameworks import Framework @@ -20,8 +19,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - kernel: Callable = GaussianRBF, - sigma: Optional[np.ndarray] = None, + kernel: Union[BaseKernel, Callable] = GaussianRBF, configure_kernel_from_x_ref: bool = True, n_permutations: int = 100, batch_size_permutations: int = 1000000, @@ -53,9 +51,6 @@ def __init__( Function to preprocess the data before computing the data drift metrics. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. configure_kernel_from_x_ref Whether to already configure the kernel bandwidth from the reference data. n_permutations @@ -77,7 +72,6 @@ def __init__( preprocess_at_init=preprocess_at_init, update_x_ref=update_x_ref, preprocess_fn=preprocess_fn, - sigma=sigma, configure_kernel_from_x_ref=configure_kernel_from_x_ref, n_permutations=n_permutations, input_shape=input_shape, @@ -88,24 +82,39 @@ def __init__( # set device self.device = get_device(device) - # initialize kernel - sigma = torch.from_numpy(sigma).to(self.device) if isinstance(sigma, # type: ignore[assignment] - np.ndarray) else None - self.kernel = kernel(sigma).to(self.device) if kernel == GaussianRBF else kernel + # initialise kernel + if isinstance(kernel, BaseKernel): + self.kernel = kernel + elif kernel == GaussianRBF: + self.kernel = kernel() + else: + raise ValueError("kernel must be an instance of alibi_detect.utils.keops.kernels.BaseKernel or a callable ") + + self.kernel_parameter_specified = True + if hasattr(kernel, 'parameter_dict'): + for param in self.kernel.parameter_dict.keys(): + kernel.parameter_dict[param].value.to(self.device) + if kernel.parameter_dict[param].requires_init: + self.given_kernel_parameter = False + break + + if self.kernel_parameter_specified and self.infer_parameter: + self.infer_parameter = False + logger.warning('parameters are specified for the kernel and `configure_kernel_from_x_ref` ' + 'is set to True. Specified parameters take priority over ' + '`configure_kernel_from_x_ref` (set to False).') # set the correct MMD^2 function based on the batch size for the permutations self.batch_size = batch_size_permutations self.n_batches = 1 + (n_permutations - 1) // batch_size_permutations # infer the kernel bandwidth from the reference data - if isinstance(sigma, torch.Tensor): - self.infer_sigma = False - elif self.infer_sigma: - x = torch.from_numpy(self.x_ref).to(self.device) - _ = self.kernel(LazyTensor(x[:, None, :]), LazyTensor(x[None, :, :]), infer_sigma=self.infer_sigma) - self.infer_sigma = False + if self.infer_parameter: + x = torch.from_numpy(self.x_ref).to(self.device).reshape(1, self.x_ref.shape[0], -1) + _ = self.kernel(x, x, infer_parameter=self.infer_parameter) + self.infer_parameter = False else: - self.infer_sigma = True + self.infer_parameter = True def _mmd2(self, x_all: torch.Tensor, perms: List[torch.Tensor], m: int, n: int) \ -> Tuple[torch.Tensor, torch.Tensor]: @@ -139,12 +148,10 @@ def _mmd2(self, x_all: torch.Tensor, perms: List[torch.Tensor], m: int, n: int) x, y = x.to(self.device), y.to(self.device) # batch-wise kernel matrix computation over the permutations - k_xy.append(self.kernel( - LazyTensor(x[:, :, None, :]), LazyTensor(y[:, None, :, :]), self.infer_sigma).sum(1).sum(1).squeeze(-1)) - k_xx.append(self.kernel( - LazyTensor(x[:, :, None, :]), LazyTensor(x[:, None, :, :])).sum(1).sum(1).squeeze(-1)) - k_yy.append(self.kernel( - LazyTensor(y[:, :, None, :]), LazyTensor(y[:, None, :, :])).sum(1).sum(1).squeeze(-1)) + k_xy.append(self.kernel(x, y, infer_parameter=self.infer_parameter).sum(1).sum(1).squeeze(-1)) + k_xx.append(self.kernel(x, x, infer_parameter=self.infer_parameter).sum(1).sum(1).squeeze(-1)) + k_yy.append(self.kernel(y, y, infer_parameter=self.infer_parameter).sum(1).sum(1).squeeze(-1)) + c_xx, c_yy, c_xy = 1 / (m * (m - 1)), 1 / (n * (n - 1)), 2. / (m * n) # Note that the MMD^2 estimates assume that the diagonal of the kernel matrix consists of 1's stats = c_xx * (torch.cat(k_xx) - m) + c_yy * (torch.cat(k_yy) - n) - c_xy * torch.cat(k_xy) diff --git a/alibi_detect/cd/keops/tests/test_learned_kernel_keops.py b/alibi_detect/cd/keops/tests/test_learned_kernel_keops.py index 646027fe3..02ce9bcdc 100644 --- a/alibi_detect/cd/keops/tests/test_learned_kernel_keops.py +++ b/alibi_detect/cd/keops/tests/test_learned_kernel_keops.py @@ -9,29 +9,35 @@ from alibi_detect.utils.pytorch import mmd2_from_kernel_matrix if has_keops: from alibi_detect.cd.keops.learned_kernel import LearnedKernelDriftKeops - from alibi_detect.utils.keops import GaussianRBF - from pykeops.torch import LazyTensor + from alibi_detect.utils.keops import GaussianRBF, BaseKernel, ProjKernel n = 50 # number of instances used for the reference and test data samples in the tests if has_keops: - class MyKernel(nn.Module): + class MyKernel(BaseKernel): def __init__(self, n_features: int, proj: bool): super().__init__() sigma = .1 - self.kernel = GaussianRBF(trainable=True, sigma=torch.Tensor([sigma])) + self.kernel_a = GaussianRBF(trainable=True, sigma=torch.Tensor([sigma])) + self.log_sigma_a = self.kernel_a.parameter_dict['log-sigma'].value self.has_proj = proj if proj: self.proj = nn.Linear(n_features, 2) self.kernel_b = GaussianRBF(trainable=True, sigma=torch.Tensor([sigma])) + self.proj_kernel = ProjKernel(self.proj, self.kernel_b) + self.comp_kernel = self.proj_kernel + self.kernel_a + self.log_sigma_b = self.kernel_b.parameter_dict['log-sigma'].value + else: + self.comp_kernel = self.kernel_a - def forward(self, x_proj: LazyTensor, y_proj: LazyTensor, x: Optional[LazyTensor] = None, - y: Optional[LazyTensor] = None) -> LazyTensor: - similarity = self.kernel(x_proj, y_proj) - if self.has_proj: - similarity = similarity + self.kernel_b(x, y) - return similarity + def kernel_function( + self, + x: torch.Tensor, + y: torch.Tensor, + infer_parameter: Optional[bool] = False + ) -> torch.Tensor: + return self.comp_kernel(x, y, infer_parameter) # test List[Any] inputs to the detector @@ -124,7 +130,7 @@ def test_lkdrift(lkdrift_params): if isinstance(preprocess_batch, Callable): x_all = preprocess_batch(x_all) - kernel = GaussianRBFTorch(sigma=cd.kernel.kernel.sigma) + kernel = GaussianRBFTorch(sigma=cd.kernel.kernel_a.sigma.cpu()) kernel_mat = kernel(x_all, x_all) mmd2_torch = mmd2_from_kernel_matrix(kernel_mat, n_test) - np.testing.assert_almost_equal(mmd2, mmd2_torch, decimal=6) + np.testing.assert_almost_equal(mmd2.cpu(), mmd2_torch.cpu(), decimal=6) diff --git a/alibi_detect/cd/keops/tests/test_mmd_keops.py b/alibi_detect/cd/keops/tests/test_mmd_keops.py index a64a78173..86ce980a0 100644 --- a/alibi_detect/cd/keops/tests/test_mmd_keops.py +++ b/alibi_detect/cd/keops/tests/test_mmd_keops.py @@ -112,9 +112,13 @@ def test_mmd(mmd_params): kernel = GaussianRBF(sigma=cd.kernel.sigma) if isinstance(preprocess_fn, Callable): x_ref, x_h1 = cd.preprocess(x_h1) - x_ref = torch.from_numpy(x_ref).float() - x_h1 = torch.from_numpy(x_h1).float() + x_ref = torch.from_numpy(x_ref).float().to(cd.kernel.sigma.device) + x_h1 = torch.from_numpy(x_h1).float().to(cd.kernel.sigma.device) x_all = torch.cat([x_ref, x_h1], 0) kernel_mat = kernel(x_all, x_all) mmd2_torch = mmd2_from_kernel_matrix(kernel_mat, x_h1.shape[0]) + if isinstance(mmd2, torch.Tensor): + mmd2 = mmd2.cpu().numpy() + if isinstance(mmd2_torch, torch.Tensor): + mmd2_torch = mmd2_torch.cpu().numpy() np.testing.assert_almost_equal(mmd2, mmd2_torch, decimal=6) diff --git a/alibi_detect/cd/lsdd.py b/alibi_detect/cd/lsdd.py index e8a45d30f..1514f5435 100644 --- a/alibi_detect/cd/lsdd.py +++ b/alibi_detect/cd/lsdd.py @@ -22,7 +22,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_permutations: int = 100, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, diff --git a/alibi_detect/cd/lsdd_online.py b/alibi_detect/cd/lsdd_online.py index d8d3d5bf6..15c36fb0f 100644 --- a/alibi_detect/cd/lsdd_online.py +++ b/alibi_detect/cd/lsdd_online.py @@ -18,7 +18,7 @@ def __init__( backend: str = 'tensorflow', preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_bootstraps: int = 1000, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, diff --git a/alibi_detect/cd/mmd.py b/alibi_detect/cd/mmd.py index 3a0c289a5..92da1f3c8 100644 --- a/alibi_detect/cd/mmd.py +++ b/alibi_detect/cd/mmd.py @@ -29,7 +29,6 @@ def __init__( update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, kernel: Callable = None, - sigma: Optional[np.ndarray] = None, configure_kernel_from_x_ref: bool = True, n_permutations: int = 100, batch_size_permutations: int = 1000000, @@ -63,9 +62,6 @@ def __init__( Function to preprocess the data before computing the data drift metrics. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. configure_kernel_from_x_ref Whether to already configure the kernel bandwidth from the reference data. n_permutations diff --git a/alibi_detect/cd/mmd_online.py b/alibi_detect/cd/mmd_online.py index a26624955..cee60e17b 100644 --- a/alibi_detect/cd/mmd_online.py +++ b/alibi_detect/cd/mmd_online.py @@ -5,9 +5,11 @@ if has_pytorch: from alibi_detect.cd.pytorch.mmd_online import MMDDriftOnlineTorch + from alibi_detect.utils.pytorch.kernels import BaseKernel as BaseKernelTorch if has_tensorflow: from alibi_detect.cd.tensorflow.mmd_online import MMDDriftOnlineTF + from alibi_detect.utils.tensorflow.kernels import BaseKernel as BaseKernelTF class MMDDriftOnline(DriftConfigMixin): @@ -19,8 +21,7 @@ def __init__( backend: str = 'tensorflow', preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - kernel: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, + kernel: Optional[Union[BaseKernelTorch, BaseKernelTF]] = None, n_bootstraps: int = 1000, device: Optional[str] = None, verbose: bool = True, @@ -51,10 +52,6 @@ def __init__( data will also be preprocessed. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. If `sigma` is not specified, the 'median - heuristic' is adopted whereby `sigma` is set as the median pairwise distance between reference samples. n_bootstraps The number of bootstrap simulations used to configure the thresholds. The larger this is the more accurately the desired ERT will be targeted. Should ideally be at least an order of magnitude diff --git a/alibi_detect/cd/pytorch/context_aware.py b/alibi_detect/cd/pytorch/context_aware.py index 7b63357ee..d3e2b89de 100644 --- a/alibi_detect/cd/pytorch/context_aware.py +++ b/alibi_detect/cd/pytorch/context_aware.py @@ -4,7 +4,7 @@ from typing import Callable, Dict, Optional, Tuple, Union from alibi_detect.cd.base import BaseContextMMDDrift from alibi_detect.utils.pytorch import get_device -from alibi_detect.utils.pytorch.kernels import GaussianRBF +from alibi_detect.utils.pytorch.kernels import BaseKernel, GaussianRBF from alibi_detect.utils.warnings import deprecated_alias from alibi_detect.utils.frameworks import Framework from alibi_detect.cd._domain_clf import _SVCDomainClf @@ -13,6 +13,29 @@ logger = logging.getLogger(__name__) +def _sigma_median_diag(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch.Tensor: + """ + Private version of the bandwidth estimation function :py:func:`~alibi_detect.utils.pytorch.kernels.sigma_median`, + with the +n (and -1) term excluded to account for the diagonal of the kernel matrix. + + Parameters + ---------- + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + + Returns + ------- + The computed bandwidth, `sigma`. + """ + n_median = np.prod(dist.shape) // 2 + sigma = (.5 * dist.flatten().sort().values[n_median].unsqueeze(dim=-1)) ** .5 + return sigma + + class ContextMMDDriftTorch(BaseContextMMDDrift): lams: Optional[Tuple[torch.Tensor, torch.Tensor]] = None @@ -26,8 +49,8 @@ def __init__( preprocess_at_init: bool = True, update_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - x_kernel: Callable = GaussianRBF, - c_kernel: Callable = GaussianRBF, + x_kernel: Union[BaseKernel, Callable] = GaussianRBF, + c_kernel: Union[BaseKernel, Callable] = GaussianRBF, n_permutations: int = 1000, prop_c_held: float = 0.25, n_folds: int = 5, @@ -111,9 +134,6 @@ def __init__( self.x_kernel = x_kernel(init_sigma_fn=_sigma_median_diag) if x_kernel == GaussianRBF else x_kernel self.c_kernel = c_kernel(init_sigma_fn=_sigma_median_diag) if c_kernel == GaussianRBF else c_kernel - # Initialize classifier (hardcoded for now) - self.clf = _SVCDomainClf(self.c_kernel) - def score(self, # type: ignore[override] x: Union[np.ndarray, list], c: np.ndarray) -> Tuple[float, float, float, Tuple]: """ @@ -137,6 +157,9 @@ def score(self, # type: ignore[override] x_ref = torch.from_numpy(x_ref).to(self.device) # type: ignore[assignment] c_ref = torch.from_numpy(self.c_ref).to(self.device) # type: ignore[assignment] + # Initialize classifier (hardcoded for now) + self.clf = _SVCDomainClf() + # Hold out a portion of contexts for conditioning on n, n_held = len(c), int(len(c)*self.prop_c_held) inds_held = np.random.choice(n, n_held, replace=False) @@ -155,12 +178,13 @@ def score(self, # type: ignore[override] L_held = self.c_kernel(c_held, c_all) # Fit and calibrate the domain classifier - c_all_np, bools_np = c_all.cpu().numpy(), bools.cpu().numpy() - self.clf.fit(c_all_np, bools_np) - self.clf.calibrate(c_all_np, bools_np) + bools_np = bools.cpu().numpy() + K_c_all_np = self.c_kernel(c_all, c_all).cpu().numpy() + self.clf.fit(K_c_all_np, bools_np) + self.clf.calibrate(K_c_all_np, bools_np) # Obtain n_permutations conditional reassignments - prop_scores = torch.as_tensor(self.clf.predict(c_all_np)) + prop_scores = torch.as_tensor(self.clf.predict(K_c_all_np)) self.redrawn_bools = [torch.bernoulli(prop_scores) for _ in range(self.n_permutations)] iters = tqdm(self.redrawn_bools, total=self.n_permutations) if self.verbose else self.redrawn_bools @@ -254,26 +278,3 @@ def _pick_lam(self, lams: torch.Tensor, K: torch.Tensor, L: torch.Tensor, n_fold kxx = torch.ones_like(lWk).to(lWk.device) * torch.max(K) losses += (lWKWl + kxx - 2*lWk).sum(-1) return lams[torch.argmin(losses)] - - -def _sigma_median_diag(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch.Tensor: - """ - Private version of the bandwidth estimation function :py:func:`~alibi_detect.utils.pytorch.kernels.sigma_median`, - with the +n (and -1) term excluded to account for the diagonal of the kernel matrix. - - Parameters - ---------- - x - Tensor of instances with dimension [Nx, features]. - y - Tensor of instances with dimension [Ny, features]. - dist - Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. - - Returns - ------- - The computed bandwidth, `sigma`. - """ - n_median = np.prod(dist.shape) // 2 - sigma = (.5 * dist.flatten().sort().values[int(n_median)].unsqueeze(dim=-1)) ** .5 - return sigma diff --git a/alibi_detect/cd/pytorch/lsdd.py b/alibi_detect/cd/pytorch/lsdd.py index cae318f97..9692024b4 100644 --- a/alibi_detect/cd/pytorch/lsdd.py +++ b/alibi_detect/cd/pytorch/lsdd.py @@ -19,7 +19,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_permutations: int = 100, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, @@ -77,7 +77,6 @@ def __init__( preprocess_at_init=preprocess_at_init, update_x_ref=update_x_ref, preprocess_fn=preprocess_fn, - sigma=sigma, n_permutations=n_permutations, n_kernel_centers=n_kernel_centers, lambda_rd_max=lambda_rd_max, @@ -97,26 +96,22 @@ def __init__( x_ref = torch.as_tensor(self.x_ref).to(self.device) # type: ignore[assignment] self._configure_normalization(x_ref) # type: ignore[arg-type] x_ref = self._normalize(x_ref) - self._initialize_kernel(x_ref) # type: ignore[arg-type] + self.kernel = GaussianRBF(sigma=torch.tensor(sigma).to(self.device) if sigma is not None else None) + _ = self.kernel(x_ref, x_ref, infer_parameter=True) # infer sigma self._configure_kernel_centers(x_ref) # type: ignore[arg-type] self.x_ref = x_ref.cpu().numpy() # type: ignore[union-attr] # For stability in high dimensions we don't divide H by (pi*sigma^2)^(d/2) # Results in an alternative test-stat of LSDD*(pi*sigma^2)^(d/2). Same p-vals etc. self.H = GaussianRBF(np.sqrt(2.) * self.kernel.sigma)(self.kernel_centers, self.kernel_centers) - def _initialize_kernel(self, x_ref: torch.Tensor): - if self.sigma is None: - self.kernel = GaussianRBF() - _ = self.kernel(x_ref, x_ref, infer_sigma=True) - else: - sigma = torch.from_numpy(self.sigma) - self.kernel = GaussianRBF(sigma) - def _configure_normalization(self, x_ref: torch.Tensor, eps: float = 1e-12): + x_ref = x_ref.to(self.device) x_ref_means = x_ref.mean(0) x_ref_stds = x_ref.std(0) - self._normalize = lambda x: (torch.as_tensor(x) - x_ref_means) / (x_ref_stds + eps) # type: ignore[assignment] - self._unnormalize = lambda x: (torch.as_tensor(x) * (x_ref_stds + eps) # type: ignore[assignment] + self._normalize = lambda x: (torch.as_tensor(x, device=self.device) # type: ignore[assignment] + - x_ref_means) / (x_ref_stds + eps) + self._unnormalize = lambda x: (torch.as_tensor(x, device=self.device) # type: ignore[assignment] + * (x_ref_stds + eps) + x_ref_means).cpu().numpy() def _configure_kernel_centers(self, x_ref: torch.Tensor): @@ -152,7 +147,8 @@ def score(self, x: Union[np.ndarray, list]) -> Tuple[float, float, float]: if self.preprocess_fn is not None and self.preprocess_at_init is False and not self.x_ref_preprocessed: self._configure_normalization(x_ref) # type: ignore[arg-type] x_ref = self._normalize(x_ref) - self._initialize_kernel(x_ref) # type: ignore[arg-type] + self.kernel = GaussianRBF() + _ = self.kernel(x_ref, x_ref, infer_parameter=True) # infer sigma self._configure_kernel_centers(x_ref) # type: ignore[arg-type] self.H = GaussianRBF(np.sqrt(2.) * self.kernel.sigma)(self.kernel_centers, self.kernel_centers) diff --git a/alibi_detect/cd/pytorch/lsdd_online.py b/alibi_detect/cd/pytorch/lsdd_online.py index a5c20ee40..c2ad0a521 100644 --- a/alibi_detect/cd/pytorch/lsdd_online.py +++ b/alibi_detect/cd/pytorch/lsdd_online.py @@ -4,7 +4,8 @@ from typing import Any, Callable, Optional, Union from alibi_detect.cd.base_online import BaseMultiDriftOnline from alibi_detect.utils.pytorch import get_device -from alibi_detect.utils.pytorch import GaussianRBF, permed_lsdds, quantile +from alibi_detect.utils.pytorch import permed_lsdds, quantile +from alibi_detect.utils.pytorch.kernels import GaussianRBF from alibi_detect.utils.frameworks import Framework from alibi_detect.base import DriftConfigMixin @@ -17,7 +18,7 @@ def __init__( window_size: int, preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_bootstraps: int = 1000, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, @@ -93,15 +94,7 @@ def __init__( self._configure_normalization() - # initialize kernel - if sigma is None: - x_ref = torch.from_numpy(self.x_ref).to(self.device) # type: ignore[assignment] - self.kernel = GaussianRBF() - _ = self.kernel(x_ref, x_ref, infer_sigma=True) - else: - sigma = torch.from_numpy(sigma).to(self.device) if isinstance(sigma, # type: ignore[assignment] - np.ndarray) else None - self.kernel = GaussianRBF(sigma) # type: ignore[arg-type] + self.kernel = GaussianRBF(torch.tensor(sigma).to(self.device) if sigma is not None else None) if self.n_kernel_centers is None: self.n_kernel_centers = 2 * window_size @@ -115,7 +108,8 @@ def _configure_normalization(self, eps: float = 1e-12): x_ref_means = x_ref.mean(0) x_ref_stds = x_ref.std(0) self._normalize = lambda x: (x - x_ref_means) / (x_ref_stds + eps) - self._unnormalize = lambda x: (torch.as_tensor(x) * (x_ref_stds + eps) + x_ref_means).cpu().numpy() + self._unnormalize = lambda x: (torch.as_tensor(x, device=self.device) * (x_ref_stds + eps) + + x_ref_means).cpu().numpy() self.x_ref = self._normalize(x_ref).cpu().numpy() def _configure_kernel_centers(self): diff --git a/alibi_detect/cd/pytorch/mmd.py b/alibi_detect/cd/pytorch/mmd.py index 666942b6c..8df7f8c97 100644 --- a/alibi_detect/cd/pytorch/mmd.py +++ b/alibi_detect/cd/pytorch/mmd.py @@ -5,7 +5,7 @@ from alibi_detect.cd.base import BaseMMDDrift from alibi_detect.utils.pytorch import get_device from alibi_detect.utils.pytorch.distance import mmd2_from_kernel_matrix -from alibi_detect.utils.pytorch.kernels import GaussianRBF +from alibi_detect.utils.pytorch.kernels import BaseKernel, GaussianRBF from alibi_detect.utils.warnings import deprecated_alias from alibi_detect.utils.frameworks import Framework @@ -22,8 +22,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - kernel: Callable = GaussianRBF, - sigma: Optional[np.ndarray] = None, + kernel: Union[BaseKernel, Callable] = GaussianRBF, configure_kernel_from_x_ref: bool = True, n_permutations: int = 100, device: Optional[str] = None, @@ -54,9 +53,6 @@ def __init__( Function to preprocess the data before computing the data drift metrics. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. configure_kernel_from_x_ref Whether to already configure the kernel bandwidth from the reference data. n_permutations @@ -76,7 +72,6 @@ def __init__( preprocess_at_init=preprocess_at_init, update_x_ref=update_x_ref, preprocess_fn=preprocess_fn, - sigma=sigma, configure_kernel_from_x_ref=configure_kernel_from_x_ref, n_permutations=n_permutations, input_shape=input_shape, @@ -87,22 +82,39 @@ def __init__( # set device self.device = get_device(device) - # initialize kernel - sigma = torch.from_numpy(sigma).to(self.device) if isinstance(sigma, # type: ignore[assignment] - np.ndarray) else None - self.kernel = kernel(sigma).to(self.device) if kernel == GaussianRBF else kernel + # initialise kernel + if isinstance(kernel, BaseKernel): + self.kernel = kernel + elif kernel == GaussianRBF: + self.kernel = kernel() + else: + raise ValueError("kernel must be an instance of alibi_detect.utils.pytorch.kernels.BaseKernel") + + self.kernel_parameter_specified = True + if hasattr(kernel, 'parameter_dict'): + for param in self.kernel.parameter_dict.keys(): + kernel.parameter_dict[param].value.to(self.device) + if kernel.parameter_dict[param].requires_init: + self.kernel_parameter_specified = False + break + + if self.kernel_parameter_specified and self.infer_parameter: + self.infer_parameter = False + logger.warning('parameters are specified for the kernel and `configure_kernel_from_x_ref` ' + 'is set to True. Specified parameters take priority over ' + '`configure_kernel_from_x_ref` (set to False).') # compute kernel matrix for the reference data - if self.infer_sigma or isinstance(sigma, torch.Tensor): + if self.infer_parameter or self.kernel_parameter_specified: x = torch.from_numpy(self.x_ref).to(self.device) - self.k_xx = self.kernel(x, x, infer_sigma=self.infer_sigma) - self.infer_sigma = False + self.k_xx = self.kernel(x, x, infer_parameter=self.infer_parameter) + self.infer_parameter = False else: - self.k_xx, self.infer_sigma = None, True + self.k_xx, self.infer_parameter = None, True def kernel_matrix(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: """ Compute and return full kernel matrix between arrays x and y. """ - k_xy = self.kernel(x, y, self.infer_sigma) + k_xy = self.kernel(x, y, self.infer_parameter) k_xx = self.k_xx if self.k_xx is not None and self.update_x_ref is None else self.kernel(x, x) k_yy = self.kernel(y, y) kernel_mat = torch.cat([torch.cat([k_xx, k_xy], 1), torch.cat([k_xy.T, k_yy], 1)], 0) diff --git a/alibi_detect/cd/pytorch/mmd_online.py b/alibi_detect/cd/pytorch/mmd_online.py index 808fe5c5d..12d9760c5 100644 --- a/alibi_detect/cd/pytorch/mmd_online.py +++ b/alibi_detect/cd/pytorch/mmd_online.py @@ -4,7 +4,7 @@ from typing import Any, Callable, Optional, Union from alibi_detect.cd.base_online import BaseMultiDriftOnline from alibi_detect.utils.pytorch import get_device -from alibi_detect.utils.pytorch.kernels import GaussianRBF +from alibi_detect.utils.pytorch.kernels import BaseKernel, GaussianRBF from alibi_detect.utils.pytorch import zero_diag, quantile from alibi_detect.utils.frameworks import Framework @@ -17,8 +17,7 @@ def __init__( window_size: int, preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - kernel: Callable = GaussianRBF, - sigma: Optional[np.ndarray] = None, + kernel: Union[BaseKernel, Callable] = GaussianRBF, n_bootstraps: int = 1000, device: Optional[str] = None, verbose: bool = True, @@ -47,10 +46,6 @@ def __init__( data will also be preprocessed. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. If `sigma` is not specified, the 'median - heuristic' is adopted whereby `sigma` is set as the median pairwise distance between reference samples. n_bootstraps The number of bootstrap simulations used to configure the thresholds. The larger this is the more accurately the desired ERT will be targeted. Should ideally be at least an order of magnitude @@ -81,14 +76,17 @@ def __init__( # set device self.device = get_device(device) - # initialize kernel - sigma = torch.from_numpy(sigma).to(self.device) if isinstance(sigma, # type: ignore[assignment] - np.ndarray) else None - self.kernel = kernel(sigma) if kernel == GaussianRBF else kernel + # initialise kernel + if isinstance(kernel, BaseKernel): + self.kernel = kernel + elif kernel == GaussianRBF: + self.kernel = kernel() + else: + raise ValueError("kernel must be an instance of alibi_detect.utils.pytorch.kernels.BaseKernel") # compute kernel matrix for the reference data self.x_ref = torch.from_numpy(self.x_ref).to(self.device) - self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_sigma=(sigma is None)) + self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_parameter=self.kernel.init_required) self._configure_thresholds() self._initialise() diff --git a/alibi_detect/cd/tensorflow/context_aware.py b/alibi_detect/cd/tensorflow/context_aware.py index 6f9b773e4..3181267bd 100644 --- a/alibi_detect/cd/tensorflow/context_aware.py +++ b/alibi_detect/cd/tensorflow/context_aware.py @@ -4,7 +4,7 @@ import tensorflow_probability as tfp from typing import Callable, Dict, Optional, Tuple, Union, List from alibi_detect.cd.base import BaseContextMMDDrift -from alibi_detect.utils.tensorflow.kernels import GaussianRBF +from alibi_detect.utils.tensorflow.kernels import GaussianRBF, BaseKernel from alibi_detect.utils.warnings import deprecated_alias from alibi_detect.utils.frameworks import Framework from alibi_detect.cd._domain_clf import _SVCDomainClf @@ -13,6 +13,29 @@ logger = logging.getLogger(__name__) +def _sigma_median_diag(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: + """ + Private version of the bandwidth estimation function :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`, + with the +n (and -1) term excluded to account for the diagonal of the kernel matrix. + + Parameters + ---------- + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + + Returns + ------- + The computed bandwidth, `sigma`. + """ + n_median = tf.math.reduce_prod(dist.shape) // 2 + sigma = tf.expand_dims((.5 * tf.sort(tf.reshape(dist, (-1,)))[n_median]) ** .5, axis=0) + return sigma + + class ContextMMDDriftTF(BaseContextMMDDrift): lams: Optional[Tuple[tf.Tensor, tf.Tensor]] @@ -26,8 +49,8 @@ def __init__( preprocess_at_init: bool = True, update_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - x_kernel: Callable = GaussianRBF, - c_kernel: Callable = GaussianRBF, + x_kernel: Union[BaseKernel, Callable] = GaussianRBF, + c_kernel: Union[BaseKernel, Callable] = GaussianRBF, n_permutations: int = 1000, prop_c_held: float = 0.25, n_folds: int = 5, @@ -104,9 +127,6 @@ def __init__( self.x_kernel = x_kernel(init_sigma_fn=_sigma_median_diag) if x_kernel == GaussianRBF else x_kernel self.c_kernel = c_kernel(init_sigma_fn=_sigma_median_diag) if c_kernel == GaussianRBF else c_kernel - # Initialize classifier (hardcoded for now) - self.clf = _SVCDomainClf(self.c_kernel) - def score(self, # type: ignore[override] x: Union[np.ndarray, list], c: np.ndarray) -> Tuple[float, float, float, Tuple]: """ @@ -128,6 +148,9 @@ def score(self, # type: ignore[override] """ x_ref, x = self.preprocess(x) + # Initialize classifier (hardcoded for now) + self.clf = _SVCDomainClf() + # Hold out a portion of contexts for conditioning on n, n_held = len(c), int(len(c)*self.prop_c_held) inds_held = np.random.choice(n, n_held, replace=False) @@ -145,12 +168,13 @@ def score(self, # type: ignore[override] L_held = self.c_kernel(c_held, c_all) # Fit and calibrate the domain classifier - c_all_np, bools_np = c_all.numpy(), bools.numpy() - self.clf.fit(c_all_np, bools_np) - self.clf.calibrate(c_all_np, bools_np) + bools_np = bools.numpy() + K_c_all_np = self.c_kernel(c_all, c_all).numpy() + self.clf.fit(K_c_all_np, bools_np) + self.clf.calibrate(K_c_all_np, bools_np) # Obtain n_permutations conditional reassignments - prop_scores = self.clf.predict(c_all_np) + prop_scores = self.clf.predict(K_c_all_np) self.redrawn_bools = [tfp.distributions.Bernoulli(probs=prop_scores).sample() for _ in range(self.n_permutations)] iters = tqdm(self.redrawn_bools, total=self.n_permutations) if self.verbose else self.redrawn_bools @@ -271,26 +295,3 @@ def _split_chunks(n: int, p: int) -> List[int]: else: chunks = [n // p + 1] * (n % p) + [n // p] * (p - n % p) return chunks - - -def _sigma_median_diag(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: - """ - Private version of the bandwidth estimation function :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`, - with the +n (and -1) term excluded to account for the diagonal of the kernel matrix. - - Parameters - ---------- - x - Tensor of instances with dimension [Nx, features]. - y - Tensor of instances with dimension [Ny, features]. - dist - Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. - - Returns - ------- - The computed bandwidth, `sigma`. - """ - n_median = tf.math.reduce_prod(dist.shape) // 2 - sigma = tf.expand_dims((.5 * tf.sort(tf.reshape(dist, (-1,)))[n_median]) ** .5, axis=0) - return sigma diff --git a/alibi_detect/cd/tensorflow/lsdd.py b/alibi_detect/cd/tensorflow/lsdd.py index ef0335ae9..8f31e9bbf 100644 --- a/alibi_detect/cd/tensorflow/lsdd.py +++ b/alibi_detect/cd/tensorflow/lsdd.py @@ -18,7 +18,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_permutations: int = 100, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, @@ -72,7 +72,6 @@ def __init__( preprocess_at_init=preprocess_at_init, update_x_ref=update_x_ref, preprocess_fn=preprocess_fn, - sigma=sigma, n_permutations=n_permutations, n_kernel_centers=n_kernel_centers, lambda_rd_max=lambda_rd_max, @@ -85,21 +84,14 @@ def __init__( x_ref = tf.convert_to_tensor(self.x_ref) self._configure_normalization(x_ref) x_ref = self._normalize(x_ref) - self._initialize_kernel(x_ref) + self.kernel = GaussianRBF(tf.cast(sigma) if sigma is not None else None) + _ = self.kernel(x_ref, x_ref, infer_parameter=True) # infer sigma self._configure_kernel_centers(x_ref) self.x_ref = x_ref.numpy() # type: ignore[union-attr] # For stability in high dimensions we don't divide H by (pi*sigma^2)^(d/2) # Results in an alternative test-stat of LSDD*(pi*sigma^2)^(d/2). Same p-vals etc. self.H = GaussianRBF(np.sqrt(2.) * self.kernel.sigma)(self.kernel_centers, self.kernel_centers) - def _initialize_kernel(self, x_ref: tf.Tensor): - if self.sigma is None: - self.kernel = GaussianRBF() - _ = self.kernel(x_ref, x_ref, infer_sigma=True) - else: - sigma = tf.convert_to_tensor(self.sigma) - self.kernel = GaussianRBF(sigma) - def _configure_normalization(self, x_ref: tf.Tensor, eps: float = 1e-12): x_ref_means = tf.reduce_mean(x_ref, axis=0) x_ref_stds = tf.math.reduce_std(x_ref, axis=0) @@ -137,7 +129,8 @@ def score(self, x: Union[np.ndarray, list]) -> Tuple[float, float, float]: if self.preprocess_fn is not None and not self.preprocess_at_init and not self.x_ref_preprocessed: self._configure_normalization(x_ref) x_ref = self._normalize(x_ref) - self._initialize_kernel(x_ref) + self.kernel = GaussianRBF() + _ = self.kernel(x_ref, x_ref, infer_parameter=True) # infer sigma self._configure_kernel_centers(x_ref) self.H = GaussianRBF(np.sqrt(2.) * self.kernel.sigma)(self.kernel_centers, self.kernel_centers) diff --git a/alibi_detect/cd/tensorflow/lsdd_online.py b/alibi_detect/cd/tensorflow/lsdd_online.py index 540884c5f..483a0a1d9 100644 --- a/alibi_detect/cd/tensorflow/lsdd_online.py +++ b/alibi_detect/cd/tensorflow/lsdd_online.py @@ -3,7 +3,8 @@ import tensorflow as tf from typing import Any, Callable, Optional, Union from alibi_detect.cd.base_online import BaseMultiDriftOnline -from alibi_detect.utils.tensorflow import GaussianRBF, quantile, permed_lsdds +from alibi_detect.utils.tensorflow import quantile, permed_lsdds +from alibi_detect.utils.tensorflow.kernels import GaussianRBF from alibi_detect.utils.frameworks import Framework @@ -15,7 +16,7 @@ def __init__( window_size: int, preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - sigma: Optional[np.ndarray] = None, + sigma: Optional[Union[np.ndarray, float]] = None, n_bootstraps: int = 1000, n_kernel_centers: Optional[int] = None, lambda_rd_max: float = 0.2, @@ -84,13 +85,7 @@ def __init__( self._configure_normalization() - # initialize kernel - if sigma is None: - self.kernel = GaussianRBF() - _ = self.kernel(self.x_ref, self.x_ref, infer_sigma=True) - else: - sigma = tf.convert_to_tensor(sigma) - self.kernel = GaussianRBF(sigma) + self.kernel = GaussianRBF(sigma=tf.cast(sigma) if sigma is not None else None) if self.n_kernel_centers is None: self.n_kernel_centers = 2*window_size diff --git a/alibi_detect/cd/tensorflow/mmd.py b/alibi_detect/cd/tensorflow/mmd.py index 977e1d18c..d6d7aa693 100644 --- a/alibi_detect/cd/tensorflow/mmd.py +++ b/alibi_detect/cd/tensorflow/mmd.py @@ -4,7 +4,7 @@ from typing import Callable, Dict, Optional, Tuple, Union from alibi_detect.cd.base import BaseMMDDrift from alibi_detect.utils.tensorflow.distance import mmd2_from_kernel_matrix -from alibi_detect.utils.tensorflow.kernels import GaussianRBF +from alibi_detect.utils.tensorflow.kernels import GaussianRBF, BaseKernel from alibi_detect.utils.warnings import deprecated_alias from alibi_detect.utils.frameworks import Framework @@ -21,8 +21,7 @@ def __init__( preprocess_at_init: bool = True, update_x_ref: Optional[Dict[str, int]] = None, preprocess_fn: Optional[Callable] = None, - kernel: Callable = GaussianRBF, - sigma: Optional[np.ndarray] = None, + kernel: Union[BaseKernel, Callable] = GaussianRBF, configure_kernel_from_x_ref: bool = True, n_permutations: int = 100, input_shape: Optional[tuple] = None, @@ -52,9 +51,6 @@ def __init__( Function to preprocess the data before computing the data drift metrics. kernel Kernel used for the MMD computation, defaults to Gaussian RBF kernel. - sigma - Optionally set the GaussianRBF kernel bandwidth. Can also pass multiple bandwidth values as an array. - The kernel evaluation is then averaged over those bandwidths. configure_kernel_from_x_ref Whether to already configure the kernel bandwidth from the reference data. n_permutations @@ -71,7 +67,6 @@ def __init__( preprocess_at_init=preprocess_at_init, update_x_ref=update_x_ref, preprocess_fn=preprocess_fn, - sigma=sigma, configure_kernel_from_x_ref=configure_kernel_from_x_ref, n_permutations=n_permutations, input_shape=input_shape, @@ -79,21 +74,37 @@ def __init__( ) self.meta.update({'backend': Framework.TENSORFLOW.value}) - # initialize kernel - if isinstance(sigma, np.ndarray): - sigma = tf.convert_to_tensor(sigma) - self.kernel = kernel(sigma) if kernel == GaussianRBF else kernel + # initialise kernel + if isinstance(kernel, BaseKernel): + self.kernel = kernel + elif kernel == GaussianRBF: + self.kernel = kernel() + else: + raise ValueError("kernel must be an instance of alibi_detect.utils.tensorflow.kernels.BaseKernel") + + self.kernel_parameter_specified = True + if hasattr(kernel, 'parameter_dict'): + for param in self.kernel.parameter_dict.keys(): + if kernel.parameter_dict[param].requires_init: + self.given_kernel_parameter = False + break + + if self.kernel_parameter_specified and self.infer_parameter: + self.infer_parameter = False + logger.warning('parameters are specified for the kernel and `configure_kernel_from_x_ref` ' + 'is set to True. Specified parameters take priority over ' + '`configure_kernel_from_x_ref` (set to False).') # compute kernel matrix for the reference data - if self.infer_sigma or isinstance(sigma, tf.Tensor): - self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_sigma=self.infer_sigma) + if self.infer_parameter or self.kernel_parameter_specified: + self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_parameter=self.infer_parameter) self.infer_sigma = False else: self.k_xx, self.infer_sigma = None, True def kernel_matrix(self, x: Union[np.ndarray, tf.Tensor], y: Union[np.ndarray, tf.Tensor]) -> tf.Tensor: """ Compute and return full kernel matrix between arrays x and y. """ - k_xy = self.kernel(x, y, self.infer_sigma) + k_xy = self.kernel(x, y, self.infer_parameter) k_xx = self.k_xx if self.k_xx is not None and self.update_x_ref is None else self.kernel(x, x) k_yy = self.kernel(y, y) kernel_mat = tf.concat([tf.concat([k_xx, k_xy], 1), tf.concat([tf.transpose(k_xy, (1, 0)), k_yy], 1)], 0) diff --git a/alibi_detect/cd/tensorflow/mmd_online.py b/alibi_detect/cd/tensorflow/mmd_online.py index 3d4a6b57a..5ae31c760 100644 --- a/alibi_detect/cd/tensorflow/mmd_online.py +++ b/alibi_detect/cd/tensorflow/mmd_online.py @@ -3,7 +3,7 @@ import tensorflow as tf from typing import Any, Callable, Optional, Union from alibi_detect.cd.base_online import BaseMultiDriftOnline -from alibi_detect.utils.tensorflow.kernels import GaussianRBF +from alibi_detect.utils.tensorflow.kernels import BaseKernel, GaussianRBF from alibi_detect.utils.tensorflow import zero_diag, quantile, subset_matrix from alibi_detect.utils.frameworks import Framework @@ -16,8 +16,7 @@ def __init__( window_size: int, preprocess_fn: Optional[Callable] = None, x_ref_preprocessed: bool = False, - kernel: Callable = GaussianRBF, - sigma: Optional[np.ndarray] = None, + kernel: Union[BaseKernel, Callable] = GaussianRBF, n_bootstraps: int = 1000, verbose: bool = True, input_shape: Optional[tuple] = None, @@ -73,13 +72,16 @@ def __init__( ) self.meta.update({'backend': Framework.TENSORFLOW.value}) - # initialize kernel - if isinstance(sigma, np.ndarray): - sigma = tf.convert_to_tensor(sigma) - self.kernel = kernel(sigma) if kernel == GaussianRBF else kernel + # initialise kernel + if isinstance(kernel, BaseKernel): + self.kernel = kernel + elif kernel == GaussianRBF: + self.kernel = kernel() + else: + raise ValueError("kernel must be an instance of alibi_detect.utils.tensorflow.kernels.BaseKernel") # compute kernel matrix for the reference data - self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_sigma=(sigma is None)) + self.k_xx = self.kernel(self.x_ref, self.x_ref, infer_parameter=self.kernel.init_required) self._configure_thresholds() self._initialise() diff --git a/alibi_detect/saving/loading.py b/alibi_detect/saving/loading.py index 977da1ac3..b1febdcb5 100644 --- a/alibi_detect/saving/loading.py +++ b/alibi_detect/saving/loading.py @@ -130,6 +130,7 @@ def _load_detector_config(filepath: Union[str, os.PathLike]) -> ConfigurableDete # Resolve and validate config cfg = validate_config(cfg) + logger.info('Validated unresolved config.') cfg = resolve_config(cfg, config_dir=config_dir) cfg = validate_config(cfg, resolved=True) @@ -369,6 +370,8 @@ def _get_nested_value(dic: dict, keys: list) -> Any: dic = dic[key] except (TypeError, KeyError): return None + except IndexError: + return None # only for scalar in composite kernels as it doesn't have any keys return dic @@ -466,8 +469,11 @@ def resolve_config(cfg: dict, config_dir: Optional[Path]) -> dict: if config_dir is not None: _prepend_cfg_filepaths(cfg, config_dir) + # get additional fields to resolve for composite kernels TODO make a private function for this part, get temp fields + FIELDS_TO_RESOLVE_TEMP = _add_composite_fields(cfg) + # Resolve filepaths (load files) and resolve function/object registries - for key in FIELDS_TO_RESOLVE: + for key in FIELDS_TO_RESOLVE_TEMP: logger.info('Resolving config field: {}.'.format(key)) src = _get_nested_value(cfg, key) obj = None @@ -519,6 +525,72 @@ def resolve_config(cfg: dict, config_dir: Optional[Path]) -> dict: return cfg +def _add_composite_fields(cfg): + """ + Check if the cfg contains a composite kernel and add the fields to resolve. + + Parameters + ---------- + cfg + Config dict. + + Returns + ------- + FIELDS_TO_RESOLVE_TEMP + List of fields to resolve. + """ + FIELDS_TO_RESOLVE_TEMP = FIELDS_TO_RESOLVE.copy() + if 'kernel' in cfg: + if isinstance(cfg['kernel'], dict): + if (cfg['kernel']['kernel_type'] == 'Sum') or (cfg['kernel']['kernel_type'] == 'Product'): + FIELDS_TO_RESOLVE_TEMP = FIELDS_TO_RESOLVE.copy() + composite_fields = _get_composite_kernel_fields(cfg['kernel']) + for field in composite_fields: + field.insert(0, 'kernel') + loc = FIELDS_TO_RESOLVE_TEMP.index(['kernel']) + FIELDS_TO_RESOLVE_TEMP[loc:loc] = composite_fields + return FIELDS_TO_RESOLVE_TEMP + + +def _get_composite_kernel_fields(cfg: dict) -> list: + """ + Get additional fields to resolve for composite kernels. + + Parameters + ---------- + cfg + The config dict. + + Returns + ------- + The additional fields to resolve. + """ + fields = [] + if 'kernel_type' in cfg: + if (cfg['kernel_type'] == 'Sum') or (cfg['kernel_type'] == 'Product'): + kernel_number = len(cfg['kernel_list']) + for i in range(kernel_number): + if isinstance(cfg['kernel_list']['comp_{}'.format(i)], dict): + if 'kernel_type' in cfg['kernel_list']['comp_{}'.format(i)]: + if (cfg['kernel_list']['comp_{}'.format(i)]['kernel_type'] == 'Sum') or \ + (cfg['kernel_list']['comp_{}'.format(i)]['kernel_type'] == 'Product'): + fields.extend(_get_composite_kernel_fields(cfg['kernel_list']['comp_{}'.format(i)])) + elif cfg['kernel_list']['comp_{}'.format(i)]['kernel_type'] == 'GaussianRBF': + fields.append(['kernel_list', 'comp_{}'.format(i), 'src']) + fields.append(['kernel_list', 'comp_{}'.format(i), 'init_sigma_fn']) + elif cfg['kernel_list']['comp_{}'.format(i)]['kernel_type'] == 'RationalQuadratic': + fields.append(['kernel_list', 'comp_{}'.format(i), 'src']) + fields.append(['kernel_list', 'comp_{}'.format(i), 'init_sigma_fn']) + fields.append(['kernel_list', 'comp_{}'.format(i), 'init_alpha_fn']) + elif cfg['kernel_list']['comp_{}'.format(i)]['kernel_type'] == 'Period': + fields.append(['kernel_list', 'comp_{}'.format(i), 'src']) + fields.append(['kernel_list', 'comp_{}'.format(i), 'init_sigma_fn']) + fields.append(['kernel_list', 'comp_{}'.format(i), 'init_tau_fn']) + else: + raise ValueError('Unknown kernel type: {}'.format(cfg['comp_{}'.format(i)]['kernel_type'])) + return fields + + def _replace(cfg: dict, orig: Optional[str], new: Optional[str]) -> dict: """ Recursively traverse a nested dictionary and replace values. diff --git a/alibi_detect/saving/registry.py b/alibi_detect/saving/registry.py index b1ad20303..41a4a5621 100644 --- a/alibi_detect/saving/registry.py +++ b/alibi_detect/saving/registry.py @@ -42,14 +42,18 @@ def my_function(x: np.ndarray) -> np.ndarray: preprocess_drift as preprocess_drift_tf from alibi_detect.utils.tensorflow.data import TFDataset as TFDataset_tf from alibi_detect.utils.tensorflow.kernels import \ - GaussianRBF as GaussianRBF_tf, sigma_median as sigma_median_tf + GaussianRBF as GaussianRBF_tf, sigma_median as sigma_median_tf, \ + log_sigma_median as log_sigma_median_tf, RationalQuadratic as RationalQuadratic_tf, \ + Periodic as Periodic_tf, SumKernel as SumKernel_tf, ProductKernel as ProductKernel_tf from alibi_detect.cd.tensorflow.context_aware import _sigma_median_diag as _sigma_median_diag_tf if has_pytorch: from alibi_detect.cd.pytorch import \ preprocess_drift as preprocess_drift_torch from alibi_detect.utils.pytorch.kernels import \ - GaussianRBF as GaussianRBF_torch, sigma_median as sigma_median_torch + GaussianRBF as GaussianRBF_torch, sigma_median as sigma_median_torch, \ + log_sigma_median as log_sigma_median_torch, RationalQuadratic as RationalQuadratic_torch, \ + Periodic as Periodic_torch, SumKernel as SumKernel_torch, ProductKernel as ProductKernel_torch from alibi_detect.cd.pytorch.context_aware import _sigma_median_diag as _sigma_median_diag_torch # Create registry @@ -58,13 +62,23 @@ def my_function(x: np.ndarray) -> np.ndarray: # Register alibi-detect classes/functions if has_tensorflow: registry.register('utils.tensorflow.kernels.GaussianRBF', func=GaussianRBF_tf) + registry.register('utils.tensorflow.kernels.RationalQuadratic', func=RationalQuadratic_tf) + registry.register('utils.tensorflow.kernels.Periodic', func=Periodic_tf) + registry.register('utils.tensorflow.kernels.SumKernel', func=SumKernel_tf) + registry.register('utils.tensorflow.kernels.ProductKernel', func=ProductKernel_tf) registry.register('utils.tensorflow.kernels.sigma_median', func=sigma_median_tf) + registry.register('utils.tensorflow.kernels.log_sigma_median', func=log_sigma_median_tf) registry.register('cd.tensorflow.context_aware._sigma_median_diag', func=_sigma_median_diag_tf) registry.register('cd.tensorflow.preprocess.preprocess_drift', func=preprocess_drift_tf) registry.register('utils.tensorflow.data.TFDataset', func=TFDataset_tf) if has_pytorch: registry.register('utils.pytorch.kernels.GaussianRBF', func=GaussianRBF_torch) + registry.register('utils.pytorch.kernels.RationalQuadratic', func=RationalQuadratic_torch) + registry.register('utils.pytorch.kernels.Periodic', func=Periodic_torch) + registry.register('utils.pytorch.kernels.SumKernel', func=SumKernel_torch) + registry.register('utils.pytorch.kernels.ProductKernel', func=ProductKernel_torch) registry.register('utils.pytorch.kernels.sigma_median', func=sigma_median_torch) + registry.register('utils.pytorch.kernels.log_sigma_median', func=log_sigma_median_torch) registry.register('cd.pytorch.context_aware._sigma_median_diag', func=_sigma_median_diag_torch) registry.register('cd.pytorch.preprocess.preprocess_drift', func=preprocess_drift_torch) diff --git a/alibi_detect/saving/saving.py b/alibi_detect/saving/saving.py index 9648e404f..3e8588207 100644 --- a/alibi_detect/saving/saving.py +++ b/alibi_detect/saving/saving.py @@ -503,6 +503,22 @@ def _save_kernel_config(kernel: Callable, if not isinstance(kernel_b, str) and kernel_b is not None: cfg_kernel['kernel_b'] = _save_kernel_config(cfg_kernel['kernel_b'], base_path, Path('kernel_b')) + # if a composite kernel + elif hasattr(kernel, 'kernel_list'): + kernel_class = kernel.__class__ + + if hasattr(kernel, 'get_config'): + cfg_kernel = kernel.get_config() # type: ignore[attr-defined] + else: + raise AttributeError("The detector's `kernel` must have a .get_config() method for it to be saved.") + + for i, k in enumerate(kernel.kernel_list): + if hasattr(k, 'get_config'): + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + _save_kernel_config(k, base_path, Path(local_path, 'kernel_{}'.format(i))) + cfg_kernel['kernel_list'] = dict(sorted(cfg_kernel['kernel_list'].items())) + cfg_kernel['src'], _ = _serialize_object(kernel_class, base_path, local_path.joinpath('kernel')) + # If any other kernel, serialize the class to disk and get config else: if isinstance(kernel, type): # if still a class @@ -512,8 +528,18 @@ def _save_kernel_config(kernel: Callable, kernel_class = kernel.__class__ if hasattr(kernel, 'get_config'): cfg_kernel = kernel.get_config() # type: ignore[attr-defined] - cfg_kernel['init_sigma_fn'], _ = _serialize_object(cfg_kernel['init_sigma_fn'], base_path, - local_path.joinpath('init_sigma_fn')) + if 'init_sigma_fn' in cfg_kernel: + if cfg_kernel['init_sigma_fn'] is not None: + cfg_kernel['init_sigma_fn'], _ = _serialize_object(cfg_kernel['init_sigma_fn'], base_path, + local_path.joinpath('init_sigma_fn')) + if 'init_alpha_fn' in cfg_kernel: + if cfg_kernel['init_alpha_fn'] is not None: + cfg_kernel['init_alpha_fn'], _ = _serialize_object(cfg_kernel['init_alpha_fn'], base_path, + local_path.joinpath('init_alpha_fn')) + if 'init_tau_fn' in cfg_kernel: + if cfg_kernel['init_tau_fn'] is not None: + cfg_kernel['init_tau_fn'], _ = _serialize_object(cfg_kernel['init_tau_fn'], base_path, + local_path.joinpath('init_tau_fn')) else: raise AttributeError("The detector's `kernel` must have a .get_config() method for it to be saved.") # Serialize the kernel class diff --git a/alibi_detect/saving/schemas.py b/alibi_detect/saving/schemas.py index 68a902929..4ba773084 100644 --- a/alibi_detect/saving/schemas.py +++ b/alibi_detect/saving/schemas.py @@ -51,6 +51,44 @@ def validate_model(cls, model: Any, values: dict) -> Any: raise TypeError('The model is not recognised as a supported type.') +def validate_composite_kernel_config(cfg_kernel_list: Dict[str, Any]) -> Dict[str, Any]: + """ + Validate composite kernel config. + + Parameters + ---------- + cfg_kernel + Composite kernel config. + + Returns + ------- + cfg_kernel + Validated composite kernel config. + """ + # cfg_kernel = CompositeKernelConfig(**cfg_kernel).dict() + comp_number = len(cfg_kernel_list) + for i in range(comp_number): + if isinstance(cfg_kernel_list['comp_' + str(i)], dict): + if 'kernel_type' in cfg_kernel_list['comp_' + str(i)]: + if (cfg_kernel_list['comp_' + str(i)]['kernel_type'] == 'Sum') or\ + (cfg_kernel_list['comp_' + str(i)]['kernel_type'] == 'Product'): + cfg_kernel_list['comp_' + str(i)] =\ + CompositeKernelConfig(**cfg_kernel_list['comp_' + str(i)]).dict() + elif cfg_kernel_list['comp_' + str(i)]['kernel_type'] == 'GaussianRBF': + cfg_kernel_list['comp_' + str(i)] =\ + RBFKernelConfig(**cfg_kernel_list['comp_' + str(i)]).dict() + elif cfg_kernel_list['comp_' + str(i)]['kernel_type'] == 'RationalQuadratic': + cfg_kernel_list['comp_' + str(i)] =\ + RationalQuadraticKernelConfig(**cfg_kernel_list['comp_' + str(i)]).dict() + elif cfg_kernel_list['comp_' + str(i)]['kernel_type'] == 'Periodic': + cfg_kernel_list['comp_' + str(i)] =\ + PeriodicKernelConfig(**cfg_kernel_list['comp_' + str(i)]).dict() + else: + raise ValueError('Kernel type not supported.') + cfg_kernel_list = dict(sorted(cfg_kernel_list.items())) # Sort dict to ensure order is consistent + return cfg_kernel_list + + class SupportedOptimizer: """ Pydantic custom type to check the optimizer is one of the supported types (conditional on what optional deps @@ -320,7 +358,7 @@ class PreprocessConfig(CustomBaseModel): """ -class KernelConfig(CustomBaseModelWithKwargs): +class RBFKernelConfig(CustomBaseModelWithKwargs): """ Unresolved schema for kernels, to be passed to a detector's `kernel` kwarg. @@ -350,6 +388,136 @@ class KernelConfig(CustomBaseModelWithKwargs): src: str "A string referencing a filepath to a serialized kernel in `.dill` format, or an object registry reference." + kernel_type: Literal['GaussianRBF'] + + # Below kwargs are only passed if kernel == @GaussianRBF + flavour: Literal['tensorflow', 'pytorch'] + """ + Whether the kernel is a `tensorflow` or `pytorch` kernel. + """ + sigma: Optional[Union[float, List[float]]] = None + """ + Bandwidth used for the kernel. Needn’t be specified if being inferred or trained. Can pass multiple values to eval + kernel with and then average. + """ + trainable: bool = False + "Whether or not to track gradients w.r.t. sigma to allow it to be trained." + + init_sigma_fn: Optional[str] = None + """ + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. The function's signature + should match :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. If `None`, it is set to + :func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. + """ + # Validators + _validate_flavour = validator('flavour', allow_reuse=True, pre=False)(validate_framework) + _coerce_sigma2tensor = validator('sigma', allow_reuse=True, pre=False)(coerce_2_tensor) + + +class RationalQuadraticKernelConfig(CustomBaseModelWithKwargs): + """ + Unresolved schema for kernels, to be passed to a detector's `kernel` kwarg. + + If `src` specifies a :class:`~alibi_detect.utils.tensorflow.RationalQuadratic` kernel, the `sigma`, `alpha`, + 'trainable' and `init_sigma_fn`, 'init_alpha_fn' fields are passed to it. Otherwise, all fields except `src` + are passed as kwargs. + + Examples + -------- + A :class:`~alibi_detect.utils.tensorflow.RationalQuadratic` kernel, with three different bandwidths and alphas: + + .. code-block :: toml + + [kernel] + src = "@alibi_detect.utils.tensorflow.GaussianRBF" + trainable = false + sigma = [0.1, 0.2, 0.3] + alpha = [1.0, 2.0, 3.0] + + A serialized kernel with keyword arguments passed: + + .. code-block :: toml + + [kernel] + src = "mykernel.dill" + sigma = 0.42 + alpha = 2.0 + custom_setting = "xyz" + """ + src: str + "A string referencing a filepath to a serialized kernel in `.dill` format, or an object registry reference." + + kernel_type: Literal['RationalQuadratic'] + + # Below kwargs are only passed if kernel == @GaussianRBF + flavour: Literal['tensorflow', 'pytorch'] + """ + Whether the kernel is a `tensorflow` or `pytorch` kernel. + """ + sigma: Optional[Union[float, List[float]]] = None + """ + Bandwidth used for the kernel. Needn’t be specified if being inferred or trained. Can pass multiple values to eval + kernel with and then average. + """ + alpha: Optional[Union[float, List[float]]] = None + """ + Exponent used for the kernel. Needn’t be specified if being inferred or trained. Can pass multiple values to eval + kernel with and then average. + """ + trainable: bool = False + "Whether or not to track gradients w.r.t. sigma to allow it to be trained." + + init_sigma_fn: Optional[str] = None + """ + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. The function's signature + should match :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. If `None`, it is set to + :func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. + """ + init_alpha_fn: Optional[str] = None + """ + Function used to compute the exponent `alpha`. Used when `alpha` is to be inferred. The function's signature + should match :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. Defaults to None. + """ + # Validators + _validate_flavour = validator('flavour', allow_reuse=True, pre=False)(validate_framework) + _coerce_sigma2tensor = validator('sigma', allow_reuse=True, pre=False)(coerce_2_tensor) + _coerce_alpha2tensor = validator('alpha', allow_reuse=True, pre=False)(coerce_2_tensor) + + +class PeriodicKernelConfig(CustomBaseModelWithKwargs): + """ + Unresolved schema for kernels, to be passed to a detector's `kernel` kwarg. + + If `src` specifies a :class:`~alibi_detect.utils.tensorflow.PeriodicKernel` kernel, the `sigma`, 'tau', `trainable` + and `init_sigma_fn`, 'init_tau_fn' fields are passed to it. Otherwise, all fields except `src` are passed as kwargs. + + Examples + -------- + A :class:`~alibi_detect.utils.tensorflow.GaussianRBF` kernel, with three different bandwidths: + + .. code-block :: toml + + [kernel] + src = "@alibi_detect.utils.tensorflow.PeriodicKernel" + trainable = false + sigma = [0.1, 0.2, 0.3] + tau = [1.0, 2.0, 3.0] + + A serialized kernel with keyword arguments passed: + + .. code-block :: toml + + [kernel] + src = "mykernel.dill" + sigma = 0.42 + tau = 1.0 + custom_setting = "xyz" + """ + src: str + "A string referencing a filepath to a serialized kernel in `.dill` format, or an object registry reference." + + kernel_type: Literal['Periodic'] + # Below kwargs are only passed if kernel == @GaussianRBF flavour: Literal['tensorflow', 'pytorch'] """ @@ -360,6 +528,11 @@ class KernelConfig(CustomBaseModelWithKwargs): Bandwidth used for the kernel. Needn’t be specified if being inferred or trained. Can pass multiple values to eval kernel with and then average. """ + tau: Optional[Union[float, List[float]]] = None + """ + Period used for the kernel. Needn’t be specified if being inferred or trained. Can pass multiple values to eval + kernel with and then average. + """ trainable: bool = False "Whether or not to track gradients w.r.t. sigma to allow it to be trained." @@ -369,9 +542,45 @@ class KernelConfig(CustomBaseModelWithKwargs): should match :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. If `None`, it is set to :func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. """ + init_tau_fn: Optional[str] = None + """ + Function used to compute the period `tau`. Used when `tau` is to be inferred. The function's signature + should match :py:func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. Defaults to None. + """ # Validators _validate_flavour = validator('flavour', allow_reuse=True, pre=False)(validate_framework) _coerce_sigma2tensor = validator('sigma', allow_reuse=True, pre=False)(coerce_2_tensor) + _coerce_tau2tensor = validator('tau', allow_reuse=True, pre=False)(coerce_2_tensor) + + +class CompositeKernelConfig(CustomBaseModelWithKwargs): + """ + Unresolved schema for composite kernels, to be passed to a detector's `kernel` kwarg. + + Examples + -------- + A :class:`~alibi_detect.utils.tensorflow.SumKernel` obtained by adding two + :class:`~alibi_detect.utils.tensorflow.GaussianRBF` instances: + + .. code-block :: toml + + [kernel] + src = "@alibi_detect.utils.tensorflow.SumKernel" + kernel_list = [ + RBFKernelConfig(src="@alibi_detect.utils.tensorflow.GaussianRBF", trainable=false, sigma=0.1), + RBFKernelConfig(src="@alibi_detect.utils.tensorflow.GaussianRBF", trainable=false, sigma=0.2) + ] + """ + src: str + + kernel_type: Literal['Sum', 'Product'] + + flavour: Literal['tensorflow', 'pytorch'] + + kernel_list: Dict + + _validate_composite_kernel =\ + validator('kernel_list', allow_reuse=True, pre=False)(validate_composite_kernel_config) class DeepKernelConfig(CustomBaseModel): @@ -401,17 +610,23 @@ class DeepKernelConfig(CustomBaseModel): [kernel.proj] src = "model/" """ + kernel_type: Literal['Deep'] + + flavour: Literal['tensorflow', 'pytorch'] + proj: Union[str, ModelConfig] """ The projection to be applied to the inputs before applying `kernel_a`. This should be a Tensorflow or PyTorch model, specified as an object registry reference, or a :class:`~alibi_detect.utils.schemas.ModelConfig`. """ - kernel_a: Union[str, KernelConfig] = "@utils.tensorflow.kernels.GaussianRBF" + kernel_a: Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, PeriodicKernelConfig, CompositeKernelConfig]\ + = "@utils.tensorflow.kernels.GaussianRBF" """ The kernel to apply to the projected inputs. Defaults to a :class:`~alibi_detect.utils.tensorflow.kernels.GaussianRBF` with trainable bandwidth. """ - kernel_b: Optional[Union[str, KernelConfig]] = "@utils.tensorflow.kernels.GaussianRBF" + kernel_b: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, PeriodicKernelConfig, + CompositeKernelConfig]] = "@utils.tensorflow.kernels.GaussianRBF" """ The kernel to apply to the raw inputs. Defaults to a :class:`~alibi_detect.utils.tensorflow.kernels.GaussianRBF` with trainable bandwidth. Set to `None` in order to use only the deep component (i.e. `eps=0`). @@ -677,8 +892,8 @@ class MMDDriftConfig(DriftDetectorConfig): p_val: float = .05 preprocess_at_init: bool = True update_x_ref: Optional[Dict[str, int]] = None - kernel: Optional[Union[str, KernelConfig]] = None - sigma: Optional[NDArray[np.float32]] = None + kernel: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, + PeriodicKernelConfig, CompositeKernelConfig]] = None configure_kernel_from_x_ref: bool = True n_permutations: int = 100 batch_size_permutations: int = 1000000 @@ -698,7 +913,6 @@ class MMDDriftConfigResolved(DriftDetectorConfigResolved): preprocess_at_init: bool = True update_x_ref: Optional[Dict[str, int]] = None kernel: Optional[Callable] = None - sigma: Optional[NDArray[np.float32]] = None configure_kernel_from_x_ref: bool = True n_permutations: int = 100 batch_size_permutations: int = 1000000 @@ -839,7 +1053,8 @@ class SpotTheDiffDriftConfig(DriftDetectorConfig): verbose: int = 0 train_kwargs: Optional[dict] = None dataset: Optional[str] = None - kernel: Optional[Union[str, KernelConfig]] = None + kernel: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, + PeriodicKernelConfig, CompositeKernelConfig]] = None n_diffs: int = 1 initial_diffs: Optional[str] = None l1_reg: float = 0.01 @@ -959,8 +1174,10 @@ class ContextMMDDriftConfig(DriftDetectorConfig): c_ref: str preprocess_at_init: bool = True update_ref: Optional[Dict[str, int]] = None - x_kernel: Optional[Union[str, KernelConfig]] = None - c_kernel: Optional[Union[str, KernelConfig]] = None + x_kernel: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, + PeriodicKernelConfig, CompositeKernelConfig]] = None + c_kernel: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, + PeriodicKernelConfig, CompositeKernelConfig]] = None n_permutations: int = 100 prop_c_held: float = 0.25 n_folds: int = 5 @@ -1004,8 +1221,8 @@ class MMDDriftOnlineConfig(DriftDetectorConfig): backend: Literal['tensorflow', 'pytorch'] = 'tensorflow' ert: float window_size: int - kernel: Optional[Union[str, KernelConfig]] = None - sigma: Optional[np.ndarray] = None + kernel: Optional[Union[str, RBFKernelConfig, RationalQuadraticKernelConfig, + PeriodicKernelConfig, CompositeKernelConfig]] = None n_bootstraps: int = 1000 device: Optional[Literal['cpu', 'cuda']] = None verbose: bool = True @@ -1024,7 +1241,6 @@ class MMDDriftOnlineConfigResolved(DriftDetectorConfigResolved): ert: float window_size: int kernel: Optional[Callable] = None - sigma: Optional[np.ndarray] = None n_bootstraps: int = 1000 device: Optional[Literal['cpu', 'cuda']] = None verbose: bool = True diff --git a/alibi_detect/saving/tests/models.py b/alibi_detect/saving/tests/models.py index 5a1b28c0e..33afbf9e5 100644 --- a/alibi_detect/saving/tests/models.py +++ b/alibi_detect/saving/tests/models.py @@ -18,9 +18,17 @@ from alibi_detect.cd.tensorflow import UAE as UAE_tf from alibi_detect.cd.tensorflow import preprocess_drift as preprocess_drift_tf from alibi_detect.utils.pytorch.kernels import GaussianRBF as GaussianRBF_pt +from alibi_detect.utils.pytorch.kernels import RationalQuadratic as RationalQuadratic_pt +from alibi_detect.utils.pytorch.kernels import Periodic as Periodic_pt from alibi_detect.utils.pytorch.kernels import DeepKernel as DeepKernel_pt +from alibi_detect.utils.pytorch.kernels import SumKernel as SumKernel_pt +from alibi_detect.utils.pytorch.kernels import ProductKernel as ProductKernel_pt from alibi_detect.utils.tensorflow.kernels import GaussianRBF as GaussianRBF_tf +from alibi_detect.utils.tensorflow.kernels import RationalQuadratic as RationalQuadratic_tf +from alibi_detect.utils.tensorflow.kernels import Periodic as Periodic_tf from alibi_detect.utils.tensorflow.kernels import DeepKernel as DeepKernel_tf +from alibi_detect.utils.tensorflow.kernels import SumKernel as SumKernel_tf +from alibi_detect.utils.tensorflow.kernels import ProductKernel as ProductKernel_tf from alibi_detect.models.pytorch import TransformerEmbedding as TransformerEmbedding_pt from alibi_detect.models.tensorflow import TransformerEmbedding as TransformerEmbedding_tf from alibi_detect.cd.pytorch import HiddenOutput as HiddenOutput_pt @@ -100,21 +108,14 @@ def preprocess_custom(encoder_model): @fixture def kernel(request, backend): """ - Gaussian RBF kernel for given backend. Settings are parametrised in the test function. + Kernel for given backend. Settings are parametrised in the test function. """ kernel = request.param - if isinstance(kernel, dict): # dict of kwargs - kernel_cfg = kernel.copy() - sigma = kernel_cfg.pop('sigma', None) if backend == 'tensorflow': - if sigma is not None and not isinstance(sigma, tf.Tensor): - sigma = tf.convert_to_tensor(sigma) - kernel = GaussianRBF_tf(sigma=sigma, **kernel_cfg) + kernel = initial_kernel_tf(kernel) elif backend == 'pytorch': - if sigma is not None and not isinstance(sigma, torch.Tensor): - sigma = torch.tensor(sigma) - kernel = GaussianRBF_pt(sigma=sigma, **kernel_cfg) + kernel = initial_kernel_pt(kernel) else: pytest.skip('`kernel` only implemented for tensorflow and pytorch.') return kernel @@ -147,8 +148,8 @@ def deep_kernel(request, backend, encoder_model): parametrised in the test function. """ # Get DeepKernel options - kernel_a = request.param.get('kernel_a', 'rbf') - kernel_b = request.param.get('kernel_b', 'rbf') + kernel_a = request.param.get('kernel_a', {'kernel_type': 'GaussianRBF'}) + kernel_b = request.param.get('kernel_b', {'kernel_type': 'GaussianRBF'}) eps = request.param.get('eps', 'trainable') # Proj model (backend managed in encoder_model fixture) @@ -156,18 +157,96 @@ def deep_kernel(request, backend, encoder_model): # Build DeepKernel if backend == 'tensorflow': - kernel_a = GaussianRBF_tf(**kernel_a) if isinstance(kernel_a, dict) else kernel_a - kernel_b = GaussianRBF_tf(**kernel_b) if isinstance(kernel_b, dict) else kernel_b + kernel_a = initial_kernel_tf(kernel_a) + kernel_b = initial_kernel_tf(kernel_b) deep_kernel = DeepKernel_tf(proj, kernel_a=kernel_a, kernel_b=kernel_b, eps=eps) elif backend == 'pytorch': - kernel_a = GaussianRBF_pt(**kernel_a) if isinstance(kernel_a, dict) else kernel_a - kernel_b = GaussianRBF_pt(**kernel_b) if isinstance(kernel_b, dict) else kernel_b + kernel_a = initial_kernel_pt(kernel_a) + kernel_b = initial_kernel_pt(kernel_b) deep_kernel = DeepKernel_pt(proj, kernel_a=kernel_a, kernel_b=kernel_b, eps=eps) else: pytest.skip('`deep_kernel` only implemented for tensorflow and pytorch.') return deep_kernel +def initial_kernel_tf(kernel_config): + kernel_config = kernel_config.copy() + if 'kernel_type' in kernel_config: + kernel_name = kernel_config.pop('kernel_type') + if ('sigma' in kernel_config) and (kernel_config['sigma'] is not None): + kernel_config['sigma'] = tf.convert_to_tensor(np.array(kernel_config['sigma']), dtype=tf.float32) + if ('alpha' in kernel_config) and (kernel_config['alpha'] is not None): + kernel_config['alpha'] = tf.convert_to_tensor(np.array(kernel_config['alpha']), dtype=tf.float32) + if ('tau' in kernel_config) and (kernel_config['tau'] is not None): + kernel_config['tau'] = tf.convert_to_tensor(np.array(kernel_config['tau']), dtype=tf.float32) + if kernel_name == 'GaussianRBF': + kernel = GaussianRBF_tf(**kernel_config) + elif kernel_name == 'RationalQuadratic': + kernel = RationalQuadratic_tf(**kernel_config) + elif kernel_name == 'Periodic': + kernel = Periodic_tf(**kernel_config) + elif kernel_name == 'Sum': + kernel_list = [] + for k_config in kernel_config.values(): + if isinstance(k_config, dict): + kernel_list.append(initial_kernel_tf(k_config)) + elif isinstance(k_config, float): + kernel_list.append(tf.cast(k_config, dtype=tf.float32)) + final_config = {'kernel_list': kernel_list} + kernel = SumKernel_tf(**final_config) + elif kernel_name == 'Product': + kernel_list = [] + for k_config in kernel_config.values(): + if isinstance(k_config, dict): + kernel_list.append(initial_kernel_tf(k_config)) + elif isinstance(k_config, float): + kernel_list.append(tf.cast(k_config, dtype=tf.float32)) + final_config = {'kernel_list': kernel_list} + kernel = ProductKernel_tf(**final_config) + else: + pytest.skip('`initial_kernel_tf` only implemented for GaussianRBF, RationalQuadratic and Periodic.') + return kernel + + +def initial_kernel_pt(kernel_config): + kernel_config = kernel_config.copy() + if 'kernel_type' in kernel_config: + kernel_name = kernel_config.pop('kernel_type') + if ('sigma' in kernel_config) and (kernel_config['sigma'] is not None): + kernel_config['sigma'] = torch.tensor(np.array(kernel_config['sigma']), dtype=torch.float32) + if ('alpha' in kernel_config) and (kernel_config['alpha'] is not None): + kernel_config['alpha'] = torch.tensor(np.array(kernel_config['alpha']), dtype=torch.float32) + if ('tau' in kernel_config) and (kernel_config['tau'] is not None): + kernel_config['tau'] = torch.tensor(np.array(kernel_config['tau']), dtype=torch.float32) + if kernel_name == 'GaussianRBF': + kernel = GaussianRBF_pt(**kernel_config) + elif kernel_name == 'RationalQuadratic': + kernel = RationalQuadratic_pt(**kernel_config) + elif kernel_name == 'Periodic': + kernel = Periodic_pt(**kernel_config) + elif kernel_name == 'Sum': + kernel_list = [] + for k_config in kernel_config.values(): + if isinstance(k_config, dict): + kernel_list.append(initial_kernel_pt(k_config)) + elif isinstance(k_config, float): + kernel_list.append(torch.tensor(k_config, dtype=torch.float32)) + final_config = {'kernel_list': kernel_list} + kernel = SumKernel_pt(**final_config) + elif kernel_name == 'Product': + kernel_list = [] + for k_config in kernel_config.values(): + if isinstance(k_config, dict): + kernel_list.append(initial_kernel_pt(k_config)) + elif isinstance(k_config, float): + kernel_list.append(torch.tensor(k_config, dtype=torch.float32)) + final_config = {'kernel_list': kernel_list} + kernel = ProductKernel_pt(**final_config) + else: + pytest.skip('`initial_kernel_pt` only implemented for GaussianRBF, RationalQuadratic and Periodic.') + return kernel + + @fixture def classifier_model(backend, current_cases): """ diff --git a/alibi_detect/saving/tests/test_saving.py b/alibi_detect/saving/tests/test_saving.py index 0ffa333c8..90e305870 100644 --- a/alibi_detect/saving/tests/test_saving.py +++ b/alibi_detect/saving/tests/test_saving.py @@ -42,7 +42,8 @@ from alibi_detect.saving.saving import _serialize_object from alibi_detect.saving.saving import (_path2str, _int2str_keys, _save_kernel_config, _save_model_config, _save_preprocess_config) -from alibi_detect.saving.schemas import DeepKernelConfig, KernelConfig, ModelConfig, PreprocessConfig +from alibi_detect.saving.schemas import DeepKernelConfig, ModelConfig, PreprocessConfig, RBFKernelConfig,\ + RationalQuadraticKernelConfig, PeriodicKernelConfig, CompositeKernelConfig from alibi_detect.utils.pytorch.kernels import DeepKernel as DeepKernel_pt from alibi_detect.utils.tensorflow.kernels import DeepKernel as DeepKernel_tf @@ -192,7 +193,13 @@ def test_save_cvmdrift(data, preprocess_custom, tmp_path): @parametrize('kernel', [ None, # Use default kernel - {'sigma': 0.5, 'trainable': False}, # pass kernel as object + {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False}, # pass kernel as object + {'kernel_type': 'RationalQuadratic', 'sigma': 0.5, 'alpha': 4.0, 'trainable': False}, + {'kernel_type': 'Periodic', 'sigma': 0.5, 'tau': 2.0, 'trainable': False}, + {'kernel_type': 'Sum', + 'comp_1': {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, + 'comp_2': {'kernel_type': 'GaussianRBF', 'sigma': 1.0, 'trainable': False, 'init_sigma_fn': None}, + 'comp_3': 0.5} ], indirect=True ) @parametrize_with_cases("data", cases=ContinuousData, prefix='data_') @@ -204,7 +211,6 @@ def test_save_mmddrift(data, kernel, preprocess_custom, backend, tmp_path, seed) """ if backend not in ('tensorflow', 'pytorch', 'keops'): pytest.skip("Detector doesn't have this backend") - # Init detector and make predictions X_ref, X_h0 = data kwargs = { @@ -214,8 +220,7 @@ def test_save_mmddrift(data, kernel, preprocess_custom, backend, tmp_path, seed) 'n_permutations': N_PERMUTATIONS, 'preprocess_at_init': True, 'kernel': kernel, - 'configure_kernel_from_x_ref': False, - 'sigma': np.array([0.5]) + 'configure_kernel_from_x_ref': False } if backend == 'pytorch': kwargs['device'] = 'cuda' if torch.cuda.is_available() else 'cpu' @@ -228,16 +233,16 @@ def test_save_mmddrift(data, kernel, preprocess_custom, backend, tmp_path, seed) with fixed_seed(seed): cd_load = load_detector(tmp_path) preds_load = cd_load.predict(X_h0) - # assertions np.testing.assert_array_equal(preprocess_custom(X_ref), cd_load._detector.x_ref) - assert not cd_load._detector.infer_sigma + assert not cd_load._detector.infer_parameter assert cd_load._detector.n_permutations == N_PERMUTATIONS assert cd_load._detector.p_val == P_VAL assert isinstance(cd_load._detector.preprocess_fn, Callable) assert cd_load._detector.preprocess_fn.func.__name__ == 'preprocess_drift' - assert cd._detector.kernel.sigma == cd_load._detector.kernel.sigma - assert cd._detector.kernel.init_sigma_fn == cd_load._detector.kernel.init_sigma_fn + if hasattr(cd._detector.kernel, 'sigma'): + assert cd._detector.kernel.sigma == cd_load._detector.kernel.sigma + assert cd._detector.kernel.init_sigma_fn == cd_load._detector.kernel.init_sigma_fn assert preds['data']['p_val'] == preds_load['data']['p_val'] @@ -459,8 +464,9 @@ def test_save_spotthediff(data, classifier_model, backend, tmp_path, seed): # n @parametrize('deep_kernel', [ - {'kernel_a': 'rbf', 'eps': 0.01} # Default for kernel_a - ], indirect=True + {'kernel_a': {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': True}, + 'eps': 0.01} + ], indirect=True ) @parametrize_with_cases("data", cases=ContinuousData, prefix='data_') def test_save_learnedkernel(data, deep_kernel, backend, tmp_path, seed): # noqa: F811 @@ -502,7 +508,10 @@ def test_save_learnedkernel(data, deep_kernel, backend, tmp_path, seed): # noqa @parametrize('kernel', [ None, # Default kernel - {'sigma': 0.5, 'trainable': False}, # pass kernels as GaussianRBF objects, with default sigma_median fn + {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False}, + # pass kernels as GaussianRBF objects, with default sigma_median fn + {'kernel_type': 'RationalQuadratic', 'sigma': 0.5, 'alpha': 4.0, 'trainable': False}, + {'kernel_type': 'Periodic', 'sigma': 0.5, 'tau': 2.0, 'trainable': False}, ], indirect=True ) @parametrize_with_cases("data", cases=ContinuousData, prefix='data_') @@ -615,7 +624,9 @@ def test_save_regressoruncertaintydrift(data, regressor, backend, tmp_path, seed @parametrize('kernel', [ None, # Use default kernel - {'sigma': 0.5, 'trainable': False}, # pass kernel as object + {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False}, # pass kernel as object + {'kernel_type': 'RationalQuadratic', 'sigma': 0.5, 'alpha': 4.0, 'trainable': False}, + {'kernel_type': 'Periodic', 'sigma': 0.5, 'tau': 2.0, 'trainable': False}, ], indirect=True ) @parametrize_with_cases("data", cases=ContinuousData, prefix='data_') @@ -657,7 +668,10 @@ def test_save_onlinemmddrift(data, kernel, preprocess_custom, backend, tmp_path, stats_load.append(pred['data']['test_stat']) # assertions - np.testing.assert_array_equal(preprocess_custom(X_ref), cd_load._detector.x_ref) + if backend == 'pytorch': + np.testing.assert_array_equal(preprocess_custom(X_ref), cd_load._detector.x_ref.cpu().numpy()) + else: + np.testing.assert_array_equal(preprocess_custom(X_ref), cd_load._detector.x_ref) assert cd_load._detector.n_bootstraps == N_BOOTSTRAPS assert cd_load._detector.ert == ERT assert isinstance(cd_load._detector.preprocess_fn, Callable) @@ -710,7 +724,11 @@ def test_save_onlinelsdddrift(data, preprocess_custom, backend, tmp_path, seed): assert cd_load._detector.ert == ERT assert isinstance(cd_load._detector.preprocess_fn, Callable) assert cd_load._detector.preprocess_fn.func.__name__ == 'preprocess_drift' - assert cd._detector.kernel.sigma == cd_load._detector.kernel.sigma + if backend == 'pytorch': + np.testing.assert_array_almost_equal(cd._detector.kernel.sigma.cpu().numpy(), + cd_load._detector.kernel.sigma.cpu().numpy(), 5) + else: + np.testing.assert_almost_equal(cd._detector.kernel.sigma, cd_load._detector.kernel.sigma, 5) assert cd._detector.kernel.init_sigma_fn == cd_load._detector.kernel.init_sigma_fn np.testing.assert_array_equal(stats, stats_load) @@ -852,9 +870,16 @@ def test_version_warning(data, tmp_path): @parametrize('kernel', [ - {'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, - {'sigma': [0.5, 0.8], 'trainable': False, 'init_sigma_fn': None}, - {'sigma': None, 'trainable': True, 'init_sigma_fn': None}, + {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, + {'kernel_type': 'GaussianRBF', 'sigma': [0.5, 0.8], 'trainable': False, 'init_sigma_fn': None}, + {'kernel_type': 'GaussianRBF', 'sigma': None, 'trainable': True, 'init_sigma_fn': None}, + {'kernel_type': 'RationalQuadratic', 'sigma': 0.5, 'alpha': 3.0, 'trainable': False, 'init_sigma_fn': None}, + {'kernel_type': 'RationalQuadratic', 'sigma': [0.5, 0.8], 'alpha': [2.0, 3.0], 'trainable': False, + 'init_sigma_fn': None}, + {'kernel_type': 'RationalQuadratic', 'sigma': None, 'alpha': None, 'trainable': True, 'init_sigma_fn': None}, + {'kernel_type': 'Periodic', 'sigma': 0.5, 'tau': 2.0, 'trainable': False, 'init_sigma_fn': None}, + {'kernel_type': 'Periodic', 'sigma': [0.5, 0.8], 'tau': [2.0, 3.0], 'trainable': False, 'init_sigma_fn': None}, + {'kernel_type': 'Periodic', 'sigma': None, 'tau': None, 'trainable': True, 'init_sigma_fn': None}, ], indirect=True ) def test_save_kernel(kernel, backend, tmp_path): # noqa: F811 @@ -867,9 +892,15 @@ def test_save_kernel(kernel, backend, tmp_path): # noqa: F811 filepath = tmp_path filename = Path('mykernel') cfg_kernel = _save_kernel_config(kernel, filepath, filename) - cfg_kernel = KernelConfig(**cfg_kernel).dict() # Pass through validator to test, and coerce sigma to Tensor if kernel.__class__.__name__ == 'GaussianRBF': assert cfg_kernel['src'] == '@utils.' + backend + '.kernels.GaussianRBF' + cfg_kernel = RBFKernelConfig(**cfg_kernel).dict() # Pass through validator to test, and coerce sigma to Tensor + elif kernel.__class__.__name__ == 'RationalQuadratic': + assert cfg_kernel['src'] == '@utils.' + backend + '.kernels.RationalQuadratic' + cfg_kernel = RationalQuadraticKernelConfig(**cfg_kernel).dict() # Pass through validator to test + elif kernel.__class__.__name__ == 'Periodic': + assert cfg_kernel['src'] == '@utils.' + backend + '.kernels.Periodic' + cfg_kernel = PeriodicKernelConfig(**cfg_kernel).dict() # Pass through validator to test else: assert Path(cfg_kernel['src']).suffix == '.dill' assert cfg_kernel['trainable'] == kernel.trainable @@ -883,6 +914,12 @@ def test_save_kernel(kernel, backend, tmp_path): # noqa: F811 # Call kernels X = np.random.standard_normal((10, 1)) + if backend == 'pytorch': + X = torch.from_numpy(X).float() + elif backend == 'tensorflow': + X = tf.convert_to_tensor(X) + else: + pytest.skip('Backend not supported.') kernel(X, X) kernel_loaded(X, X) @@ -893,14 +930,98 @@ def test_save_kernel(kernel, backend, tmp_path): # noqa: F811 else: np.testing.assert_array_almost_equal(np.array(kernel_loaded.sigma), np.array(kernel.sigma), 5) assert kernel_loaded.trainable == kernel.trainable - assert kernel_loaded.init_sigma_fn == kernel.init_sigma_fn + for tmp_key in kernel.parameter_dict.keys(): + assert kernel_loaded.parameter_dict[tmp_key].init_fn == kernel.parameter_dict[tmp_key].init_fn + + +@parametrize('kernel', [ + {'kernel_type': 'Sum', + 'comp_1': {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, + 'comp_2': {'kernel_type': 'GaussianRBF', 'sigma': 1.0, 'trainable': False, 'init_sigma_fn': None}, + 'comp_3': 0.01}, + {'kernel_type': 'Product', + 'comp_1': {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, + 'comp_2': {'kernel_type': 'GaussianRBF', 'sigma': 1.0, 'trainable': False, 'init_sigma_fn': None}}, + {'kernel_type': 'Product', + 'comp_1': 0.5, + 'comp_2': {'kernel_type': 'Sum', + 'comp_1': {'kernel_type': 'GaussianRBF', 'sigma': 0.5, 'trainable': False, 'init_sigma_fn': None}, + 'comp_2': {'kernel_type': 'GaussianRBF', 'sigma': 1.0, 'trainable': False, 'init_sigma_fn': None}, + 'comp_3': 0.5}}, + ], indirect=True +) +def test_save_composite_kernel(kernel, backend, tmp_path): # noqa: F811 + """ + Unit test for _save/_load_kernel_config, when kernel is a GaussianRBF kernel. + + Kernels are saved and then loaded, with assertions to check equivalence. + """ + # Save kernel to config + filepath = tmp_path + filename = Path('mykernel') + cfg_kernel = _save_kernel_config(kernel, filepath, filename) + if kernel.__class__.__name__ == 'SumKernel': + assert cfg_kernel['src'] == '@utils.' + backend + '.kernels.SumKernel' + cfg_kernel = CompositeKernelConfig(**cfg_kernel).dict() # Pass through validator to test + # cfg_kernel = _validate_composite_kernel_config(cfg_kernel) # Pass through validator to test + elif kernel.__class__.__name__ == 'ProductKernel': + assert cfg_kernel['src'] == '@utils.' + backend + '.kernels.ProductKernel' + cfg_kernel = CompositeKernelConfig(**cfg_kernel).dict() # Pass through validator to test + # cfg_kernel = _validate_composite_kernel_config(cfg_kernel) # Pass through validator to test + else: + assert Path(cfg_kernel['src']).suffix == '.dill' + + # Resolve and load config (_load_kernel_config is called within resolve_config) + cfg = {'kernel': cfg_kernel, 'backend': backend} + _prepend_cfg_filepaths(cfg, tmp_path) + kernel_loaded = resolve_config(cfg, tmp_path)['kernel'] + + # Call kernels + X = np.random.standard_normal((10, 1)) + if backend == 'pytorch': + X = torch.from_numpy(X).float() + elif backend == 'tensorflow': + X = tf.convert_to_tensor(X) + else: + pytest.skip('Backend not supported.') + K_0 = kernel(X, X) + K_1 = kernel_loaded(X, X) + + # Final checks + assert type(kernel_loaded) == type(kernel) + if backend == 'pytorch': + K_0 = K_0.detach().numpy().ravel() + K_1 = K_1.detach().numpy().ravel() + np.testing.assert_array_almost_equal(K_0, K_1, 5) + elif backend == 'tensorflow': + K_0 = K_0.numpy().ravel() + K_1 = K_1.numpy().ravel() + np.testing.assert_array_almost_equal(K_0, K_1, 5) + else: + raise NotImplementedError('Backend not supported.') + for i in range(len(kernel.kernel_list)): + if hasattr(kernel.kernel_list[i], 'sigma'): + if backend == 'pytorch': + np.testing.assert_array_almost_equal(kernel_loaded.kernel_list[i].sigma.detach().numpy(), + kernel.kernel_list[i].sigma.detach().numpy(), 5) + else: + np.testing.assert_array_almost_equal(np.array(kernel_loaded.kernel_list[i].sigma), + np.array(kernel.kernel_list[i].sigma), 5) + assert kernel_loaded.kernel_list[i].trainable == kernel.kernel_list[i].trainable + for tmp_key in kernel.kernel_list[i].parameter_dict.keys(): + assert kernel_loaded.kernel_list[i].parameter_dict[tmp_key].init_fn == \ + kernel.kernel_list[i].parameter_dict[tmp_key].init_fn # `data` passed below as needed in encoder_model, which is used in deep_kernel @parametrize_with_cases("data", cases=ContinuousData.data_synthetic_nd) @parametrize('deep_kernel', [ - {'kernel_a': 'rbf', 'kernel_b': 'rbf', 'eps': 'trainable'}, # Default for kernel_a and kernel_b, trainable eps - {'kernel_a': {'trainable': True}, 'kernel_b': 'rbf', 'eps': 0.01}, # Explicit kernel_a, fixed eps + {'kernel_a': {'kernel_type': 'GaussianRBF'}, + 'kernel_b': {'kernel_type': 'GaussianRBF'}, + 'eps': 'trainable'}, # Default for kernel_a and kernel_b, trainable eps + {'kernel_a': {'kernel_type': 'GaussianRBF', 'trainable': True}, + 'kernel_b': {'kernel_type': 'GaussianRBF'}, + 'eps': 0.01}, # Explicit kernel_a, fixed eps ], indirect=True ) def test_save_deepkernel(data, deep_kernel, backend, tmp_path): # noqa: F811 @@ -930,6 +1051,12 @@ def test_save_deepkernel(data, deep_kernel, backend, tmp_path): # noqa: F811 kernel_loaded = resolve_config(cfg, tmp_path)['kernel'] # implicitly calls _load_kernel_config # Call kernels + if backend == 'pytorch': + X = torch.from_numpy(X).float() + elif backend == 'tensorflow': + X = tf.convert_to_tensor(X) + else: + pytest.skip('Backend not supported.') deep_kernel.kernel_a(X, X) deep_kernel.kernel_b(X, X) kernel_loaded.kernel_a(X, X) diff --git a/alibi_detect/saving/tests/test_validate.py b/alibi_detect/saving/tests/test_validate.py index b9a777209..05680b5d3 100644 --- a/alibi_detect/saving/tests/test_validate.py +++ b/alibi_detect/saving/tests/test_validate.py @@ -3,7 +3,7 @@ from pydantic import ValidationError from alibi_detect.saving import validate_config -from alibi_detect.saving.schemas import KernelConfig +from alibi_detect.saving.schemas import RBFKernelConfig from alibi_detect.saving.saving import X_REF_FILENAME from alibi_detect.version import __version__ from copy import deepcopy @@ -101,11 +101,12 @@ def test_validate_kernel_and_coerce_2_tensor(flavour, sigma): kernel_cfg = { 'src': f'@utils.{flavour}.kernels.GaussianRBF', 'flavour': flavour, - 'sigma': sigma + 'sigma': sigma, + 'kernel_type': 'GaussianRBF' } # Pass through validation and check results - kernel_cfg_val = KernelConfig(**kernel_cfg).dict() + kernel_cfg_val = RBFKernelConfig(**kernel_cfg).dict() assert kernel_cfg_val['src'] == kernel_cfg['src'] assert kernel_cfg_val['flavour'] == flavour if sigma is None: diff --git a/alibi_detect/saving/validate.py b/alibi_detect/saving/validate.py index 672ee7431..bf9907526 100644 --- a/alibi_detect/saving/validate.py +++ b/alibi_detect/saving/validate.py @@ -1,7 +1,8 @@ import warnings from alibi_detect.saving.schemas import ( # type: ignore[attr-defined] - DETECTOR_CONFIGS, DETECTOR_CONFIGS_RESOLVED) + DETECTOR_CONFIGS, DETECTOR_CONFIGS_RESOLVED, + RBFKernelConfig, RationalQuadraticKernelConfig, PeriodicKernelConfig) from alibi_detect.version import __version__ @@ -54,3 +55,43 @@ def validate_config(cfg: dict, resolved: bool = False) -> dict: cfg['meta'].update({'version_warning': True}) return cfg + + +def validate_composite_kernel_config(cfg_kernel): + """ + Validate composite kernel config. + + Parameters + ---------- + cfg_kernel + Composite kernel config. + + Returns + ------- + cfg_kernel + Validated composite kernel config. + """ + # cfg_kernel = CompositeKernelConfig(**cfg_kernel).dict() + comp_number = len(cfg_kernel['kernel_list']) + for i in range(comp_number): + if isinstance(cfg_kernel['kernel_list']['comp_' + str(i)], dict): + if 'kernel_type' in cfg_kernel['kernel_list']['comp_' + str(i)]: + if cfg_kernel['kernel_list']['comp_' + str(i)]['kernel_type'] == 'Sum': + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + validate_composite_kernel_config(cfg_kernel['kernel_list']['comp_' + str(i)]) + elif cfg_kernel['kernel_list']['comp_' + str(i)]['kernel_type'] == 'Product': + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + validate_composite_kernel_config(cfg_kernel['kernel_list']['comp_' + str(i)]) + elif cfg_kernel['kernel_list']['comp_' + str(i)]['kernel_type'] == 'GaussianRBF': + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + RBFKernelConfig(**cfg_kernel['kernel_list']['comp_' + str(i)]).dict() + elif cfg_kernel['kernel_list']['comp_' + str(i)]['kernel_type'] == 'RationalQuadratic': + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + RationalQuadraticKernelConfig(**cfg_kernel['kernel_list']['comp_' + str(i)]).dict() + elif cfg_kernel['kernel_list']['comp_' + str(i)]['kernel_type'] == 'Periodic': + cfg_kernel['kernel_list']['comp_' + str(i)] =\ + PeriodicKernelConfig(**cfg_kernel['kernel_list']['comp_' + str(i)]).dict() + else: + raise ValueError('Kernel type not supported.') + cfg_kernel = dict(sorted(cfg_kernel.items())) # Sort dict to ensure order is consistent + return cfg_kernel diff --git a/alibi_detect/utils/keops/__init__.py b/alibi_detect/utils/keops/__init__.py index 36dc22971..bf8490260 100644 --- a/alibi_detect/utils/keops/__init__.py +++ b/alibi_detect/utils/keops/__init__.py @@ -1,12 +1,14 @@ from alibi_detect.utils.missing_optional_dependency import import_optional -GaussianRBF, DeepKernel = import_optional( +GaussianRBF, DeepKernel, BaseKernel, ProjKernel = import_optional( 'alibi_detect.utils.keops.kernels', - names=['GaussianRBF', 'DeepKernel'] + names=['GaussianRBF', 'DeepKernel', 'BaseKernel', 'ProjKernel'] ) __all__ = [ "GaussianRBF", - "DeepKernel" + "DeepKernel", + "BaseKernel", + "ProjKernel" ] diff --git a/alibi_detect/utils/keops/kernels.py b/alibi_detect/utils/keops/kernels.py index 7da7a3ee9..cecba108b 100644 --- a/alibi_detect/utils/keops/kernels.py +++ b/alibi_detect/utils/keops/kernels.py @@ -1,7 +1,46 @@ +from abc import abstractmethod from pykeops.torch import LazyTensor +import numpy as np import torch import torch.nn as nn -from typing import Callable, Optional, Union +from typing import Callable, Optional, Union, List +from copy import deepcopy + + +def infer_kernel_parameter( + kernel: 'BaseKernel', + x: LazyTensor, + y: LazyTensor, + dist: torch.Tensor, + infer_parameter: bool = True +) -> None: + """ + Infer the kernel parameter from the data. + + Parameters + ---------- + kernel + The kernel function. + x + LazyTensor of instances with dimension [Nx, 1, features] or [batch_size, Nx, 1, features]. + The singleton dimension is necessary for broadcasting. + y + LazyTensor of instances with dimension [1, Ny, features] or [batch_size, 1, Ny, features]. + The singleton dimension is necessary for broadcasting. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + infer_parameter + Whether to infer the kernel parameter. + """ + if kernel.trainable and infer_parameter: + raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") + for parameter in kernel.parameter_dict.values(): + if parameter.requires_init: + if parameter.init_fn is not None: + with torch.no_grad(): + parameter.value.data = parameter.init_fn(x, y, dist).reshape(-1) + parameter.requires_init = False + kernel.init_required = False def sigma_mean(x: LazyTensor, y: LazyTensor, dist: LazyTensor, n_min: int = 100) -> torch.Tensor: @@ -53,20 +92,296 @@ def sigma_mean(x: LazyTensor, y: LazyTensor, dist: LazyTensor, n_min: int = 100) return sigma -class GaussianRBF(nn.Module): +class KernelParameter: def __init__( self, - sigma: Optional[torch.Tensor] = None, - init_sigma_fn: Optional[Callable] = None, - trainable: bool = False + value: torch.Tensor = None, + init_fn: Optional[Callable] = None, + requires_grad: bool = False, + requires_init: bool = False + ) -> None: + """ + Parameter class for kernels. + + Parameters + ---------- + value + The pre-specified value of the parameter. + init_fn + The function used to initialize the parameter. + requires_grad + Whether the parameter requires gradient. + requires_init + Whether the parameter requires initialization. + """ + super().__init__() + self.value = nn.Parameter(value if value is not None else torch.ones(1), + requires_grad=requires_grad) + self.init_fn = init_fn + self.requires_init = requires_init + + +class BaseKernel(nn.Module): + def __init__(self, active_dims: list = None) -> None: + """ + The base class for all kernels. + + Parameters + ---------- + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__() + self.parameter_dict: dict = {} + if active_dims is not None: + self.active_dims = torch.as_tensor(active_dims) + else: + self.active_dims = None + self.init_required = False + + @abstractmethod + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: Optional[bool] = False) -> torch.Tensor: + raise NotImplementedError + + def forward(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + if self.active_dims is not None: + x = torch.index_select(x, -1, self.active_dims) + y = torch.index_select(y, -1, self.active_dims) + if len(self.parameter_dict) > 0: + return self.kernel_function(x, y, infer_parameter) + else: + return self.kernel_function(x, y) + + def __add__( + self, + other: Union['BaseKernel', torch.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + other.kernel_list.append(self) + return other + elif isinstance(other, (BaseKernel, ProductKernel, torch.Tensor)): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.kernel_list.append(other) + return sum_kernel + else: + raise ValueError('Kernels can only added to another kernel or a constant.') + + def __radd__(self, other: 'BaseKernel') -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union['BaseKernel', torch.Tensor] + ) -> 'BaseKernel': + if isinstance(other, ProductKernel): + other.kernel_factors.append(self) + return other + elif isinstance(other, SumKernel): + sum_kernel = SumKernel() + for k in other.kernel_list: + sum_kernel.kernel_list.append(self * k) + return sum_kernel + else: + prod_kernel = ProductKernel() + prod_kernel.kernel_factors.append(self) + prod_kernel.kernel_factors.append(other) + return prod_kernel + + def __rmul__( + self, + other: 'BaseKernel' + ) -> 'BaseKernel': + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> 'BaseKernel': + if isinstance(other, torch.Tensor): + return self.__mul__(1. / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + +class SumKernel(BaseKernel): + def __init__(self) -> None: + """ + Construct a kernel by summing different kernels. + """ + super().__init__() + self.kernel_list: List[Union[BaseKernel, torch.Tensor]] = [] + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + K_sum = torch.tensor(0., device=x.device) + for k in self.kernel_list: + if isinstance(k, (BaseKernel, SumKernel, ProductKernel)): + K_sum = K_sum + k(x, y, infer_parameter) + elif isinstance(k, torch.Tensor): + K_sum = K_sum + k + else: + raise ValueError(type(k) + 'is not supported by SumKernel.') + return K_sum + + def __add__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + for k in other.kernel_list: + self.kernel_list.append(k) + else: + self.kernel_list.append(other) + return self + + def __radd__(self, other: BaseKernel) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> BaseKernel: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for ki in self.kernel_list: + for kj in other.kernel_list: + sum_kernel.kernel_list.append((ki * kj)) + return sum_kernel + elif isinstance(other, ProductKernel): + return other * self + elif isinstance(other, BaseKernel) or isinstance(other, torch.Tensor): + sum_kernel = SumKernel() + for ki in self.kernel_list: + sum_kernel.kernel_list.append(other * ki) + return sum_kernel + else: + raise ValueError(type(other) + 'is not supported by SumKernel.') + + def __rmul__( + self, + other: BaseKernel + ) -> BaseKernel: + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> BaseKernel: + if isinstance(other, torch.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + +class ProductKernel(BaseKernel): + def __init__(self) -> None: + """ + Construct a kernel by multiplying different kernels. + """ + super().__init__() + self.kernel_factors: List[Union[BaseKernel, torch.Tensor]] = [] + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + K_prod = torch.tensor(1., device=x.device) + for k in self.kernel_factors: + if isinstance(k, BaseKernel) or isinstance(k, SumKernel) or isinstance(k, ProductKernel): + K_prod = K_prod * k(x, y, infer_parameter) + elif isinstance(k, torch.Tensor): + K_prod = K_prod * k + else: + raise ValueError(type(k) + 'is not supported by ProductKernel.') + return K_prod + + def __add__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + other.kernel_list.append(self) + return other + else: + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.kernel_list.append(other) + return sum_kernel + + def __radd__( + self, + other: BaseKernel + ) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> BaseKernel: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for k in other.kernel_list: + tmp_prod_kernel = deepcopy(self) + tmp_prod_kernel.kernel_factors.append(k) + sum_kernel.kernel_list.append(tmp_prod_kernel) + return sum_kernel + elif isinstance(other, ProductKernel): + for k in other.kernel_factors: + self.kernel_factors.append(k) + return self + elif isinstance(other, BaseKernel) or isinstance(other, torch.Tensor): + self.kernel_factors.append(other) + return self + else: + raise ValueError(type(other) + 'is not supported by ProductKernel.') + + def __rmul__( + self, + other: BaseKernel + ) -> BaseKernel: + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> BaseKernel: + if isinstance(other, torch.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + +class GaussianRBF(BaseKernel): + def __init__( + self, + sigma: Optional[torch.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: list = None ) -> None: """ Gaussian RBF kernel: k(x,y) = exp(-(1/(2*sigma^2)||x-y||^2). A forward pass takes - a batch of instances x and y and returns the kernel matrix. - x can be of shape [Nx, 1, features] or [batch_size, Nx, 1, features]. - y can be of shape [1, Ny, features] or [batch_size, 1, Ny, features]. - The returned kernel matrix can be of shape [Nx, Ny] or [batch_size, Nx, Ny]. - x, y and the returned kernel matrix are all lazy tensors. + a batch of instances x [Nx, features] and y [Ny, features] and returns the kernel + matrix [Nx, Ny]. Parameters ---------- @@ -75,38 +390,50 @@ def __init__( Can pass multiple values to eval kernel with and then average. init_sigma_fn Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. - The function's signature should match :py:func:`~alibi_detect.utils.keops.kernels.sigma_mean`, - meaning that it should take in the lazy tensors `x`, `y` and `dist` and return a tensor `sigma`. + The function's signature should match :py:func:`~alibi_detect.utils.pytorch.kernels.sigma_median`, + meaning that it should take in the tensors `x`, `y` and `dist` and return `sigma`. If `None`, it is set to + :func:`~alibi_detect.utils.pytorch.kernels.sigma_median`. trainable Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. """ - super().__init__() + super().__init__(active_dims) init_sigma_fn = sigma_mean if init_sigma_fn is None else init_sigma_fn - if sigma is None: - self.log_sigma = nn.Parameter(torch.empty(1), requires_grad=trainable) - self.init_required = True - else: - sigma = sigma.reshape(-1) # [Ns,] - self.log_sigma = nn.Parameter(sigma.log(), requires_grad=trainable) - self.init_required = False - self.init_sigma_fn = init_sigma_fn + self.config = {'sigma': sigma, 'trainable': trainable, 'init_sigma_fn': init_sigma_fn} + self.parameter_dict['log-sigma'] = KernelParameter( + value=sigma.log().reshape(-1) if sigma is not None else None, + init_fn=init_sigma_fn, + requires_grad=trainable, + requires_init=True if sigma is None else False, + ) self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) @property def sigma(self) -> torch.Tensor: - return self.log_sigma.exp() + return self.parameter_dict['log-sigma'].value.exp() + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> LazyTensor: + if len(x.shape) == 3: + x = LazyTensor(x[:, :, None, :]) + elif len(x.shape) == 2: + x = LazyTensor(x[:, None, :]) + else: + raise ValueError('x should be of shape [batch_size, n_instances, features] or [batch_size, features].') - def forward(self, x: LazyTensor, y: LazyTensor, infer_sigma: bool = False) -> LazyTensor: + if len(y.shape) == 3: + y = LazyTensor(y[:, None, :, :]) + elif len(y.shape) == 2: + y = LazyTensor(y[None, :, :]) + else: + raise ValueError('y should be of shape [batch_size, n_instances, features] or [batch_size, features].') dist = ((x - y) ** 2).sum(-1) - if infer_sigma or self.init_required: - if self.trainable and infer_sigma: - raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") - sigma = self.init_sigma_fn(x, y, dist) - with torch.no_grad(): - self.log_sigma.copy_(sigma.log().clone()) - self.init_required = False + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) gamma = 1. / (2. * self.sigma ** 2) gamma = LazyTensor(gamma[None, None, :]) if len(dist.shape) == 2 else LazyTensor(gamma[None, None, None, :]) @@ -116,45 +443,84 @@ def forward(self, x: LazyTensor, y: LazyTensor, infer_sigma: bool = False) -> La return kernel_mat -class DeepKernel(nn.Module): +class ProjKernel(BaseKernel): def __init__( self, proj: nn.Module, - kernel_a: nn.Module = GaussianRBF(trainable=True), - kernel_b: Optional[nn.Module] = GaussianRBF(trainable=True), - eps: Union[float, str] = 'trainable' + raw_kernel: BaseKernel = GaussianRBF(trainable=True), ) -> None: """ - Computes similarities as k(x,y) = (1-eps)*k_a(proj(x), proj(y)) + eps*k_b(x,y). - A forward pass takes an already projected batch of instances x_proj and y_proj and optionally - (if k_b is present) a batch of instances x and y and returns the kernel matrix. - x_proj can be of shape [Nx, 1, features_proj] or [batch_size, Nx, 1, features_proj]. - y_proj can be of shape [1, Ny, features_proj] or [batch_size, 1, Ny, features_proj]. - x can be of shape [Nx, 1, features] or [batch_size, Nx, 1, features]. - y can be of shape [1, Ny, features] or [batch_size, 1, Ny, features]. - The returned kernel matrix can be of shape [Nx, Ny] or [batch_size, Nx, Ny]. - x, y and the returned kernel matrix are all lazy tensors. + A kernel that combines a raw kernel (e.g. RBF) with a projection function (e.g. deep net) as + k(x, y) = k(proj(x), proj(y)). A forward pass takes a batch of instances x [Nx, features] and + y [Ny, features] and returns the kernel matrix [Nx, Ny]. - Parameters + Parameters: ---------- proj - The projection to be applied to the inputs before applying kernel_a - kernel_a + The projection to be applied to the inputs before applying raw_kernel + raw_kernel The kernel to apply to the projected inputs. Defaults to a Gaussian RBF with trainable bandwidth. - kernel_b - The kernel to apply to the raw inputs. Defaults to a Gaussian RBF with trainable bandwidth. - Set to None in order to use only the deep component (i.e. eps=0). - eps - The proportion (in [0,1]) of weight to assign to the kernel applied to raw inputs. This can be - either specified or set to 'trainable'. Only relavent if kernel_b is not None. """ super().__init__() + self.proj = proj + self.raw_kernel = raw_kernel + self.init_required = False + + def kernel_function( + self, + x: Union[np.ndarray, torch.Tensor], + y: Union[np.ndarray, torch.Tensor], + infer_parameter: Optional[bool] = False + ) -> torch.Tensor: + return self.raw_kernel(self.proj(x), self.proj(y), infer_parameter) + + +class DeepKernel(BaseKernel): + """ + Computes similarities as k(x,y) = (1-eps)*k_a(proj(x), proj(y)) + eps*k_b(x,y). + A forward pass takes a batch of instances x [Nx, features] and y [Ny, features] and returns + the kernel matrix [Nx, Ny]. + + Parameters + ---------- + proj + The projection to be applied to the inputs before applying kernel_a + kernel_a + The kernel to apply to the projected inputs. Defaults to a Gaussian RBF with trainable bandwidth. + kernel_b + The kernel to apply to the raw inputs. Defaults to a Gaussian RBF with trainable bandwidth. + Set to None in order to use only the deep component (i.e. eps=0). + eps + The proportion (in [0,1]) of weight to assign to the kernel applied to raw inputs. This can be + either specified or set to 'trainable'. Only relavent if kernel_b is not None. + + """ + def __init__( + self, + proj: nn.Module, + kernel_a: BaseKernel = GaussianRBF(trainable=True), + kernel_b: Optional[BaseKernel] = GaussianRBF(trainable=True), + eps: Union[float, str] = 'trainable' + ) -> None: + super().__init__() + self.proj = proj self.kernel_a = kernel_a self.kernel_b = kernel_b - self.proj = proj + + if hasattr(self.kernel_a, 'parameter_dict'): + for param in self.kernel_a.parameter_dict.keys(): + setattr(self, param, self.kernel_a.parameter_dict[param].value) + + self.proj_kernel = ProjKernel(proj=self.proj, raw_kernel=self.kernel_a) if kernel_b is not None: self._init_eps(eps) + self.comp_kernel = (1-self.logit_eps.sigmoid())*self.proj_kernel + self.logit_eps.sigmoid()*self.kernel_b + if hasattr(self.kernel_b, 'parameter_dict'): + for param in self.kernel_b.parameter_dict.keys(): + setattr(self, param, self.kernel_b.parameter_dict[param].value) + else: + self.comp_kernel = self.proj_kernel def _init_eps(self, eps: Union[float, str]) -> None: if isinstance(eps, float): @@ -170,9 +536,10 @@ def _init_eps(self, eps: Union[float, str]) -> None: def eps(self) -> torch.Tensor: return self.logit_eps.sigmoid() if self.kernel_b is not None else torch.tensor(0.) - def forward(self, x_proj: LazyTensor, y_proj: LazyTensor, x: Optional[LazyTensor] = None, - y: Optional[LazyTensor] = None) -> LazyTensor: - similarity = self.kernel_a(x_proj, y_proj) - if self.kernel_b is not None: - similarity = (1-self.eps)*similarity + self.eps*self.kernel_b(x, y) - return similarity + def kernel_function( + self, + x: torch.Tensor, + y: torch.Tensor, + infer_parameter: Optional[bool] = False + ) -> torch.Tensor: + return self.comp_kernel(x, y, infer_parameter) diff --git a/alibi_detect/utils/keops/tests/test_kernels_keops.py b/alibi_detect/utils/keops/tests/test_kernels_keops.py index b25554818..979da8c7d 100644 --- a/alibi_detect/utils/keops/tests/test_kernels_keops.py +++ b/alibi_detect/utils/keops/tests/test_kernels_keops.py @@ -6,7 +6,7 @@ import torch.nn as nn if has_keops: from pykeops.torch import LazyTensor - from alibi_detect.utils.keops import DeepKernel, GaussianRBF + from alibi_detect.utils.keops import DeepKernel, GaussianRBF, BaseKernel sigma = [None, np.array([1.]), np.array([1., 2.])] n_features = [5, 10] @@ -34,21 +34,15 @@ def test_gaussian_kernel(gaussian_kernel_params): sigma = sigma if sigma is None else torch.from_numpy(sigma).float() x = torch.from_numpy(np.random.random(xshape)).float() y = torch.from_numpy(np.random.random(yshape)).float() - if batch_size: - x_lazy, y_lazy = LazyTensor(x[:, :, None, :]), LazyTensor(y[:, None, :, :]) - x_lazy2 = LazyTensor(x[:, None, :, :]) - else: - x_lazy, y_lazy = LazyTensor(x[:, None, :]), LazyTensor(y[None, :, :]) - x_lazy2 = LazyTensor(x[None, :, :]) kernel = GaussianRBF(sigma=sigma, trainable=trainable) - infer_sigma = True if sigma is None else False - if trainable and infer_sigma: + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: with pytest.raises(ValueError): - kernel(x_lazy, y_lazy, infer_sigma=infer_sigma) + kernel(x, y, infer_parameter=infer_parameter) else: - k_xy = kernel(x_lazy, y_lazy, infer_sigma=infer_sigma) - k_xx = kernel(x_lazy, x_lazy2, infer_sigma=infer_sigma) + k_xx = kernel(x, x, infer_parameter=infer_parameter) + k_xy = kernel(x, y, infer_parameter=infer_parameter) k_xy_shape = n_instances k_xx_shape = (n_instances[0], n_instances[0]) axis = 1 @@ -66,11 +60,26 @@ def test_gaussian_kernel(gaussian_kernel_params): if has_keops: - class MyKernel(nn.Module): + class MyKernel(BaseKernel): def __init__(self): super().__init__() - def forward(self, x: LazyTensor, y: LazyTensor) -> LazyTensor: + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> LazyTensor: + if len(x.shape) == 3: + x = LazyTensor(x[:, :, None, :]) + elif len(x.shape) == 2: + x = LazyTensor(x[:, None, :]) + else: + raise ValueError('x should be of shape [batch_size, n_instances, features] or [batch_size, features].') + + if len(y.shape) == 3: + y = LazyTensor(y[:, None, :, :]) + elif len(y.shape) == 2: + y = LazyTensor(y[None, :, :]) + else: + raise ValueError('y should be of shape [batch_size, n_instances, features] or [batch_size, features].') + return (- ((x - y) ** 2).sum(-1)).exp() @@ -104,18 +113,10 @@ def test_deep_kernel(deep_kernel_params): xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) x = torch.as_tensor(np.random.random(xshape).astype('float32')) y = torch.as_tensor(np.random.random(yshape).astype('float32')) - x_proj, y_proj = kernel.proj(x), kernel.proj(y) - x2_proj, x_proj = LazyTensor(x_proj[None, :, :]), LazyTensor(x_proj[:, None, :]) - y2_proj, y_proj = LazyTensor(y_proj[None, :, :]), LazyTensor(y_proj[:, None, :]) - if kernel_b: - x2, x = LazyTensor(x[None, :, :]), LazyTensor(x[:, None, :]) - y2, y = LazyTensor(y[None, :, :]), LazyTensor(y[:, None, :]) - else: - x, x2, y, y2 = None, None, None, None - k_xy = kernel(x_proj, y2_proj, x, y2) - k_yx = kernel(y_proj, x2_proj, y, x2) - k_xx = kernel(x_proj, x2_proj, x, x2) + k_xy = kernel(x, y) + k_yx = kernel(y, x) + k_xx = kernel(x, x) assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) assert (k_xx.Kmin_argKmin(1, axis=1)[0] > 0.).all() assert (torch.abs(k_xy.sum(1).sum(1) - k_yx.t().sum(1).sum(1)) < 1e-5).all() diff --git a/alibi_detect/utils/pytorch/__init__.py b/alibi_detect/utils/pytorch/__init__.py index 35918f8a3..df17f2937 100644 --- a/alibi_detect/utils/pytorch/__init__.py +++ b/alibi_detect/utils/pytorch/__init__.py @@ -12,9 +12,9 @@ 'permed_lsdds', 'batch_compute_kernel_matrix'] ) -GaussianRBF, DeepKernel = import_optional( +GaussianRBF, DeepKernel, BaseKernel, RationalQuadratic, Periodic, log_sigma_median = import_optional( 'alibi_detect.utils.pytorch.kernels', - names=['GaussianRBF', 'DeepKernel'] + names=['GaussianRBF', 'DeepKernel', 'BaseKernel', 'RationalQuadratic', 'Periodic', 'log_sigma_median'] ) predict_batch, predict_batch_transformer = import_optional( @@ -32,7 +32,10 @@ "mmd2", "mmd2_from_kernel_matrix", "squared_pairwise_distance", + "BaseKernel", "GaussianRBF", + "RationalQuadratic", + "Periodic", "DeepKernel", "permed_lsdds", "predict_batch", @@ -40,5 +43,6 @@ "get_device", "quantile", "zero_diag", + "log_sigma_median", "TorchDataset" ] diff --git a/alibi_detect/utils/pytorch/distance.py b/alibi_detect/utils/pytorch/distance.py index b5b5e85de..86b1b0aa8 100644 --- a/alibi_detect/utils/pytorch/distance.py +++ b/alibi_detect/utils/pytorch/distance.py @@ -24,8 +24,8 @@ def squared_pairwise_distance(x: torch.Tensor, y: torch.Tensor, a_min: float = 1 ------- Pairwise squared Euclidean distance [Nx, Ny]. """ - x2 = x.pow(2).sum(dim=-1, keepdim=True) - y2 = y.pow(2).sum(dim=-1, keepdim=True) + x2 = torch.square(x).sum(dim=-1, keepdim=True) + y2 = torch.square(y).sum(dim=-1, keepdim=True) dist = torch.addmm(y2.transpose(-2, -1), x, y.transpose(-2, -1), alpha=-2).add_(x2) return dist.clamp_min_(a_min) diff --git a/alibi_detect/utils/pytorch/kernels.py b/alibi_detect/utils/pytorch/kernels.py index 78e730fb8..f8c58006c 100644 --- a/alibi_detect/utils/pytorch/kernels.py +++ b/alibi_detect/utils/pytorch/kernels.py @@ -1,11 +1,47 @@ +from abc import abstractmethod import numpy as np import torch from torch import nn from . import distance -from typing import Optional, Union, Callable +from typing import Optional, Union, Callable, List +from copy import deepcopy from alibi_detect.utils.frameworks import Framework +def infer_kernel_parameter( + kernel: 'BaseKernel', + x: torch.Tensor, + y: torch.Tensor, + dist: torch.Tensor, + infer_parameter: bool = True +) -> None: + """ + Infer the kernel parameter from the data. + + Parameters + ---------- + kernel + The kernel function. + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + infer_parameter + Whether to infer the kernel parameter. + """ + if kernel.trainable and infer_parameter: + raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") + for parameter in kernel.parameter_dict.values(): + if parameter.requires_init: + if parameter.init_fn is not None: + with torch.no_grad(): + parameter.value.data = parameter.init_fn(x, y, dist).reshape(-1) + parameter.requires_init = False + kernel.init_required = False + + def sigma_median(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch.Tensor: """ Bandwidth estimation using the median heuristic :cite:t:`Gretton2012`. @@ -21,7 +57,7 @@ def sigma_median(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch. Returns ------- - The computed bandwidth, `sigma`. + The computed bandwidth, `log-sigma`. """ n = min(x.shape[0], y.shape[0]) n = n if (x[:n] == y[:n]).all() and x.shape == y.shape else 0 @@ -30,12 +66,444 @@ def sigma_median(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch. return sigma -class GaussianRBF(nn.Module): +def log_sigma_median(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch.Tensor: + """ + Bandwidth estimation using the median heuristic :cite:t:`Gretton2012`. + + Parameters + ---------- + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + + Returns + ------- + The logrithm of the computed bandwidth, `log-sigma`. + """ + return torch.log(sigma_median(x, y, dist)) + + +class KernelParameter: def __init__( self, - sigma: Optional[torch.Tensor] = None, - init_sigma_fn: Optional[Callable] = None, - trainable: bool = False + value: torch.Tensor = None, + init_fn: Optional[Callable] = None, + requires_grad: bool = False, + requires_init: bool = False + ) -> None: + """ + Parameter class for kernels. + + Parameters + ---------- + value + The pre-specified value of the parameter. + init_fn + The function used to initialize the parameter. + requires_grad + Whether the parameter requires gradient. + requires_init + Whether the parameter requires initialization. + """ + super().__init__() + self.value = nn.Parameter(value if value is not None else torch.ones(1), + requires_grad=requires_grad) + self.init_fn = init_fn + self.requires_init = requires_init + + +class BaseKernel(nn.Module): + def __init__(self, active_dims: list = None) -> None: + """ + The base class for all kernels. + + Parameters + ---------- + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__() + self.parameter_dict: dict = {} + self.config: dict = {} + if active_dims is not None: + self.active_dims = torch.as_tensor(active_dims) + else: + self.active_dims = None + self.init_required = False + + @abstractmethod + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: Optional[bool] = False) -> torch.Tensor: + raise NotImplementedError + + def forward(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + if self.active_dims is not None: + x = torch.index_select(x, -1, self.active_dims) + y = torch.index_select(y, -1, self.active_dims) + if len(self.parameter_dict) > 0: + return self.kernel_function(x, y, infer_parameter) + else: + return self.kernel_function(x, y) + + def __add__( + self, + other: Union['BaseKernel', torch.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + kernel_count = len(other.kernel_list) + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(kernel_count)] = self.config # type: ignore + return other + elif isinstance(other, (BaseKernel, ProductKernel)): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.config # type: ignore + return sum_kernel + elif isinstance(other, torch.Tensor): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.detach().cpu().item() # type: ignore + return sum_kernel + else: + raise ValueError('Kernels can only added to another kernel or a constant.') + + def __radd__(self, other: 'BaseKernel') -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union['BaseKernel', torch.Tensor] + ) -> 'BaseKernel': + if isinstance(other, ProductKernel): + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(len(other.kernel_list))] = self.config # type: ignore + return other + elif isinstance(other, SumKernel): + sum_kernel = SumKernel() + kernel_count = 0 + for k in other.kernel_list: + sum_kernel.kernel_list.append(self * k) + sum_kernel.config['kernel_list']['comp_' + str(kernel_count)] = self.config # type: ignore + kernel_count += 1 + return sum_kernel + elif isinstance(other, BaseKernel): + prod_kernel = ProductKernel() + prod_kernel.kernel_list.append(self) + prod_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + prod_kernel.kernel_list.append(other) + prod_kernel.config['kernel_list']['comp_1'] = other.config # type: ignore + return prod_kernel + elif isinstance(other, torch.Tensor): + prod_kernel = ProductKernel() + prod_kernel.kernel_list.append(self) + prod_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + prod_kernel.kernel_list.append(other) + prod_kernel.config['kernel_list']['comp_1'] = other.detach().cpu().item() # type: ignore + return prod_kernel + else: + raise ValueError('Kernels can only be multiplied by another kernel or a constant.') + + def __rmul__( + self, + other: 'BaseKernel' + ) -> 'BaseKernel': + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> 'BaseKernel': + if isinstance(other, torch.Tensor): + return self.__mul__(1. / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + return self.config.copy() + + +class SumKernel(BaseKernel): + def __init__(self, + kernel_list: Optional[List[Union[BaseKernel, torch.Tensor]]] = None) -> None: + """ + Construct a kernel by summing different kernels. + """ + super().__init__() + self.kernel_list = [] + self.config: dict = {'kernel_type': 'Sum', 'kernel_list': {}} + if kernel_list is not None: + self.kernel_list = kernel_list + for i in range(len(self.kernel_list)): + if isinstance(self.kernel_list[i], BaseKernel): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].config # type: ignore + elif isinstance(self.kernel_list[i], torch.Tensor): + self.config['kernel_list']['comp_' + str(i)] = \ + self.kernel_list[i].detach().cpu().item() # type: ignore + else: + raise ValueError(str(type(self.kernel_list[i])) + 'is not supported by SumKernel.') + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + value_list: List[torch.Tensor] = [] + for k in self.kernel_list: + k.to(x.device) + if isinstance(k, (BaseKernel, SumKernel, ProductKernel)): + value_list.append(k(x, y, infer_parameter)) + elif isinstance(k, torch.Tensor): + value_list.append(k * torch.ones((x.shape[0], y.shape[0]), device=x.device)) + else: + raise ValueError(type(k) + 'is not supported by SumKernel.') + return torch.sum(torch.stack(value_list), dim=0) + + def __add__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> 'SumKernel': + kernel_count = len(self.kernel_list) + if isinstance(other, SumKernel): + for k in other.kernel_list: + self.kernel_list.append(k) + if isinstance(k, BaseKernel): + self.config['kernel_list']['comp_' + str(kernel_count)] = k.config + elif isinstance(k, torch.Tensor): + self.config['kernel_list']['comp_' + str(kernel_count)] = k.detach().cpu().item() + kernel_count += 1 + elif isinstance(other, BaseKernel): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(kernel_count)] = other.config + elif isinstance(other, torch.Tensor): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(kernel_count)] = other.detach().cpu().item() + else: + raise ValueError(type(other) + 'is not supported by SumKernel.') + return self + + def __radd__(self, other: BaseKernel) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> BaseKernel: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for ki in self.kernel_list: + for kj in other.kernel_list: + sum_kernel.kernel_list.append((ki * kj)) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list) - 1)] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + elif isinstance(other, ProductKernel): + return other * self + elif isinstance(other, BaseKernel) or isinstance(other, torch.Tensor): + sum_kernel = SumKernel() + for ki in self.kernel_list: + sum_kernel.kernel_list.append(other * ki) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list) - 1)] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + else: + raise ValueError(type(other) + 'is not supported by SumKernel.') + + def __rmul__( + self, + other: BaseKernel + ) -> BaseKernel: + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> BaseKernel: + if isinstance(other, torch.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + config = fill_composite_config(config) + return cls(**config) + + +class ProductKernel(BaseKernel): + def __init__(self, + kernel_list: Optional[List[Union[BaseKernel, torch.Tensor]]] = None) -> None: + """ + Construct a kernel by multiplying different kernels. + """ + super().__init__() + self.kernel_list = [] + self.config: dict = {'kernel_type': 'Product', 'kernel_list': {}} + if kernel_list is not None: + self.kernel_list = kernel_list + for i in range(len(self.kernel_list)): + if isinstance(self.kernel_list[i], BaseKernel): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].config # type: ignore + elif isinstance(self.kernel_list[i], torch.Tensor): + self.config['kernel_list']['comp_' + str(i)] = \ + self.kernel_list[i].detach().cpu().item() # type: ignore + else: + raise ValueError(str(type(self.kernel_list[i])) + 'is not supported by ProductKernel.') + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + value_list: List[torch.Tensor] = [] + for k in self.kernel_list: + k.to(x.device) + if isinstance(k, BaseKernel) or isinstance(k, SumKernel) or isinstance(k, ProductKernel): + value_list.append(k(x, y, infer_parameter)) + elif isinstance(k, torch.Tensor): + value_list.append(k * torch.ones((x.shape[0], y.shape[0]), device=x.device)) + else: + raise ValueError(type(k) + 'is not supported by ProductKernel.') + return torch.prod(torch.stack(value_list), dim=0) + + def __add__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(len(other.kernel_list))] = self.config + return other + elif isinstance(other, ProductKernel) or isinstance(other, BaseKernel): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.config + return sum_kernel + elif isinstance(other, torch.Tensor): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.detach().cpu().item() + return sum_kernel + else: + raise ValueError(type(other) + 'is not supported by ProductKernel.') + + def __radd__( + self, + other: BaseKernel + ) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, torch.Tensor] + ) -> BaseKernel: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for k in other.kernel_list: + tmp_prod_kernel = deepcopy(self) + tmp_prod_kernel.kernel_list.append(k) + sum_kernel.kernel_list.append(tmp_prod_kernel) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list))] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + elif isinstance(other, ProductKernel): + for k in other.kernel_list: + self.kernel_list.append(k) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] = k.config # type: ignore + return self + elif isinstance(other, BaseKernel): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] = other.config # type: ignore + return self + elif isinstance(other, torch.Tensor): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] =\ + other.detach().cpu().item() # type: ignore + return self + else: + raise ValueError(type(other) + 'is not supported by ProductKernel.') + + def __rmul__( + self, + other: BaseKernel + ) -> BaseKernel: + return self.__mul__(other) + + def __truediv__(self, other: torch.Tensor) -> BaseKernel: + if isinstance(other, torch.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + config = fill_composite_config(config) + return cls(**config) + + +class GaussianRBF(BaseKernel): + def __init__( + self, + sigma: Optional[torch.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: Optional[list] = None ) -> None: """ Gaussian RBF kernel: k(x,y) = exp(-(1/(2*sigma^2)||x-y||^2). A forward pass takes @@ -54,37 +522,34 @@ def __init__( :func:`~alibi_detect.utils.pytorch.kernels.sigma_median`. trainable Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. """ - super().__init__() - init_sigma_fn = sigma_median if init_sigma_fn is None else init_sigma_fn - self.config = {'sigma': sigma, 'trainable': trainable, 'init_sigma_fn': init_sigma_fn} - if sigma is None: - self.log_sigma = nn.Parameter(torch.empty(1), requires_grad=trainable) - self.init_required = True - else: - sigma = sigma.reshape(-1) # [Ns,] - self.log_sigma = nn.Parameter(sigma.log(), requires_grad=trainable) - self.init_required = False - self.init_sigma_fn = init_sigma_fn + super().__init__(active_dims) + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.config = {'sigma': sigma, 'trainable': trainable, 'init_sigma_fn': self.init_sigma_fn, + 'active_dims': active_dims, 'kernel_type': 'GaussianRBF'} + self.parameter_dict['log-sigma'] = KernelParameter( + value=sigma.log().reshape(-1) if sigma is not None else torch.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False, + ) self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) @property def sigma(self) -> torch.Tensor: - return self.log_sigma.exp() + return self.parameter_dict['log-sigma'].value.exp() - def forward(self, x: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch.Tensor], - infer_sigma: bool = False) -> torch.Tensor: + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + n_x, n_y = x.shape[0], y.shape[0] + dist = distance.squared_pairwise_distance(x.reshape(n_x, -1), y.reshape(n_y, -1)) # [Nx, Ny] - x, y = torch.as_tensor(x), torch.as_tensor(y) - dist = distance.squared_pairwise_distance(x.flatten(1), y.flatten(1)) # [Nx, Ny] - - if infer_sigma or self.init_required: - if self.trainable and infer_sigma: - raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") - sigma = self.init_sigma_fn(x, y, dist) - with torch.no_grad(): - self.log_sigma.copy_(sigma.log().clone()) - self.init_required = False + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) gamma = 1. / (2. * self.sigma ** 2) # [Ns,] # TODO: do matrix multiplication after all? @@ -93,11 +558,116 @@ def forward(self, x: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch def get_config(self) -> dict: """ - Returns a serializable config dict (excluding the input_sigma_fn, which is serialized in alibi_detect.saving). + Returns a serializable config dict (excluding the infer_sigma_fn, which is serialized in alibi_detect.saving). + """ + cfg = self.config.copy() + if isinstance(cfg['sigma'], torch.Tensor): + cfg['sigma'] = cfg['sigma'].detach().cpu().numpy().tolist() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = torch.tensor(np.array(config['sigma'])) + return cls(**config) + + +class RationalQuadratic(BaseKernel): + def __init__( + self, + alpha: Optional[torch.Tensor] = None, + init_alpha_fn: Optional[Callable] = None, + sigma: Optional[torch.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: Optional[list] = None + ) -> None: + """ + Rational Quadratic kernel: k(x,y) = (1 + ||x-y||^2 / (2*sigma^2))^(-alpha). + A forward pass takesa batch of instances x [Nx, features] and y [Ny, features] + and returns the kernel matrix [Nx, Ny]. + + Parameters + ---------- + alpha + Exponent parameter of the kernel. + init_alpha_fn + Function used to compute the exponent parameter `alpha`. Used when `alpha` is to be inferred. + sigma + Bandwidth used for the kernel. + init_sigma_fn + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. + trainable + Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__(active_dims) + if alpha is not None and sigma is not None: + if alpha.shape != sigma.shape: + raise ValueError('alpha and sigma must have the same shape.') + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.init_alpha_fn = init_alpha_fn + self.config = {'alpha': alpha, 'sigma': sigma, 'trainable': trainable, 'active_dims': active_dims, + 'init_alpha_fn': self.init_alpha_fn, 'init_sigma_fn': self.init_sigma_fn, + 'kernel_type': 'RationalQuadratic'} + self.parameter_dict['log-alpha'] = KernelParameter( + value=alpha.log().reshape(-1) if alpha is not None else torch.zeros(1), + init_fn=self.init_alpha_fn, # type: ignore + requires_grad=trainable, + requires_init=True if alpha is None else False + ) + self.parameter_dict['log-sigma'] = KernelParameter( + value=sigma.log().reshape(-1) if sigma is not None else torch.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False + ) + self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) + + @property + def alpha(self) -> torch.Tensor: + return self.parameter_dict['log-alpha'].value.exp() + + @property + def sigma(self) -> torch.Tensor: + return self.parameter_dict['log-sigma'].value.exp() + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + dist = distance.squared_pairwise_distance(x.flatten(1), y.flatten(1)) + + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + + kernel_mat = torch.stack([(1 + torch.square(dist) / + (2 * self.alpha[i] * (self.sigma[i] ** 2))) + ** (-self.alpha[i]) for i in range(len(self.sigma))], dim=0) + + return kernel_mat.mean(dim=0) + + def get_config(self) -> dict: + """ + Returns a serializable config dict (excluding the infer_sigma_fn and infer_alpha_fn, + which is serialized in alibi_detect.saving). """ cfg = self.config.copy() if isinstance(cfg['sigma'], torch.Tensor): cfg['sigma'] = cfg['sigma'].detach().cpu().numpy().tolist() + if isinstance(cfg['alpha'], torch.Tensor): + cfg['alpha'] = cfg['alpha'].detach().cpu().numpy().tolist() cfg.update({'flavour': Framework.PYTORCH.value}) return cfg @@ -112,10 +682,165 @@ def from_config(cls, config): A kernel config dictionary. """ config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = torch.tensor(np.array(config['sigma'])) + if 'alpha' in config and config['alpha'] is not None: + config['alpha'] = torch.tensor(np.array(config['alpha'])) return cls(**config) -class DeepKernel(nn.Module): +class Periodic(BaseKernel): + def __init__( + self, + tau: Optional[torch.Tensor] = None, + init_tau_fn: Optional[Callable] = None, + sigma: Optional[torch.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: Optional[list] = None + ) -> None: + """ + Periodic kernel: k(x,y) = exp(-2 * sin(pi * |x - y| / tau)^2 / (sigma^2)). + A forward pass takesa batch of instances x [Nx, features] and y [Ny, features] + and returns the kernel matrix [Nx, Ny]. + + Parameters + ---------- + tau + Period of the periodic kernel. + init_tau_fn + Function used to compute the period `tau`. Used when `tau` is to be inferred. + sigma + Bandwidth used for the kernel. + init_sigma_fn + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. + trainable + Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__(active_dims) + if tau is not None and sigma is not None: + if tau.shape != sigma.shape: + raise ValueError('tau and sigma must have the same shape.') + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.init_tau_fn = init_tau_fn + self.config = {'tau': tau, 'sigma': sigma, 'trainable': trainable, 'active_dims': active_dims, + 'init_tau_fn': self.init_tau_fn, 'init_sigma_fn': self.init_sigma_fn, + 'kernel_type': 'Periodic'} + self.parameter_dict['log-tau'] = KernelParameter( + value=tau.log().reshape(-1) if tau is not None else torch.zeros(1), + init_fn=self.init_tau_fn, # type: ignore + requires_grad=trainable, + requires_init=True if tau is None else False + ) + self.parameter_dict['log-sigma'] = KernelParameter( + value=sigma.log().reshape(-1) if sigma is not None else torch.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False + ) + self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) + + @property + def tau(self) -> torch.Tensor: + return self.parameter_dict['log-tau'].value.exp() + + @property + def sigma(self) -> torch.Tensor: + return self.parameter_dict['log-sigma'].value.exp() + + def kernel_function(self, x: torch.Tensor, y: torch.Tensor, + infer_parameter: bool = False) -> torch.Tensor: + dist = distance.squared_pairwise_distance(x.flatten(1), y.flatten(1)) + + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + + kernel_mat = torch.stack([torch.exp(-2 * torch.square( + torch.sin(torch.as_tensor(np.pi) * dist / self.tau[i])) / (self.sigma[i] ** 2)) + for i in range(len(self.sigma))], dim=0) + return kernel_mat.mean(dim=0) + + def get_config(self) -> dict: + """ + Returns a serializable config dict (excluding the infer_sigma_fn and infer_tau_fn, + which is serialized in alibi_detect.saving). + """ + cfg = self.config.copy() + if isinstance(cfg['sigma'], torch.Tensor): + cfg['sigma'] = cfg['sigma'].detach().cpu().numpy().tolist() + if isinstance(cfg['tau'], torch.Tensor): + cfg['tau'] = cfg['tau'].detach().cpu().numpy().tolist() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = torch.tensor(np.array(config['sigma'])) + if 'tau' in config and config['tau'] is not None: + config['tau'] = torch.tensor(np.array(config['tau'])) + return cls(**config) + + +class ProjKernel(BaseKernel): + def __init__( + self, + proj: nn.Module, + raw_kernel: BaseKernel = GaussianRBF(trainable=True), + ) -> None: + """ + A kernel that combines a raw kernel (e.g. RBF) with a projection function (e.g. deep net) as + k(x, y) = k(proj(x), proj(y)). A forward pass takes a batch of instances x [Nx, features] and + y [Ny, features] and returns the kernel matrix [Nx, Ny]. + + Parameters: + ---------- + proj + The projection to be applied to the inputs before applying raw_kernel + raw_kernel + The kernel to apply to the projected inputs. Defaults to a Gaussian RBF with trainable bandwidth. + """ + super().__init__() + self.config = {'proj': proj, 'raw_kernel': raw_kernel, 'kernel_type': 'Proj'} + self.proj = proj + self.raw_kernel = raw_kernel + self.init_required = False + + def kernel_function( + self, + x: Union[np.ndarray, torch.Tensor], + y: Union[np.ndarray, torch.Tensor], + infer_parameter: Optional[bool] = False + ) -> torch.Tensor: + return self.raw_kernel(self.proj(x), self.proj(y), infer_parameter) + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg + + @classmethod + def from_config(cls, config): + config.pop('flavour') + config.pop('kernel_type') + return cls(**config) + + +class DeepKernel(BaseKernel): """ Computes similarities as k(x,y) = (1-eps)*k_a(proj(x), proj(y)) + eps*k_b(x,y). A forward pass takes a batch of instances x [Nx, features] and y [Ny, features] and returns @@ -138,21 +863,29 @@ class DeepKernel(nn.Module): def __init__( self, proj: nn.Module, - kernel_a: Union[nn.Module, str] = 'rbf', - kernel_b: Optional[Union[nn.Module, str]] = 'rbf', + kernel_a: BaseKernel = GaussianRBF(trainable=True), + kernel_b: BaseKernel = GaussianRBF(trainable=True), eps: Union[float, str] = 'trainable' ) -> None: super().__init__() - self.config = {'proj': proj, 'kernel_a': kernel_a, 'kernel_b': kernel_b, 'eps': eps} - if kernel_a == 'rbf': - kernel_a = GaussianRBF(trainable=True) - if kernel_b == 'rbf': - kernel_b = GaussianRBF(trainable=True) + self.config = {'proj': proj, 'kernel_a': kernel_a, 'kernel_b': kernel_b, 'eps': eps, 'kernel_type': 'Deep'} + self.proj = proj self.kernel_a = kernel_a self.kernel_b = kernel_b - self.proj = proj + + if hasattr(self.kernel_a, 'parameter_dict'): + for param in self.kernel_a.parameter_dict.keys(): + setattr(self, param, self.kernel_a.parameter_dict[param].value) + + self.proj_kernel = ProjKernel(proj=proj, raw_kernel=kernel_a) if kernel_b is not None: self._init_eps(eps) + self.comp_kernel = (1-self.eps)*self.proj_kernel + self.eps*self.kernel_b + if hasattr(self.kernel_b, 'parameter_dict'): + for param in self.kernel_b.parameter_dict.keys(): + setattr(self, param, self.kernel_b.parameter_dict[param].value) + else: + self.comp_kernel = self.proj_kernel def _init_eps(self, eps: Union[float, str]) -> None: if isinstance(eps, float): @@ -168,15 +901,46 @@ def _init_eps(self, eps: Union[float, str]) -> None: def eps(self) -> torch.Tensor: return self.logit_eps.sigmoid() if self.kernel_b is not None else torch.tensor(0.) - def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: - similarity = self.kernel_a(self.proj(x), self.proj(y)) # type: ignore[operator] - if self.kernel_b is not None: - similarity = (1-self.eps)*similarity + self.eps*self.kernel_b(x, y) # type: ignore[operator] - return similarity + def kernel_function( + self, + x: torch.Tensor, + y: torch.Tensor, + infer_parameter: Optional[bool] = False + ) -> torch.Tensor: + return self.comp_kernel(x, y, infer_parameter) def get_config(self) -> dict: - return self.config.copy() + cfg = self.config.copy() + cfg.update({'flavour': Framework.PYTORCH.value}) + return cfg @classmethod def from_config(cls, config): + config.pop('kernel_type') + config.pop('flavour') return cls(**config) + + +def fill_composite_config(config: dict) -> dict: + final_config: dict = {'kernel_list': []} + for k_config in config['kernel_list'].values(): + if isinstance(k_config, dict): + k_config.pop('src') + if k_config['kernel_type'] == 'Sum': + final_config['kernel_list'].append(SumKernel.from_config(k_config)) + elif k_config['kernel_type'] == 'Product': + final_config['kernel_list'].append(ProductKernel.from_config(k_config)) + elif k_config['kernel_type'] == 'GaussianRBF': + final_config['kernel_list'].append(GaussianRBF.from_config(k_config)) + elif k_config['kernel_type'] == 'Periodic': + final_config['kernel_list'].append(Periodic.from_config(k_config)) + elif k_config['kernel_type'] == 'RationalQuadratic': + final_config['kernel_list'].append(RationalQuadratic.from_config(k_config)) + else: + raise ValueError('Unknown kernel type.') + elif isinstance(k_config, np.ndarray) or isinstance(k_config, float) or \ + isinstance(k_config, np.float32) or isinstance(k_config, np.float64): + final_config['kernel_list'].append(torch.tensor(np.array(k_config))) + else: + raise ValueError('Unknown component type.') + return final_config diff --git a/alibi_detect/utils/pytorch/prediction.py b/alibi_detect/utils/pytorch/prediction.py index 05aded4aa..d8c47dbe2 100644 --- a/alibi_detect/utils/pytorch/prediction.py +++ b/alibi_detect/utils/pytorch/prediction.py @@ -35,6 +35,8 @@ def predict_batch(x: Union[list, np.ndarray, torch.Tensor], model: Union[Callabl Numpy array, torch tensor or tuples of those with model outputs. """ device = get_device(device) + if isinstance(model, nn.Module): + model = model.to(device) if isinstance(x, np.ndarray): x = torch.from_numpy(x) n = len(x) diff --git a/alibi_detect/utils/pytorch/tests/test_kernels_pt.py b/alibi_detect/utils/pytorch/tests/test_kernels_pt.py index ba351678d..dbf6efd11 100644 --- a/alibi_detect/utils/pytorch/tests/test_kernels_pt.py +++ b/alibi_detect/utils/pytorch/tests/test_kernels_pt.py @@ -3,7 +3,10 @@ import pytest import torch from torch import nn -from alibi_detect.utils.pytorch import GaussianRBF, DeepKernel +from typing import Union +from alibi_detect.utils.pytorch import GaussianRBF, DeepKernel, BaseKernel, RationalQuadratic, Periodic, \ + log_sigma_median +from alibi_detect.utils.pytorch.distance import squared_pairwise_distance sigma = [None, np.array([1.]), np.array([1., 2.])] n_features = [5, 10] @@ -27,24 +30,200 @@ def test_gaussian_kernel(gaussian_kernel_params): y = torch.from_numpy(np.random.random(yshape)).float() kernel = GaussianRBF(sigma=sigma, trainable=trainable) - infer_sigma = True if sigma is None else False - if trainable and infer_sigma: + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: with pytest.raises(Exception): - kernel(x, y, infer_sigma=infer_sigma) + kernel(x, y, infer_parameter=infer_parameter) else: - k_xy = kernel(x, y, infer_sigma=infer_sigma).detach().numpy() - k_xx = kernel(x, x, infer_sigma=infer_sigma).detach().numpy() + k_xy = kernel(x, y, infer_parameter=infer_parameter).detach().numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).detach().numpy() assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) assert (k_xx > 0.).all() and (k_xy > 0.).all() -class MyKernel(nn.Module): # TODO: Support then test models using keras functional API +def log_sigma_mean(x: torch.Tensor, y: torch.Tensor, dist: torch.Tensor) -> torch.Tensor: + sigma = (.5 * torch.mean(dist.flatten()) ** .5).unsqueeze(-1) + return torch.log(sigma) + + +kernel_ref = ['GaussianRBF', 'RationalQuadratic', 'Periodic'] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +init_fn = [None, log_sigma_median, log_sigma_mean] +tests_init_fn = list(product(kernel_ref, n_features, n_instances, trainable, init_fn)) + + +@pytest.fixture +def init_fn_params(request): + return tests_init_fn[request.param] + + +@pytest.mark.parametrize('init_fn_params', list(range(len(tests_init_fn))), indirect=True) +def test_init_fn(init_fn_params): + kernel_ref, n_features, n_instances, trainable, init_fn = init_fn_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + x = torch.from_numpy(np.random.random(xshape)).float() + y = torch.from_numpy(np.random.random(yshape)).float() + + if kernel_ref == 'GaussianRBF': + kernel = GaussianRBF(trainable=trainable, init_sigma_fn=init_fn) + elif kernel_ref == 'RationalQuadratic': + kernel = RationalQuadratic(trainable=trainable, init_sigma_fn=init_fn) + elif kernel_ref == 'Periodic': + kernel = Periodic(trainable=trainable, init_sigma_fn=init_fn) + else: + raise NotImplementedError + if trainable: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=True) + else: + k_xy = kernel(x, y, infer_parameter=True).numpy() + k_xx = kernel(x, x, infer_parameter=True).numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + if init_fn is not None: + np.testing.assert_almost_equal(kernel.sigma.numpy(), + np.exp(init_fn(x, y, squared_pairwise_distance(x, y)).numpy()), + decimal=4) + + +sigma = [None, np.array([1.]), np.array([2.])] +alpha = [None, np.array([1.]), np.array([2.])] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_rqk = list(product(sigma, alpha, n_features, n_instances, trainable)) +n_tests_rqk = len(tests_rqk) + + +@pytest.fixture +def rationalquadratic_kernel_params(request): + return tests_rqk[request.param] + + +@pytest.mark.parametrize('rationalquadratic_kernel_params', list(range(n_tests_rqk)), indirect=True) +def test_rationalquadratic_kernel(rationalquadratic_kernel_params): + sigma, alpha, n_features, n_instances, trainable = rationalquadratic_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + sigma = sigma if sigma is None else torch.from_numpy(sigma) + alpha = alpha if alpha is None else torch.from_numpy(alpha) + x = torch.from_numpy(np.random.random(xshape)).float() + y = torch.from_numpy(np.random.random(yshape)).float() + + kernel = RationalQuadratic(sigma=sigma, alpha=alpha, trainable=trainable) + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).detach().numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).detach().numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +sigma = [None, np.array([1.]), np.array([2.])] +tau = [None, np.array([8.]), np.array([24.])] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_pk = list(product(sigma, tau, n_features, n_instances, trainable)) +n_tests_pk = len(tests_pk) + + +@pytest.fixture +def periodic_kernel_params(request): + return tests_pk[request.param] + + +@pytest.mark.parametrize('periodic_kernel_params', list(range(n_tests_pk)), indirect=True) +def test_periodic_kernel(periodic_kernel_params): + sigma, tau, n_features, n_instances, trainable = periodic_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + sigma = sigma if sigma is None else torch.from_numpy(sigma) + tau = tau if tau is None else torch.from_numpy(tau) + x = torch.from_numpy(np.random.random(xshape)).float() + y = torch.from_numpy(np.random.random(yshape)).float() + + kernel = Periodic(sigma=sigma, tau=tau, trainable=trainable) + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).detach().numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).detach().numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +sigma_0 = [None, np.array([1.])] +sigma_1 = [None, np.array([1.])] +sigma_2 = [None, np.array([1.])] +operation_0 = ['*', '+'] +operation_1 = ['*', '+'] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_ck = list(product(sigma_0, sigma_1, sigma_2, + operation_0, operation_1, n_features, n_instances, trainable)) +n_tests_ck = len(tests_ck) + + +@pytest.fixture +def comp_kernel_params(request): + return tests_ck[request.param] + + +@pytest.mark.parametrize('comp_kernel_params', list(range(n_tests_ck)), indirect=True) +def test_comp_kernel(comp_kernel_params): + (sigma_0, sigma_1, sigma_2, operation_0, operation_1, + n_features, n_instances, trainable) = comp_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + sigma_0 = sigma_0 if sigma_0 is None else torch.from_numpy(sigma_0) + sigma_1 = sigma_1 if sigma_1 is None else torch.from_numpy(sigma_1) + sigma_2 = sigma_2 if sigma_2 is None else torch.from_numpy(sigma_2) + x = torch.from_numpy(np.random.random(xshape)).float() + y = torch.from_numpy(np.random.random(yshape)).float() + + kernel_0 = GaussianRBF(sigma=sigma_0, trainable=trainable) + kernel_1 = GaussianRBF(sigma=sigma_1, trainable=trainable) + kernel_2 = GaussianRBF(sigma=sigma_2, trainable=trainable) + if operation_0 == '*' and operation_1 == '*': + kernel = kernel_0 * kernel_1 * kernel_2 + elif operation_0 == '*' and operation_1 == '+': + kernel = (kernel_0 * kernel_1 + kernel_2) / torch.tensor(2.0) # ensure k(x, x) = 1 + elif operation_0 == '+' and operation_1 == '*': + kernel = (kernel_0 + kernel_1 * kernel_2) / torch.tensor(2.0) # ensure k(x, x) = 1 + elif operation_0 == '+' and operation_1 == '+': + kernel = (kernel_0 + kernel_1 + kernel_2) / torch.tensor(3.0) # ensure k(x, x) = 1 + else: + with pytest.raises(Exception): + raise Exception('Invalid operation') + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).detach().numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).detach().numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +class MyKernel(BaseKernel): # TODO: Support then test models using keras functional API def __init__(self, n_features: int): super().__init__() self.linear = nn.Linear(n_features, 20) - def forward(self, x: torch.Tensor, y: torch.Tensor) -> torch.Tensor: + def forward(self, x: Union[np.ndarray, torch.Tensor], y: Union[np.ndarray, torch.Tensor], + infer_parameter: bool = False) -> torch.Tensor: return torch.einsum('ji,ki->jk', self.linear(x), self.linear(y)) diff --git a/alibi_detect/utils/tensorflow/__init__.py b/alibi_detect/utils/tensorflow/__init__.py index 25c182df4..099fdd1d9 100644 --- a/alibi_detect/utils/tensorflow/__init__.py +++ b/alibi_detect/utils/tensorflow/__init__.py @@ -8,9 +8,9 @@ ) -GaussianRBF, DeepKernel = import_optional( +GaussianRBF, DeepKernel, BaseKernel, RationalQuadratic, Periodic, log_sigma_median = import_optional( 'alibi_detect.utils.tensorflow.kernels', - names=['GaussianRBF', 'DeepKernel'] + names=['GaussianRBF', 'DeepKernel', 'BaseKernel', 'RationalQuadratic', 'Periodic', 'log_sigma_median'] ) @@ -45,6 +45,9 @@ "relative_euclidean_distance", "squared_pairwise_distance", "GaussianRBF", + "BaseKernel", + "RationalQuadratic", + "Periodic", "DeepKernel", "permed_lsdds", "predict_batch", @@ -52,6 +55,7 @@ "quantile", "subset_matrix", "zero_diag", + "log_sigma_median", "mutate_categorical", "TFDataset" ] diff --git a/alibi_detect/utils/tensorflow/kernels.py b/alibi_detect/utils/tensorflow/kernels.py index b2ec3cb13..6ecb6ce8d 100644 --- a/alibi_detect/utils/tensorflow/kernels.py +++ b/alibi_detect/utils/tensorflow/kernels.py @@ -1,11 +1,46 @@ +from abc import abstractmethod import tensorflow as tf import numpy as np from . import distance -from typing import Optional, Union, Callable +from typing import Optional, Union, Callable, List from scipy.special import logit +from copy import deepcopy from alibi_detect.utils.frameworks import Framework +def infer_kernel_parameter( + kernel: 'BaseKernel', + x: tf.Tensor, + y: tf.Tensor, + dist: tf.Tensor, + infer_parameter: bool = True, +) -> None: + """ + Infer the kernel parameter from the data. + + Parameters + ---------- + kernel + The kernel function. + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + infer_parameter + Whether to infer the kernel parameter. + """ + if kernel.trainable and infer_parameter: + raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") + for parameter in kernel.parameter_dict.values(): + if parameter.requires_init: + if parameter.init_fn is not None: + parameter.value.assign(tf.reshape(parameter.init_fn(x, y, dist), -1)) + parameter.requires_init = False + kernel.init_required = False + + def sigma_median(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: """ Bandwidth estimation using the median heuristic :cite:t:`Gretton2012`. @@ -21,7 +56,7 @@ def sigma_median(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: Returns ------- - The computed bandwidth, `sigma`. + The logrithm of the computed bandwidth, `log-sigma`. """ n = min(x.shape[0], y.shape[0]) n = n if tf.reduce_all(x[:n] == y[:n]) and x.shape == y.shape else 0 @@ -30,12 +65,436 @@ def sigma_median(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: return sigma -class GaussianRBF(tf.keras.Model): +def log_sigma_median(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: + """ + Bandwidth estimation using the median heuristic :cite:t:`Gretton2012`. + + Parameters + ---------- + x + Tensor of instances with dimension [Nx, features]. + y + Tensor of instances with dimension [Ny, features]. + dist + Tensor with dimensions [Nx, Ny], containing the pairwise distances between `x` and `y`. + + Returns + ------- + The logrithm of the computed bandwidth, `log-sigma`. + """ + return tf.math.log(sigma_median(x, y, dist)) + + +class KernelParameter: + def __init__( + self, + value: tf.Tensor = None, + init_fn: Optional[Callable] = None, + requires_grad: bool = False, + requires_init: bool = False + ) -> None: + """ + Parameter class for kernels. + + Parameters + ---------- + value + The pre-specified value of the parameter. If `None`, the parameter is set to 1 by default. + init_fn + The function used to initialize the parameter. + requires_grad + Whether the parameter requires gradient. + requires_init + Whether the parameter requires initialization. + """ + self.value = tf.Variable(value if value is not None + else tf.ones(1, dtype=tf.keras.backend.floatx()), + trainable=requires_grad) + self.init_fn = init_fn + self.requires_init = requires_init + + def __repr__(self) -> str: + return self.value.__repr__() + + +class BaseKernel(tf.keras.Model): + def __init__(self, active_dims: list = None) -> None: + """ + The base class for all kernels. + + Parameters + ---------- + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__() + self.parameter_dict: dict = {} + self.config: dict = {} + self.active_dims = active_dims + self.init_required = False + + @abstractmethod + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, + infer_parameter: Optional[bool] = False) -> tf.Tensor: + return NotImplementedError + + def call(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: + y = tf.cast(y, x.dtype) + if self.active_dims is not None: + x = tf.gather(x, self.active_dims, axis=-1) + y = tf.gather(y, self.active_dims, axis=-1) + return self.kernel_function(x, y, infer_parameter) + + def __add__( + self, + other: Union['BaseKernel', tf.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + kernel_count = len(other.kernel_list) + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(kernel_count)] = self.config # type: ignore + return other + elif isinstance(other, (BaseKernel, ProductKernel)): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.config # type: ignore + return sum_kernel + elif isinstance(other, tf.Tensor): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.numpy() # type: ignore + return sum_kernel + else: + raise ValueError('Kernels can only added to another kernel or a constant.') + + def __radd__(self, other: 'BaseKernel') -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union['BaseKernel', tf.Tensor] + ) -> 'BaseKernel': + if isinstance(other, ProductKernel): + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(len(other.kernel_list))] = self.config # type: ignore + return other + elif isinstance(other, SumKernel): + sum_kernel = SumKernel() + kernel_count = 0 + for k in other.kernel_list: + sum_kernel.kernel_list.append(self * k) + sum_kernel.config['kernel_list']['comp_' + str(kernel_count)] = self.config # type: ignore + kernel_count += 1 + return sum_kernel + elif isinstance(other, BaseKernel): + prod_kernel = ProductKernel() + prod_kernel.kernel_list.append(self) + prod_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + prod_kernel.kernel_list.append(other) + prod_kernel.config['kernel_list']['comp_1'] = other.config # type: ignore + return prod_kernel + elif isinstance(other, tf.Tensor): + prod_kernel = ProductKernel() + prod_kernel.kernel_list.append(self) + prod_kernel.config['kernel_list']['comp_0'] = self.config # type: ignore + prod_kernel.kernel_list.append(other) + prod_kernel.config['kernel_list']['comp_1'] = other.numpy() # type: ignore + return prod_kernel + else: + raise ValueError('Kernels can only be multiplied by another kernel or a constant.') + + def __rmul__( + self, + other: 'BaseKernel' + ) -> 'BaseKernel': + return self.__mul__(other) + + def __truediv__(self, other: tf.Tensor) -> 'ProductKernel': + if isinstance(other, tf.Tensor): + return self.__mul__(1. / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + return self.config.copy() + + +class SumKernel(BaseKernel): + def __init__(self, + kernel_list: Optional[List[Union[BaseKernel, tf.Tensor]]] = None) -> None: + """ + Construct a kernel by summing different kernels. + """ + super().__init__() + self.kernel_list = [] + self.config: dict = {'kernel_type': 'Sum', 'kernel_list': {}} + if kernel_list is not None: + self.kernel_list = kernel_list + for i in range(len(self.kernel_list)): + if isinstance(self.kernel_list[i], BaseKernel): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].config # type: ignore + elif isinstance(self.kernel_list[i], tf.Tensor): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].numpy() # type: ignore + else: + raise ValueError(str(type(self.kernel_list[i])) + 'is not supported by SumKernel.') + + def call(self, x: Union[np.ndarray, tf.Tensor], y: Union[np.ndarray, tf.Tensor], + infer_parameter: bool = False) -> tf.Tensor: + value_list: List[tf.Tensor] = [] + for k in self.kernel_list: + if isinstance(k, BaseKernel) or isinstance(k, SumKernel) or isinstance(k, ProductKernel): + value_list.append(k(x, y, infer_parameter)) + elif isinstance(k, tf.Tensor): + value_list.append(k * tf.ones((x.shape[0], y.shape[0]))) + else: + raise ValueError(type(k) + 'is not supported by SumKernel.') + return tf.reduce_sum(tf.stack(value_list), axis=0) + + def __add__( + self, + other: Union[BaseKernel, tf.Tensor] + ) -> 'SumKernel': + kernel_count = len(self.kernel_list) + if isinstance(other, SumKernel): + for k in other.kernel_list: + self.kernel_list.append(k) + if isinstance(k, BaseKernel): + self.config['kernel_list']['comp_' + str(kernel_count)] = k.config + elif isinstance(k, tf.Tensor): + self.config['kernel_list']['comp_' + str(kernel_count)] = k.numpy() + kernel_count += 1 + elif isinstance(other, BaseKernel): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(kernel_count)] = other.config + elif isinstance(other, tf.Tensor): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(kernel_count)] = other.numpy() + else: + raise ValueError(type(other) + 'is not supported by SumKernel.') + return self + + def __radd__(self, other: BaseKernel) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, tf.Tensor] + ) -> BaseKernel: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for ki in self.kernel_list: + for kj in other.kernel_list: + sum_kernel.kernel_list.append((ki * kj)) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list) - 1)] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + elif isinstance(other, ProductKernel): + return other * self + elif isinstance(other, BaseKernel) or isinstance(other, tf.Tensor): + sum_kernel = SumKernel() + for ki in self.kernel_list: + sum_kernel.kernel_list.append(other * ki) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list) - 1)] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + else: + raise ValueError(type(other) + 'is not supported by SumKernel.') + + def __rmul__( + self, + other: BaseKernel + ) -> BaseKernel: + return self.__mul__(other) + + def __truediv__(self, other: tf.Tensor) -> BaseKernel: + if isinstance(other, tf.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + config = fill_composite_config(config) + return cls(**config) + + +class ProductKernel(tf.keras.Model): + def __init__(self, + kernel_list: Optional[List[Union[BaseKernel, tf.Tensor]]] = None) -> None: + """ + Construct a kernel by multiplying different kernels. + """ + super().__init__() + self.kernel_list = [] + self.config: dict = {'kernel_type': 'Product', 'kernel_list': {}} + if kernel_list is not None: + self.kernel_list = kernel_list + for i in range(len(self.kernel_list)): + if isinstance(self.kernel_list[i], BaseKernel): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].config # type: ignore + elif isinstance(self.kernel_list[i], tf.Tensor): + self.config['kernel_list']['comp_' + str(i)] = self.kernel_list[i].cpu().numpy() # type: ignore + else: + raise ValueError(str(type(self.kernel_list[i])) + 'is not supported by ProductKernel.') + + def call(self, x: Union[np.ndarray, tf.Tensor], y: Union[np.ndarray, tf.Tensor], + infer_parameter: bool = False) -> tf.Tensor: + value_list: List[tf.Tensor] = [] + for k in self.kernel_list: + if isinstance(k, BaseKernel) or isinstance(k, SumKernel) or isinstance(k, ProductKernel): + value_list.append(k(x, y, infer_parameter)) + elif isinstance(k, tf.Tensor): + value_list.append(k * tf.ones((x.shape[0], y.shape[0]))) + else: + raise ValueError(type(k) + 'is not supported by ProductKernel.') + return tf.reduce_prod(tf.stack(value_list), axis=0) + + def __add__( + self, + other: Union[BaseKernel, 'SumKernel', 'ProductKernel', tf.Tensor] + ) -> 'SumKernel': + if isinstance(other, SumKernel): + other.kernel_list.append(self) + other.config['kernel_list']['comp_' + str(len(other.kernel_list))] = self.config + return other + elif isinstance(other, ProductKernel) or isinstance(other, BaseKernel): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.config + return sum_kernel + elif isinstance(other, tf.Tensor): + sum_kernel = SumKernel() + sum_kernel.kernel_list.append(self) + sum_kernel.config['kernel_list']['comp_0'] = self.config + sum_kernel.kernel_list.append(other) + sum_kernel.config['kernel_list']['comp_1'] = other.numpy() + return sum_kernel + else: + raise ValueError(type(other) + 'is not supported by ProductKernel.') + + def __radd__( + self, + other: Union[BaseKernel, 'SumKernel', 'ProductKernel'] + ) -> 'SumKernel': + return self.__add__(other) + + def __mul__( + self, + other: Union[BaseKernel, 'SumKernel', 'ProductKernel', tf.Tensor] + ) -> Union['SumKernel', 'ProductKernel']: + if isinstance(other, SumKernel): + sum_kernel = SumKernel() + for k in other.kernel_list: + tmp_prod_kernel = deepcopy(self) + tmp_prod_kernel.kernel_list.append(k) + sum_kernel.kernel_list.append(tmp_prod_kernel) + sum_kernel.config['kernel_list']['comp_' + str(len(sum_kernel.kernel_list))] = \ + sum_kernel.kernel_list[-1].config # type: ignore + return sum_kernel + elif isinstance(other, ProductKernel): + for k in other.kernel_list: + self.kernel_list.append(k) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] = k.config # type: ignore + return self + elif isinstance(other, BaseKernel): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] = other.config # type: ignore + return self + elif isinstance(other, tf.Tensor): + self.kernel_list.append(other) + self.config['kernel_list']['comp_' + str(len(self.kernel_list))] = other.numpy() # type: ignore + return self + else: + raise ValueError(type(other) + 'is not supported by ProductKernel.') + + def __rmul__( + self, + other: Union[BaseKernel, 'SumKernel', 'ProductKernel'] + ) -> Union['SumKernel', 'ProductKernel']: + return self.__mul__(other) + + def __truediv__(self, other: tf.Tensor) -> Union['SumKernel', 'ProductKernel']: + if isinstance(other, tf.Tensor): + return self.__mul__(1 / other) + else: + raise ValueError('Kernels can only be divided by a constant.') + + def __rtruediv__(self, other): + raise ValueError('Kernels can not be used as divisor.') + + def __sub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def __rsub__(self, other): + raise ValueError('Kernels do not support subtraction.') + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + config = fill_composite_config(config) + return cls(**config) + + +class GaussianRBF(BaseKernel): def __init__( self, sigma: Optional[tf.Tensor] = None, init_sigma_fn: Optional[Callable] = None, - trainable: bool = False + trainable: bool = False, + active_dims: Optional[list] = None ) -> None: """ Gaussian RBF kernel: k(x,y) = exp(-(1/(2*sigma^2)||x-y||^2). A forward pass takes @@ -54,35 +513,35 @@ def __init__( :func:`~alibi_detect.utils.tensorflow.kernels.sigma_median`. trainable Whether or not to track gradients w.r.t. sigma to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. """ - super().__init__() - init_sigma_fn = sigma_median if init_sigma_fn is None else init_sigma_fn - self.config = {'sigma': sigma, 'trainable': trainable, 'init_sigma_fn': init_sigma_fn} - if sigma is None: - self.log_sigma = tf.Variable(np.empty(1), dtype=tf.keras.backend.floatx(), trainable=trainable) - self.init_required = True - else: - sigma = tf.cast(tf.reshape(sigma, (-1,)), dtype=tf.keras.backend.floatx()) # [Ns,] - self.log_sigma = tf.Variable(tf.math.log(sigma), trainable=trainable) - self.init_required = False - self.init_sigma_fn = init_sigma_fn + super().__init__(active_dims) + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.config = {'sigma': sigma, 'trainable': trainable, 'init_sigma_fn': self.init_sigma_fn, + 'active_dims': active_dims, 'kernel_type': 'GaussianRBF'} + self.parameter_dict['log-sigma'] = KernelParameter( + value=tf.reshape(tf.math.log( + tf.cast(sigma, tf.keras.backend.floatx())), -1) if sigma is not None else tf.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False + ) self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) @property def sigma(self) -> tf.Tensor: - return tf.math.exp(self.log_sigma) + return tf.math.exp(self.parameter_dict['log-sigma'].value) - def call(self, x: tf.Tensor, y: tf.Tensor, infer_sigma: bool = False) -> tf.Tensor: + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: y = tf.cast(y, x.dtype) x, y = tf.reshape(x, (x.shape[0], -1)), tf.reshape(y, (y.shape[0], -1)) # flatten dist = distance.squared_pairwise_distance(x, y) # [Nx, Ny] - if infer_sigma or self.init_required: - if self.trainable and infer_sigma: - raise ValueError("Gradients cannot be computed w.r.t. an inferred sigma value") - sigma = self.init_sigma_fn(x, y, dist) - self.log_sigma.assign(tf.math.log(sigma)) - self.init_required = False + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) gamma = tf.constant(1. / (2. * self.sigma ** 2), dtype=x.dtype) # [Ns,] # TODO: do matrix multiplication after all? @@ -91,7 +550,7 @@ def call(self, x: tf.Tensor, y: tf.Tensor, infer_sigma: bool = False) -> tf.Tens def get_config(self) -> dict: """ - Returns a serializable config dict (excluding the input_sigma_fn, which is serialized in alibi_detect.saving). + Returns a serializable config dict (excluding the infer_sigma_fn, which is serialized in alibi_detect.saving). """ cfg = self.config.copy() if isinstance(cfg['sigma'], tf.Tensor): @@ -110,10 +569,270 @@ def from_config(cls, config): A kernel config dictionary. """ config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = tf.convert_to_tensor(np.array(config['sigma'])) return cls(**config) -class DeepKernel(tf.keras.Model): +class RationalQuadratic(BaseKernel): + def __init__( + self, + alpha: Optional[tf.Tensor] = None, + init_alpha_fn: Optional[Callable] = None, + sigma: Optional[tf.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: Optional[list] = None + ) -> None: + """ + Rational Quadratic kernel: k(x,y) = (1 + ||x-y||^2 / (2*sigma^2))^(-alpha). + A forward pass takesa batch of instances x [Nx, features] and y [Ny, features] + and returns the kernel matrix [Nx, Ny]. + + Parameters + ---------- + alpha + Exponent parameter of the kernel. + init_alpha_fn + Function used to compute the exponent parameter `alpha`. Used when `alpha` is to be inferred. + sigma + Bandwidth used for the kernel. + init_sigma_fn + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. + trainable + Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__(active_dims) + if alpha is not None and sigma is not None: + if alpha.shape != sigma.shape: + raise ValueError('alpha and sigma must have the same shape.') + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.init_alpha_fn = init_alpha_fn + self.config = {'alpha': alpha, 'sigma': sigma, 'trainable': trainable, 'active_dims': active_dims, + 'init_sigma_fn': self.init_sigma_fn, 'init_alpha_fn': self.init_alpha_fn, + 'kernel_type': 'RationalQuadratic'} + self.parameter_dict['log-alpha'] = KernelParameter( + value=tf.reshape(tf.math.log( + tf.cast(alpha, tf.keras.backend.floatx())), -1) if alpha is not None else tf.zeros(1), + init_fn=self.init_alpha_fn, # type: ignore + requires_grad=trainable, + requires_init=True if alpha is None else False + ) + self.parameter_dict['log-sigma'] = KernelParameter( + value=tf.reshape(tf.math.log( + tf.cast(sigma, tf.keras.backend.floatx())), -1) if sigma is not None else tf.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False + ) + self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) + + @property + def sigma(self) -> tf.Tensor: + return tf.math.exp(self.parameter_dict['log-sigma'].value) + + @property + def alpha(self) -> tf.Tensor: + return tf.math.exp(self.parameter_dict['log-alpha'].value) + + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: + y = tf.cast(y, x.dtype) + x, y = tf.reshape(x, (x.shape[0], -1)), tf.reshape(y, (y.shape[0], -1)) + dist = distance.squared_pairwise_distance(x, y) + + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + + kernel_mat = tf.stack([(1 + tf.square(dist) / + (2 * self.alpha[i] * (self.sigma[i] ** 2))) + ** (-self.alpha[i]) for i in range(len(self.sigma))], axis=0) + return tf.reduce_mean(kernel_mat, axis=0) + + def get_config(self) -> dict: + """ + Returns a serializable config dict (excluding the infer_sigma_fn and infer_alpha_fn, + which is serialized in alibi_detect.saving). + """ + cfg = self.config.copy() + if isinstance(cfg['sigma'], tf.Tensor): + cfg['sigma'] = cfg['sigma'].numpy().tolist() + if isinstance(cfg['alpha'], tf.Tensor): + cfg['alpha'] = cfg['alpha'].numpy().tolist() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = tf.convert_to_tensor(np.array(config['sigma'])) + if 'alpha' in config and config['alpha'] is not None: + config['alpha'] = tf.convert_to_tensor(np.array(config['alpha'])) + return cls(**config) + + +class Periodic(BaseKernel): + def __init__( + self, + tau: Optional[tf.Tensor] = None, + init_tau_fn: Optional[Callable] = None, + sigma: Optional[tf.Tensor] = None, + init_sigma_fn: Optional[Callable] = None, + trainable: bool = False, + active_dims: Optional[list] = None + ) -> None: + """ + Periodic kernel: k(x,y) = exp(-2 * sin(pi * |x - y| / tau)^2 / (sigma^2)). + A forward pass takesa batch of instances x [Nx, features] and y [Ny, features] + and returns the kernel matrix [Nx, Ny]. + + Parameters + ---------- + tau + Period of the periodic kernel. + init_tau_fn + Function used to compute the period `tau`. Used when `tau` is to be inferred. + sigma + Bandwidth used for the kernel. + init_sigma_fn + Function used to compute the bandwidth `sigma`. Used when `sigma` is to be inferred. + trainable + Whether or not to track gradients w.r.t. `sigma` to allow it to be trained. + active_dims + Indices of the dimensions of the feature to be used for the kernel. If None, all dimensions are used. + """ + super().__init__(active_dims) + if tau is not None and sigma is not None: + if tau.shape != sigma.shape: + raise ValueError('tau and sigma must have the same shape.') + self.init_sigma_fn = log_sigma_median if init_sigma_fn is None else init_sigma_fn + self.init_tau_fn = init_tau_fn + self.config = {'tau': tau, 'sigma': sigma, 'trainable': trainable, 'active_dims': active_dims, + 'init_tau_fn': self.init_tau_fn, 'init_sigma_fn': self.init_sigma_fn, + 'kernel_type': 'Periodic'} + self.parameter_dict['log-tau'] = KernelParameter( + value=tf.reshape(tf.math.log( + tf.cast(tau, tf.keras.backend.floatx())), -1) if tau is not None else tf.zeros(1), + init_fn=self.init_tau_fn, # type: ignore + requires_grad=trainable, + requires_init=True if tau is None else False + ) + self.parameter_dict['log-sigma'] = KernelParameter( + value=tf.reshape(tf.math.log( + tf.cast(sigma, tf.keras.backend.floatx())), -1) if sigma is not None else tf.zeros(1), + init_fn=self.init_sigma_fn, # type: ignore + requires_grad=trainable, + requires_init=True if sigma is None else False + ) + self.trainable = trainable + self.init_required = any([param.requires_init for param in self.parameter_dict.values()]) + + @property + def tau(self) -> tf.Tensor: + return tf.math.exp(self.parameter_dict['log-tau'].value) + + @property + def sigma(self) -> tf.Tensor: + return tf.math.exp(self.parameter_dict['log-sigma'].value) + + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: + y = tf.cast(y, x.dtype) + x, y = tf.reshape(x, (x.shape[0], -1)), tf.reshape(y, (y.shape[0], -1)) + dist = distance.squared_pairwise_distance(x, y) + + if infer_parameter or self.init_required: + infer_kernel_parameter(self, x, y, dist, infer_parameter) + + kernel_mat = tf.stack([tf.math.exp(-2 * tf.square( + tf.math.sin(tf.cast(np.pi, x.dtype) * dist / self.tau[i])) / (self.sigma[i] ** 2)) + for i in range(len(self.sigma))], axis=0) + return tf.reduce_mean(kernel_mat, axis=0) + + def get_config(self) -> dict: + """ + Returns a serializable config dict (excluding the infer_sigma_fn and infer_tau_fn, + which is serialized in alibi_detect.saving). + """ + cfg = self.config.copy() + if isinstance(cfg['sigma'], tf.Tensor): + cfg['sigma'] = cfg['sigma'].numpy().tolist() + if isinstance(cfg['tau'], tf.Tensor): + cfg['tau'] = cfg['tau'].numpy().tolist() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg + + @classmethod + def from_config(cls, config): + """ + Instantiates a kernel from a config dictionary. + + Parameters + ---------- + config + A kernel config dictionary. + """ + config.pop('flavour') + config.pop('kernel_type') + if 'sigma' in config and config['sigma'] is not None: + config['sigma'] = tf.convert_to_tensor(np.array(config['sigma'])) + if 'tau' in config and config['tau'] is not None: + config['tau'] = tf.convert_to_tensor(np.array(config['tau'])) + return cls(**config) + + +class ProjKernel(BaseKernel): + def __init__( + self, + proj: tf.keras.Model, + raw_kernel: BaseKernel = GaussianRBF(trainable=True), + ) -> None: + """ + A kernel that combines a raw kernel (e.g. RBF) with a projection function (e.g. deep net) as + k(x, y) = k(proj(x), proj(y)). A forward pass takes a batch of instances x [Nx, features] and + y [Ny, features] and returns the kernel matrix [Nx, Ny]. + + Parameters: + ---------- + proj + The projection to be applied to the inputs before applying raw_kernel + raw_kernel + The kernel to apply to the projected inputs. Defaults to a Gaussian RBF with trainable bandwidth. + """ + super().__init__() + self.config = {'proj': proj, 'raw_kernel': raw_kernel, 'kernel_type': 'Proj'} + self.proj = proj + self.raw_kernel = raw_kernel + self.init_required = False + + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: + return self.raw_kernel(self.proj(x), self.proj(y), infer_parameter) + + def get_config(self) -> dict: + cfg = self.config.copy() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg + + @classmethod + def from_config(cls, config): + config.pop('flavour') + config.pop('kernel_type') + return cls(**config) + + +class DeepKernel(BaseKernel): """ Computes similarities as k(x,y) = (1-eps)*k_a(proj(x), proj(y)) + eps*k_b(x,y). A forward pass takes a batch of instances x [Nx, features] and y [Ny, features] and returns @@ -136,21 +855,21 @@ class DeepKernel(tf.keras.Model): def __init__( self, proj: tf.keras.Model, - kernel_a: Union[tf.keras.Model, str] = 'rbf', - kernel_b: Optional[Union[tf.keras.Model, str]] = 'rbf', + kernel_a: BaseKernel = GaussianRBF(trainable=True), + kernel_b: BaseKernel = GaussianRBF(trainable=True), eps: Union[float, str] = 'trainable' ) -> None: super().__init__() - self.config = {'proj': proj, 'kernel_a': kernel_a, 'kernel_b': kernel_b, 'eps': eps} - if kernel_a == 'rbf': - kernel_a = GaussianRBF(trainable=True) - if kernel_b == 'rbf': - kernel_b = GaussianRBF(trainable=True) + self.proj = proj self.kernel_a = kernel_a self.kernel_b = kernel_b - self.proj = proj + proj_kernel = ProjKernel(proj=proj, raw_kernel=kernel_a) if kernel_b is not None: self._init_eps(eps) + self.comp_kernel = (1-tf.sigmoid(self.logit_eps))*proj_kernel + tf.sigmoid(self.logit_eps)*kernel_b + else: + self.comp_kernel = proj_kernel + self.config = {'proj': proj, 'kernel_a': kernel_a, 'kernel_b': kernel_b, 'eps': eps, 'kernel_type': 'Deep'} def _init_eps(self, eps: Union[float, str]) -> None: if isinstance(eps, float): @@ -167,15 +886,41 @@ def _init_eps(self, eps: Union[float, str]) -> None: def eps(self) -> tf.Tensor: return tf.math.sigmoid(self.logit_eps) if self.kernel_b is not None else tf.constant(0.) - def call(self, x: tf.Tensor, y: tf.Tensor) -> tf.Tensor: - similarity = self.kernel_a(self.proj(x), self.proj(y)) # type: ignore[operator] - if self.kernel_b is not None: - similarity = (1-self.eps)*similarity + self.eps*self.kernel_b(x, y) # type: ignore[operator] - return similarity + def kernel_function(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: + return self.comp_kernel(x, y, infer_parameter) def get_config(self) -> dict: - return self.config.copy() + cfg = self.config.copy() + cfg.update({'flavour': Framework.TENSORFLOW.value}) + return cfg @classmethod def from_config(cls, config): + config.pop('kernel_type') + config.pop('flavour') return cls(**config) + + +def fill_composite_config(config: dict) -> dict: + final_config: dict = {'kernel_list': []} + for k_config in config['kernel_list'].values(): + if isinstance(k_config, dict): + k_config.pop('src') + if k_config['kernel_type'] == 'Sum': + final_config['kernel_list'].append(SumKernel.from_config(k_config)) + elif k_config['kernel_type'] == 'Product': + final_config['kernel_list'].append(ProductKernel.from_config(k_config)) + elif k_config['kernel_type'] == 'GaussianRBF': + final_config['kernel_list'].append(GaussianRBF.from_config(k_config)) + elif k_config['kernel_type'] == 'Periodic': + final_config['kernel_list'].append(Periodic.from_config(k_config)) + elif k_config['kernel_type'] == 'RationalQuadratic': + final_config['kernel_list'].append(RationalQuadratic.from_config(k_config)) + else: + raise ValueError('Unknown kernel type.') + elif isinstance(k_config, np.ndarray) or isinstance(k_config, float) or \ + isinstance(k_config, np.float32) or isinstance(k_config, np.float64): + final_config['kernel_list'].append(tf.cast(np.array(k_config), tf.keras.backend.floatx())) + else: + raise ValueError('Unknown component type.') + return final_config diff --git a/alibi_detect/utils/tensorflow/tests/test_kernels_tf.py b/alibi_detect/utils/tensorflow/tests/test_kernels_tf.py index 20f26962a..80c40498a 100644 --- a/alibi_detect/utils/tensorflow/tests/test_kernels_tf.py +++ b/alibi_detect/utils/tensorflow/tests/test_kernels_tf.py @@ -3,7 +3,9 @@ import pytest import tensorflow as tf from tensorflow.keras.layers import Dense, Input -from alibi_detect.utils.tensorflow import GaussianRBF, DeepKernel +from alibi_detect.utils.tensorflow import GaussianRBF, DeepKernel, BaseKernel, RationalQuadratic, Periodic, \ + log_sigma_median +from alibi_detect.utils.tensorflow.distance import squared_pairwise_distance sigma = [None, np.array([1.]), np.array([1., 2.])] n_features = [5, 10] @@ -26,24 +28,192 @@ def test_gaussian_kernel(gaussian_kernel_params): y = tf.convert_to_tensor(np.random.random(yshape).astype('float32')) kernel = GaussianRBF(sigma=sigma, trainable=trainable) - infer_sigma = True if sigma is None else False - if trainable and infer_sigma: + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: with pytest.raises(Exception): - kernel(x, y, infer_sigma=infer_sigma) + kernel(x, y, infer_parameter=infer_parameter) else: - k_xy = kernel(x, y, infer_sigma=infer_sigma).numpy() - k_xx = kernel(x, x, infer_sigma=infer_sigma).numpy() + k_xy = kernel(x, y, infer_parameter=infer_parameter).numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).numpy() assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) assert (k_xx > 0.).all() and (k_xy > 0.).all() -class MyKernel(tf.keras.Model): # TODO: Support then test models using keras functional API +def log_sigma_mean(x: tf.Tensor, y: tf.Tensor, dist: tf.Tensor) -> tf.Tensor: + sigma = tf.expand_dims(.5 * tf.reduce_mean(tf.reshape(dist, (-1,))) ** .5, axis=0) + return tf.math.log(sigma) + + +kernel_ref = ['GaussianRBF', 'RationalQuadratic', 'Periodic'] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +init_fn = [None, log_sigma_median, log_sigma_mean] +tests_init_fn = list(product(kernel_ref, n_features, n_instances, trainable, init_fn)) + + +@pytest.fixture +def init_fn_params(request): + return tests_init_fn[request.param] + + +@pytest.mark.parametrize('init_fn_params', list(range(len(tests_init_fn))), indirect=True) +def test_init_fn(init_fn_params): + kernel_ref, n_features, n_instances, trainable, init_fn = init_fn_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + x = tf.convert_to_tensor(np.random.random(xshape).astype('float32')) + y = tf.convert_to_tensor(np.random.random(yshape).astype('float32')) + + if kernel_ref == 'GaussianRBF': + kernel = GaussianRBF(trainable=trainable, init_sigma_fn=init_fn) + elif kernel_ref == 'RationalQuadratic': + kernel = RationalQuadratic(trainable=trainable, init_sigma_fn=init_fn) + elif kernel_ref == 'Periodic': + kernel = Periodic(trainable=trainable, init_sigma_fn=init_fn) + else: + raise NotImplementedError + if trainable: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=True) + else: + k_xy = kernel(x, y, infer_parameter=True).numpy() + k_xx = kernel(x, x, infer_parameter=True).numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + if init_fn is not None: + np.testing.assert_almost_equal(kernel.sigma.numpy(), + np.exp(init_fn(x, y, squared_pairwise_distance(x, y)).numpy()), + decimal=4) + + +sigma = [None, np.array([1.]), np.array([2.])] +alpha = [None, np.array([1.]), np.array([2.])] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_rqk = list(product(sigma, alpha, n_features, n_instances, trainable)) +n_tests_rqk = len(tests_rqk) + + +@pytest.fixture +def rationalquadratic_kernel_params(request): + return tests_rqk[request.param] + + +@pytest.mark.parametrize('rationalquadratic_kernel_params', list(range(n_tests_rqk)), indirect=True) +def test_rationalquadratic_kernel(rationalquadratic_kernel_params): + sigma, alpha, n_features, n_instances, trainable = rationalquadratic_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + x = tf.convert_to_tensor(np.random.random(xshape).astype('float32')) + y = tf.convert_to_tensor(np.random.random(yshape).astype('float32')) + + kernel = RationalQuadratic(sigma=sigma, alpha=alpha, trainable=trainable) + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +sigma = [None, np.array([1.]), np.array([2.])] +tau = [None, np.array([8.]), np.array([24.])] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_pk = list(product(sigma, tau, n_features, n_instances, trainable)) +n_tests_pk = len(tests_pk) + + +@pytest.fixture +def periodic_kernel_params(request): + return tests_pk[request.param] + + +@pytest.mark.parametrize('periodic_kernel_params', list(range(n_tests_pk)), indirect=True) +def test_periodic_kernel(periodic_kernel_params): + sigma, tau, n_features, n_instances, trainable = periodic_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + x = tf.convert_to_tensor(np.random.random(xshape).astype('float32')) + y = tf.convert_to_tensor(np.random.random(yshape).astype('float32')) + + kernel = Periodic(sigma=sigma, tau=tau, trainable=trainable) + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +sigma_0 = [None, np.array([1.])] +sigma_1 = [None, np.array([1.])] +sigma_2 = [None, np.array([1.])] +operation_0 = ['*', '+'] +operation_1 = ['*', '+'] +n_features = [5, 10] +n_instances = [(100, 100), (100, 75)] +trainable = [True, False] +tests_ck = list(product(sigma_0, sigma_1, sigma_2, + operation_0, operation_1, n_features, n_instances, trainable)) +n_tests_ck = len(tests_ck) + + +@pytest.fixture +def comp_kernel_params(request): + return tests_ck[request.param] + + +@pytest.mark.parametrize('comp_kernel_params', list(range(n_tests_ck)), indirect=True) +def test_comp_kernel(comp_kernel_params): + (sigma_0, sigma_1, sigma_2, operation_0, operation_1, + n_features, n_instances, trainable) = comp_kernel_params + xshape, yshape = (n_instances[0], n_features), (n_instances[1], n_features) + x = tf.convert_to_tensor(np.random.random(xshape).astype('float32')) + y = tf.convert_to_tensor(np.random.random(yshape).astype('float32')) + + kernel_0 = GaussianRBF(sigma=sigma_0, trainable=trainable) + kernel_1 = GaussianRBF(sigma=sigma_1, trainable=trainable) + kernel_2 = GaussianRBF(sigma=sigma_2, trainable=trainable) + if operation_0 == '*' and operation_1 == '*': + kernel = kernel_0 * kernel_1 * kernel_2 + elif operation_0 == '*' and operation_1 == '+': + kernel = (kernel_0 * kernel_1 + kernel_2) / tf.convert_to_tensor(2.0) # ensure k(x, x) = 1 + elif operation_0 == '+' and operation_1 == '*': + kernel = (kernel_0 + kernel_1 * kernel_2) / tf.convert_to_tensor(2.0) # ensure k(x, x) = 1 + elif operation_0 == '+' and operation_1 == '+': + kernel = (kernel_0 + kernel_1 + kernel_2) / tf.convert_to_tensor(3.0) # ensure k(x, x) = 1 + else: + with pytest.raises(Exception): + raise Exception('Invalid operation') + infer_parameter = True if sigma is None else False + if trainable and infer_parameter: + with pytest.raises(Exception): + kernel(x, y, infer_parameter=infer_parameter) + else: + k_xy = kernel(x, y, infer_parameter=infer_parameter).numpy() + k_xx = kernel(x, x, infer_parameter=infer_parameter).numpy() + assert k_xy.shape == n_instances and k_xx.shape == (xshape[0], xshape[0]) + np.testing.assert_almost_equal(k_xx.trace(), xshape[0], decimal=4) + assert (k_xx > 0.).all() and (k_xy > 0.).all() + + +class MyKernel(BaseKernel): # TODO: Support then test models using keras functional API def __init__(self, n_features: int): super().__init__() self.dense = Dense(20) - def call(self, x: tf.Tensor, y: tf.Tensor) -> tf.Tensor: + def call(self, x: tf.Tensor, y: tf.Tensor, infer_parameter: bool = False) -> tf.Tensor: return tf.einsum('ji,ki->jk', self.dense(x), self.dense(y)) diff --git a/doc/source/examples/cd_combined_kernel.ipynb b/doc/source/examples/cd_combined_kernel.ipynb new file mode 100644 index 000000000..773fa9aed --- /dev/null +++ b/doc/source/examples/cd_combined_kernel.ipynb @@ -0,0 +1,333 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create sum and product kernels with exsisting kernels\n", + "\n", + "\n", + "### From time to time, out dataset might contain values and features that might be of different types or scales. For instance, a temperture dataset might have two features with one being the timestamp and the other being the reading. As a result, we might want to apply differnt kernels on these two features respectively, and use the combined kernel for the drift detectors for a better test power." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy.stats as stats\n", + "import torch\n", + "import matplotlib.pyplot as plt\n", + "import tensorflow as tf\n", + "\n", + "backend = 'tensorflow'\n", + "\n", + "from alibi_detect.cd import MMDDrift\n", + "if backend == 'pytorch':\n", + " from alibi_detect.utils.pytorch.kernels import GaussianRBF, Periodic\n", + "elif backend == 'tensorflow':\n", + " from alibi_detect.utils.tensorflow.kernels import GaussianRBF, Periodic\n", + "else:\n", + " raise ValueError('Backend {} not supported'.format(backend))\n", + "\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "def get_sin(N):\n", + " c_0 = np.random.uniform(0, 168, N)\n", + " x_0 = np.sin(c_0 / (12 / np.pi)) + np.random.normal(0, 0.1, N)\n", + "\n", + " c_1 = stats.beta.rvs(a=1.2, b=1.2, size=N) * 24 + np.random.choice([0, 24, 48, 72, 96, 120, 144], size=N)\n", + " x_1 = np.sin(c_1 / (12 / np.pi)) * (np.mod(c_1, 24) < 12) + \\\n", + " np.sin(c_1 / (12 / np.pi)) * (np.mod(c_1, 24) >= 12) * 1.25 + \\\n", + " + np.random.normal(0, 0.1, N)\n", + " \n", + " x_ref = np.hstack([c_0.reshape(-1, 1), x_0.reshape(-1, 1)])\n", + " x_test = np.hstack([c_1.reshape(-1, 1), x_1.reshape(-1, 1)]) \n", + " \n", + " return x_ref, x_test" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "x_ref, x_test = get_sin(N=1000)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Here, we create two simple datasets with waves and have two features. The test data shows apparent drift around the wave through." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.5, 1.5)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(8, 3), dpi=128)\n", + "plt.plot(x_ref[:, 0], x_ref[:, 1], 'bo', alpha=0.5, markersize=2.5, label='Reference')\n", + "plt.plot(x_test[:, 0], x_test[:, 1], 'ro', alpha=0.5, markersize=2.5, label='Test')\n", + "plt.legend()\n", + "plt.ylim(-1.5, 1.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### If we use the standard RBF kernel on both features with the MMD drift detector, we can see that the drift is not detected." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "Kernel_RBF = GaussianRBF()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "cd_RBF = MMDDrift(x_ref=x_ref,\n", + " backend=backend,\n", + " kernel=Kernel_RBF)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'data': {'is_drift': 0,\n", + " 'distance': -0.000772655,\n", + " 'p_val': 0.8,\n", + " 'threshold': 0.05,\n", + " 'distance_threshold': 0.0021861196},\n", + " 'meta': {'name': 'MMDDriftTF',\n", + " 'detector_type': 'offline',\n", + " 'data_type': None,\n", + " 'version': '0.9.2dev',\n", + " 'backend': 'tensorflow'}}" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preds_RBF = cd_RBF.predict(x_test)\n", + "preds_RBF" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### To facilitate our knowledge that the data contain waves, we use a combined kernel averaged from two kernels. The first kernel is a periodic kernel with a specified period of 24 and only works on the first feature. The second kernel is an RBF kernel with an inferred bandwidth and only works on the second feature." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "if backend == 'pytorch':\n", + " Kernel_0 = Periodic(tau=torch.tensor([24.0]), active_dims=[0])\n", + " Kernel_1 = GaussianRBF(active_dims=[1])\n", + " Kernel_avg = (Kernel_0 + Kernel_1) / torch.tensor(2.0)\n", + "elif backend == 'tensorflow':\n", + " Kernel_0 = Periodic(tau=tf.convert_to_tensor([24.0]), active_dims=[0])\n", + " Kernel_1 = GaussianRBF(active_dims=[1])\n", + " Kernel_avg = (Kernel_0 + Kernel_1) / tf.convert_to_tensor(2.0)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "cd_avg = MMDDrift(x_ref=x_ref,\n", + " backend=backend,\n", + " kernel=Kernel_avg)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### We can see the drift is detected with the combined kernel." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'data': {'is_drift': 1,\n", + " 'distance': 0.0052251816,\n", + " 'p_val': 0.0,\n", + " 'threshold': 0.05,\n", + " 'distance_threshold': 0.0009160042},\n", + " 'meta': {'name': 'MMDDriftTF',\n", + " 'detector_type': 'offline',\n", + " 'data_type': None,\n", + " 'version': '0.9.2dev',\n", + " 'backend': 'tensorflow'}}" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preds_avg = cd_avg.predict(x_test)\n", + "preds_avg" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### The kernel, its components and associated parameters can be inspected as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "ListWrapper([, ])\n", + "ListWrapper([, ])\n" + ] + } + ], + "source": [ + "print(cd_avg._detector.kernel)\n", + "print(cd_avg._detector.kernel.kernel_list[0].kernel_factors)\n", + "print(cd_avg._detector.kernel.kernel_list[1].kernel_factors)" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tf.Tensor([24.], shape=(1,), dtype=float32)\n", + "tf.Tensor([34.68171], shape=(1,), dtype=float32)\n" + ] + } + ], + "source": [ + "print(Kernel_avg.kernel_list[0].kernel_factors[0].tau)\n", + "print(Kernel_avg.kernel_list[0].kernel_factors[0].sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "tf.Tensor([0.5185638], shape=(1,), dtype=float32)\n" + ] + } + ], + "source": [ + "print(Kernel_avg.kernel_list[1].kernel_factors[0].sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/doc/source/examples/cd_create_customised_kernel.ipynb b/doc/source/examples/cd_create_customised_kernel.ipynb new file mode 100644 index 000000000..c3a8052aa --- /dev/null +++ b/doc/source/examples/cd_create_customised_kernel.ipynb @@ -0,0 +1,363 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Create customised kernel to be used with drift detectors\n", + "\n", + "### Sometimes we might prefer to use some prior knowledge or pre-trained embeddings to build a customised kernel (distance) function instead. In this notebook, we will demonstrate how to implement a user-defined kernel with either a customised distance function or a specific feature projection function. " + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import scipy.stats as stats\n", + "import torch\n", + "import matplotlib.pyplot as plt\n", + "import tensorflow as tf\n", + "\n", + "backend = 'pytorch'\n", + "\n", + "from alibi_detect.cd import MMDDrift\n", + "if backend == 'pytorch':\n", + " from alibi_detect.utils.pytorch.kernels import BaseKernel, ProjKernel, GaussianRBF\n", + "elif backend == 'tensorflow':\n", + " from alibi_detect.utils.tensorflow.kernels import BaseKernel, ProjKernel, GaussianRBF\n", + "else:\n", + " raise ValueError('Backend {} not supported'.format(backend))\n", + "\n", + "import matplotlib.pyplot as plt\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### We first consider to create a kernel that uses a user specified distance function. For instance, we can write a periodic kernel's distance function with the Trigonometric functions: $k(x,y) = exp(-2 \\cdot \\frac{sin(pi \\cdot \\frac{|x - y|}{\\tau})^2}{\\sigma^2})$. To do so, the easiest way is to import and inherit the BaseKernel class from the corresponding backend (here we use Pytorch), and overload the kernelfunction method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### For this example, we manually specified the kernel's parameters in the kernel function. To implement these parameters as variables for training or initialisation heuristics, please refer to the implementations in the built-in kernels." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class PeriodicKernel(BaseKernel):\n", + " def __init__(self) -> None:\n", + " super().__init__()\n", + "\n", + " def kernel_function(self, x, y):\n", + " tau = 24.0 # period parameter\n", + " sigma = 0.05 # bandwidth parameter\n", + " x, y = torch.as_tensor(x), torch.as_tensor(y)\n", + " x2 = x.pow(2).sum(dim=-1, keepdim=True)\n", + " y2 = y.pow(2).sum(dim=-1, keepdim=True)\n", + " dist = torch.addmm(y2.transpose(-2, -1), x, y.transpose(-2, -1), alpha=-2).add_(x2)\n", + " kernel_mat = torch.exp(-2 * torch.square(torch.sin(torch.as_tensor(np.pi) * dist / tau)) / (sigma ** 2))\n", + " return kernel_mat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now we create a toy dataset to test our new kernel, where the test data shows an apparent drift around the wave through." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "def get_sin(N):\n", + " c_0 = np.random.uniform(0, 168, N)\n", + " x_0 = np.sin(c_0 / (12 / np.pi)) + np.random.normal(0, 0.1, N)\n", + "\n", + " c_1 = stats.beta.rvs(a=1.2, b=1.2, size=N) * 24 + np.random.choice([0, 24, 48, 72, 96, 120, 144], size=N)\n", + " x_1 = np.sin(c_1 / (12 / np.pi)) * (np.mod(c_1, 24) < 12) + \\\n", + " np.sin(c_1 / (12 / np.pi)) * (np.mod(c_1, 24) >= 12) * 1.25 + \\\n", + " + np.random.normal(0, 0.1, N)\n", + " \n", + " x_ref = np.hstack([c_0.reshape(-1, 1), x_0.reshape(-1, 1)])\n", + " x_test = np.hstack([c_1.reshape(-1, 1), x_1.reshape(-1, 1)]) \n", + " \n", + " return x_ref, x_test" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "x_ref, x_test = get_sin(N=1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.5, 1.5)" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "plt.figure(figsize=(8, 3), dpi=128)\n", + "plt.plot(x_ref[:, 0], x_ref[:, 1], 'bo', alpha=0.5, markersize=2.5, label='Reference')\n", + "plt.plot(x_test[:, 0], x_test[:, 1], 'ro', alpha=0.5, markersize=2.5, label='Test')\n", + "plt.legend()\n", + "plt.ylim(-1.5, 1.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### We can now create an instance of the periodic kernel implemented above and use it with the MMD detector. " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "No GPU detected, fall back on CPU.\n" + ] + } + ], + "source": [ + "kernel_period = PeriodicKernel()\n", + "\n", + "cd = MMDDrift(x_ref=x_ref,\n", + " backend=backend,\n", + " kernel=kernel_period)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'data': {'is_drift': 1,\n", + " 'distance': 0.0006290622733601328,\n", + " 'p_val': 0.029999999329447746,\n", + " 'threshold': 0.05,\n", + " 'distance_threshold': array(0.00055086, dtype=float32)},\n", + " 'meta': {'name': 'MMDDriftTorch',\n", + " 'detector_type': 'offline',\n", + " 'data_type': None,\n", + " 'version': '0.9.2dev',\n", + " 'backend': 'pytorch'}}" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preds = cd.predict(x_test)\n", + "preds" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Alternatively, we might consider using a projection function (which could be anything from a straightforward linear transform to a deep net) to imply our knowledge about the dataset. In this case, we can consider implementing the kernel with the ProjKernel class, where we can define the projection function using the model class from the corresponding backend (i.e. torch.nn.Module)." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "class MyProj(torch.nn.Module):\n", + " def __init__(self) -> None:\n", + " super().__init__()\n", + "\n", + " def forward(self, x):\n", + " x = torch.as_tensor(x)\n", + " return torch.cat([torch.remainder(x[:, 0], 24).reshape(-1, 1), x[:, 1].reshape(-1, 1)], axis=1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### As indicated by the code above, here we create a simple projection function by getting the remainder of the first feature after dividing by 24, while the second feature is kept." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "(-1.5, 1.5)" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "proj = MyProj()\n", + "\n", + "x_proj_ref = proj(x_ref)\n", + "\n", + "x_proj_test = proj(x_test)\n", + "\n", + "plt.figure(figsize=(4, 3), dpi=128)\n", + "plt.plot(x_proj_ref[:, 0], x_proj_ref[:, 1], 'bo', alpha=0.5, markersize=2.5, label='Reference')\n", + "plt.plot(x_proj_test[:, 0], x_proj_test[:, 1], 'ro', alpha=0.5, markersize=2.5, label='Test')\n", + "plt.legend()\n", + "plt.ylim(-1.5, 1.5)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### We can then create the kernel with the projection model and a base RBF kernel and use it together with the MMD detector. " + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "No GPU detected, fall back on CPU.\n" + ] + } + ], + "source": [ + "kernel_proj = ProjKernel(proj = proj,\n", + " raw_kernel= GaussianRBF(sigma=torch.as_tensor(0.05)))\n", + "\n", + "cd_proj = MMDDrift(x_ref=x_ref,\n", + " backend=backend,\n", + " kernel=kernel_proj)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "{'data': {'is_drift': 1,\n", + " 'distance': 0.0009441937452792366,\n", + " 'p_val': 0.0,\n", + " 'threshold': 0.05,\n", + " 'distance_threshold': array(0.00010083, dtype=float32)},\n", + " 'meta': {'name': 'MMDDriftTorch',\n", + " 'detector_type': 'offline',\n", + " 'data_type': None,\n", + " 'version': '0.9.2dev',\n", + " 'backend': 'pytorch'}}" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "preds_proj = cd_proj.predict(x_test)\n", + "preds_proj" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/cd_combined_kernel.ipynb b/examples/cd_combined_kernel.ipynb new file mode 100644 index 000000000..d713eeff1 --- /dev/null +++ b/examples/cd_combined_kernel.ipynb @@ -0,0 +1 @@ +../doc/source/examples/cd_combined_kernel.ipynb \ No newline at end of file