GPy Documentation

repository·devel·Indexed 24 days ago

https://github.com/sheffieldml/gpy

A Gaussian processes framework implemented in Python providing tools for modeling and optimization. GPy allows users to assemble models from kernels, data, and noise representations, optimize hyperparameters, and generate predictions. The framework includes detailed guides on implementing custom kernels by inheriting from GPy.kern.src.kern.Kern and creating custom models by inheriting from GPy.core.model.Model, including instructions for managing parameter handles and analytical gradients.

Tokens
9.3K
Snippets
14
Records
55
Agent score
80%

What's inside GPy

  1. How GPy models work

    devel

    The core workflow in GPy revolves around the GPy.models object. To use GPy, you follow a general pattern of assembling a model from its constituent parts:

    1. Assemble the Model: Create a model object by assigning a kernel (GPy.kern), data, and a noise representation to it.
    2. Optimize Hyperparameters: The kernel and noise are controlled by hyperparameters. You can find the optimal values for these by calling the .optimize() method on the model.
    3. Use the Model: Once optimized, the model object can be used to generate predictions via .predict() or to create visualizations via plotting utilities.

    Conceptual Workflow:

    • Inputs: Data, Kernel, and Noise.
    • Control: Hyperparameters influence both the Kernel and the Noise.
    • Actions: Optimization (updates hyperparameters) and Prediction/Plotting (uses the model state).
  2. Understand Parameterization and Parameter Handles in GPy

    devel

    Parameterization in GPy is managed through parameter handles. These handles act as references to parameters within a model, allowing them to be constrained, fixed, randomized, or otherwise manipulated.

    Key characteristics include:

    • Access: Parameters can be accessed by their name within a model, but the most common programmatic method is via variable names.
    • Memory Management: All parameter handles share a common memory space, which is implemented as a flat numpy array stored in the highest parent of the model hierarchy. This design allows GPy to handle parameter distributions and model updates efficiently.
  3. Save and load GPy models consistently

    devel

    Because pickle is inconsistent across Python versions and class structures, do not use it for long-term model storage. Instead, save the model's param_array using numpy.save.

    To load the model:

    1. Recreate the model instance with initialize=False.
    2. Call update_model(False) to avoid expensive algebra during loading.
    3. Call initialize_parameter() to connect the parameters.
    4. Load the saved parameters into the model using slice assignment (m[:] = ...).
    5. Call update_model(True) to finalize the model state.
    # let X, Y be data loaded above
    # Model creation:
    m = GPy.models.GPRegression(X, Y)
    m.optimize()
    
    # 1: Saving a model:
    np.save('model_save.npy', m.param_array)
    
    # 2: loading a model
    # Model creation, without initialization:
    m_load = GPy.models.GPRegression(X, Y, initialize=False)
    m_load.update_model(False) # do not call the underlying expensive algebra on load
    m_load.initialize_parameter() # Initialize the parameters (connect the parameters up)
    m_load[:] = np.load('model_save.npy') # Load the parameters
    m_load.update_model(True) # Call the algebra only once
    print(m_load)
  4. Change the plotting backend on-the-fly

    devel

    GPy supports multiple plotting backends (e.g., matplotlib, plotly) that can be switched during runtime. This allows you to change how plots are rendered without modifying your core logic.

    Use GPy.plotting.change_plotting_library('backend_name') to switch the active backend. Once changed, calling .plot() on a GPy object will use the new backend.

    To display the resulting plot, use `GPy.plotting.show(fig, <backend_specific_kwargs>)".

  5. Define a new plotting function in GPy

    devel

    To extend GPy with new plotting capabilities, you should implement your plotting functions within the GPy.plotting hierarchy. All plotting-related code must reside in GPy.plotting or its submodules.

    Implementation Steps

    1. Create a module: Write your plotting function into a module under GPy.plotting.gpy_plot.<module_name>.
    2. Use the plotting library: Access functionality via the GPy.plotting.plotting_library. It is recommended to use the pattern from . import plotting_library as pl and access methods via pl()..
    3. Define parameters: The first argument of the plotting function must be self (to allow attaching the function to a class). Ensure you document all parameters, including kwargs for the specific backend.
    4. Prepare data: Use helper_for_plot_data from .plot_util to generate the necessary grids (Xgrid, xx, yy) and handle dimension selection.
    5. Create a canvas: Use pl().new_canvas(...) to initialize the plot. Always pass through kwargs to the new_canvas method to support backend-specific options.
    6. Apply defaults: Use update_not_existing_kwargs(kwargs, pl().defaults.<default_name>) to ensure your plot respects the standard GPy plotting defaults for that specific backend and plot type.
    7. Execute plotting: Use pl().plot(), pl().contour(), or pl().surface() depending on the dimensionality and projection, then return the result of pl().add_to_canvas(canvas, plots).
    8. Attach to a class: To make the function available on a GPy object (e.g., a Kernel), inject it into the class in GPy.plotting.__init__.

    Example of injecting a function into the Kern class:

    from ..kern import Kern
    Kern.plot_covariance = gpy_plot.kernel_plots.plot_covariance
    from . import plotting_library as pl
    
    def plot_covariance(kernel, x=None, label=None,
                 plot_limits=None, visible_dims=None, resolution=None,
                 projection=None, levels=20, **kwargs):
        # ... implementation ...
        canvas, kwargs = pl().new_canvas(projection=projection, xlabel=xlabel, ylabel=ylabel, zlabel=zlabel, **kwargs)
        # ...
        return pl().add_to_canvas(canvas, plots)
  6. Install GPy via pip

    devel

    The recommended way to install GPy is using pip. It is strongly recommended to use the Anaconda Python distribution and ensure you have a recent version of scipy (1.3.0 or later) installed first.

    Steps for Anaconda users:

    1. Update scipy via conda.
    2. Install system dependencies (on Linux/Ubuntu) if necessary.
    3. Install GPy via pip.
    conda update scipy
    
    # On Linux/Ubuntu, you may also need:
    sudo apt-get update
    sudo apt-get install python3-dev
    sudo apt-get install build-essential
    
    conda update anaconda
    
    pip install gpy
  7. How to create a new kernel in GPy

    devel

    To implement a new covariance function in GPy, follow these steps:

    1. Create a new class that inherits from GPy.kern.src.kern.Kern.
    2. Implement the mandatory methods: __init__, K, and Kdiag.
    3. Implement update_gradients_full to enable parameter optimization.
    4. (Optional) Implement other methods like update_gradients_diag, gradients_X, or psi statistics if you plan to use the kernel with specific models like BGPLVM, GPLVM, or sparse models.
    5. Register the new kernel by updating the GPy.kern.src file.
  8. How to create a new Model in GPy

    devel

    To create a custom model in GPy, you must inherit from the GPy.core.model.Model class. All models are built upon the GPy.core.parameterized.Parameterized base class, which handles parameter tying, bounding, fixing, and regex-based manipulation.

    To ensure the model can be optimized and provide parameter introspection, you must implement three obligatory methods:

    1. __init__(self, ...): Initialize the model and register parameters using self.add_parameter(param). Parameters should be created using GPy.Param (or Param).
    2. log_likelihood(self): Return the log-likelihood of the model. For optimization tasks where you want to minimize a function, you should return the negative of that function.
    3. parameters_changed(self): Update the internal state of the model and set the gradient for each parameter handle with respect to the log-likelihood. This is where you assign the analytical gradients to the parameter's .gradient attribute.

    Below is a complete implementation of a custom model wrapping the Rosenbrock function.

    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)
  9. Install GPy from source for development

    devel

    If you are using a developmental version of GPy (e.g., installed with the -e or develop option), you should install the dependencies by running python setup.py develop from within the GPy installation folder.

    If you encounter issues with compiled extensions, it is recommended to clean the repository and reinstall.

    python setup.py develop
  10. Troubleshoot GPy installation problems

    devel

    If pip install GPy fails, you can attempt to build and test the installation from source using the following steps:

    1. Clone the repository.
    2. Checkout the devel branch.
    3. Build the extensions in-place.
    4. Run tests using pytest to verify the build.
    git clone https://github.com/SheffieldML/GPy.git
    cd GPy
    git checkout devel
    python setup.py build_ext --inplace
    pytest .
  11. Verify gradients and optimize a custom model

    devel

    After creating a model, you can verify the correctness of your analytical gradients using m.checkgrad(verbose=True). This compares the analytical gradients against numerical approximations. Once verified, you can use m.optimize() to find the optimal parameter values.

    >>> m = Rosen(np.array([-1,-1]))
    >>> 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.input
        Index  |  rosenbrock.input  |  Constraint  |   Prior   |  Tied to
         [0]   |        0.99999994  |              |           |    N/A    
         [1]   |        0.99999987  |              |           |    N/A