PyTorch backend walkthrough
pyvinecopulib.torch re-implements the pv.Bicop / pv.Vinecop evaluation cascade in pure PyTorch. Every class is a torch.nn.Module, so you get three things the default pv.Vinecop can’t give you: GPU placement (.to('cuda') moves the whole vine to device), autograd (gradients flow back through pdf / Rosenblatt outputs to upstream parameters), and composability with any other nn.Module in a larger PyTorch pipeline. Fitting — structure selection and the TLL
pair fits — runs natively in torch, so nothing here depends on the default backend. This notebook walks the public API end-to-end.
Power-user knobs — the TLL grid (grid_size / grid_type / mult) on FitControlsTorchBicop, and structure selection (tree_algorithm / trunc_lvl / tree_criterion / threshold / seeds), cache_integrals, batched, and the device / dtype targets on FitControlsTorchVinecop — live in those two docstrings; the defaults below are the right answer for almost every use case.
[1]:
import numpy as np
import torch
import pyvinecopulib as pv
from pyvinecopulib.torch import TorchBicop, TorchVinecop
torch.set_num_threads(1)
rng = np.random.default_rng(0)
TorchBicop
TorchBicop.from_data(u) fits a pure-torch TLL pair-copula directly on pseudo-observations. The fitted module evaluates pdf / cdf / hfunc1 / hfunc2 / hinv1 / hinv2 / simulate exactly like pv.Bicop, with the same numerics.
[2]:
n = 2000
cop_true = pv.Bicop(family=pv.families.gaussian, parameters=np.array([[0.6]]))
u_np = cop_true.simulate(n, seeds=[1, 2, 3])
u = torch.from_numpy(u_np)
bc = TorchBicop.from_data(u)
print(f"TorchBicop fitted: pdf(u[:3]) = {bc.pdf(u[:3]).numpy()}")
TorchBicop fitted: pdf(u[:3]) = [1.18544693 2.06963469 1.99535195]
TorchVinecop
There are two routes to a fitted TorchVinecop. TorchVinecop.from_data(u) fits one directly — selecting the R-vine structure and fitting the TLL pairs natively in torch (the same Dissmann selection pv.Vinecop runs, down to the matrix encoding). TorchVinecop.from_vinecop(cop) instead lifts an already-fitted pv.Vinecop into pure torch, mirroring every pair-copula grid into a torch.nn.Module. Either way the post-fit module exposes the standard surface: pdf, cdf,
rosenblatt, inverse_rosenblatt, simulate.
[ ]:
d = 3
data3 = pv.to_pseudo_obs(np.random.default_rng(0).normal(size=(n, d)))
# Fit + select the structure natively in torch -- no pv.Vinecop round-trip.
vine_fit = TorchVinecop.from_data(torch.from_numpy(data3))
print(
f"from_data -> selected order {list(vine_fit.structure.order)}, "
f"mean log-pdf = {vine_fit.pdf(torch.from_numpy(data3)).log().mean().item():.3f}"
)
from_data -> selected order [1, 2, 3], mean log-pdf = 0.055
[3]:
d = 3
cop3 = pv.Vinecop.from_data(
pv.to_pseudo_obs(rng.normal(size=(n, d))),
controls=pv.FitControlsVinecop(family_set=[pv.families.tll], num_threads=1),
)
u3 = torch.from_numpy(cop3.simulate(n, seeds=[4, 5, 6]))
vine = TorchVinecop.from_vinecop(cop3)
print(
f"TorchVinecop fitted on (n={n}, d={d}); "
f"mean log-pdf on training data = {vine.pdf(u3).log().mean().item():.3f}"
)
synth = vine.simulate(500, seeds=[7, 8, 9])
print(
f"simulate -> shape {tuple(synth.shape)}, "
f"min={synth.min().item():.3f}, max={synth.max().item():.3f}"
)
TorchVinecop fitted on (n=2000, d=3); mean log-pdf on training data = 0.012
simulate -> shape (500, 3), min=0.000, max=1.000
GPU + autograd
A fitted TorchVinecop is a regular nn.Module. .to(device) moves every tensor to the GPU in one call, and pdf(u) returns a tensor that participates in autograd — so gradients of any downstream loss flow back through the cascade to whatever inputs you marked requires_grad=True.
[4]:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
vine_dev = vine.to(device)
# Autograd through the vine: gradient of mean log-pdf wrt the input.
u_grad = u3.clone().to(device).requires_grad_(True)
loss = vine_dev.pdf(u_grad).log().mean()
loss.backward()
print(f"device = {device}")
print(
f"loss = {loss.item():.3f}; |grad| max = {u_grad.grad.abs().max().item():.3f}"
)
device = cuda
loss = 0.012; |grad| max = 0.373
Plugging the torch backend into the sklearn estimators
The same module powers pyvinecopulib.sklearn. Wrap a configured TorchVinecopBackend and pass it as backend= to any of the sklearn estimators — VineDensity, VineRegressor, VineForestDensity, VineForestRegressor. Below: a VineDensity fit on the Concrete Compressive Strength dataset (the same one used by notebooks 08 and 09).
[5]:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from pyvinecopulib.sklearn import VineDensity, VineForestDensity
from pyvinecopulib.sklearn.backends import TorchVinecopBackend
# Same Concrete dataset as notebooks 08 and 09.
url = "https://archive.ics.uci.edu/ml/machine-learning-databases/concrete/compressive/Concrete_Data.xls"
df = pd.read_excel(url)
X = df.iloc[:, :-1].values
y = df.iloc[:, -1].values
joint = np.hstack([X, y.reshape(-1, 1)])
J_train, J_test = train_test_split(joint, test_size=0.2, random_state=42)
scaler = StandardScaler().fit(J_train)
J_train = scaler.transform(J_train)
J_test = scaler.transform(J_test)
dens = VineDensity(backend=TorchVinecopBackend()).fit(J_train)
print(
f"VineDensity[TorchVinecopBackend]: "
f"test NLL = {-float(dens.score_samples(J_test).mean()):.3f}"
)
print(
f"sample(3) -> shape {tuple(dens.sample(n_samples=3, random_state=0).shape)}"
)
VineDensity[TorchVinecopBackend]: test NLL = 4.365
sample(3) -> shape (3, 9)
The same plug-and-play works for forests: pass backend=TorchVinecopBackend() and every candidate vine in the ensemble runs through the torch cascade.
[6]:
dens_forest = VineForestDensity(
base_params={"backend": TorchVinecopBackend()},
n_vines=10,
n_jobs=1,
random_state=0,
).fit(J_train)
print(
f"VineForestDensity[TorchVinecopBackend]: "
f"{len(dens_forest._estimators)} survivor(s), "
f"test NLL = {-float(dens_forest.score_samples(J_test).mean()):.3f}"
)
VineForestDensity[TorchVinecopBackend]: 3 survivor(s), test NLL = 3.953
What’s next
Advanced controls.
FitControlsTorchBicopsets the TLL grid —grid_size,grid_type,mult;FitControlsTorchVinecopsets structure selection (tree_algorithm,trunc_lvl,tree_criterion,threshold,seeds),cache_integrals,batched, and the device / dtype targets. Both docstrings — visible from the API page or via?in any notebook — document every knob.Family coverage. The torch backend implements the non-parametric TLL family only. For Gaussian / Student / Clayton / Gumbel / Frank / Joe / BB families, use the default
VinecopBackend(orpv.Vinecopdirectly).