Source code for mulearn.kernel

"""Kernel implementation.

This module implements the kernel used in mulearn.
"""

from itertools import zip_longest

import json_fix
import numpy as np


def to_list(arg):
    return arg.tolist()  if isinstance(arg, np.ndarray) else arg


[docs]class Kernel: """Base kernel class."""
[docs] def __init__(self): """Create an instance of :class:`Kernel`.""" pass
[docs] def compute(self, arg_1, arg_2): """Compute the kernel value, given two arrays of arguments. :param arg_1: First kernel array argument. :type arg_1: Object :param arg_2: Second kernel array argument. :type arg_2: Object :raises: NotImplementedError (:class:`Kernel` is abstract) :returns: `array` -- kernel values. """ raise NotImplementedError( 'The base class does not implement the `compute` method')
def __str__(self): """Return the string representation of a kernel.""" return self.__repr__() def __eq__(self, other): """Check kernel equality w.r.t. other objects.""" return type(self) is type(other) and self.__dict__ == other.__dict__ def __ne__(self, other): """Check kernel inequality w.r.t. other objects.""" return not self == other @staticmethod def __nonzero__(): """Check if a kernel is non-null.""" return True def __hash__(self): """Generate hashcode for a kernel.""" return hash(self.__repr__())
[docs] @classmethod def get_default(cls): """Return the default kernel. :returns: `LinearKernel()` -- the default kernel. """ return LinearKernel()
def __getstate__(self): return self.__dict__ def __setstate__(self, state): self.__dict__ = state def __json__(self): return {'class': self.__class__.__name__} | self.__dict__
[docs]class LinearKernel(Kernel): """Linear kernel class."""
[docs] def compute(self, arg_1, arg_2): r""" Compute the kernel value over several arguments. The value $k(x_1, x_2)$ of a linear kernel is equal to the dot product $x_1 \cdot x_2$, that is to $\sum_{i=1}^n (x_1)_i (x_2)_i$, $n$ being the common dimension of $x_1$ and $x_2$. Given the two arrays of kernels $Y$ and $Z$, the return value will be $(k(y_1,z_1), k(y_2,z_2),..., k(y_m, z_m))$, $m$ being the number of elements in the arrays. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ return np.sum(np.array(arg_1) * np.array(arg_2), axis=1)
def __repr__(self): """Return the python representation of the kernel.""" return 'LinearKernel()'
[docs]class PolynomialKernel(Kernel): """Polynomial kernel class."""
[docs] def __init__(self, degree): r"""Create an instance of `PolynomialKernel`. :param degree: degree of the polynomial kernel. :type degree: `int` :raises: ValueError if `degree` is not an integer or if it has a negative value. """ super().__init__() if degree > 0 and isinstance(degree, int): self.degree = degree else: raise ValueError(f"{degree} is not usable as a polynomial degree")
[docs] def compute(self, arg_1, arg_2): r""" Compute the kernel value. The value $k(x_1, x_2)$ of a polynomial kernel is equal to the quantity $(x_1 \cdot x_2 + 1)^d$, $d$ being the polynomial degree of the kernel. Given the two arrays of kernels $Y$ and $Z$, the return value will be $(k(y_1,z_1), k(y_2,z_2),..., k(y_m, z_m))$, $m$ being the number of elements in the arrays. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ return (np.sum(np.array(arg_1) * np.array(arg_2), axis=1) + 1) ** self.degree
def __eq__(self, other): """Check kernel equality w.r.t. other objects.""" return type(self) == type(other) and self.degree == other.degree def __repr__(self): """Return the python representation of the kernel.""" return f"PolynomialKernel({self.degree})"
[docs]class HomogeneousPolynomialKernel(PolynomialKernel): """Homogeneous polynomial kernel class."""
[docs] def __init__(self, degree): r"""Create an instance of `HomogeneousPolynomialKernel`. :param degree: degree of the polynomial kernel. :type degree: `int` :raises: ValueError if `degree` is not an integer or if it has a negative value. """ super().__init__(degree)
[docs] def compute(self, arg_1, arg_2): r"""Compute the kernel value. The value $k(x_1, x_2)$ of a homogeneous polynomial kernel is intended as the quantity $(x_1 \cdot x_2)^d$, $d$ being the polynomial degree of the kernel. Given the two arrays of kernels $Y$ and $Z$, the return value will be $(k(y_1,z_1), k(y_2,z_2),..., k(y_m, z_m))$, $m$ being the number of elements in the arrays. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ return np.sum(np.array(arg_1) * np.array(arg_2), axis=1) ** self.degree
def __repr__(self): """Return the python representation of the kernel.""" return f"HomogeneousPolynomialKernel({self.degree})"
[docs]class GaussianKernel(Kernel): """Gaussian kernel class.""" default_sigma = 1
[docs] def __init__(self, sigma=default_sigma): r"""Create an instance of `GaussianKernel`. :param sigma: gaussian standard deviation, defaults to 1. :type sigma: `float` :raises: ValueError if `sigma` has a negative value. """ super().__init__() if sigma > 0: self.sigma = sigma else: raise ValueError(f'{sigma} is not usable ' 'as a gaussian standard deviation')
[docs] def compute(self, arg_1, arg_2): r"""Compute the kernel value. The value $k(x_1, x_2)$ of a gaussian kernel is intended as the quantity $\mathrm e^{-\frac{||x_1 - x_2||^2}{2 \sigma^2}}$, $\sigma$ being the kernel standard deviation. Given the two arrays of kernels $Y$ and $Z$, the return value will be $(k(y_1,z_1), k(y_2,z_2),..., k(y_m, z_m))$, $m$ being the number of elements in the arrays. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ diff = np.linalg.norm(np.array(arg_1) - np.array(arg_2), axis=1) ** 2 return np.exp(-1. * diff / (2 * self.sigma ** 2))
def __repr__(self): """Return the python representation of the kernel.""" obj_repr = "GaussianKernel(" if self.sigma != self.default_sigma: obj_repr += f"sigma={self.sigma}" obj_repr += ")" return obj_repr
[docs]class HyperbolicKernel(Kernel): """Hyperbolic kernel class.""" default_scale = 1 default_offset = 0
[docs] def __init__(self, scale=default_scale, offset=default_offset): r"""Create an instance of `HyperbolicKernel`. :param scale: scale constant, defaults to 1. :type scale: `float` :param offset: offset constant, defaults to 0. :type offset: `float` """ super().__init__() self.scale = scale self.offset = offset
[docs] def compute(self, arg_1, arg_2): r"""Compute the kernel value. The value $k(x_1, x_2)$ of a hyperbolic kernel is intended as the quantity $\tanh(\alpha x_1 \cdot x_2 + \beta)$, $\alpha$ and $\beta$ being the scale and offset parameters, respectively. Given the two arrays of kernels $Y$ and $Z$, the return value will be $(k(y_1,z_1), k(y_2,z_2),..., k(y_m, z_m))$, $m$ being the number of elements in the arrays. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ dot_orig = np.sum(np.array(arg_1) * np.array(arg_2), axis=1) return np.tanh(self.scale * dot_orig + self.offset)
def __repr__(self): """Return the python representation of the kernel.""" obj_repr = "HyperbolicKernel(" if self.scale != self.default_scale: obj_repr += f"scale={self.scale}, " if self.offset != self.default_offset: obj_repr += f"offset={self.offset}, " if obj_repr.endswith(", "): return obj_repr[:-2] + ")" else: return "HyperbolicKernel()"
[docs]class PrecomputedKernel(Kernel): """Precomputed kernel class."""
[docs] def __init__(self, kernel_computations): r"""Create an instance of `PrecomputedKernel`. :param kernel_computations: kernel computations. :type kernel_computations: square matrix of float elements :raises: ValueError if `kernel_computations` is not a square bidimensional array. """ super().__init__() try: (rows, columns) = np.array(kernel_computations).shape except ValueError: raise ValueError('The supplied matrix is not array-like ') if rows != columns: raise ValueError('The supplied matrix is not square') self.kernel_computations = np.array(kernel_computations)
[docs] def compute(self, arg_1, arg_2): r"""Compute the kernel value. The value of a precomputed kernel is retrieved according to the indices of the corresponding objects. Note that each index should be enclosed within an iterable in order to be compatible with sklearn. :param arg_1: First kernel array argument. :type arg_1: Object convertible to np.array :param arg_2: Second kernel array argument. :type arg_2: Object convertible to np.array :returns: `array` -- kernel values. """ arg_1 = to_list(arg_1) arg_2 = to_list(arg_2) return [self.kernel_computations[a[0], b[0]] for a in arg_1 for b in arg_2]
# arg_1 = arg_1.reshape(len(arg_1), 1) # z = (np.array(list(zip_longest(arg_1, arg_2,fillvalue=arg_1[0]))) # .reshape(len(arg_2), 2)) # return self.kernel_computations[z[:,0], z[:,1]].reshape(len(arg_2),) # #return (np.array([self.kernel_computations[x,y] for x,y in z]) # # .reshape(len(arg_2),)) def __repr__(self): """Return the python representation of the kernel.""" return f"PrecomputedKernel({self.kernel_computations})" def __eq__(self, other): """Check kernel equality w.r.t. other objects.""" return type(self) is type(other) \ and np.array_equal(self.kernel_computations, other.kernel_computations)