"""Kernel implementation.
This module implements the kernel used in mulearn.
"""
import numpy as np
[docs]class Kernel:
"""Base kernel class."""
[docs] def __init__(self):
"""Create an instance of :class:`Kernel`."""
self.precomputed = False
self.kernel_computations = None
[docs] def compute(self, arg_1, arg_2):
"""Compute the kernel value, given two arguments.
:param arg_1: First kernel argument.
:type arg_1: Object
:param arg_2: Second kernel argument.
:type arg_2: Object
:raises: NotImplementedError (:class:`Kernel` is abstract)
:returns: `float` -- kernel value.
"""
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) == type(other)
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()
[docs]class LinearKernel(Kernel):
"""Linear kernel class."""
[docs] def compute(self, arg_1, arg_2):
r"""
Compute the kernel value.
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$.
:param arg_1: First kernel argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
return float(np.dot(arg_1, arg_2))
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.
:param arg_1: First kernel argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
return float((np.dot(arg_1, arg_2) + 1) ** self.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.
:param arg_1: First kernel argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
return float(np.dot(arg_1, arg_2) ** 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.
:param arg_1: First kernel argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
diff = np.linalg.norm(np.array(arg_1) - np.array(arg_2)) ** 2
return float(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.
:param arg_1: First kernel argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
dot_orig = np.dot(np.array(arg_1), np.array(arg_2))
return float(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__()
self.precomputed = True
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 = 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 argument.
:type arg_1: iterable of `float`
:param arg_2: Second kernel argument.
:type arg_2: iterable of `float`
:returns: `float` -- kernel value.
"""
return float(self.kernel_computations[arg_1[0]][arg_2[0]])
def __repr__(self):
"""Return the python representation of the kernel."""
return f"PrecomputedKernel({self.kernel_computations})"