Source code for syntropy.neural.utils

import torch
import torch.optim as optim
from torch.utils.data import DataLoader, TensorDataset

import nflows
from nflows.distributions.normal import StandardNormal
from nflows.flows.base import Flow
from nflows.transforms.autoregressive import MaskedAffineAutoregressiveTransform
from nflows.transforms.base import CompositeTransform
from nflows.transforms.permutations import RandomPermutation


[docs] def initialize_flow( dim: int, dim_context: int = 0, num_layers: int = 5, hidden_features: int = 64, dropout_probability: float = 0.1, ) -> nflows.flows.base.Flow: """ Initializes a new normalizing flow network. Parameters ---------- dim : int The number of input dimensions. dim_context : int The number of conditioning dimension. The default is zero. num_layers : int The number of hidden layers. The default is 5 layers. hidden_features : int The number of neurons in each hidden layer. The default is 64 neurons. dropout_probability : int The probability of a neuron dropping out. Returns ------- nflows.flows.base.Flow """ transforms: list = [] for _ in range(num_layers): transforms.append( MaskedAffineAutoregressiveTransform( features=dim, hidden_features=hidden_features, context_features=dim_context, dropout_probability=dropout_probability, ) ) transforms.append(RandomPermutation(features=dim)) transform = CompositeTransform(transforms) base_dist = StandardNormal(shape=[dim]) return Flow(transform, base_dist)
[docs] def train_flow( flow: nflows.flows.base.Flow, data: torch.Tensor, context: None | torch.Tensor = None, batch_size: int = 256, lr: float = 1e-4, num_epochs: int = 100, weight_decay: float = 1e-5, convergence_threshold: float = 0.0, alpha: float = 0.1, verbose: bool = False, ) -> nflows.flows.base.Flow: """ Trains a normalizing flow network to approximate the maximally likely distribution to have generated the given data. Parameters ---------- flow : nflows.flows.base.Flow An untrained normalizing flow network. data : torch.Tensor The training data, in samples x features format. context : None | torch.Tensor Conditioning random variables. Default is None. batch_size : int The size of each batch. The default value is 256. lr : float The learning rate. The default value is 1e-4 num_epochs : int The number of training epochs. The default value is 100, weight_decay: float The rate at which parameter weights decay. A regularizer to reduce over-fitting. The default is 1e-5. convergence_threshold : float The value of the coefficient of variation below which the training terminates. The default value is 0.0, which means the training will not stop before num_epochs is hit. alpha : float How quickly the exponentially weighted moving standard deviation downweights older data. The default is 0.2. verbose : bool Whether to print the loss for each epoch throughout training. The default is False. Returns ------- nflows.flows.base.Flow """ if context is None: dataset = TensorDataset(data) elif context is not None: dataset = TensorDataset(data, context) loader = DataLoader(dataset, batch_size=batch_size, shuffle=True) optimizer = optim.Adam(flow.parameters(), lr=lr, weight_decay=weight_decay) flow.train() running_mean: float = 0.0 running_var: float = 0.0 for epoch in range(num_epochs): epoch_nll_sum: float = 0 for batch in loader: optimizer.zero_grad() if context is None: x = batch[0] batch_nll = -flow.log_prob(x).mean() else: x, c = batch batch_nll = -flow.log_prob(x, context=c).mean() batch_nll.backward() optimizer.step() epoch_nll_sum += batch_nll.item() * x.shape[0] epoch_avg_nll: float = epoch_nll_sum / data.shape[0] delta = epoch_avg_nll - running_mean running_mean += alpha * delta running_var = (1 - alpha) * (running_var + alpha * delta**2) running_std = running_var ** (1 / 2) coef_var = running_std / (running_mean + 1e-8) if verbose is True: print( f"Epoch {epoch + 1}, NLL: {epoch_avg_nll:.3f}, Coef. var: {coef_var:.3f}" ) if coef_var < convergence_threshold: return flow return flow
[docs] def evaluate_flow( flow: nflows.flows.base.Flow, data: torch.Tensor, context: None | torch.Tensor = None, ) -> tuple[float, float]: """ Evaluates a trainied normalizing flow network on the given data. Parameters ---------- flow : nflows.flows.base.Flow A trained normalilzing flow network. data : torch.Tensor The testing data in samples x features format. context : None | torch.Tensor Returns ------- tuple[float, float] The average entropy (in nat) The standard error of the estimate. """ flow.eval() with torch.no_grad(): if context is None: log_probs = flow.log_prob(data) else: log_probs = flow.log_prob(data, context=context) h = -log_probs.mean().item() return -log_probs, h