Skip to content

INRs in Inro

An implicit neural representation (INR) represents a signal as a parametric function of its coordinates, fitted to samples of the signal by minimizing a reconstruction loss.

\[ \hat{s}_\theta : [-1, 1]^C \to \mathbb{R}^D \]

a signal x₁, x₂ INR FUTON, SIREN, ... value R, G, B

Signal Coordinates Values
Image a pixel, in \([-1, 1]^2\) \(D = 3\), its color
Occupancy volume a voxel, in \([-1, 1]^3\) \(D = 1\), inside or outside
Radiance field a position and a view direction a density and a color

Inro keeps this abstraction explicit: models are modules on coordinates, datasets are coordinate/value pairs, and one training loop serves every task that fits samples directly.

flowchart TB
    datasets["<b>Datasets</b><br/>coordinates and targets"] --> train["<b>inro.train</b><br/>one loop"]
    models["<b>Models</b><br/>FUTON, SIREN, InstantNGP, ..."] --> train
    losses["<b>Losses</b>"] -.-> train
    train --> evaluate["<b>inro.evaluate</b><br/>chunked_inference"]
    evaluate --> metrics["<b>Metrics</b><br/>psnr, ssim, lpips, iou, ..."]
    models --> field["<b>inro.nerf</b><br/>RadianceField, renderers, train, evaluate"]

Models

Every model maps a tensor of coordinates (..., in_features) in \([-1, 1]\) to values (..., out_features), so it can be called on a single point, a batch or a whole grid:

model = inro.FUTON(
    2, 3, basis=("cosine", {"num_components": 64}), combiner=("cp", {"rank": 32}), head="linear"
)
model(torch.rand(10, 2) * 2 - 1).shape      # (10, 3)
model(torch.rand(4, 5, 2) * 2 - 1).shape    # (4, 5, 3)

The constructors share in_features, out_features and, where they apply, hidden_features, hidden_layers and output_activation. The model zoo lists them all.

Datasets

A dataset yields dictionaries with four keys:

Key Content
id A name for the signal, used in logs and file names
input Coordinates to evaluate the model at
target The values to fit, in the model's output range
_original The reference signal in its native form, used by the metrics; keys with a leading underscore stay on the CPU

ImageCoordinateDataset and OccupancyCoordinateDataset follow this scheme; the conventions page tabulates their shapes.

  • One signal, one item. len(dataset) == 1, and random subsampling happens on access: with subsample=0.1, every dataset[0] returns a fresh random tenth of the coordinates and their values, flattened.
  • Benchmarks. The occupancy benchmark trains this way, on \(2^{18}\) fresh voxels per step; the image benchmark takes the whole image at every step.
  • Helpers. preprocess maps native values to targets, postprocess maps model output back, save writes a reconstruction to disk, and to(device) moves the training tensors so that subsampling runs on the GPU.

Training

inro.train(model, train_dataset, eval_dataset, **options) fits a model in place.

  1. Draw a batchthe whole signal or a random subset
  2. Compute the lossloss_fn(batch, model)
  3. StepAdam under a cosine schedule
  4. Log and evaluateevery few steps, off the clock
Part What it does Options
Optimizer Adam. Every benchmark trains every model with PyTorch's defaults, (0.9, 0.999) and 1e-8, so that the optimizer favors none of them lr, weight_decay, adam_betas, adam_eps
Schedule Steps of one batch each, with CosineAnnealingLR down to lr / 100 num_steps
Loss loss_fn(batch, model) -> (loss, metrics); by default the mean squared error, logging the PSNR for targets in \([-1, 1]\) loss_fn
Evaluation inro.evaluate reconstructs each item in chunks, maps the output through postprocess, and calls each metric as fn(reconstruction, original) eval_interval, metrics, chunk_size
Logging Records the training metrics of the step's batch, printed above a progress bar, and writes log.txt, log.json, each evaluation's reconstruction and checkpoint.pt log_interval, log_dir, ckpt_path

The return value is {"config", "model_dict", "history"}: the run's settings, the final parameters, and one record per logged step with elapsed, the seconds spent training so far, the training metrics of the step's batch and, when the step was evaluated, the mean evaluation metrics under "eval". The records are plain dictionaries, so pd.DataFrame(result["history"]) gives a learning curve.

Evaluation and inference

  • inro.evaluate(model, dataset, metrics=...) runs the evaluation half of the loop on its own, for example on a checkpoint (ckpt_path) or on another grid than the one trained on.
  • inro.chunked_inference(model, coordinates, chunk_size=...) evaluates a model on any coordinate tensor under torch.no_grad(), flattening the leading dimensions for chunking and restoring them afterwards.

Radiance fields

Novel view synthesis does not fit samples of the signal directly: the supervision is posed images, and the INR is queried along rays. inro.nerf therefore has its own dataset (BlenderDataset), field (RadianceField, which wraps any model from the zoo as its density network), renderers, and train and evaluate functions with the same shape of return value. The radiance field tutorial covers them.