mirror of
https://github.com/SheffieldML/GPy.git
synced 2026-04-30 23:36:23 +02:00
Merge pull request #497 from SheffieldML/devel
New fixes for parallel optimize and minor fixes
This commit is contained in:
commit
a537ccd9a6
20 changed files with 660 additions and 143 deletions
|
|
@ -16,8 +16,9 @@ addons:
|
||||||
env:
|
env:
|
||||||
- PYTHON_VERSION=2.7
|
- PYTHON_VERSION=2.7
|
||||||
#- PYTHON_VERSION=3.3
|
#- PYTHON_VERSION=3.3
|
||||||
- PYTHON_VERSION=3.4
|
#- PYTHON_VERSION=3.4
|
||||||
- PYTHON_VERSION=3.5
|
- PYTHON_VERSION=3.5
|
||||||
|
- PYTHON_VERSION=3.6
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- wget https://github.com/mzwiessele/travis_scripts/raw/master/download_miniconda.sh
|
- wget https://github.com/mzwiessele/travis_scripts/raw/master/download_miniconda.sh
|
||||||
|
|
|
||||||
100
CHANGELOG.md
100
CHANGELOG.md
|
|
@ -1,5 +1,105 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## v1.7.2 (2017-06-17)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Appveyor build python 3.6. [mzwiessele]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Bump version: 1.7.1 → 1.7.2. [mzwiessele]
|
||||||
|
|
||||||
|
|
||||||
|
## v1.7.1 (2017-06-17)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Appveyor build python 3.6. [mzwiessele]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Bump version: 1.7.0 → 1.7.1. [mzwiessele]
|
||||||
|
|
||||||
|
|
||||||
|
## v1.7.0 (2017-06-17)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Support for 3.5 and higher now that 3.6 is out. [mzwiessele]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Bump version: 1.6.3 → 1.7.0. [mzwiessele]
|
||||||
|
|
||||||
|
|
||||||
|
## v1.6.3 (2017-06-17)
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Bump version: 1.6.2 → 1.6.3. [mzwiessele]
|
||||||
|
|
||||||
|
* Merge pull request #504 from rmcantin/devel. [Max Zwiessele]
|
||||||
|
|
||||||
|
* Fix python 2-3 compatibility. [Ruben Martinez-Cantin]
|
||||||
|
|
||||||
|
* Merge pull request #511 from dirmeier/devel. [Max Zwiessele]
|
||||||
|
|
||||||
|
* Added LICENSE file to MANIFEST.in. [dirmeier]
|
||||||
|
|
||||||
|
|
||||||
|
## v1.6.2 (2017-04-12)
|
||||||
|
|
||||||
|
### Fix
|
||||||
|
|
||||||
|
* Updated keywords. [mzwiessele]
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
* Bump version: 1.6.1 → 1.6.2. [mzwiessele]
|
||||||
|
|
||||||
|
* Merge pull request #491 from alexfeld/parallel_opt. [Max Zwiessele]
|
||||||
|
|
||||||
|
fix for parallel optimization
|
||||||
|
|
||||||
|
* Fix in sparse_gp_mpi optimizer. [Alex Feldstein]
|
||||||
|
|
||||||
|
* Fix for parallel optimization. [Alex Feldstein]
|
||||||
|
|
||||||
|
* Merge pull request #492 from pgmoren/devel. [Zhenwen Dai]
|
||||||
|
|
||||||
|
We did some benchmarking on classification. These changes should be fine. Let's merge it in.
|
||||||
|
|
||||||
|
* Changes in EP/EPDTC to fix numerical issues and increase the flexibility of the inference. [Moreno]
|
||||||
|
|
||||||
|
Changes to avoid numerical issues and improve the performance:
|
||||||
|
- Keep value of the EP parameters between calls
|
||||||
|
- Enforce positivity of tau_tilde
|
||||||
|
- Stable computation of the EP moments for the Bernoulli likelihood
|
||||||
|
- Compute marginal in the GP model without directly inverting tau_tilde
|
||||||
|
|
||||||
|
Changes to improve the flexibility:
|
||||||
|
- Add parameter for maximum number of iterations
|
||||||
|
- Distinguish between alternated/nested mode
|
||||||
|
- Distinguish between sequential/parallel updates in EP
|
||||||
|
|
||||||
|
* Merge pull request #489 from SheffieldML/linalg_cython-1. [Max Zwiessele]
|
||||||
|
|
||||||
|
cython in linalg fix #458
|
||||||
|
|
||||||
|
* Cython in linalg. [Max Zwiessele]
|
||||||
|
|
||||||
|
did set cython to working if linalg_cython was importable.
|
||||||
|
|
||||||
|
* Merge pull request #486 from SheffieldML/deploy. [Max Zwiessele]
|
||||||
|
|
||||||
|
Merge pull request #471 from SheffieldML/devel
|
||||||
|
|
||||||
|
* Merge pull request #471 from SheffieldML/devel. [Max Zwiessele]
|
||||||
|
|
||||||
|
new version
|
||||||
|
|
||||||
|
|
||||||
## v1.6.1 (2017-02-28)
|
## v1.6.1 (2017-02-28)
|
||||||
|
|
||||||
### Fix
|
### Fix
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
__version__ = "1.6.1"
|
__version__ = "1.7.2"
|
||||||
|
|
|
||||||
|
|
@ -562,11 +562,12 @@ class GP(Model):
|
||||||
"""
|
"""
|
||||||
self.inference_method.on_optimization_start()
|
self.inference_method.on_optimization_start()
|
||||||
try:
|
try:
|
||||||
super(GP, self).optimize(optimizer, start, messages, max_iters, ipython_notebook, clear_after_finish, **kwargs)
|
ret = super(GP, self).optimize(optimizer, start, messages, max_iters, ipython_notebook, clear_after_finish, **kwargs)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print("KeyboardInterrupt caught, calling on_optimization_end() to round things up")
|
print("KeyboardInterrupt caught, calling on_optimization_end() to round things up")
|
||||||
self.inference_method.on_optimization_end()
|
self.inference_method.on_optimization_end()
|
||||||
raise
|
raise
|
||||||
|
return ret
|
||||||
|
|
||||||
def infer_newX(self, Y_new, optimize=True):
|
def infer_newX(self, Y_new, optimize=True):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -88,9 +88,9 @@ class SparseGP_MPI(SparseGP):
|
||||||
def optimize(self, optimizer=None, start=None, **kwargs):
|
def optimize(self, optimizer=None, start=None, **kwargs):
|
||||||
self._IN_OPTIMIZATION_ = True
|
self._IN_OPTIMIZATION_ = True
|
||||||
if self.mpi_comm==None:
|
if self.mpi_comm==None:
|
||||||
super(SparseGP_MPI, self).optimize(optimizer,start,**kwargs)
|
ret = super(SparseGP_MPI, self).optimize(optimizer,start,**kwargs)
|
||||||
elif self.mpi_comm.rank==0:
|
elif self.mpi_comm.rank==0:
|
||||||
super(SparseGP_MPI, self).optimize(optimizer,start,**kwargs)
|
ret = super(SparseGP_MPI, self).optimize(optimizer,start,**kwargs)
|
||||||
self.mpi_comm.Bcast(np.int32(-1),root=0)
|
self.mpi_comm.Bcast(np.int32(-1),root=0)
|
||||||
elif self.mpi_comm.rank>0:
|
elif self.mpi_comm.rank>0:
|
||||||
x = self.optimizer_array.copy()
|
x = self.optimizer_array.copy()
|
||||||
|
|
@ -111,6 +111,7 @@ class SparseGP_MPI(SparseGP):
|
||||||
self._IN_OPTIMIZATION_ = False
|
self._IN_OPTIMIZATION_ = False
|
||||||
raise Exception("Unrecognizable flag for synchronization!")
|
raise Exception("Unrecognizable flag for synchronization!")
|
||||||
self._IN_OPTIMIZATION_ = False
|
self._IN_OPTIMIZATION_ = False
|
||||||
|
return ret
|
||||||
|
|
||||||
def parameters_changed(self):
|
def parameters_changed(self):
|
||||||
if isinstance(self.inference_method,VarDTC_minibatch):
|
if isinstance(self.inference_method,VarDTC_minibatch):
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,16 @@
|
||||||
# Copyright (c) 2012-2014, GPy authors (see AUTHORS.txt).
|
# Copyright (c) 2012-2014, GPy authors (see AUTHORS.txt).
|
||||||
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from ...util.linalg import jitchol, DSYR, dtrtrs, dtrtri
|
from ...util.linalg import jitchol, DSYR, dtrtrs, dtrtri, pdinv, dpotrs, tdot, symmetrify
|
||||||
from paramz import ObsAr
|
from paramz import ObsAr
|
||||||
from . import ExactGaussianInference, VarDTC
|
from . import ExactGaussianInference, VarDTC
|
||||||
from ...util import diag
|
from ...util import diag
|
||||||
|
from .posterior import PosteriorEP as Posterior
|
||||||
|
|
||||||
log_2_pi = np.log(2*np.pi)
|
log_2_pi = np.log(2*np.pi)
|
||||||
|
|
||||||
class EPBase(object):
|
class EPBase(object):
|
||||||
def __init__(self, epsilon=1e-6, eta=1., delta=1., always_reset=False):
|
def __init__(self, epsilon=1e-6, eta=1., delta=1., always_reset=False, max_iters=np.inf, ep_mode="alternated", parallel_updates=False):
|
||||||
"""
|
"""
|
||||||
The expectation-propagation algorithm.
|
The expectation-propagation algorithm.
|
||||||
For nomenclature see Rasmussen & Williams 2006.
|
For nomenclature see Rasmussen & Williams 2006.
|
||||||
|
|
@ -22,11 +23,15 @@ class EPBase(object):
|
||||||
:type delta: float64
|
:type delta: float64
|
||||||
:param always_reset: setting to always reset the approximation at the beginning of every inference call.
|
:param always_reset: setting to always reset the approximation at the beginning of every inference call.
|
||||||
:type always_reest: boolean
|
:type always_reest: boolean
|
||||||
|
:max_iters: int
|
||||||
|
:ep_mode: string. It can be "nested" (EP is run every time the Hyperparameters change) or "alternated" (It runs EP at the beginning and then optimize the Hyperparameters).
|
||||||
|
:parallel_updates: boolean. If true, updates of the parameters of the sites in parallel
|
||||||
"""
|
"""
|
||||||
super(EPBase, self).__init__()
|
super(EPBase, self).__init__()
|
||||||
self.always_reset = always_reset
|
self.always_reset = always_reset
|
||||||
self.epsilon, self.eta, self.delta = epsilon, eta, delta
|
self.epsilon, self.eta, self.delta, self.max_iters = epsilon, eta, delta, max_iters
|
||||||
|
self.ep_mode = ep_mode
|
||||||
|
self.parallel_updates = parallel_updates
|
||||||
self.reset()
|
self.reset()
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
|
|
@ -59,25 +64,28 @@ class EP(EPBase, ExactGaussianInference):
|
||||||
if K is None:
|
if K is None:
|
||||||
K = kern.K(X)
|
K = kern.K(X)
|
||||||
|
|
||||||
if getattr(self, '_ep_approximation', None) is None:
|
if self.ep_mode=="nested":
|
||||||
#if we don't yet have the results of runnign EP, run EP and store the computed factors in self._ep_approximation
|
#Force EP at each step of the optimization
|
||||||
mu, Sigma, mu_tilde, tau_tilde, Z_tilde = self._ep_approximation = self.expectation_propagation(K, Y, likelihood, Y_metadata)
|
self._ep_approximation = None
|
||||||
|
mu, Sigma, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation = self.expectation_propagation(K, Y, likelihood, Y_metadata)
|
||||||
|
elif self.ep_mode=="alternated":
|
||||||
|
if getattr(self, '_ep_approximation', None) is None:
|
||||||
|
#if we don't yet have the results of runnign EP, run EP and store the computed factors in self._ep_approximation
|
||||||
|
mu, Sigma, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation = self.expectation_propagation(K, Y, likelihood, Y_metadata)
|
||||||
|
else:
|
||||||
|
#if we've already run EP, just use the existing approximation stored in self._ep_approximation
|
||||||
|
mu, Sigma, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation
|
||||||
else:
|
else:
|
||||||
#if we've already run EP, just use the existing approximation stored in self._ep_approximation
|
raise ValueError("ep_mode value not valid")
|
||||||
mu, Sigma, mu_tilde, tau_tilde, Z_tilde = self._ep_approximation
|
|
||||||
|
|
||||||
return super(EP, self).inference(kern, X, likelihood, mu_tilde[:,None], mean_function=mean_function, Y_metadata=Y_metadata, variance=1./tau_tilde, K=K, Z_tilde=np.log(Z_tilde).sum())
|
v_tilde = mu_tilde * tau_tilde
|
||||||
|
return self._inference(K, tau_tilde, v_tilde, likelihood, Y_metadata=Y_metadata, Z_tilde=log_Z_tilde.sum())
|
||||||
|
|
||||||
def expectation_propagation(self, K, Y, likelihood, Y_metadata):
|
def expectation_propagation(self, K, Y, likelihood, Y_metadata):
|
||||||
|
|
||||||
num_data, data_dim = Y.shape
|
num_data, data_dim = Y.shape
|
||||||
assert data_dim == 1, "This EP methods only works for 1D outputs"
|
assert data_dim == 1, "This EP methods only works for 1D outputs"
|
||||||
|
|
||||||
#Initial values - Posterior distribution parameters: q(f|X,Y) = N(f|mu,Sigma)
|
|
||||||
mu = np.zeros(num_data)
|
|
||||||
Sigma = K.copy()
|
|
||||||
diag.add(Sigma, 1e-7)
|
|
||||||
|
|
||||||
# Makes computing the sign quicker if we work with numpy arrays rather
|
# Makes computing the sign quicker if we work with numpy arrays rather
|
||||||
# than ObsArrays
|
# than ObsArrays
|
||||||
Y = Y.values.copy()
|
Y = Y.values.copy()
|
||||||
|
|
@ -91,12 +99,19 @@ class EP(EPBase, ExactGaussianInference):
|
||||||
v_cav = np.empty(num_data,dtype=np.float64)
|
v_cav = np.empty(num_data,dtype=np.float64)
|
||||||
|
|
||||||
#initial values - Gaussian factors
|
#initial values - Gaussian factors
|
||||||
|
#Initial values - Posterior distribution parameters: q(f|X,Y) = N(f|mu,Sigma)
|
||||||
if self.old_mutilde is None:
|
if self.old_mutilde is None:
|
||||||
tau_tilde, mu_tilde, v_tilde = np.zeros((3, num_data))
|
tau_tilde, mu_tilde, v_tilde = np.zeros((3, num_data))
|
||||||
|
Sigma = K.copy()
|
||||||
|
diag.add(Sigma, 1e-7)
|
||||||
|
mu = np.zeros(num_data)
|
||||||
else:
|
else:
|
||||||
assert self.old_mutilde.size == num_data, "data size mis-match: did you change the data? try resetting!"
|
assert self.old_mutilde.size == num_data, "data size mis-match: did you change the data? try resetting!"
|
||||||
mu_tilde, v_tilde = self.old_mutilde, self.old_vtilde
|
mu_tilde, v_tilde = self.old_mutilde, self.old_vtilde
|
||||||
tau_tilde = v_tilde/mu_tilde
|
tau_tilde = v_tilde/mu_tilde
|
||||||
|
mu, Sigma, _ = self._ep_compute_posterior(K, tau_tilde, v_tilde)
|
||||||
|
diag.add(Sigma, 1e-7)
|
||||||
|
# TODO: Check the log-marginal under both conditions and choose the best one
|
||||||
|
|
||||||
#Approximation
|
#Approximation
|
||||||
tau_diff = self.epsilon + 1.
|
tau_diff = self.epsilon + 1.
|
||||||
|
|
@ -104,7 +119,7 @@ class EP(EPBase, ExactGaussianInference):
|
||||||
tau_tilde_old = np.nan
|
tau_tilde_old = np.nan
|
||||||
v_tilde_old = np.nan
|
v_tilde_old = np.nan
|
||||||
iterations = 0
|
iterations = 0
|
||||||
while (tau_diff > self.epsilon) or (v_diff > self.epsilon):
|
while ((tau_diff > self.epsilon) or (v_diff > self.epsilon)) and (iterations < self.max_iters):
|
||||||
update_order = np.random.permutation(num_data)
|
update_order = np.random.permutation(num_data)
|
||||||
for i in update_order:
|
for i in update_order:
|
||||||
#Cavity distribution parameters
|
#Cavity distribution parameters
|
||||||
|
|
@ -122,21 +137,25 @@ class EP(EPBase, ExactGaussianInference):
|
||||||
#Site parameters update
|
#Site parameters update
|
||||||
delta_tau = self.delta/self.eta*(1./sigma2_hat[i] - 1./Sigma[i,i])
|
delta_tau = self.delta/self.eta*(1./sigma2_hat[i] - 1./Sigma[i,i])
|
||||||
delta_v = self.delta/self.eta*(mu_hat[i]/sigma2_hat[i] - mu[i]/Sigma[i,i])
|
delta_v = self.delta/self.eta*(mu_hat[i]/sigma2_hat[i] - mu[i]/Sigma[i,i])
|
||||||
|
tau_tilde_prev = tau_tilde[i]
|
||||||
tau_tilde[i] += delta_tau
|
tau_tilde[i] += delta_tau
|
||||||
|
|
||||||
|
# Enforce positivity of tau_tilde. Even though this is guaranteed for logconcave sites, it is still possible
|
||||||
|
# to get negative values due to numerical errors. Moreover, the value of tau_tilde should be positive in order to
|
||||||
|
# update the marginal likelihood without inestability issues.
|
||||||
|
if tau_tilde[i] < np.finfo(float).eps:
|
||||||
|
tau_tilde[i] = np.finfo(float).eps
|
||||||
|
delta_tau = tau_tilde[i] - tau_tilde_prev
|
||||||
v_tilde[i] += delta_v
|
v_tilde[i] += delta_v
|
||||||
#Posterior distribution parameters update
|
|
||||||
ci = delta_tau/(1.+ delta_tau*Sigma[i,i])
|
if self.parallel_updates == False:
|
||||||
DSYR(Sigma, Sigma[:,i].copy(), -ci)
|
#Posterior distribution parameters update
|
||||||
mu = np.dot(Sigma, v_tilde)
|
ci = delta_tau/(1.+ delta_tau*Sigma[i,i])
|
||||||
|
DSYR(Sigma, Sigma[:,i].copy(), -ci)
|
||||||
|
mu = np.dot(Sigma, v_tilde)
|
||||||
|
|
||||||
#(re) compute Sigma and mu using full Cholesky decompy
|
#(re) compute Sigma and mu using full Cholesky decompy
|
||||||
tau_tilde_root = np.sqrt(tau_tilde)
|
mu, Sigma, _ = self._ep_compute_posterior(K, tau_tilde, v_tilde)
|
||||||
Sroot_tilde_K = tau_tilde_root[:,None] * K
|
|
||||||
B = np.eye(num_data) + Sroot_tilde_K * tau_tilde_root[None,:]
|
|
||||||
L = jitchol(B)
|
|
||||||
V, _ = dtrtrs(L, Sroot_tilde_K, lower=1)
|
|
||||||
Sigma = K - np.dot(V.T,V)
|
|
||||||
mu = np.dot(Sigma,v_tilde)
|
|
||||||
|
|
||||||
#monitor convergence
|
#monitor convergence
|
||||||
if iterations > 0:
|
if iterations > 0:
|
||||||
|
|
@ -150,15 +169,70 @@ class EP(EPBase, ExactGaussianInference):
|
||||||
mu_tilde = v_tilde/tau_tilde
|
mu_tilde = v_tilde/tau_tilde
|
||||||
mu_cav = v_cav/tau_cav
|
mu_cav = v_cav/tau_cav
|
||||||
sigma2_sigma2tilde = 1./tau_cav + 1./tau_tilde
|
sigma2_sigma2tilde = 1./tau_cav + 1./tau_tilde
|
||||||
Z_tilde = np.exp(np.log(Z_hat) + 0.5*np.log(2*np.pi) + 0.5*np.log(sigma2_sigma2tilde)
|
|
||||||
+ 0.5*((mu_cav - mu_tilde)**2) / (sigma2_sigma2tilde))
|
# Z_tilde after removing the terms that can lead to infinite terms due to tau_tilde close to zero.
|
||||||
return mu, Sigma, mu_tilde, tau_tilde, Z_tilde
|
# This terms cancel with the coreresponding terms in the marginal loglikelihood
|
||||||
|
log_Z_tilde = (np.log(Z_hat) + 0.5*np.log(2*np.pi) + 0.5*np.log(1+tau_tilde/tau_cav)
|
||||||
|
- 0.5 * ((v_tilde)**2 * 1./(tau_cav + tau_tilde)) + 0.5*(v_cav * ( ( (tau_tilde/tau_cav) * v_cav - 2.0 * v_tilde ) * 1./(tau_cav + tau_tilde))))
|
||||||
|
# - 0.5*np.log(tau_tilde) + 0.5*(v_tilde*v_tilde*1./tau_tilde)
|
||||||
|
|
||||||
|
self.old_mutilde = mu_tilde
|
||||||
|
self.old_vtilde = v_tilde
|
||||||
|
|
||||||
|
return mu, Sigma, mu_tilde, tau_tilde, log_Z_tilde
|
||||||
|
|
||||||
|
def _ep_compute_posterior(self, K, tau_tilde, v_tilde):
|
||||||
|
num_data = len(tau_tilde)
|
||||||
|
tau_tilde_root = np.sqrt(tau_tilde)
|
||||||
|
Sroot_tilde_K = tau_tilde_root[:,None] * K
|
||||||
|
B = np.eye(num_data) + Sroot_tilde_K * tau_tilde_root[None,:]
|
||||||
|
L = jitchol(B)
|
||||||
|
V, _ = dtrtrs(L, Sroot_tilde_K, lower=1)
|
||||||
|
Sigma = K - np.dot(V.T,V) #K - KS^(1/2)BS^(1/2)K = (K^(-1) + \Sigma^(-1))^(-1)
|
||||||
|
mu = np.dot(Sigma,v_tilde)
|
||||||
|
return (mu, Sigma, L)
|
||||||
|
|
||||||
|
def _ep_marginal(self, K, tau_tilde, v_tilde, Z_tilde):
|
||||||
|
mu, Sigma, L = self._ep_compute_posterior(K, tau_tilde, v_tilde)
|
||||||
|
|
||||||
|
# Gaussian log marginal excluding terms that can go to infinity due to arbitrarily small tau_tilde.
|
||||||
|
# These terms cancel out with the terms excluded from Z_tilde
|
||||||
|
B_logdet = np.sum(2.0*np.log(np.diag(L)))
|
||||||
|
log_marginal = 0.5*(-len(tau_tilde) * log_2_pi - B_logdet + np.sum(v_tilde * np.dot(Sigma,v_tilde)))
|
||||||
|
log_marginal += Z_tilde
|
||||||
|
|
||||||
|
return log_marginal, mu, Sigma, L
|
||||||
|
|
||||||
|
def _inference(self, K, tau_tilde, v_tilde, likelihood, Z_tilde, Y_metadata=None):
|
||||||
|
log_marginal, mu, Sigma, L = self._ep_marginal(K, tau_tilde, v_tilde, Z_tilde)
|
||||||
|
|
||||||
|
tau_tilde_root = np.sqrt(tau_tilde)
|
||||||
|
Sroot_tilde_K = tau_tilde_root[:,None] * K
|
||||||
|
|
||||||
|
aux_alpha , _ = dpotrs(L, np.dot(Sroot_tilde_K, v_tilde), lower=1)
|
||||||
|
alpha = (v_tilde - tau_tilde_root * aux_alpha)[:,None] #(K + Sigma^(\tilde))^(-1) /mu^(/tilde)
|
||||||
|
LWi, _ = dtrtrs(L, np.diag(tau_tilde_root), lower=1)
|
||||||
|
Wi = np.dot(LWi.T,LWi)
|
||||||
|
symmetrify(Wi) #(K + Sigma^(\tilde))^(-1)
|
||||||
|
|
||||||
|
dL_dK = 0.5 * (tdot(alpha) - Wi)
|
||||||
|
dL_dthetaL = likelihood.exact_inference_gradients(np.diag(dL_dK), Y_metadata)
|
||||||
|
|
||||||
|
return Posterior(woodbury_inv=Wi, woodbury_vector=alpha, K=K), log_marginal, {'dL_dK':dL_dK, 'dL_dthetaL':dL_dthetaL, 'dL_dm':alpha}
|
||||||
|
|
||||||
|
|
||||||
class EPDTC(EPBase, VarDTC):
|
class EPDTC(EPBase, VarDTC):
|
||||||
def inference(self, kern, X, Z, likelihood, Y, mean_function=None, Y_metadata=None, Lm=None, dL_dKmm=None, psi0=None, psi1=None, psi2=None):
|
def inference(self, kern, X, Z, likelihood, Y, mean_function=None, Y_metadata=None, Lm=None, dL_dKmm=None, psi0=None, psi1=None, psi2=None):
|
||||||
assert Y.shape[1]==1, "ep in 1D only (for now!)"
|
if self.always_reset:
|
||||||
|
self.reset()
|
||||||
|
|
||||||
|
num_data, output_dim = Y.shape
|
||||||
|
assert output_dim == 1, "ep in 1D only (for now!)"
|
||||||
|
|
||||||
|
if Lm is None:
|
||||||
|
Kmm = kern.K(Z)
|
||||||
|
Lm = jitchol(Kmm)
|
||||||
|
|
||||||
Kmm = kern.K(Z)
|
|
||||||
if psi1 is None:
|
if psi1 is None:
|
||||||
try:
|
try:
|
||||||
Kmn = kern.K(Z, X)
|
Kmn = kern.K(Z, X)
|
||||||
|
|
@ -167,35 +241,36 @@ class EPDTC(EPBase, VarDTC):
|
||||||
else:
|
else:
|
||||||
Kmn = psi1.T
|
Kmn = psi1.T
|
||||||
|
|
||||||
if getattr(self, '_ep_approximation', None) is None:
|
if self.ep_mode=="nested":
|
||||||
mu, Sigma, mu_tilde, tau_tilde, Z_tilde = self._ep_approximation = self.expectation_propagation(Kmm, Kmn, Y, likelihood, Y_metadata)
|
#Force EP at each step of the optimization
|
||||||
|
self._ep_approximation = None
|
||||||
|
mu, Sigma_diag, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation = self.expectation_propagation(Kmm, Kmn, Y, likelihood, Y_metadata)
|
||||||
|
elif self.ep_mode=="alternated":
|
||||||
|
if getattr(self, '_ep_approximation', None) is None:
|
||||||
|
#if we don't yet have the results of runnign EP, run EP and store the computed factors in self._ep_approximation
|
||||||
|
mu, Sigma_diag, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation = self.expectation_propagation(Kmm, Kmn, Y, likelihood, Y_metadata)
|
||||||
|
else:
|
||||||
|
#if we've already run EP, just use the existing approximation stored in self._ep_approximation
|
||||||
|
mu, Sigma_diag, mu_tilde, tau_tilde, log_Z_tilde = self._ep_approximation
|
||||||
else:
|
else:
|
||||||
mu, Sigma, mu_tilde, tau_tilde, Z_tilde = self._ep_approximation
|
raise ValueError("ep_mode value not valid")
|
||||||
|
|
||||||
return super(EPDTC, self).inference(kern, X, Z, likelihood, mu_tilde,
|
return super(EPDTC, self).inference(kern, X, Z, likelihood, mu_tilde,
|
||||||
mean_function=mean_function,
|
mean_function=mean_function,
|
||||||
Y_metadata=Y_metadata,
|
Y_metadata=Y_metadata,
|
||||||
precision=tau_tilde,
|
precision=tau_tilde,
|
||||||
Lm=Lm, dL_dKmm=dL_dKmm,
|
Lm=Lm, dL_dKmm=dL_dKmm,
|
||||||
psi0=psi0, psi1=psi1, psi2=psi2, Z_tilde=np.log(Z_tilde).sum())
|
psi0=psi0, psi1=psi1, psi2=psi2, Z_tilde=log_Z_tilde.sum())
|
||||||
|
|
||||||
|
|
||||||
def expectation_propagation(self, Kmm, Kmn, Y, likelihood, Y_metadata):
|
def expectation_propagation(self, Kmm, Kmn, Y, likelihood, Y_metadata):
|
||||||
|
|
||||||
num_data, output_dim = Y.shape
|
num_data, output_dim = Y.shape
|
||||||
assert output_dim == 1, "This EP methods only works for 1D outputs"
|
assert output_dim == 1, "This EP methods only works for 1D outputs"
|
||||||
|
|
||||||
LLT0 = Kmm.copy()
|
# Makes computing the sign quicker if we work with numpy arrays rather
|
||||||
#diag.add(LLT0, 1e-8)
|
# than ObsArrays
|
||||||
|
Y = Y.values.copy()
|
||||||
Lm = jitchol(LLT0)
|
|
||||||
Lmi = dtrtri(Lm)
|
|
||||||
Kmmi = np.dot(Lmi.T,Lmi)
|
|
||||||
KmmiKmn = np.dot(Kmmi,Kmn)
|
|
||||||
Qnn_diag = np.sum(Kmn*KmmiKmn,-2)
|
|
||||||
|
|
||||||
#Initial values - Posterior distribution parameters: q(f|X,Y) = N(f|mu,Sigma)
|
|
||||||
mu = np.zeros(num_data)
|
|
||||||
LLT = Kmm.copy() #Sigma = K.copy()
|
|
||||||
Sigma_diag = Qnn_diag.copy() + 1e-8
|
|
||||||
|
|
||||||
#Initial values - Marginal moments
|
#Initial values - Marginal moments
|
||||||
Z_hat = np.zeros(num_data,dtype=np.float64)
|
Z_hat = np.zeros(num_data,dtype=np.float64)
|
||||||
|
|
@ -206,73 +281,110 @@ class EPDTC(EPBase, VarDTC):
|
||||||
v_cav = np.empty(num_data,dtype=np.float64)
|
v_cav = np.empty(num_data,dtype=np.float64)
|
||||||
|
|
||||||
#initial values - Gaussian factors
|
#initial values - Gaussian factors
|
||||||
|
#Initial values - Posterior distribution parameters: q(f|X,Y) = N(f|mu,Sigma)
|
||||||
|
LLT0 = Kmm.copy()
|
||||||
|
Lm = jitchol(LLT0) #K_m = L_m L_m^\top
|
||||||
|
Vm,info = dtrtrs(Lm,Kmn,lower=1)
|
||||||
|
# Lmi = dtrtri(Lm)
|
||||||
|
# Kmmi = np.dot(Lmi.T,Lmi)
|
||||||
|
# KmmiKmn = np.dot(Kmmi,Kmn)
|
||||||
|
# Qnn_diag = np.sum(Kmn*KmmiKmn,-2)
|
||||||
|
Qnn_diag = np.sum(Vm*Vm,-2) #diag(Knm Kmm^(-1) Kmn)
|
||||||
|
#diag.add(LLT0, 1e-8)
|
||||||
if self.old_mutilde is None:
|
if self.old_mutilde is None:
|
||||||
|
#Initial values - Posterior distribution parameters: q(f|X,Y) = N(f|mu,Sigma)
|
||||||
|
LLT = LLT0.copy() #Sigma = K.copy()
|
||||||
|
mu = np.zeros(num_data)
|
||||||
|
Sigma_diag = Qnn_diag.copy() + 1e-8
|
||||||
tau_tilde, mu_tilde, v_tilde = np.zeros((3, num_data))
|
tau_tilde, mu_tilde, v_tilde = np.zeros((3, num_data))
|
||||||
else:
|
else:
|
||||||
assert self.old_mutilde.size == num_data, "data size mis-match: did you change the data? try resetting!"
|
assert self.old_mutilde.size == num_data, "data size mis-match: did you change the data? try resetting!"
|
||||||
mu_tilde, v_tilde = self.old_mutilde, self.old_vtilde
|
mu_tilde, v_tilde = self.old_mutilde, self.old_vtilde
|
||||||
tau_tilde = v_tilde/mu_tilde
|
tau_tilde = v_tilde/mu_tilde
|
||||||
|
mu, Sigma_diag, LLT = self._ep_compute_posterior(LLT0, Kmn, tau_tilde, v_tilde)
|
||||||
|
Sigma_diag += 1e-8
|
||||||
|
# TODO: Check the log-marginal under both conditions and choose the best one
|
||||||
|
|
||||||
#Approximation
|
#Approximation
|
||||||
tau_diff = self.epsilon + 1.
|
tau_diff = self.epsilon + 1.
|
||||||
v_diff = self.epsilon + 1.
|
v_diff = self.epsilon + 1.
|
||||||
|
tau_tilde_old = np.nan
|
||||||
|
v_tilde_old = np.nan
|
||||||
iterations = 0
|
iterations = 0
|
||||||
tau_tilde_old = 0.
|
while ((tau_diff > self.epsilon) or (v_diff > self.epsilon)) and (iterations < self.max_iters):
|
||||||
v_tilde_old = 0.
|
update_order = np.random.permutation(num_data)
|
||||||
update_order = np.random.permutation(num_data)
|
|
||||||
|
|
||||||
while (tau_diff > self.epsilon) or (v_diff > self.epsilon):
|
|
||||||
for i in update_order:
|
for i in update_order:
|
||||||
#Cavity distribution parameters
|
#Cavity distribution parameters
|
||||||
tau_cav[i] = 1./Sigma_diag[i] - self.eta*tau_tilde[i]
|
tau_cav[i] = 1./Sigma_diag[i] - self.eta*tau_tilde[i]
|
||||||
v_cav[i] = mu[i]/Sigma_diag[i] - self.eta*v_tilde[i]
|
v_cav[i] = mu[i]/Sigma_diag[i] - self.eta*v_tilde[i]
|
||||||
|
if Y_metadata is not None:
|
||||||
|
# Pick out the relavent metadata for Yi
|
||||||
|
Y_metadata_i = {}
|
||||||
|
for key in Y_metadata.keys():
|
||||||
|
Y_metadata_i[key] = Y_metadata[key][i, :]
|
||||||
|
else:
|
||||||
|
Y_metadata_i = None
|
||||||
|
|
||||||
#Marginal moments
|
#Marginal moments
|
||||||
Z_hat[i], mu_hat[i], sigma2_hat[i] = likelihood.moments_match_ep(Y[i], tau_cav[i], v_cav[i])#, Y_metadata=None)#=(None if Y_metadata is None else Y_metadata[i]))
|
Z_hat[i], mu_hat[i], sigma2_hat[i] = likelihood.moments_match_ep(Y[i], tau_cav[i], v_cav[i], Y_metadata_i=Y_metadata_i)
|
||||||
#Site parameters update
|
#Site parameters update
|
||||||
delta_tau = self.delta/self.eta*(1./sigma2_hat[i] - 1./Sigma_diag[i])
|
delta_tau = self.delta/self.eta*(1./sigma2_hat[i] - 1./Sigma_diag[i])
|
||||||
delta_v = self.delta/self.eta*(mu_hat[i]/sigma2_hat[i] - mu[i]/Sigma_diag[i])
|
delta_v = self.delta/self.eta*(mu_hat[i]/sigma2_hat[i] - mu[i]/Sigma_diag[i])
|
||||||
|
tau_tilde_prev = tau_tilde[i]
|
||||||
tau_tilde[i] += delta_tau
|
tau_tilde[i] += delta_tau
|
||||||
|
|
||||||
|
# Enforce positivity of tau_tilde. Even though this is guaranteed for logconcave sites, it is still possible
|
||||||
|
# to get negative values due to numerical errors. Moreover, the value of tau_tilde should be positive in order to
|
||||||
|
# update the marginal likelihood without inestability issues.
|
||||||
|
if tau_tilde[i] < np.finfo(float).eps:
|
||||||
|
tau_tilde[i] = np.finfo(float).eps
|
||||||
|
delta_tau = tau_tilde[i] - tau_tilde_prev
|
||||||
v_tilde[i] += delta_v
|
v_tilde[i] += delta_v
|
||||||
|
|
||||||
#Posterior distribution parameters update
|
#Posterior distribution parameters update
|
||||||
|
if self.parallel_updates == False:
|
||||||
|
#DSYR(Sigma, Sigma[:,i].copy(), -delta_tau/(1.+ delta_tau*Sigma[i,i]))
|
||||||
|
DSYR(LLT,Kmn[:,i].copy(),delta_tau)
|
||||||
|
L = jitchol(LLT)
|
||||||
|
V,info = dtrtrs(L,Kmn,lower=1)
|
||||||
|
Sigma_diag = np.maximum(np.sum(V*V,-2), np.finfo(float).eps) #diag(K_nm (L L^\top)^(-1)) K_mn
|
||||||
|
si = np.sum(V.T*V[:,i],-1) #(V V^\top)[:,i]
|
||||||
|
mu += (delta_v-delta_tau*mu[i])*si
|
||||||
|
#mu = np.dot(Sigma, v_tilde)
|
||||||
|
|
||||||
#DSYR(Sigma, Sigma[:,i].copy(), -delta_tau/(1.+ delta_tau*Sigma[i,i]))
|
#(re) compute Sigma, Sigma_diag and mu using full Cholesky decompy
|
||||||
DSYR(LLT,Kmn[:,i].copy(),delta_tau)
|
mu, Sigma_diag, LLT = self._ep_compute_posterior(LLT0, Kmn, tau_tilde, v_tilde)
|
||||||
L = jitchol(LLT+np.eye(LLT.shape[0])*1e-7)
|
Sigma_diag = np.maximum(Sigma_diag, np.finfo(float).eps)
|
||||||
|
|
||||||
V,info = dtrtrs(L,Kmn,lower=1)
|
|
||||||
Sigma_diag = np.sum(V*V,-2)
|
|
||||||
si = np.sum(V.T*V[:,i],-1)
|
|
||||||
mu += (delta_v-delta_tau*mu[i])*si
|
|
||||||
#mu = np.dot(Sigma, v_tilde)
|
|
||||||
|
|
||||||
#(re) compute Sigma and mu using full Cholesky decompy
|
|
||||||
LLT = LLT0 + np.dot(Kmn*tau_tilde[None,:],Kmn.T)
|
|
||||||
#diag.add(LLT, 1e-8)
|
|
||||||
L = jitchol(LLT)
|
|
||||||
V, _ = dtrtrs(L,Kmn,lower=1)
|
|
||||||
V2, _ = dtrtrs(L.T,V,lower=0)
|
|
||||||
#Sigma_diag = np.sum(V*V,-2)
|
|
||||||
#Knmv_tilde = np.dot(Kmn,v_tilde)
|
|
||||||
#mu = np.dot(V2.T,Knmv_tilde)
|
|
||||||
Sigma = np.dot(V2.T,V2)
|
|
||||||
mu = np.dot(Sigma,v_tilde)
|
|
||||||
|
|
||||||
#monitor convergence
|
#monitor convergence
|
||||||
#if iterations>0:
|
if iterations>0:
|
||||||
tau_diff = np.mean(np.square(tau_tilde-tau_tilde_old))
|
tau_diff = np.mean(np.square(tau_tilde-tau_tilde_old))
|
||||||
v_diff = np.mean(np.square(v_tilde-v_tilde_old))
|
v_diff = np.mean(np.square(v_tilde-v_tilde_old))
|
||||||
|
|
||||||
tau_tilde_old = tau_tilde.copy()
|
tau_tilde_old = tau_tilde.copy()
|
||||||
v_tilde_old = v_tilde.copy()
|
v_tilde_old = v_tilde.copy()
|
||||||
|
|
||||||
# Only to while loop once:?
|
|
||||||
tau_diff = 0
|
|
||||||
v_diff = 0
|
|
||||||
iterations += 1
|
iterations += 1
|
||||||
|
|
||||||
mu_tilde = v_tilde/tau_tilde
|
mu_tilde = v_tilde/tau_tilde
|
||||||
mu_cav = v_cav/tau_cav
|
mu_cav = v_cav/tau_cav
|
||||||
sigma2_sigma2tilde = 1./tau_cav + 1./tau_tilde
|
sigma2_sigma2tilde = 1./tau_cav + 1./tau_tilde
|
||||||
Z_tilde = np.exp(np.log(Z_hat) + 0.5*np.log(2*np.pi) + 0.5*np.log(sigma2_sigma2tilde)
|
|
||||||
|
log_Z_tilde = (np.log(Z_hat) + 0.5*np.log(2*np.pi) + 0.5*np.log(sigma2_sigma2tilde)
|
||||||
+ 0.5*((mu_cav - mu_tilde)**2) / (sigma2_sigma2tilde))
|
+ 0.5*((mu_cav - mu_tilde)**2) / (sigma2_sigma2tilde))
|
||||||
return mu, Sigma, ObsAr(mu_tilde[:,None]), tau_tilde, Z_tilde
|
|
||||||
|
self.old_mutilde = mu_tilde
|
||||||
|
self.old_vtilde = v_tilde
|
||||||
|
|
||||||
|
return mu, Sigma_diag, ObsAr(mu_tilde[:,None]), tau_tilde, log_Z_tilde
|
||||||
|
|
||||||
|
def _ep_compute_posterior(self, LLT0, Kmn, tau_tilde, v_tilde):
|
||||||
|
LLT = LLT0 + np.dot(Kmn*tau_tilde[None,:],Kmn.T)
|
||||||
|
L = jitchol(LLT)
|
||||||
|
V, _ = dtrtrs(L,Kmn,lower=1)
|
||||||
|
#Sigma_diag = np.sum(V*V,-2)
|
||||||
|
#Knmv_tilde = np.dot(Kmn,v_tilde)
|
||||||
|
#mu = np.dot(V2.T,Knmv_tilde)
|
||||||
|
Sigma = np.dot(V.T,V)
|
||||||
|
mu = np.dot(Sigma,v_tilde)
|
||||||
|
Sigma_diag = np.diag(Sigma).copy()
|
||||||
|
|
||||||
|
return (mu, Sigma_diag, LLT)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ class Posterior(object):
|
||||||
def _raw_predict(self, kern, Xnew, pred_var, full_cov=False):
|
def _raw_predict(self, kern, Xnew, pred_var, full_cov=False):
|
||||||
woodbury_vector = self.woodbury_vector
|
woodbury_vector = self.woodbury_vector
|
||||||
woodbury_inv = self.woodbury_inv
|
woodbury_inv = self.woodbury_inv
|
||||||
|
|
||||||
if not isinstance(Xnew, VariationalPosterior):
|
if not isinstance(Xnew, VariationalPosterior):
|
||||||
Kx = kern.K(pred_var, Xnew)
|
Kx = kern.K(pred_var, Xnew)
|
||||||
mu = np.dot(Kx.T, woodbury_vector)
|
mu = np.dot(Kx.T, woodbury_vector)
|
||||||
|
|
@ -220,7 +220,7 @@ class Posterior(object):
|
||||||
else:
|
else:
|
||||||
psi0_star = kern.psi0(pred_var, Xnew)
|
psi0_star = kern.psi0(pred_var, Xnew)
|
||||||
psi1_star = kern.psi1(pred_var, Xnew)
|
psi1_star = kern.psi1(pred_var, Xnew)
|
||||||
psi2_star = kern.psi2n(pred_var, Xnew)
|
psi2_star = kern.psi2n(pred_var, Xnew)
|
||||||
la = woodbury_vector
|
la = woodbury_vector
|
||||||
mu = np.dot(psi1_star, la) # TODO: dimensions?
|
mu = np.dot(psi1_star, la) # TODO: dimensions?
|
||||||
N,M,D = psi0_star.shape[0],psi1_star.shape[1], la.shape[1]
|
N,M,D = psi0_star.shape[0],psi1_star.shape[1], la.shape[1]
|
||||||
|
|
@ -231,18 +231,19 @@ class Posterior(object):
|
||||||
di = np.diag_indices(la.shape[1])
|
di = np.diag_indices(la.shape[1])
|
||||||
else:
|
else:
|
||||||
tmp = psi2_star - psi1_star[:,:,None]*psi1_star[:,None,:]
|
tmp = psi2_star - psi1_star[:,:,None]*psi1_star[:,None,:]
|
||||||
var = (tmp.reshape(-1,M).dot(la).reshape(N,M,D)*la[None,:,:]).sum(1) + psi0_star[:,None]
|
var = (tmp.reshape(-1,M).dot(la).reshape(N,M,D)*la[None,:,:]).sum(1) + psi0_star[:,None]
|
||||||
if woodbury_inv.ndim==2:
|
if woodbury_inv.ndim==2:
|
||||||
var += -psi2_star.reshape(N,-1).dot(woodbury_inv.flat)[:,None]
|
var += -psi2_star.reshape(N,-1).dot(woodbury_inv.flat)[:,None]
|
||||||
else:
|
else:
|
||||||
var += -psi2_star.reshape(N,-1).dot(woodbury_inv.reshape(-1,D))
|
var += -psi2_star.reshape(N,-1).dot(woodbury_inv.reshape(-1,D))
|
||||||
var = np.clip(var,1e-15,np.inf)
|
var = np.clip(var,1e-15,np.inf)
|
||||||
return mu, var
|
return mu, var
|
||||||
|
|
||||||
|
|
||||||
class PosteriorExact(Posterior):
|
class PosteriorExact(Posterior):
|
||||||
|
|
||||||
def _raw_predict(self, kern, Xnew, pred_var, full_cov=False):
|
def _raw_predict(self, kern, Xnew, pred_var, full_cov=False):
|
||||||
|
|
||||||
Kx = kern.K(pred_var, Xnew)
|
Kx = kern.K(pred_var, Xnew)
|
||||||
mu = np.dot(Kx.T, self.woodbury_vector)
|
mu = np.dot(Kx.T, self.woodbury_vector)
|
||||||
if len(mu.shape)==1:
|
if len(mu.shape)==1:
|
||||||
|
|
@ -270,3 +271,37 @@ class PosteriorExact(Posterior):
|
||||||
var[:, i] = (Kxx - np.square(tmp).sum(0))
|
var[:, i] = (Kxx - np.square(tmp).sum(0))
|
||||||
var = var
|
var = var
|
||||||
return mu, var
|
return mu, var
|
||||||
|
|
||||||
|
class PosteriorEP(Posterior):
|
||||||
|
|
||||||
|
def _raw_predict(self, kern, Xnew, pred_var, full_cov=False):
|
||||||
|
|
||||||
|
Kx = kern.K(pred_var, Xnew)
|
||||||
|
mu = np.dot(Kx.T, self.woodbury_vector)
|
||||||
|
if len(mu.shape)==1:
|
||||||
|
mu = mu.reshape(-1,1)
|
||||||
|
|
||||||
|
if full_cov:
|
||||||
|
Kxx = kern.K(Xnew)
|
||||||
|
if self._woodbury_inv.ndim == 2:
|
||||||
|
tmp = np.dot(Kx.T,np.dot(self._woodbury_inv, Kx))
|
||||||
|
var = Kxx - tmp
|
||||||
|
elif self._woodbury_inv.ndim == 3: # Missing data
|
||||||
|
var = np.empty((Kxx.shape[0],Kxx.shape[1],self._woodbury_inv.shape[2]))
|
||||||
|
for i in range(var.shape[2]):
|
||||||
|
tmp = np.dot(Kx.T,np.dot(self._woodbury_inv[:,:,i], Kx))
|
||||||
|
var[:, :, i] = (Kxx - tmp)
|
||||||
|
var = var
|
||||||
|
else:
|
||||||
|
Kxx = kern.Kdiag(Xnew)
|
||||||
|
if self._woodbury_inv.ndim == 2:
|
||||||
|
tmp = (np.dot(self._woodbury_inv, Kx) * Kx).sum(0)
|
||||||
|
var = (Kxx - tmp)[:,None]
|
||||||
|
elif self._woodbury_inv.ndim == 3: # Missing data
|
||||||
|
var = np.empty((Kxx.shape[0],self._woodbury_inv.shape[2]))
|
||||||
|
for i in range(var.shape[1]):
|
||||||
|
tmp = (Kx * np.dot(self._woodbury_inv[:,:,i], Kx)).sum(0)
|
||||||
|
var[:, i] = (Kxx - tmp)
|
||||||
|
var = var
|
||||||
|
|
||||||
|
return mu, var
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,10 @@ class VarDTC(LatentFunctionInference):
|
||||||
Kmm = kern.K(Z).copy()
|
Kmm = kern.K(Z).copy()
|
||||||
diag.add(Kmm, self.const_jitter)
|
diag.add(Kmm, self.const_jitter)
|
||||||
Lm = jitchol(Kmm)
|
Lm = jitchol(Kmm)
|
||||||
|
else:
|
||||||
|
Kmm = tdot(Lm)
|
||||||
|
symmetrify(Kmm)
|
||||||
|
|
||||||
|
|
||||||
# The rather complex computations of A, and the psi stats
|
# The rather complex computations of A, and the psi stats
|
||||||
if uncertain_inputs:
|
if uncertain_inputs:
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
# Licensed under the BSD 3-clause license (see LICENSE.txt)
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from ..util.univariate_Gaussian import std_norm_pdf, std_norm_cdf
|
from ..util.univariate_Gaussian import std_norm_pdf, std_norm_cdf, derivLogCdfNormal, logCdfNormal
|
||||||
from . import link_functions
|
from . import link_functions
|
||||||
from .likelihood import Likelihood
|
from .likelihood import Likelihood
|
||||||
|
|
||||||
|
|
@ -59,24 +59,24 @@ class Bernoulli(Likelihood):
|
||||||
raise ValueError("bad value for Bernoulli observation (0, 1)")
|
raise ValueError("bad value for Bernoulli observation (0, 1)")
|
||||||
if isinstance(self.gp_link, link_functions.Probit):
|
if isinstance(self.gp_link, link_functions.Probit):
|
||||||
z = sign*v_i/np.sqrt(tau_i**2 + tau_i)
|
z = sign*v_i/np.sqrt(tau_i**2 + tau_i)
|
||||||
Z_hat = std_norm_cdf(z)
|
phi_div_Phi = derivLogCdfNormal(z)
|
||||||
Z_hat = np.where(Z_hat==0, 1e-15, Z_hat)
|
log_Z_hat = logCdfNormal(z)
|
||||||
phi = std_norm_pdf(z)
|
|
||||||
|
|
||||||
mu_hat = v_i/tau_i + sign*phi/(Z_hat*np.sqrt(tau_i**2 + tau_i))
|
mu_hat = v_i/tau_i + sign*phi_div_Phi/np.sqrt(tau_i**2 + tau_i)
|
||||||
sigma2_hat = 1./tau_i - (phi/((tau_i**2+tau_i)*Z_hat))*(z+phi/Z_hat)
|
sigma2_hat = 1./tau_i - (phi_div_Phi/(tau_i**2+tau_i))*(z+phi_div_Phi)
|
||||||
|
|
||||||
elif isinstance(self.gp_link, link_functions.Heaviside):
|
elif isinstance(self.gp_link, link_functions.Heaviside):
|
||||||
a = sign*v_i/np.sqrt(tau_i)
|
z = sign*v_i/np.sqrt(tau_i)
|
||||||
Z_hat = np.max(1e-13, std_norm_cdf(z))
|
phi_div_Phi = derivLogCdfNormal(z)
|
||||||
N = std_norm_pdf(a)
|
log_Z_hat = logCdfNormal(z)
|
||||||
mu_hat = v_i/tau_i + sign*N/Z_hat/np.sqrt(tau_i)
|
mu_hat = v_i/tau_i + sign*phi_div_Phi/np.sqrt(tau_i)
|
||||||
sigma2_hat = (1. - a*N/Z_hat - np.square(N/Z_hat))/tau_i
|
sigma2_hat = (1. - a*phi_div_Phi - np.square(phi_div_Phi))/tau_i
|
||||||
else:
|
else:
|
||||||
#TODO: do we want to revert to numerical quadrature here?
|
#TODO: do we want to revert to numerical quadrature here?
|
||||||
raise ValueError("Exact moment matching not available for link {}".format(self.gp_link.__name__))
|
raise ValueError("Exact moment matching not available for link {}".format(self.gp_link.__name__))
|
||||||
|
|
||||||
return Z_hat, mu_hat, sigma2_hat
|
# TODO: Output log_Z_hat instead of Z_hat (needs to be change in all others likelihoods)
|
||||||
|
return np.exp(log_Z_hat), mu_hat, sigma2_hat
|
||||||
|
|
||||||
def variational_expectations(self, Y, m, v, gh_points=None, Y_metadata=None):
|
def variational_expectations(self, Y, m, v, gh_points=None, Y_metadata=None):
|
||||||
if isinstance(self.gp_link, link_functions.Probit):
|
if isinstance(self.gp_link, link_functions.Probit):
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,43 @@ class InferenceXTestCase(unittest.TestCase):
|
||||||
m.optimize()
|
m.optimize()
|
||||||
x, mi = m.infer_newX(m.Y, optimize=True)
|
x, mi = m.infer_newX(m.Y, optimize=True)
|
||||||
np.testing.assert_array_almost_equal(m.X, mi.X, decimal=2)
|
np.testing.assert_array_almost_equal(m.X, mi.X, decimal=2)
|
||||||
|
class InferenceGPEP(unittest.TestCase):
|
||||||
|
|
||||||
|
def genData(self):
|
||||||
|
np.random.seed(1)
|
||||||
|
k = GPy.kern.RBF(1, variance=7., lengthscale=0.2)
|
||||||
|
X = np.random.rand(200,1)
|
||||||
|
f = np.random.multivariate_normal(np.zeros(200), k.K(X) + 1e-5 * np.eye(X.shape[0]))
|
||||||
|
lik = GPy.likelihoods.Bernoulli()
|
||||||
|
p = lik.gp_link.transf(f) # squash the latent function
|
||||||
|
Y = lik.samples(f).reshape(-1,1)
|
||||||
|
return X, Y
|
||||||
|
|
||||||
|
def test_inference_EP(self):
|
||||||
|
from paramz import ObsAr
|
||||||
|
X, Y = self.genData()
|
||||||
|
lik = GPy.likelihoods.Bernoulli()
|
||||||
|
k = GPy.kern.RBF(1, variance=7., lengthscale=0.2)
|
||||||
|
inf = GPy.inference.latent_function_inference.expectation_propagation.EP(max_iters=30, delta=0.5)
|
||||||
|
self.model = GPy.core.GP(X=X,
|
||||||
|
Y=Y,
|
||||||
|
kernel=k,
|
||||||
|
inference_method=inf,
|
||||||
|
likelihood=lik)
|
||||||
|
K = self.model.kern.K(X)
|
||||||
|
mu, Sigma, mu_tilde, tau_tilde, log_Z_tilde = self.model.inference_method.expectation_propagation(K, ObsAr(Y), lik, None)
|
||||||
|
|
||||||
|
v_tilde = mu_tilde * tau_tilde
|
||||||
|
p, m, d = self.model.inference_method._inference(K, tau_tilde, v_tilde, lik, Y_metadata=None, Z_tilde=log_Z_tilde.sum())
|
||||||
|
p0, m0, d0 = super(GPy.inference.latent_function_inference.expectation_propagation.EP, inf).inference(k, X,lik ,mu_tilde[:,None], mean_function=None, variance=1./tau_tilde, K=K, Z_tilde=log_Z_tilde.sum() + np.sum(- 0.5*np.log(tau_tilde) + 0.5*(v_tilde*v_tilde*1./tau_tilde)))
|
||||||
|
|
||||||
|
assert (np.sum(np.array([m - m0,
|
||||||
|
np.sum(d['dL_dK'] - d0['dL_dK']),
|
||||||
|
np.sum(d['dL_dthetaL'] - d0['dL_dthetaL']),
|
||||||
|
np.sum(d['dL_dm'] - d0['dL_dm']),
|
||||||
|
np.sum(p._woodbury_vector - p0._woodbury_vector),
|
||||||
|
np.sum(p.woodbury_inv - p0.woodbury_inv)])) < 1e6)
|
||||||
|
|
||||||
|
|
||||||
class HMCSamplerTest(unittest.TestCase):
|
class HMCSamplerTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
@ -64,7 +101,7 @@ class HMCSamplerTest(unittest.TestCase):
|
||||||
|
|
||||||
hmc = GPy.inference.mcmc.HMC(m,stepsize=1e-2)
|
hmc = GPy.inference.mcmc.HMC(m,stepsize=1e-2)
|
||||||
s = hmc.sample(num_samples=3)
|
s = hmc.sample(num_samples=3)
|
||||||
|
|
||||||
class MCMCSamplerTest(unittest.TestCase):
|
class MCMCSamplerTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_sampling(self):
|
def test_sampling(self):
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
#===============================================================================
|
#===============================================================================
|
||||||
# Copyright (c) 2016, Max Zwiessele, Alan Saul
|
# Copyright (c) 2016, Max Zwiessele, Alan Saul
|
||||||
# All rights reserved.
|
# All rights reserved.
|
||||||
#
|
#
|
||||||
# Redistribution and use in source and binary forms, with or without
|
# Redistribution and use in source and binary forms, with or without
|
||||||
# modification, are permitted provided that the following conditions are met:
|
# modification, are permitted provided that the following conditions are met:
|
||||||
#
|
#
|
||||||
# * Redistributions of source code must retain the above copyright notice, this
|
# * Redistributions of source code must retain the above copyright notice, this
|
||||||
# list of conditions and the following disclaimer.
|
# list of conditions and the following disclaimer.
|
||||||
#
|
#
|
||||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
# this list of conditions and the following disclaimer in the documentation
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
# and/or other materials provided with the distribution.
|
# and/or other materials provided with the distribution.
|
||||||
#
|
#
|
||||||
# * Neither the name of GPy.testing.util_tests nor the names of its
|
# * Neither the name of GPy.testing.util_tests nor the names of its
|
||||||
# contributors may be used to endorse or promote products derived from
|
# contributors may be used to endorse or promote products derived from
|
||||||
# this software without specific prior written permission.
|
# this software without specific prior written permission.
|
||||||
#
|
#
|
||||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
|
@ -36,7 +36,7 @@ class TestDebug(unittest.TestCase):
|
||||||
from GPy.util.debug import checkFinite
|
from GPy.util.debug import checkFinite
|
||||||
array = np.random.normal(0, 1, 100).reshape(25,4)
|
array = np.random.normal(0, 1, 100).reshape(25,4)
|
||||||
self.assertTrue(checkFinite(array, name='test'))
|
self.assertTrue(checkFinite(array, name='test'))
|
||||||
|
|
||||||
array[np.random.binomial(1, .3, array.shape).astype(bool)] = np.nan
|
array[np.random.binomial(1, .3, array.shape).astype(bool)] = np.nan
|
||||||
self.assertFalse(checkFinite(array))
|
self.assertFalse(checkFinite(array))
|
||||||
|
|
||||||
|
|
@ -45,10 +45,10 @@ class TestDebug(unittest.TestCase):
|
||||||
from GPy.util.linalg import tdot
|
from GPy.util.linalg import tdot
|
||||||
array = np.random.normal(0, 1, 100).reshape(25,4)
|
array = np.random.normal(0, 1, 100).reshape(25,4)
|
||||||
self.assertFalse(checkFullRank(tdot(array), name='test'))
|
self.assertFalse(checkFullRank(tdot(array), name='test'))
|
||||||
|
|
||||||
array = np.random.normal(0, 1, (25,25))
|
array = np.random.normal(0, 1, (25,25))
|
||||||
self.assertTrue(checkFullRank(tdot(array)))
|
self.assertTrue(checkFullRank(tdot(array)))
|
||||||
|
|
||||||
def test_fixed_inputs_median(self):
|
def test_fixed_inputs_median(self):
|
||||||
""" test fixed_inputs convenience function """
|
""" test fixed_inputs convenience function """
|
||||||
from GPy.plotting.matplot_dep.util import fixed_inputs
|
from GPy.plotting.matplot_dep.util import fixed_inputs
|
||||||
|
|
@ -113,8 +113,8 @@ class TestDebug(unittest.TestCase):
|
||||||
#test data set. Not using random noise just in case it occasionally
|
#test data set. Not using random noise just in case it occasionally
|
||||||
#causes it not to cluster correctly.
|
#causes it not to cluster correctly.
|
||||||
#groundtruth cluster identifiers are: [0,1,1,0]
|
#groundtruth cluster identifiers are: [0,1,1,0]
|
||||||
|
|
||||||
#data contains a list of the four sets of time series (3 per data point)
|
#data contains a list of the four sets of time series (3 per data point)
|
||||||
|
|
||||||
data = [np.array([[ 2.18094245, 1.96529789, 2.00265523, 2.18218742, 2.06795428],
|
data = [np.array([[ 2.18094245, 1.96529789, 2.00265523, 2.18218742, 2.06795428],
|
||||||
[ 1.62254829, 1.75748448, 1.83879347, 1.87531326, 1.52503496],
|
[ 1.62254829, 1.75748448, 1.83879347, 1.87531326, 1.52503496],
|
||||||
|
|
@ -130,7 +130,7 @@ class TestDebug(unittest.TestCase):
|
||||||
[ 1.69336013, 1.72285186, 1.6339506 , 1.61212022, 1.39198698]])]
|
[ 1.69336013, 1.72285186, 1.6339506 , 1.61212022, 1.39198698]])]
|
||||||
|
|
||||||
#inputs contains their associated X values
|
#inputs contains their associated X values
|
||||||
|
|
||||||
inputs = [np.array([[ 0. ],
|
inputs = [np.array([[ 0. ],
|
||||||
[ 0.68040097],
|
[ 0.68040097],
|
||||||
[ 1.20316795],
|
[ 1.20316795],
|
||||||
|
|
@ -148,10 +148,66 @@ class TestDebug(unittest.TestCase):
|
||||||
[ 1.71881079],
|
[ 1.71881079],
|
||||||
[ 2.67162871],
|
[ 2.67162871],
|
||||||
[ 3.23761907]])]
|
[ 3.23761907]])]
|
||||||
|
|
||||||
#try doing the clustering
|
#try doing the clustering
|
||||||
active = GPy.util.cluster_with_offset.cluster(data,inputs)
|
active = GPy.util.cluster_with_offset.cluster(data,inputs)
|
||||||
#check to see that the clustering has correctly clustered the time series.
|
#check to see that the clustering has correctly clustered the time series.
|
||||||
clusters = set([frozenset(cluster) for cluster in active])
|
clusters = set([frozenset(cluster) for cluster in active])
|
||||||
assert set([1,2]) in clusters, "Offset Clustering algorithm failed"
|
assert set([1,2]) in clusters, "Offset Clustering algorithm failed"
|
||||||
assert set([0,3]) in clusters, "Offset Clustering algoirthm failed"
|
assert set([0,3]) in clusters, "Offset Clustering algoirthm failed"
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnivariateGaussian(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.zz = [-5.0, -0.8, 0.0, 0.5, 2.0, 10.0]
|
||||||
|
|
||||||
|
def test_logPdfNormal(self):
|
||||||
|
from GPy.util.univariate_Gaussian import logPdfNormal
|
||||||
|
pySols = [-13.4189385332,
|
||||||
|
-1.2389385332,
|
||||||
|
-0.918938533205,
|
||||||
|
-1.0439385332,
|
||||||
|
-2.9189385332,
|
||||||
|
-50.9189385332]
|
||||||
|
diff = 0.0
|
||||||
|
for i in range(len(pySols)):
|
||||||
|
diff += abs(logPdfNormal(self.zz[i]) - pySols[i])
|
||||||
|
self.assertTrue(diff < 1e-10)
|
||||||
|
|
||||||
|
def test_cdfNormal(self):
|
||||||
|
from GPy.util.univariate_Gaussian import cdfNormal
|
||||||
|
pySols = [2.86651571879e-07,
|
||||||
|
0.211855398583,
|
||||||
|
0.5,
|
||||||
|
0.691462461274,
|
||||||
|
0.977249868052,
|
||||||
|
1.0]
|
||||||
|
diff = 0.0
|
||||||
|
for i in range(len(pySols)):
|
||||||
|
diff += abs(cdfNormal(self.zz[i]) - pySols[i])
|
||||||
|
self.assertTrue(diff < 1e-10)
|
||||||
|
|
||||||
|
def test_logCdfNormal(self):
|
||||||
|
from GPy.util.univariate_Gaussian import logCdfNormal
|
||||||
|
pySols = [-15.064998394,
|
||||||
|
-1.55185131919,
|
||||||
|
-0.69314718056,
|
||||||
|
-0.368946415289,
|
||||||
|
-0.023012909329,
|
||||||
|
0.0]
|
||||||
|
diff = 0.0
|
||||||
|
for i in range(len(pySols)):
|
||||||
|
diff += abs(logCdfNormal(self.zz[i]) - pySols[i])
|
||||||
|
self.assertTrue(diff < 1e-10)
|
||||||
|
def test_derivLogCdfNormal(self):
|
||||||
|
from GPy.util.univariate_Gaussian import derivLogCdfNormal
|
||||||
|
pySols = [5.18650396941,
|
||||||
|
1.3674022693,
|
||||||
|
0.79788456081,
|
||||||
|
0.50916043387,
|
||||||
|
0.0552478626962,
|
||||||
|
0.0]
|
||||||
|
diff = 0.0
|
||||||
|
for i in range(len(pySols)):
|
||||||
|
diff += abs(derivLogCdfNormal(self.zz[i]) - pySols[i])
|
||||||
|
self.assertTrue(diff < 1e-8)
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,10 @@ def authorize_download(dataset_name=None):
|
||||||
|
|
||||||
def download_data(dataset_name=None):
|
def download_data(dataset_name=None):
|
||||||
"""Check with the user that the are happy with terms and conditions for the data set, then download it."""
|
"""Check with the user that the are happy with terms and conditions for the data set, then download it."""
|
||||||
import itertools
|
try:
|
||||||
|
from itertools import zip_longest
|
||||||
|
except ImportError:
|
||||||
|
from itertools import izip_longest as zip_longest
|
||||||
|
|
||||||
dr = data_resources[dataset_name]
|
dr = data_resources[dataset_name]
|
||||||
if not authorize_download(dataset_name):
|
if not authorize_download(dataset_name):
|
||||||
|
|
@ -220,8 +223,8 @@ def download_data(dataset_name=None):
|
||||||
if 'suffices' in dr: zip_urls += (dr['suffices'], )
|
if 'suffices' in dr: zip_urls += (dr['suffices'], )
|
||||||
else: zip_urls += ([],)
|
else: zip_urls += ([],)
|
||||||
|
|
||||||
for url, files, save_names, suffices in itertools.zip_longest(*zip_urls, fillvalue=[]):
|
for url, files, save_names, suffices in zip_longest(*zip_urls, fillvalue=[]):
|
||||||
for f, save_name, suffix in itertools.zip_longest(files, save_names, suffices, fillvalue=None):
|
for f, save_name, suffix in zip_longest(files, save_names, suffices, fillvalue=None):
|
||||||
download_url(os.path.join(url,f), dataset_name, save_name, suffix=suffix)
|
download_url(os.path.join(url,f), dataset_name, save_name, suffix=suffix)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
|
||||||
|
|
@ -11,12 +11,8 @@ from scipy.linalg import lapack, blas
|
||||||
from .config import config
|
from .config import config
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try:
|
if config.getboolean('cython', 'working'):
|
||||||
from . import linalg_cython
|
from . import linalg_cython
|
||||||
config.set('cython', 'working', 'True')
|
|
||||||
except ImportError:
|
|
||||||
config.set('cython', 'working', 'False')
|
|
||||||
|
|
||||||
|
|
||||||
def force_F_ordered_symmetric(A):
|
def force_F_ordered_symmetric(A):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
'''
|
'''
|
||||||
Created on Aug 27, 2014
|
Created on Aug 27, 2014
|
||||||
|
|
||||||
@author: t-mazwie
|
@author: Max Zwiessele
|
||||||
'''
|
'''
|
||||||
import logging
|
import logging
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
|
||||||
|
|
@ -23,3 +23,164 @@ def inv_std_norm_cdf(x):
|
||||||
inv_erf = np.sign(z) * np.sqrt( np.sqrt(b**2 - ln1z2/a) - b )
|
inv_erf = np.sign(z) * np.sqrt( np.sqrt(b**2 - ln1z2/a) - b )
|
||||||
return np.sqrt(2) * inv_erf
|
return np.sqrt(2) * inv_erf
|
||||||
|
|
||||||
|
def logPdfNormal(z):
|
||||||
|
"""
|
||||||
|
Robust implementations of log pdf of a standard normal.
|
||||||
|
|
||||||
|
@see [[https://github.com/mseeger/apbsint/blob/master/src/eptools/potentials/SpecfunServices.h original implementation]]
|
||||||
|
in C from Matthias Seeger.
|
||||||
|
"""
|
||||||
|
return -0.5 * (M_LN2PI + z * z)
|
||||||
|
|
||||||
|
def cdfNormal(z):
|
||||||
|
"""
|
||||||
|
Robust implementations of cdf of a standard normal.
|
||||||
|
|
||||||
|
@see [[https://github.com/mseeger/apbsint/blob/master/src/eptools/potentials/SpecfunServices.h original implementation]]
|
||||||
|
in C from Matthias Seeger.
|
||||||
|
*/
|
||||||
|
"""
|
||||||
|
if (abs(z) < ERF_CODY_LIMIT1):
|
||||||
|
# Phi(z) approx (1+y R_3(y^2))/2, y=z/sqrt(2)
|
||||||
|
return 0.5 * (1.0 + (z / M_SQRT2) * _erfRationalHelperR3(0.5 * z * z))
|
||||||
|
elif (z < 0.0):
|
||||||
|
# Phi(z) approx N(z)Q(-z)/(-z), z<0
|
||||||
|
return np.exp(logPdfNormal(z)) * _erfRationalHelper(-z) / (-z)
|
||||||
|
else:
|
||||||
|
return 1.0 - np.exp(logPdfNormal(z)) * _erfRationalHelper(z) / z
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def logCdfNormal(z):
|
||||||
|
"""
|
||||||
|
Robust implementations of log cdf of a standard normal.
|
||||||
|
|
||||||
|
@see [[https://github.com/mseeger/apbsint/blob/master/src/eptools/potentials/SpecfunServices.h original implementation]]
|
||||||
|
in C from Matthias Seeger.
|
||||||
|
"""
|
||||||
|
if (abs(z) < ERF_CODY_LIMIT1):
|
||||||
|
# Phi(z) approx (1+y R_3(y^2))/2, y=z/sqrt(2)
|
||||||
|
return np.log1p((z / M_SQRT2) * _erfRationalHelperR3(0.5 * z * z)) - M_LN2
|
||||||
|
elif (z < 0.0):
|
||||||
|
# Phi(z) approx N(z)Q(-z)/(-z), z<0
|
||||||
|
return logPdfNormal(z) - np.log(-z) + np.log(_erfRationalHelper(-z))
|
||||||
|
else:
|
||||||
|
return np.log1p(-(np.exp(logPdfNormal(z))) * _erfRationalHelper(z) / z)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def derivLogCdfNormal(z):
|
||||||
|
"""
|
||||||
|
Robust implementations of derivative of the log cdf of a standard normal.
|
||||||
|
|
||||||
|
@see [[https://github.com/mseeger/apbsint/blob/master/src/eptools/potentials/SpecfunServices.h original implementation]]
|
||||||
|
in C from Matthias Seeger.
|
||||||
|
"""
|
||||||
|
if (abs(z) < ERF_CODY_LIMIT1):
|
||||||
|
# Phi(z) approx (1 + y R_3(y^2))/2, y = z/sqrt(2)
|
||||||
|
return 2.0 * np.exp(logPdfNormal(z)) / (1.0 + (z / M_SQRT2) * _erfRationalHelperR3(0.5 * z * z))
|
||||||
|
elif (z < 0.0):
|
||||||
|
# Phi(z) approx N(z) Q(-z)/(-z), z<0
|
||||||
|
return -z / _erfRationalHelper(-z)
|
||||||
|
else:
|
||||||
|
t = np.exp(logPdfNormal(z))
|
||||||
|
return t / (1.0 - t * _erfRationalHelper(z) / z)
|
||||||
|
|
||||||
|
|
||||||
|
def _erfRationalHelper(x):
|
||||||
|
assert x > 0.0, "Arg of erfRationalHelper should be >0.0; was {}".format(x)
|
||||||
|
|
||||||
|
if (x >= ERF_CODY_LIMIT2):
|
||||||
|
"""
|
||||||
|
x/sqrt(2) >= 4
|
||||||
|
|
||||||
|
Q(x) = 1 + sqrt(pi) y R_1(y),
|
||||||
|
R_1(y) = poly(p_j,y) / poly(q_j,y), where y = 2/(x*x)
|
||||||
|
|
||||||
|
Ordering of arrays: 4,3,2,1,0,5 (only for numerator p_j; q_5=1)
|
||||||
|
ATTENTION: The p_j are negative of the entries here
|
||||||
|
p (see P1_ERF)
|
||||||
|
q (see Q1_ERF)
|
||||||
|
"""
|
||||||
|
y = 2.0 / (x * x)
|
||||||
|
|
||||||
|
res = y * P1_ERF[5]
|
||||||
|
den = y
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while (i <= 3):
|
||||||
|
res = (res + P1_ERF[i]) * y
|
||||||
|
den = (den + Q1_ERF[i]) * y
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
# Minus, because p(j) values have to be negated
|
||||||
|
return 1.0 - M_SQRTPI * y * (res + P1_ERF[4]) / (den + Q1_ERF[4])
|
||||||
|
else:
|
||||||
|
"""
|
||||||
|
x/sqrt(2) < 4, x/sqrt(2) >= 0.469
|
||||||
|
|
||||||
|
Q(x) = sqrt(pi) y R_2(y),
|
||||||
|
R_2(y) = poly(p_j,y) / poly(q_j,y), y = x/sqrt(2)
|
||||||
|
|
||||||
|
Ordering of arrays: 7,6,5,4,3,2,1,0,8 (only p_8; q_8=1)
|
||||||
|
p (see P2_ERF)
|
||||||
|
q (see Q2_ERF
|
||||||
|
"""
|
||||||
|
y = x / M_SQRT2
|
||||||
|
res = y * P2_ERF[8]
|
||||||
|
den = y
|
||||||
|
i = 0
|
||||||
|
|
||||||
|
while (i <= 6):
|
||||||
|
res = (res + P2_ERF[i]) * y
|
||||||
|
den = (den + Q2_ERF[i]) * y
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return M_SQRTPI * y * (res + P2_ERF[7]) / (den + Q2_ERF[7])
|
||||||
|
|
||||||
|
def _erfRationalHelperR3(y):
|
||||||
|
assert y >= 0.0, "Arg of erfRationalHelperR3 should be >=0.0; was {}".format(y)
|
||||||
|
|
||||||
|
nom = y * P3_ERF[4]
|
||||||
|
den = y
|
||||||
|
i = 0
|
||||||
|
while (i <= 2):
|
||||||
|
nom = (nom + P3_ERF[i]) * y
|
||||||
|
den = (den + Q3_ERF[i]) * y
|
||||||
|
i += 1
|
||||||
|
return (nom + P3_ERF[3]) / (den + Q3_ERF[3])
|
||||||
|
|
||||||
|
ERF_CODY_LIMIT1 = 0.6629
|
||||||
|
ERF_CODY_LIMIT2 = 5.6569
|
||||||
|
M_LN2PI = 1.83787706640934533908193770913
|
||||||
|
M_LN2 = 0.69314718055994530941723212146
|
||||||
|
M_SQRTPI = 1.77245385090551602729816748334
|
||||||
|
M_SQRT2 = 1.41421356237309504880168872421
|
||||||
|
|
||||||
|
#weights for the erfHelpers (defined here to avoid redefinitions at every call)
|
||||||
|
P1_ERF = [
|
||||||
|
3.05326634961232344e-1, 3.60344899949804439e-1,
|
||||||
|
1.25781726111229246e-1, 1.60837851487422766e-2,
|
||||||
|
6.58749161529837803e-4, 1.63153871373020978e-2]
|
||||||
|
Q1_ERF = [
|
||||||
|
2.56852019228982242e+0, 1.87295284992346047e+0,
|
||||||
|
5.27905102951428412e-1, 6.05183413124413191e-2,
|
||||||
|
2.33520497626869185e-3]
|
||||||
|
P2_ERF = [
|
||||||
|
5.64188496988670089e-1, 8.88314979438837594e+0,
|
||||||
|
6.61191906371416295e+1, 2.98635138197400131e+2,
|
||||||
|
8.81952221241769090e+2, 1.71204761263407058e+3,
|
||||||
|
2.05107837782607147e+3, 1.23033935479799725e+3,
|
||||||
|
2.15311535474403846e-8]
|
||||||
|
Q2_ERF = [
|
||||||
|
1.57449261107098347e+1, 1.17693950891312499e+2,
|
||||||
|
5.37181101862009858e+2, 1.62138957456669019e+3,
|
||||||
|
3.29079923573345963e+3, 4.36261909014324716e+3,
|
||||||
|
3.43936767414372164e+3, 1.23033935480374942e+3]
|
||||||
|
P3_ERF = [
|
||||||
|
3.16112374387056560e+0, 1.13864154151050156e+2,
|
||||||
|
3.77485237685302021e+2, 3.20937758913846947e+3,
|
||||||
|
1.85777706184603153e-1]
|
||||||
|
Q3_ERF = [
|
||||||
|
2.36012909523441209e+1, 2.44024637934444173e+2,
|
||||||
|
1.28261652607737228e+3, 2.84423683343917062e+3]
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ recursive-include GPy *.c
|
||||||
recursive-include GPy *.h
|
recursive-include GPy *.h
|
||||||
recursive-include GPy *.pyx
|
recursive-include GPy *.pyx
|
||||||
|
|
||||||
|
# LICENSE
|
||||||
|
include LICENSE.txt
|
||||||
|
|
||||||
# Testing
|
# Testing
|
||||||
#include GPy/testing/baseline/*.png
|
#include GPy/testing/baseline/*.png
|
||||||
#include GPy/testing/pickle_test.pickle
|
#include GPy/testing/pickle_test.pickle
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ If that is the case, it is best to clean the repo and reinstall.
|
||||||
[<img src="https://upload.wikimedia.org/wikipedia/commons/8/8e/OS_X-Logo.svg" height=40px>](http://www.apple.com/osx/)
|
[<img src="https://upload.wikimedia.org/wikipedia/commons/8/8e/OS_X-Logo.svg" height=40px>](http://www.apple.com/osx/)
|
||||||
[<img src="https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg" height=40px>](https://en.wikipedia.org/wiki/List_of_Linux_distributions)
|
[<img src="https://upload.wikimedia.org/wikipedia/commons/3/35/Tux.svg" height=40px>](https://en.wikipedia.org/wiki/List_of_Linux_distributions)
|
||||||
|
|
||||||
Python 2.7, 3.4 and higher
|
Python 2.7, 3.5 and higher
|
||||||
|
|
||||||
## Citation
|
## Citation
|
||||||
|
|
||||||
|
|
|
||||||
18
appveyor.yml
18
appveyor.yml
|
|
@ -3,12 +3,14 @@ environment:
|
||||||
secure: 8/ZjXFwtd1S7ixd7PJOpptupKKEDhm2da/q3unabJ00=
|
secure: 8/ZjXFwtd1S7ixd7PJOpptupKKEDhm2da/q3unabJ00=
|
||||||
COVERALLS_REPO_TOKEN:
|
COVERALLS_REPO_TOKEN:
|
||||||
secure: d3Luic/ESkGaWnZrvWZTKrzO+xaVwJWaRCEP0F+K/9DQGPSRZsJ/Du5g3s4XF+tS
|
secure: d3Luic/ESkGaWnZrvWZTKrzO+xaVwJWaRCEP0F+K/9DQGPSRZsJ/Du5g3s4XF+tS
|
||||||
gpy_version: 1.6.1
|
gpy_version: 1.7.2
|
||||||
matrix:
|
matrix:
|
||||||
- PYTHON_VERSION: 2.7
|
- PYTHON_VERSION: 2.7
|
||||||
MINICONDA: C:\Miniconda-x64
|
MINICONDA: C:\Miniconda-x64
|
||||||
- PYTHON_VERSION: 3.5
|
- PYTHON_VERSION: 3.5
|
||||||
MINICONDA: C:\Miniconda35-x64
|
MINICONDA: C:\Miniconda35-x64
|
||||||
|
- PYTHON_VERSION: 3.6
|
||||||
|
MINICONDA: C:\Miniconda36-x64
|
||||||
|
|
||||||
#configuration:
|
#configuration:
|
||||||
# - Debug
|
# - Debug
|
||||||
|
|
@ -62,19 +64,19 @@ deploy_script:
|
||||||
- echo test >> %USERPROFILE%\\.pypirc
|
- echo test >> %USERPROFILE%\\.pypirc
|
||||||
- echo[
|
- echo[
|
||||||
- echo [pypi] >> %USERPROFILE%\\.pypirc
|
- echo [pypi] >> %USERPROFILE%\\.pypirc
|
||||||
- echo username:maxz >> %USERPROFILE%\\.pypirc
|
- echo username = maxz >> %USERPROFILE%\\.pypirc
|
||||||
- echo password:%pip_access% >> %USERPROFILE%\\.pypirc
|
- echo password = %pip_access% >> %USERPROFILE%\\.pypirc
|
||||||
- echo[
|
- echo[
|
||||||
- echo [test] >> %USERPROFILE%\\.pypirc
|
- echo [test] >> %USERPROFILE%\\.pypirc
|
||||||
- echo repository:https://test.pypi.org/legacy/ >> %USERPROFILE%\\.pypirc
|
- echo repository = https://testpypi.python.org/pypi >> %USERPROFILE%\\.pypirc
|
||||||
- echo username:maxz >> %USERPROFILE%\\.pypirc
|
- echo username = maxz >> %USERPROFILE%\\.pypirc
|
||||||
- echo password:%pip_access% >> %USERPROFILE%\\.pypirc
|
- echo password = %pip_access% >> %USERPROFILE%\\.pypirc
|
||||||
- ps: >-
|
- ps: >-
|
||||||
if ($env:APPVEYOR_REPO_BRANCH -eq 'devel') {
|
if ($env:APPVEYOR_REPO_BRANCH -eq 'devel') {
|
||||||
twine upload --skip-existing -r test dist/*
|
twine upload --skip-existing -r test "dist/*"
|
||||||
}
|
}
|
||||||
elseif ($env:APPVEYOR_REPO_BRANCH -eq 'deploy') {
|
elseif ($env:APPVEYOR_REPO_BRANCH -eq 'deploy') {
|
||||||
twine upload --skip-existing dist/*
|
twine upload --skip-existing "dist/*"
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
echo not deploying on other branches
|
echo not deploying on other branches
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
[bumpversion]
|
[bumpversion]
|
||||||
current_version = 1.6.1
|
current_version = 1.7.2
|
||||||
tag = True
|
tag = True
|
||||||
commit = True
|
commit = True
|
||||||
|
|
||||||
|
|
|
||||||
9
setup.py
9
setup.py
|
|
@ -169,9 +169,14 @@ setup(name = 'GPy',
|
||||||
'Operating System :: Microsoft :: Windows',
|
'Operating System :: Microsoft :: Windows',
|
||||||
'Operating System :: POSIX :: Linux',
|
'Operating System :: POSIX :: Linux',
|
||||||
'Programming Language :: Python :: 2.7',
|
'Programming Language :: Python :: 2.7',
|
||||||
'Programming Language :: Python :: 3.3',
|
|
||||||
'Programming Language :: Python :: 3.4',
|
|
||||||
'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
'Framework :: IPython',
|
||||||
|
'Intended Audience :: Science/Research',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Topic :: Software Development',
|
||||||
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue