Source code for graphdot.microkernel.convolution

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import numpy as np
from graphdot.codegen.cpptool import cpptype
from graphdot.codegen.template import Template
from graphdot.util.pretty_tuple import pretty_tuple
from ._base import MicroKernel


[docs]def Convolution(kernel: MicroKernel, mean=True): r"""Creates a convolutional microkernel, which averages evaluations of a base microkernel between pairs of elements of two variable-length feature sequences. Parameters ---------- kernel: MicroKernel The base kernel can be any elementary or composite microkernels, while the attribute to be convolved should be sequences. mean: bool If True, return the arithmetic mean of the kernel evaluations, i.e. :math:`k_{conv}(X, Y) = \frac{\sum_{x \in X} \sum_{y \in Y} k_{base}(x, y)}{|X||Y|}`. Otherwise, return the sum of the kernel evaluations, i.e. :math:`k_{conv}(X, Y) = \sum_{x \in X} \sum_{y \in Y} k_{base}(x, y)`. Thus, this serves as a means of normalization beyonds the dot product based one. """ @cpptype(kernel=kernel.dtype) class ConvolutionOf(MicroKernel): @property def name(self): return 'Convolution' def __init__(self, kernel, mean): self.kernel = kernel self.mean = mean def __call__(self, X, Y, jac=False): if jac is True: Fxy, Jxy = list(zip(*[ self.kernel(x, y, jac=True) for x in X for y in Y ])) if self.mean: return np.mean(Fxy), np.mean(Jxy, axis=0) else: return np.sum(Fxy), np.sum(Jxy, axis=0) else: if self.mean: return np.mean([self.kernel(x, y) for x in X for y in Y]) else: return np.sum([self.kernel(x, y) for x in X for y in Y]) def __repr__(self): return f'{self.name}({repr(self.kernel)})' def gen_expr(self, x, y, theta_scope=''): F, J = self.kernel.gen_expr('_1', '_2', theta_scope + 'kernel.') f = Template( r'''convolution<${mean}>( [&](auto _1, auto _2){return ${f};}, ${x}, ${y} )''' ).render( mean='true' if self.mean else 'false', x=x, y=y, f=F ) template = Template( r'''convolution_jacobian<${mean}>( [&](auto _1, auto _2){return ${j};}, ${x}, ${y} )''' ) mean = 'true' if self.mean else 'false' jacobian = [template.render(mean=mean, x=x, y=y, j=j) for j in J] return f, jacobian @property def theta(self): return pretty_tuple( self.name, ['base'] )(self.kernel.theta) @theta.setter def theta(self, seq): self.kernel.theta = seq[0] @property def bounds(self): return (self.kernel.bounds,) @property def minmax(self): return self.kernel.minmax return ConvolutionOf(kernel, mean=mean)