[docs] updated and testing

This commit is contained in:
mzwiessele 2015-10-06 14:04:15 +01:00
parent 55668306cb
commit a6ad9c33a6
43 changed files with 567 additions and 116 deletions

View file

@ -1 +0,0 @@
maxz@maxz-sitran.8058:1442579222

View file

@ -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

View file

@ -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
==================

View 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'})]

View 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.

View 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.