PyTorch layers¶
fdatools.nn: differentiable BasisLayer and SmoothingLayer, and the
FDataDataset for torch.utils.data.DataLoader. This module needs the
optional extra: uv add "fdatools[torch]". import fdatools does not import
PyTorch; fdatools.nn is loaded on first use.
PyTorch building blocks for functional data (the fdatools[torch] extra).
Three pieces connect fdatools' basis expansions to :mod:torch:
- :class:
BasisLayermaps basis coefficients to curve values,x(t) = Φ(t) c-- a differentiablefd(t)for use inside a network, with gradients to the coefficients and, when the points are given at call time, to the points too. - :class:
SmoothingLayermaps raw observations on a grid to basis coefficients by penalised least squares,c = (ΦᵀWΦ + λR)⁻¹ΦᵀW y, exactly as :func:fdatools.smoothing.smoothdoes;λcan be learned. - :class:
FDataDatasetserves the curves of an :class:~fdatools.core.FData(coefficients, or values on a grid) and optional labels to a :class:torch.utils.data.DataLoader.
Basis and penalty matrices depend only on the basis and the grid, so they are
built once in float64 and stored as buffers: layer.to("cuda") or
layer.to(torch.float32) moves them with the module.
This module imports PyTorch; import fdatools itself never does.
Examples:
>>> import numpy as np
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import BasisLayer, SmoothingLayer
>>> basis = fdt.BSpline(domain=(0.0, 1.0), n_basis=8)
>>> t = np.linspace(0.0, 1.0, 50)
>>> smoother, evaluate = SmoothingLayer(basis, t, lam=1e-6), BasisLayer(basis, t)
>>> y = torch.sin(2 * torch.pi * torch.tensor(t))[None, :]
>>> bool(torch.max(torch.abs(evaluate(smoother(y)) - y)) < 1e-2)
True
BasisLayer ¶
BasisLayer(
basis: Basis,
t: Any = None,
*,
deriv: int | LDO = 0,
dtype: dtype = torch.float64,
)
Bases: Module
Evaluate basis expansions: map coefficients c to x(t) = Φ(t) c.
A differentiable fd(t): the input's last axis holds the n_basis
coefficients of one curve, the output's last axis the curve's values at
the evaluation points. The layer has no parameters of its own; put an
:class:torch.nn.Parameter in front of it to learn a functional weight.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basis
|
Basis
|
The expansion basis. |
required |
t
|
array_like
|
Fixed evaluation points. The basis matrix is precomputed and stored as
the buffer |
None
|
deriv
|
int or LDO
|
Derivative order, or linear differential operator, to evaluate.
Default |
0
|
dtype
|
dtype
|
Dtype of the stored basis matrix. Default |
float64
|
Examples:
>>> import numpy as np
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import BasisLayer
>>> layer = BasisLayer(fdt.BSpline(domain=(0.0, 1.0), n_basis=6), np.linspace(0.0, 1.0, 11))
>>> layer(torch.ones(4, 6, dtype=torch.float64)).shape
torch.Size([4, 11])
forward ¶
forward(coefs: Tensor, t: Any = None) -> torch.Tensor
Evaluate the curves whose coefficients are coefs.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
coefs
|
Tensor
|
Coefficients of shape |
required |
t
|
array_like or Tensor
|
Evaluation points of shape |
None
|
Returns:
| Type | Description |
|---|---|
Tensor
|
Values of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If there are no evaluation points, or the last axis of |
Examples:
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import BasisLayer
>>> layer = BasisLayer(fdt.Fourier(domain=(0.0, 1.0), n_basis=3))
>>> t = torch.linspace(0.0, 1.0, 5, dtype=torch.float64)
>>> layer(torch.ones(3, dtype=torch.float64), t).shape
torch.Size([5])
SmoothingLayer ¶
SmoothingLayer(
basis: Basis,
t: Any,
*,
lam: float = 0.0,
penalty: int | LDO = 2,
weights: Any = None,
trainable_lam: bool = False,
dtype: dtype = torch.float64,
)
Bases: Module
Penalised least-squares smoothing: map observations to basis coefficients.
Computes c = (ΦᵀWΦ + λR)⁻¹ ΦᵀW y for observations y on a fixed
grid -- the fit of :func:fdatools.smoothing.smooth with a given λ --
as a differentiable layer. With a fixed λ this is one matrix product
with a precomputed map; with trainable_lam=True the parameter
log_lam = log λ is learned and the system is solved on every call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
basis
|
Basis
|
The expansion basis. |
required |
t
|
array_like
|
Observation points, shape |
required |
lam
|
float
|
Smoothing parameter |
0.0
|
penalty
|
int or LDO
|
Roughness operator |
2
|
weights
|
array_like
|
Observation weights, shape |
None
|
trainable_lam
|
bool
|
Learn |
False
|
dtype
|
dtype
|
Dtype of the stored matrices and of |
float64
|
Attributes:
| Name | Type | Description |
|---|---|---|
lam |
float
|
The current smoothing parameter. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> import numpy as np
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import SmoothingLayer
>>> t = np.linspace(0.0, 1.0, 30)
>>> layer = SmoothingLayer(fdt.BSpline(domain=(0.0, 1.0), n_basis=8), t, lam=1e-4)
>>> layer(torch.zeros(5, 30, dtype=torch.float64)).shape
torch.Size([5, 8])
lam
property
¶
lam: float
The current smoothing parameter λ.
Returns:
| Type | Description |
|---|---|
float
|
|
Examples:
>>> import numpy as np
>>> import fdatools as fdt
>>> from fdatools.nn import SmoothingLayer
>>> b = fdt.BSpline(n_basis=5)
>>> SmoothingLayer(b, np.linspace(0, 1, 9), lam=0.5, trainable_lam=True).lam
0.5
forward ¶
forward(y: Tensor) -> torch.Tensor
Smooth observations into basis coefficients.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
Tensor
|
Observations of shape |
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Coefficients of shape |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the last axis of |
Examples:
>>> import numpy as np
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import SmoothingLayer
>>> layer = SmoothingLayer(fdt.Monomial(n_basis=2), np.linspace(0.0, 1.0, 5))
>>> y = torch.linspace(1.0, 3.0, 5, dtype=torch.float64) # y = 1 + 2t
>>> [round(v, 10) for v in layer(y).tolist()]
[1.0, 2.0]
FDataDataset ¶
FDataDataset(
fd: FData,
labels: Sequence[Any] | Any = None,
*,
t: Any = None,
dtype: dtype = torch.float64,
)
Bases: Dataset[Any]
Serve the curves of an :class:~fdatools.core.FData to a DataLoader.
Item i is curve i -- its coefficients, or its values on a grid --
optionally paired with its label.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fd
|
FData
|
The curves. |
required |
labels
|
array_like
|
One label per curve. Numeric labels become a tensor; anything else (strings, say) is kept as a list. |
None
|
t
|
array_like
|
Evaluation grid. When given, items are the curves' values at |
None
|
dtype
|
dtype
|
Dtype of the items. Default |
float64
|
Raises:
| Type | Description |
|---|---|
TypeError
|
If |
ValueError
|
If the number of labels differs from the number of curves. |
Examples:
>>> import numpy as np
>>> import torch
>>> import fdatools as fdt
>>> from fdatools.nn import FDataDataset
>>> fd = fdt.FData(np.eye(5), fdt.BSpline(n_basis=5))
>>> ds = FDataDataset(fd, labels=[0, 1, 0, 1, 0])
>>> x, y = next(iter(torch.utils.data.DataLoader(ds, batch_size=5)))
>>> tuple(x.shape), y.tolist()
((5, 5), [0, 1, 0, 1, 0])
__getitem__ ¶
__getitem__(index: int) -> Any
Return curve index, with its label when the dataset has labels.