class_docstr: This method approximates the Gaussian process posterior using a multivariate Gaussian.
  
  The idea is that the posterior over the function-value vector F is
  approximated by a Gaussian, and the KL divergence is minimised between
  the approximation and the posterior.
  
  This implementation is equivalent to SVGP with X=Z, but is more efficient.
  The whitened representation is used to aid optimization.
  
  The posterior approximation is
  
  .. math::
  
     q(\mathbf f) = N(\mathbf f \,|\, \boldsymbol \mu, \boldsymbol \Sigma)
  
  For a use example see
  :doc:`../../../../notebooks/getting_started/classification_and_other_data_distributions`.
docstr: This method computes the variational lower bound on the likelihood,
  which is:
  
      E_{q(F)} [ \log p(Y|F) ] - KL[ q(F) || p(F)]
  
  with
  
      q(\mathbf f) = N(\mathbf f \,|\, \boldsymbol \mu, \boldsymbol \Sigma)
docstr_tok: ["This","method","computes","the","variational","lower","bound","on","the","likelihood",",","which","is",":","E_","{","q","(","F",")","}","[","\\log","p","(","Y|F",")","]","-","KL","[","q","(","F",")","||","p","(","F",")","]","with","q","(","\\mathbf","f",")","=","N","(","\\mathbf","f","\\",",","|\\",",","\\boldsymbol","\\mu",",","\\boldsymbol","\\Sigma",")"]
code: def elbo(self) -> tf.Tensor:
      r"""
      This method computes the variational lower bound on the likelihood,
      which is:
  
          E_{q(F)} [ \log p(Y|F) ] - KL[ q(F) || p(F)]
  
      with
  
          q(\mathbf f) = N(\mathbf f \,|\, \boldsymbol \mu, \boldsymbol \Sigma)
  
      """
      X_data, Y_data = self.data
      num_data = tf.convert_to_tensor(self.num_data)
  
      # Get prior KL.
      KL = gauss_kl(self.q_mu, self.q_sqrt)
  
      # Get conditionals
      K = (
          self.kernel(X_data)
          + tf.eye(num_data, dtype=default_float()) * default_jitter()
      )
      L = tf.linalg.cholesky(K)
      fmean = tf.linalg.matmul(L, self.q_mu) + self.mean_function(
          X_data
      )  # [NN, ND] -> ND
      q_sqrt_dnn = tf.linalg.band_part(self.q_sqrt, -1, 0)  # [D, N, N]
      L_tiled = tf.tile(
          tf.expand_dims(L, 0), tf.stack([self.num_latent_gps, 1, 1])
      )
      LTA = tf.linalg.matmul(L_tiled, q_sqrt_dnn)  # [D, N, N]
      fvar = tf.reduce_sum(tf.square(LTA), 2)
  
      fvar = tf.transpose(fvar)
  
      # Get variational expectations.
      var_exp = self.likelihood.variational_expectations(
          X_data, fmean, fvar, Y_data
      )
  
      return tf.reduce_sum(var_exp) - KL
  
imports: from typing import Optional
  import numpy
  import tensorflow
  from check_shapes import check_shapes, inherit_check_shapes
  import gpflow
  from ..None import posteriors
  from ..base import InputData, MeanAndVariance, Parameter, RegressionData
  from ..conditionals import conditional
  from ..config import default_float, default_jitter
  from ..kernels import Kernel
  from ..kullback_leiblers import gauss_kl
  from ..likelihoods import Likelihood
  from ..mean_functions import MeanFunction
  from ..utilities import assert_params_false, is_variable, triangular, triangular_size
  from .model import GPModel
  from .training_mixins import InternalDataTrainingLossMixin
  from .util import data_input_to_tensor
label_desc: Use image node_id 3 for calling the VGP_deprecated obj's underlying member method code with example usage: obj.elbo() and returns: unknown