Source code for smac.model.gaussian_process.kernels.hamming_kernel

from __future__ import annotations

from typing import Any

import numpy as np
import sklearn.gaussian_process.kernels as kernels

from smac.model.gaussian_process.kernels.base_kernels import AbstractKernel
from smac.model.gaussian_process.priors.abstract_prior import AbstractPrior

__copyright__ = "Copyright 2022, automl.org"
__license__ = "3-clause BSD"


[docs]class HammingKernel( AbstractKernel, kernels.StationaryKernelMixin, kernels.NormalizedKernelMixin, kernels.Kernel, ): """Hamming kernel implementation.""" def __init__( self, length_scale: float | tuple[float, ...] | np.ndarray = 1.0, length_scale_bounds: tuple[float, float] | list[tuple[float, float]] | np.ndarray = (1e-5, 1e5), operate_on: np.ndarray | None = None, has_conditions: bool = False, prior: AbstractPrior | None = None, ) -> None: self.length_scale = length_scale self.length_scale_bounds = length_scale_bounds super().__init__( operate_on=operate_on, has_conditions=has_conditions, prior=prior, ) @property def meta(self) -> dict[str, Any]: # noqa: D102 meta = super().meta length_scale = self.length_scale if isinstance(length_scale, np.ndarray): length_scale = length_scale.tolist() length_scale_bounds = self.length_scale_bounds if isinstance(length_scale_bounds, np.ndarray): length_scale_bounds = length_scale_bounds.tolist() meta.update( { "length_scale": length_scale, "lengthscale_bounds": length_scale_bounds, } ) return meta @property def hyperparameter_length_scale(self) -> kernels.Hyperparameter: """Hyperparameter of the length scale.""" length_scale = self.length_scale anisotropic = np.iterable(length_scale) and len(length_scale) > 1 # type: ignore if anisotropic: return kernels.Hyperparameter( "length_scale", "numeric", self.length_scale_bounds, len(length_scale), # type: ignore ) return kernels.Hyperparameter( "length_scale", "numeric", self.length_scale_bounds, ) def _call( self, X: np.ndarray, Y: np.ndarray | None = None, eval_gradient: bool = False, active: np.ndarray | None = None, ) -> np.ndarray | tuple[np.ndarray, np.ndarray]: X = np.atleast_2d(X) length_scale = kernels._check_length_scale(X, self.length_scale) if Y is None: Y = X elif eval_gradient: raise ValueError("gradient can be evaluated only when Y != X") else: Y = np.atleast_2d(Y) indicator = np.expand_dims(X, axis=1) != Y K = (-1 / (2 * length_scale**2) * indicator).sum(axis=2) K = np.exp(K) if active is not None: K = K * active if eval_gradient: # dK / d theta = (dK / dl) * (dl / d theta) # theta = log(l) => dl / d (theta) = e^theta = l # dK / d theta = l * dK / dl # dK / dL computation if np.iterable(length_scale) and length_scale.shape[0] > 1: # type: ignore grad = np.expand_dims(K, axis=-1) * np.array(indicator, dtype=np.float32) else: grad = np.expand_dims(K * np.sum(indicator, axis=2), axis=-1) grad *= 1 / length_scale**3 return K, grad return K