class_docstr: Riemannian Hamiltonian VAE model.
  
  
  Args:
      model_config (RHVAEConfig): A model configuration setting the main parameters of the model.
  
      encoder (BaseEncoder): An instance of BaseEncoder (inheriting from `torch.nn.Module` which
          plays the role of encoder. This argument allows you to use your own neural networks
          architectures if desired. If None is provided, a simple Multi Layer Preception
          (https://en.wikipedia.org/wiki/Multilayer_perceptron) is used. Default: None.
  
      decoder (BaseDecoder): An instance of BaseDecoder (inheriting from `torch.nn.Module` which
          plays the role of decoder. This argument allows you to use your own neural networks
          architectures if desired. If None is provided, a simple Multi Layer Preception
          (https://en.wikipedia.org/wiki/Multilayer_perceptron) is used. Default: None.
  
  
  .. note::
      For high dimensional data we advice you to provide you own network architectures. With the
      provided MLP you may end up with a ``MemoryError``.
class_docstr_tok: ["Riemannian","Hamiltonian","VAE","model",".","Args",":","model_config","(","RHVAEConfig",")",":","A","model","configuration","setting","the","main","parameters","of","the","model",".","encoder","(","BaseEncoder",")",":","An","instance","of","BaseEncoder","(","inheriting","from","`","torch.nn.Module","`","which","plays","the","role","of","encoder",".","This","argument","allows","you","to","use","your","own","neural","networks","architectures","if","desired",".","If","None","is","provided",",","a","simple","Multi","Layer","Preception","(","https",":","\/\/en.wikipedia.org\/wiki\/Multilayer_perceptron",")","is","used",".","Default",":","None",".","decoder","(","BaseDecoder",")",":","An","instance","of","BaseDecoder","(","inheriting","from","`","torch.nn.Module","`","which","plays","the","role","of","decoder",".","This","argument","allows","you","to","use","your","own","neural","networks","architectures","if","desired",".","If","None","is","provided",",","a","simple","Multi","Layer","Preception","(","https",":","\/\/en.wikipedia.org\/wiki\/Multilayer_perceptron",")","is","used",".","Default",":","None",".","..","note",":",":","For","high","dimensional","data","we","advice","you","to","provide","you","own","network","architectures",".","With","the","provided","MLP","you","may","end","up","with","a","``","MemoryError","``","."]
docstr: Function computed the estimate negative log-likelihood of the model. It uses importance
  sampling method with the approximate posterior distribution. This may take a while.
  
  Args:
      data (torch.Tensor): The input data from which the log-likelihood should be estimated.
          Data must be of shape [Batch x n_channels x ...]
      n_samples (int): The number of importance samples to use for estimation
      batch_size (int): The batchsize to use to avoid memory issues
docstr_tok: ["Function","computed","the","estimate","negative","log-likelihood","of","the","model",".","It","uses","importance","sampling","method","with","the","approximate","posterior","distribution",".","This","may","take","a","while",".","Args",":","data","(","torch.Tensor",")",":","The","input","data","from","which","the","log-likelihood","should","be","estimated",".","Data","must","be","of","shape","[","Batch","x","n_channels","x","...","]","n_samples","(","int",")",":","The","number","of","importance","samples","to","use","for","estimation","batch_size","(","int",")",":","The","batchsize","to","use","to","avoid","memory","issues"]
code: def get_nll(self, data, n_samples=1, batch_size=100):
      """
      Function computed the estimate negative log-likelihood of the model. It uses importance
      sampling method with the approximate posterior distribution. This may take a while.
  
      Args:
          data (torch.Tensor): The input data from which the log-likelihood should be estimated.
              Data must be of shape [Batch x n_channels x ...]
          n_samples (int): The number of importance samples to use for estimation
          batch_size (int): The batchsize to use to avoid memory issues
      """
      normal = torch.distributions.MultivariateNormal(
          loc=torch.zeros(self.model_config.latent_dim).to(data.device),
          covariance_matrix=torch.eye(self.model_config.latent_dim).to(
              data.device
          ),
      )
  
      if n_samples <= batch_size:
          n_full_batch = 1
      else:
          n_full_batch = n_samples // batch_size
          n_samples = batch_size
  
      log_p = []
  
      for i in range(len(data)):
          x = data[i].unsqueeze(0)
  
          log_p_x = []
  
          for j in range(n_full_batch):
              x_rep = torch.cat(batch_size * [x])
  
              encoder_output = self.encoder(x_rep)
              mu, log_var = (
                  encoder_output.embedding,
                  encoder_output.log_covariance,
              )
  
              std = torch.exp(0.5 * log_var)
              z0, eps = self._sample_gauss(mu, std)
              gamma = torch.randn_like(z0, device=x.device)
              rho = gamma / self.beta_zero_sqrt
  
              z = z0
              beta_sqrt_old = self.beta_zero_sqrt
  
              G = self.G(z0)
              G_inv = self.G_inv(z0)
              G_log_det = -torch.logdet(G_inv)
              L = torch.linalg.cholesky(G)
  
              # initialization
              gamma = torch.randn_like(z0, device=z.device)
              rho = gamma / self.beta_zero_sqrt
              beta_sqrt_old = self.beta_zero_sqrt
  
              rho = (L @ rho.unsqueeze(-1)).squeeze(
                  -1
              )  # sample from the multivariate N(0, G)
  
              recon_x = self.decoder(z)["reconstruction"]
  
              for k in range(self.n_lf):
                  # perform leapfrog steps
  
                  # step 1
                  rho_ = self._leap_step_1(
                      recon_x, x_rep, z, rho, G_inv, G_log_det
                  )
  
                  # step 2
                  z = self._leap_step_2(
                      recon_x, x_rep, z, rho_, G_inv, G_log_det
                  )
  
                  recon_x = self.decoder(z)["reconstruction"]
  
                  G_inv = self.G_inv(z)
                  G_log_det = -torch.logdet(G_inv)
  
                  # step 3
                  rho__ = self._leap_step_3(
                      recon_x, x_rep, z, rho_, G_inv, G_log_det
                  )
  
                  # tempering steps
                  beta_sqrt = self._tempering(k + 1, self.n_lf)
                  rho = (beta_sqrt_old / beta_sqrt) * rho__
                  beta_sqrt_old = beta_sqrt
  
              log_q_z0_given_x = -0.5 * (
                  log_var + (z0 - mu) ** 2 / torch.exp(log_var)
              ).sum(dim=-1)
              log_p_z = -0.5 * (z**2).sum(dim=-1)
  
              log_p_rho0 = normal.log_prob(gamma) - torch.logdet(
                  L / self.beta_zero_sqrt
              )  # rho0 ~ N(0, 1/beta_0 * G(z0))
  
              log_p_rho = (
                  -0.5
                  * (
                      torch.transpose(rho.unsqueeze(-1), 1, 2)
                      @ G_inv
                      @ rho.unsqueeze(-1)
                  )
                  .squeeze()
                  .squeeze()
                  - 0.5 * G_log_det
              ) - torch.log(
                  torch.tensor([2 * np.pi]).to(z.device)
              ) * self.latent_dim / 2  # rho0 ~ N(0, G(z))
  
              if self.model_config.reconstruction_loss == "mse":
                  log_p_x_given_z = -0.5 * F.mse_loss(
                      recon_x.reshape(x_rep.shape[0], -1),
                      x_rep.reshape(x_rep.shape[0], -1),
                      reduction="none",
                  ).sum(dim=-1) - torch.tensor(
                      [np.prod(self.input_dim) / 2 * np.log(np.pi * 2)]
                  ).to(
                      data.device
                  )  # decoding distribution is assumed unit variance  N(mu, I)
  
              elif self.model_config.reconstruction_loss == "bce":
                  log_p_x_given_z = -F.binary_cross_entropy(
                      recon_x.reshape(x_rep.shape[0], -1),
                      x_rep.reshape(x_rep.shape[0], -1),
                      reduction="none",
                  ).sum(dim=-1)
  
              log_p_x.append(
                  log_p_x_given_z
                  + log_p_z
                  + log_p_rho
                  - log_p_rho0
                  - log_q_z0_given_x
              )  # N*log(2*pi) simplifies in prior and posterior
  
          log_p_x = torch.cat(log_p_x)
  
          log_p.append(
              (
                  torch.logsumexp(log_p_x, 0) - np.log(len(log_p_x))
              ).item()
          )
  
      return np.mean(log_p)
  
imports: import inspect
  import logging
  import os
  import warnings
  from collections import deque
  from copy import deepcopy
  from typing import Optional
  import cloudpickle
  import numpy
  import torch
  import torch.nn
  import torch.nn.functional
  from torch.autograd import grad
  from ...customexception import BadInheritanceError
  from ...data.datasets import BaseDataset
  from ..base.base_utils import CPU_Unpickler, ModelOutput, hf_hub_is_available
  from ..nn import BaseDecoder, BaseEncoder, BaseMetric
  from ..nn.default_architectures import Metric_MLP
  from ..vae import VAE
  from .rhvae_config import RHVAEConfig
  from .rhvae_utils import create_inverse_metric, create_metric
label_desc: Use image node_id 17 for calling the RHVAE obj's underlying member method code with example usage: obj.get_nll(data, n_samples, batch_size) and returns: np