mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-06-17 15:35:20 +02:00
[docs] updated and testing
This commit is contained in:
parent
55668306cb
commit
a6ad9c33a6
43 changed files with 567 additions and 116 deletions
|
|
@ -1 +0,0 @@
|
|||
maxz@maxz-sitran.8058:1442579222
|
||||
|
|
@ -36,6 +36,7 @@ extensions = [
|
|||
'sphinx.ext.viewcode',
|
||||
]
|
||||
|
||||
#----- Autodoc
|
||||
import sys
|
||||
try:
|
||||
from unittest.mock import MagicMock
|
||||
|
|
@ -52,6 +53,19 @@ MOCK_MODULES = ['scipy.linalg.blas', 'blas', 'scipy.optimize', 'scipy.optimize.l
|
|||
'nose', 'nose.tools']
|
||||
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
|
||||
|
||||
autodoc_default_flags = ['members',
|
||||
#'undoc-members',
|
||||
#'private-members',
|
||||
#'special-members',
|
||||
#'inherited-members',
|
||||
'show-inheritance']
|
||||
autodoc_member_order = 'groupwise'
|
||||
add_function_parentheses = False
|
||||
add_module_names = False
|
||||
modindex_common_prefix = ['GPy.']
|
||||
show_authors = True
|
||||
|
||||
# ------ Sphinx
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
|
|
@ -119,7 +133,7 @@ exclude_patterns = []
|
|||
#show_authors = False
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
#pygments_style = 'sphinx'
|
||||
|
||||
# A list of ignored prefixes for module index sorting.
|
||||
#modindex_common_prefix = []
|
||||
|
|
@ -135,12 +149,12 @@ todo_include_todos = False
|
|||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'alabaster'
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
|
||||
# Theme options are theme-specific and customize the look and feel of a theme
|
||||
# further. For a list of options available for each theme, see the
|
||||
# documentation.
|
||||
#html_theme_options = {}
|
||||
#html_theme_options = dict(sidebarwidth='20}
|
||||
|
||||
# Add any paths that contain custom themes here, relative to this directory.
|
||||
#html_theme_path = []
|
||||
|
|
@ -180,20 +194,22 @@ html_static_path = ['_static']
|
|||
#html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#html_sidebars = {}
|
||||
|
||||
#html_sidebars = {
|
||||
# '**': ['globaltoc.html', 'localtoc.html', 'sourcelink.html', 'searchbox.html'],
|
||||
# 'using/windows': ['windowssidebar.html', 'searchbox.html'],
|
||||
#}
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#html_domain_indices = True
|
||||
#html_domain_indices = False
|
||||
|
||||
# If false, no index is generated.
|
||||
#html_use_index = True
|
||||
#html_use_index = False
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#html_split_index = False
|
||||
html_split_index = True
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#html_show_sourcelink = True
|
||||
|
|
|
|||
|
|
@ -13,13 +13,30 @@ This documentation is mostly aimed at developers interacting closely with the co
|
|||
|
||||
The code can be found on our `Github project page <https://github.com/SheffieldML/GPy>`_. It is open source and provided under the BSD license.
|
||||
|
||||
For developers:
|
||||
|
||||
- `Writing new kernels <tuto_creating_new_kernels.html>`_
|
||||
- `Writing new models <tuto_creating_new_models.html>`_
|
||||
- `Parameterization handles <tuto_parameterized.html>`_
|
||||
|
||||
Contents:
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 4
|
||||
|
||||
GPy
|
||||
:maxdepth: 1
|
||||
|
||||
GPy.models
|
||||
GPy.kern
|
||||
GPy.likelihoods
|
||||
GPy.mappings
|
||||
GPy.examples
|
||||
GPy.util
|
||||
GPy.plotting.gpy_plot
|
||||
GPy.plotting.matplot_dep
|
||||
GPy.core
|
||||
GPy.core.parameterization
|
||||
GPy.inference.optimization
|
||||
GPy.inference.latent_function_inference
|
||||
GPy.inference.mcmc
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
|
|
|||
236
doc/source/tuto_creating_new_kernels.rst
Normal file
236
doc/source/tuto_creating_new_kernels.rst
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
********************
|
||||
Creating new kernels
|
||||
********************
|
||||
|
||||
We will see in this tutorial how to create new kernels in GPy. We will also give details on how to implement each function of the kernel and illustrate with a running example: the rational quadratic kernel.
|
||||
|
||||
Structure of a kernel in GPy
|
||||
============================
|
||||
|
||||
In GPy a kernel object is made of a list of kernpart objects, which correspond to symetric positive definite functions. More precisely, the kernel should be understood as the sum of the kernparts. In order to implement a new covariance, the following steps must be followed
|
||||
|
||||
1. implement the new covariance as a :py:class:`GPy.kern._src.kern.Kern` object
|
||||
2. update the :py:mod:`GPy.kern._src` file
|
||||
|
||||
Theses three steps are detailed below.
|
||||
|
||||
Implementing a Kern object
|
||||
==============================
|
||||
|
||||
We advise the reader to start with copy-pasting an existing kernel and
|
||||
to modify the new file. We will now give a description of the various
|
||||
functions that can be found in a Kern object, some of which are
|
||||
mandatory for the new kernel to work.
|
||||
|
||||
Header
|
||||
~~~~~~
|
||||
|
||||
The header is similar to all kernels: ::
|
||||
|
||||
from .kern import Kern
|
||||
import numpy as np
|
||||
|
||||
class RationalQuadratic(Kern):
|
||||
|
||||
:py:func:`GPy.kern._src.kern.Kern.__init__` ``(self, input_dim, param1, param2, *args)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The implementation of this function in mandatory.
|
||||
|
||||
For all Kerns the first parameter ``input_dim`` corresponds to the
|
||||
dimension of the input space, and the following parameters stand for
|
||||
the parameterization of the kernel.
|
||||
|
||||
You have to call ``super(<class_name>, self).__init__(input_dim,
|
||||
name)`` to make sure the input dimension and name of the kernel are
|
||||
stored in the right place. These attributes are available as
|
||||
``self.input_dim`` and ``self.name`` at runtime. Parameterization is
|
||||
done by adding :py:class:`~GPy.core.parameterization.param.Param`
|
||||
objects to ``self`` and use them as normal numpy ``array-like`` s in
|
||||
your code. The parameters have to be added by calling
|
||||
:py:func:`~GPy.core.parameterization.parameterized.Parameterized.link_parameters`
|
||||
``(*parameters)`` with the
|
||||
:py:class:`~GPy.core.parameterization.param.Param` objects as
|
||||
arguments::
|
||||
|
||||
def __init__(self,input_dim,variance=1.,lengthscale=1.,power=1.):
|
||||
super(RationalQuadratic, self).__init__(input_dim, 'rat_quad')
|
||||
assert input_dim == 1, "For this kernel we assume input_dim=1"
|
||||
self.variance = Param('variance', variance)
|
||||
self.lengthscale = Param('lengtscale', lengthscale)
|
||||
self.power = Param('power', power)
|
||||
self.add_parameters(self.variance, self.lengthscale, self.power)
|
||||
|
||||
From now on you can use the parameters ``self.variance,
|
||||
self.lengthscale, self.power`` as normal numpy ``array-like`` s in your
|
||||
code. Updates from the optimization routine will be done
|
||||
automatically.
|
||||
|
||||
:py:func:`~GPy.core.parameterization.parameter_core.Parameterizable.parameters_changed` ``(self)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The implementation of this function is optional.
|
||||
|
||||
This functions deals as a callback for each optimization iteration. If
|
||||
one optimization step was successfull and the parameters (added by
|
||||
:py:func:`~GPy.core.parameterization.parameterized.Parameterized.link_parameters`
|
||||
``(*parameters)``) this callback function will be called to be able to
|
||||
update any precomputations for the kernel. Do not implement the
|
||||
gradient updates here, as those are being done by the model enclosing
|
||||
the kernel::
|
||||
|
||||
def parameters_changed(self):
|
||||
# nothing todo here
|
||||
pass
|
||||
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.K` ``(self,X,X2)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The implementation of this function in mandatory.
|
||||
|
||||
This function is used to compute the covariance matrix associated with
|
||||
the inputs X, X2 (np.arrays with arbitrary number of line (say
|
||||
:math:`n_1`, :math:`n_2`) and ``self.input_dim`` columns). ::
|
||||
|
||||
def K(self,X,X2):
|
||||
if X2 is None: X2 = X
|
||||
dist2 = np.square((X-X2.T)/self.lengthscale)
|
||||
return self.variance*(1 + dist2/2.)**(-self.power)
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.Kdiag` ``(self,X)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The implementation of this function is mandatory.
|
||||
|
||||
This function is similar to ``K`` but it computes only the values of
|
||||
the kernel on the diagonal. Thus, ``target`` is a 1-dimensional
|
||||
np.array of length :math:`n \times 1`. ::
|
||||
|
||||
def Kdiag(self,X):
|
||||
return self.variance*np.ones(X.shape[0])
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.update_gradients_full` ``(self, dL_dK, X, X2=None)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function is required for the optimization of the parameters.
|
||||
|
||||
Computes the gradients and sets them on the parameters of this model.
|
||||
For example, if the kernel is parameterized by
|
||||
:math:`\sigma^2, \theta`, then
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{\partial L}{\partial\sigma^2}
|
||||
= \frac{\partial L}{\partial K} \frac{\partial K}{\partial\sigma^2}
|
||||
|
||||
is added to the gradient of :math:`\sigma^2`: ``self.variance.gradient = <gradient>``
|
||||
and
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{\partial L}{\partial\theta}
|
||||
= \frac{\partial L}{\partial K} \frac{\partial K}{\partial\theta}
|
||||
|
||||
to :math:`\theta`. ::
|
||||
|
||||
def update_gradients_full(self, dL_dK, X, X2):
|
||||
if X2 is None: X2 = X
|
||||
dist2 = np.square((X-X2.T)/self.lengthscale)
|
||||
|
||||
dvar = (1 + dist2/2.)**(-self.power)
|
||||
dl = self.power * self.variance * dist2 * self.lengthscale**(-3) * (1 + dist2/2./self.power)**(-self.power-1)
|
||||
dp = - self.variance * np.log(1 + dist2/2.) * (1 + dist2/2.)**(-self.power)
|
||||
|
||||
self.variance.gradient = np.sum(dvar*dL_dK)
|
||||
self.lengthscale.gradient = np.sum(dl*dL_dK)
|
||||
self.power.gradient = np.sum(dp*dL_dK)
|
||||
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.update_gradients_diag` ``(self,dL_dKdiag,X,target)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function is required for BGPLVM, sparse models and uncertain inputs.
|
||||
|
||||
As previously, target is an ``self.num_params`` array and
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{\partial L}{\partial Kdiag}
|
||||
\frac{\partial Kdiag}{\partial param}
|
||||
|
||||
is set to each ``param``. ::
|
||||
|
||||
def update_gradients_diag(self, dL_dKdiag, X):
|
||||
self.variance.gradient = np.sum(dL_dKdiag)
|
||||
# here self.lengthscale and self.power have no influence on Kdiag so target[1:] are unchanged
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.gradients_X` ``(self,dL_dK, X, X2)``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function is required for GPLVM, BGPLVM, sparse models and uncertain inputs.
|
||||
|
||||
Computes the derivative of the likelihood with respect to the inputs
|
||||
``X`` (a :math:`n \times q` np.array). The result is returned by the
|
||||
function which is a :math:`n \times q` np.array. ::
|
||||
|
||||
def gradients_X(self,dL_dK,X,X2):
|
||||
"""derivative of the covariance matrix with respect to X."""
|
||||
if X2 is None: X2 = X
|
||||
dist2 = np.square((X-X2.T)/self.lengthscale)
|
||||
|
||||
dX = -self.variance*self.power * (X-X2.T)/self.lengthscale**2 * (1 + dist2/2./self.lengthscale)**(-self.power-1)
|
||||
return np.sum(dL_dK*dX,1)[:,None]
|
||||
|
||||
:py:func:`~GPy.kern._src.kern.Kern.gradients_X_diag` ``(self,dL_dKdiag,X)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
This function is required for BGPLVM, sparse models and uncertain
|
||||
inputs. As for ``dKdiag_dtheta``,
|
||||
|
||||
.. math::
|
||||
|
||||
\frac{\partial L}{\partial Kdiag} \frac{\partial Kdiag}{\partial X}
|
||||
|
||||
is added to each element of target. ::
|
||||
|
||||
def gradients_X_diag(self,dL_dKdiag,X):
|
||||
# no diagonal gradients
|
||||
pass
|
||||
|
||||
**Second order derivatives**
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These functions are required for the magnification factor and are the same as the first order gradients for X, but
|
||||
as the second order derivatives:
|
||||
|
||||
.. math:: \frac{\partial^2 K}{\partial X\partial X2}
|
||||
|
||||
- :py:func:`GPy.kern._src.kern.gradients_XX` ``(self,dL_dK, X, X2)``
|
||||
- :py:func:`GPy.kern._src.kern.gradients_XX_diag` ``(self,dL_dKdiag, X)``
|
||||
|
||||
**Psi statistics**
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
The psi statistics and their derivatives are required for BGPLVM and
|
||||
GPS with uncertain inputs only, the expressions are as follows
|
||||
|
||||
- `psi0(self, Z, variational_posterior)`
|
||||
.. math::
|
||||
|
||||
\psi_0 = \sum_{i=0}^{n}E_{q(X)}[k(X_i, X_i)]
|
||||
|
||||
- `psi1(self, Z, variational_posterior)`::
|
||||
.. math::
|
||||
|
||||
\psi_1^{n,m} = E_{q(X)}[k(X_n, Z_m)]
|
||||
|
||||
- `psi2(self, Z, variational_posterior)`
|
||||
.. math::
|
||||
|
||||
\psi_2^{m,m'} = \sum_{i=0}^{n}E_{q(X)}[ k(Z_m, X_i) k(X_i, Z_{m'})]
|
||||
|
||||
- `psi2n(self, Z, variational_posterior)`
|
||||
.. math::
|
||||
|
||||
\psi_2^{n,m,m'} = E_{q(X)}[ k(Z_m, X_n) k(X_n, Z_{m'})]
|
||||
100
doc/source/tuto_creating_new_models.rst
Normal file
100
doc/source/tuto_creating_new_models.rst
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
.. _creating_new_models:
|
||||
|
||||
*******************
|
||||
Creating new Models
|
||||
*******************
|
||||
|
||||
In GPy all models inherit from the base class :py:class:`~GPy.core.parameterized.Parameterized`. :py:class:`~GPy.core.parameterized.Parameterized` is a class which allows for parameterization of objects. All it holds is functionality for tying, bounding and fixing of parameters. It also provides the functionality of searching and manipulating parameters by regular expression syntax. See :py:class:`~GPy.core.parameterized.Parameterized` for more information.
|
||||
|
||||
The :py:class:`~GPy.core.model.Model` class provides parameter introspection, objective function and optimization.
|
||||
|
||||
In order to fully use all functionality of
|
||||
:py:class:`~GPy.core.model.Model` some methods need to be implemented
|
||||
/ overridden. And the model needs to be told its parameters, such
|
||||
that it can provide optimized parameter distribution and handling.
|
||||
In order to explain the functionality of those methods
|
||||
we will use a wrapper to the numpy ``rosen`` function, which holds
|
||||
input parameters :math:`\mathbf{X}`. Where
|
||||
:math:`\mathbf{X}\in\mathbb{R}^{N\times 1}`.
|
||||
|
||||
Obligatory methods
|
||||
==================
|
||||
|
||||
:py:func:`~GPy.core.model.Model.__init__` :
|
||||
Initialize the model with the given parameters. These need to
|
||||
be added to the model by calling
|
||||
`self.add_parameter(<param>)`, where param needs to be a
|
||||
parameter handle (See parameterized_ for details).::
|
||||
|
||||
self.X = GPy.Param("input", X)
|
||||
self.add_parameter(self.X)
|
||||
|
||||
:py:meth:`~GPy.core.model.Model.log_likelihood` :
|
||||
Returns the log-likelihood of the new model. For our example
|
||||
this is just the call to ``rosen`` and as we want to minimize
|
||||
it, we need to negate the objective.::
|
||||
|
||||
return -scipy.optimize.rosen(self.X)
|
||||
|
||||
:py:meth:`~GPy.core.model.Model.parameters_changed` :
|
||||
Updates the internal state of the model and sets the gradient of
|
||||
each parameter handle in the hierarchy with respect to the
|
||||
log_likelihod. Thus here we need to set the negative derivative of
|
||||
the rosenbrock function for the parameters. In this case it is the
|
||||
gradient for self.X.::
|
||||
|
||||
self.X.gradient = -scipy.optimize.rosen_der(self.X)
|
||||
|
||||
|
||||
Here the full code for the `Rosen` class::
|
||||
|
||||
from GPy import Model, Param
|
||||
import scipy
|
||||
class Rosen(Model):
|
||||
def __init__(self, X, name='rosenbrock'):
|
||||
super(Rosen, self).__init__(name=name)
|
||||
self.X = Param("input", X)
|
||||
self.add_parameter(self.X)
|
||||
def log_likelihood(self):
|
||||
return -scipy.optimize.rosen(self.X)
|
||||
def parameters_changed(self):
|
||||
self.X.gradient = -scipy.optimize.rosen_der(self.X)
|
||||
|
||||
In order to test the newly created model, we can check the gradients
|
||||
and optimize a standard rosenbrock run::
|
||||
|
||||
>>> m = Rosen(np.array([-1,-1]))
|
||||
>>> print m
|
||||
Name : rosenbrock
|
||||
Log-likelihood : -404.0
|
||||
Number of Parameters : 2
|
||||
Parameters:
|
||||
rosenbrock. | Value | Constraint | Prior | Tied to
|
||||
input | (2,) | | |
|
||||
>>> m.checkgrad(verbose=True)
|
||||
Name | Ratio | Difference | Analytical | Numerical
|
||||
------------------------------------------------------------------------------------------
|
||||
rosenbrock.input[[0]] | 1.000000 | 0.000000 | -804.000000 | -804.000000
|
||||
rosenbrock.input[[1]] | 1.000000 | 0.000000 | -400.000000 | -400.000000
|
||||
>>> m.optimize()
|
||||
>>> print m
|
||||
Name : rosenbrock
|
||||
Log-likelihood : -6.52150088871e-15
|
||||
Number of Parameters : 2
|
||||
Parameters:
|
||||
rosenbrock. | Value | Constraint | Prior | Tied to
|
||||
input | (2,) | | |
|
||||
>>> print m.input
|
||||
Index | rosenbrock.input | Constraint | Prior | Tied to
|
||||
[0] | 0.99999994 | | | N/A
|
||||
[1] | 0.99999987 | | | N/A
|
||||
>>> print m.gradient
|
||||
[ -1.91169809e-06, 1.01852309e-06]
|
||||
|
||||
This is the optimium for the 2D Rosenbrock function, as expected, and
|
||||
the gradient of the inputs are almost zero.
|
||||
|
||||
Optional methods
|
||||
================
|
||||
|
||||
Currently none.
|
||||
23
doc/source/tuto_parameterized.rst
Normal file
23
doc/source/tuto_parameterized.rst
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.. _parameterized:
|
||||
|
||||
*******************
|
||||
Parameterization handling
|
||||
*******************
|
||||
|
||||
Parameterization in GPy is done through so called parameter handles. The parameter handles are handles to parameters of a model of any kind. A parameter handle can be constrained, fixed, randomized and others. All parameters in GPy have a name, with which they can be accessed in the model. The most common way of accesssing a parameter programmatically though, is by variable name.
|
||||
|
||||
Parameter handles
|
||||
==============
|
||||
|
||||
A parameter handle in GPy is a handle on a parameter, as the name suggests. A parameter can be constrained, fixed, randomized and more (See e.g. `working with models`). This gives the freedom to the model to handle parameter distribution and model updates as efficiently as possible. All parameter handles share a common memory space, which is just a flat numpy array, stored in the highest parent of a model hierarchy.
|
||||
In the following we will introduce and elucidate the different parameter handles which exist in GPy.
|
||||
|
||||
:py:class:`~GPy.core.parameterization.parameterized.Parameterized`
|
||||
==========
|
||||
|
||||
A parameterized object itself holds parameter handles and is just a summarization of the parameters below. It can use those parameters to change the internal state of the model and GPy ensures those parameters to allways hold the right value when in an optimization routine or any other update.
|
||||
|
||||
:py:class:`~GPy.core.parameterization.param.Param`
|
||||
===========
|
||||
|
||||
The lowest level of parameter is a numpy array. This Param class inherits all functionality of a numpy array and can simply be used as if it where a numpy array. These parameters can be accessed in the same way as a numpy array is indexed.
|
||||
Loading…
Add table
Add a link
Reference in a new issue