Source code for gpflow.likelihoods.scalar_discrete

# Copyright 2017-2020 The GPflow Contributors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Any, Callable

import numpy as np
import tensorflow as tf
from check_shapes import check_shapes, inherit_check_shapes

from .. import logdensities
from ..base import AnyNDArray, MeanAndVariance, Parameter, TensorType
from ..config import default_float
from ..utilities import positive, to_default_int
from .base import ScalarLikelihood
from .utils import inv_probit


[docs]class Poisson(ScalarLikelihood): r""" Poisson likelihood for use with count data, where the rate is given by the (transformed) GP. let g(.) be the inverse-link function, then this likelihood represents p(yᵢ | fᵢ) = Poisson(yᵢ | g(fᵢ) * binsize) Note:binsize For use in a Log Gaussian Cox process (doubly stochastic model) where the rate function of an inhomogeneous Poisson process is given by a GP. The intractable likelihood can be approximated via a Riemann sum (with bins of size 'binsize') and using this Poisson likelihood. """ def __init__( self, invlink: Callable[[tf.Tensor], tf.Tensor] = tf.exp, binsize: float = 1.0, **kwargs: Any, ) -> None: super().__init__(**kwargs) self.invlink = invlink self.binsize: AnyNDArray = np.array(binsize, dtype=default_float()) @inherit_check_shapes def _scalar_log_prob(self, X: TensorType, F: TensorType, Y: TensorType) -> tf.Tensor: return logdensities.poisson(Y, self.invlink(F) * self.binsize) @inherit_check_shapes def _conditional_variance(self, X: TensorType, F: TensorType) -> tf.Tensor: return self.invlink(F) * self.binsize @inherit_check_shapes def _conditional_mean(self, X: TensorType, F: TensorType) -> tf.Tensor: return self.invlink(F) * self.binsize @inherit_check_shapes def _variational_expectations( self, X: TensorType, Fmu: TensorType, Fvar: TensorType, Y: TensorType ) -> tf.Tensor: if self.invlink is tf.exp: return tf.reduce_sum( Y * Fmu - tf.exp(Fmu + Fvar / 2) * self.binsize - tf.math.lgamma(Y + 1) + Y * tf.math.log(self.binsize), axis=-1, ) return super()._variational_expectations(X, Fmu, Fvar, Y)
[docs]class Bernoulli(ScalarLikelihood): def __init__( self, invlink: Callable[[tf.Tensor], tf.Tensor] = inv_probit, **kwargs: Any ) -> None: super().__init__(**kwargs) self.invlink = invlink @inherit_check_shapes def _scalar_log_prob(self, X: TensorType, F: TensorType, Y: TensorType) -> tf.Tensor: return logdensities.bernoulli(Y, self.invlink(F)) @inherit_check_shapes def _predict_mean_and_var( self, X: TensorType, Fmu: TensorType, Fvar: TensorType ) -> MeanAndVariance: if self.invlink is inv_probit: p = inv_probit(Fmu / tf.sqrt(1 + Fvar)) return p, p - tf.square(p) else: # for other invlink, use quadrature return super()._predict_mean_and_var(X, Fmu, Fvar) @inherit_check_shapes def _predict_log_density( self, X: TensorType, Fmu: TensorType, Fvar: TensorType, Y: TensorType ) -> tf.Tensor: p = self.predict_mean_and_var(X, Fmu, Fvar)[0] return tf.reduce_sum(logdensities.bernoulli(Y, p), axis=-1) @inherit_check_shapes def _conditional_mean(self, X: TensorType, F: TensorType) -> tf.Tensor: return self.invlink(F) @inherit_check_shapes def _conditional_variance(self, X: TensorType, F: TensorType) -> tf.Tensor: p = self.conditional_mean(X, F) return p - (p ** 2)
[docs]class Ordinal(ScalarLikelihood): """ A likelihood for doing ordinal regression. The data are integer values from 0 to k, and the user must specify (k-1) 'bin edges' which define the points at which the labels switch. Let the bin edges be [a₀, a₁, ... aₖ₋₁], then the likelihood is p(Y=0|F) = ɸ((a₀ - F) / σ) p(Y=1|F) = ɸ((a₁ - F) / σ) - ɸ((a₀ - F) / σ) p(Y=2|F) = ɸ((a₂ - F) / σ) - ɸ((a₁ - F) / σ) ... p(Y=K|F) = 1 - ɸ((aₖ₋₁ - F) / σ) where ɸ is the cumulative density function of a Gaussian (the inverse probit function) and σ is a parameter to be learned. A reference is :cite:t:`chu2005gaussian`. """ @check_shapes( "bin_edges: [num_bins_minus_1]", ) def __init__(self, bin_edges: AnyNDArray, **kwargs: Any) -> None: """ bin_edges is a numpy array specifying at which function value the output label should switch. If the possible Y values are 0...K, then the size of bin_edges should be (K-1). """ super().__init__(**kwargs) self.bin_edges = bin_edges self.num_bins = bin_edges.size + 1 self.sigma = Parameter(1.0, transform=positive()) @inherit_check_shapes def _scalar_log_prob(self, X: TensorType, F: TensorType, Y: TensorType) -> tf.Tensor: Y = to_default_int(Y) scaled_bins_left = tf.concat([self.bin_edges / self.sigma, np.array([np.inf])], 0) scaled_bins_right = tf.concat([np.array([-np.inf]), self.bin_edges / self.sigma], 0) selected_bins_left = tf.gather(scaled_bins_left, Y) selected_bins_right = tf.gather(scaled_bins_right, Y) return tf.math.log( inv_probit(selected_bins_left - F / self.sigma) - inv_probit(selected_bins_right - F / self.sigma) + 1e-6 ) @check_shapes( "F: [batch..., latent_dim]", "return: [batch_and_latent_dim, num_bins]", ) def _make_phi(self, F: TensorType) -> tf.Tensor: """ A helper function for making predictions. Constructs a probability matrix where each row output the probability of the corresponding label, and the rows match the entries of F. Note that a matrix of F values is flattened. """ scaled_bins_left = tf.concat([self.bin_edges / self.sigma, np.array([np.inf])], 0) scaled_bins_right = tf.concat([np.array([-np.inf]), self.bin_edges / self.sigma], 0) return inv_probit(scaled_bins_left - tf.reshape(F, (-1, 1)) / self.sigma) - inv_probit( scaled_bins_right - tf.reshape(F, (-1, 1)) / self.sigma ) @inherit_check_shapes def _conditional_mean(self, X: TensorType, F: TensorType) -> tf.Tensor: phi = self._make_phi(F) Ys = tf.reshape(np.arange(self.num_bins, dtype=default_float()), (-1, 1)) return tf.reshape(tf.linalg.matmul(phi, Ys), tf.shape(F)) @inherit_check_shapes def _conditional_variance(self, X: TensorType, F: TensorType) -> tf.Tensor: phi = self._make_phi(F) Ys = tf.reshape(np.arange(self.num_bins, dtype=default_float()), (-1, 1)) E_y = phi @ Ys E_y2 = phi @ (Ys ** 2) return tf.reshape(E_y2 - E_y ** 2, tf.shape(F))