class_docstr: This method approximates the Gaussian process posterior using a multivariate Gaussian.
  
  The key reference is :cite:t:`Opper:2009`.
  
  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. It turns out that the optimal
  posterior precision shares off-diagonal elements with the prior, so
  only the diagonal elements of the precision need be adjusted.
  The posterior approximation is
  
  .. math::
  
     q(\mathbf f) = N(\mathbf f \,|\, \mathbf K \boldsymbol \alpha,
                       [\mathbf K^{-1} + \textrm{diag}(\boldsymbol \lambda))^2]^{-1})
  
  This approach has only 2ND parameters, rather than the N + N^2 of vgp,
  but the optimization is non-convex and in practice may cause difficulty.
docstr: data = (X, Y) contains the input points [N, D] and the observations [N, P]
  kernel, likelihood, mean_function are appropriate GPflow objects
docstr_tok: ["data","=","(","X",",","Y",")","contains","the","input","points","[","N",",","D","]","and","the","observations","[","N",",","P","]","kernel",",","likelihood",",","mean_function","are","appropriate","GPflow","objects"]
code: def __init__(
      self,
      data: RegressionData,
      kernel: Kernel,
      likelihood: Likelihood,
      mean_function: Optional[MeanFunction] = None,
      num_latent_gps: Optional[int] = None,
  ):
      """
      data = (X, Y) contains the input points [N, D] and the observations [N, P]
      kernel, likelihood, mean_function are appropriate GPflow objects
      """
      if num_latent_gps is None:
          num_latent_gps = self.calc_num_latent_gps_from_data(
              data, kernel, likelihood
          )
      super().__init__(
          kernel, likelihood, mean_function, num_latent_gps
      )
  
      self.data = data_input_to_tensor(data)
      X_data, _Y_data = self.data
      self.num_data = X_data.shape[0]
      self.q_alpha = Parameter(
          np.zeros((self.num_data, self.num_latent_gps))
      )
      self.q_lambda = Parameter(
          np.ones((self.num_data, self.num_latent_gps)),
          transform=gpflow.utilities.positive(),
      )
  
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 1 to create a new VGPOpperArchambeau object from inherited base classes: GPModel, InternalDataTrainingLossMixin with example: obj = VGPOpperArchambeau(data, kernel, likelihood, mean_function, num_latent_gps)