PotentialODE.reference.ipynb#
[1]:
%load_ext nb_black
# pip install neural-diffeqs
import neural_diffeqs
print(f"Version: {neural_diffeqs.__version__}")
import torch
Version: 0.3.2
Default PotentialODE#
As in the NeuralODE and NeuralSDE, the only required parameter is:
state_size
[2]:
ODE = neural_diffeqs.PotentialODE(state_size=20, mu_hidden=[64, 64])
print(ODE)
PotentialODE(
(mu): TorchNet(
(hidden_1): Sequential(
(linear): Linear(in_features=20, out_features=64, bias=True)
(activation): LeakyReLU(negative_slope=0.01)
)
(hidden_2): Sequential(
(linear): Linear(in_features=64, out_features=64, bias=True)
(activation): LeakyReLU(negative_slope=0.01)
)
(output): Sequential(
(linear): Linear(in_features=64, out_features=1, bias=False)
)
)
)
Notice that, the output layer of the mu function (drift network) contains only a single feature, without bias (by default):
(output): Sequential( (linear): Linear(in_features=512, out_features=1, bias=False) )
[3]:
# 5 samples x 20 dim
y = torch.randn([5, 20])
print(y)
tensor([[-0.3228, 0.1208, -0.4852, -1.5084, -1.5721, -0.3639, 0.7440, -0.3686,
0.0311, 0.0771, -0.2299, 0.6744, 1.0946, 1.8330, -0.0296, 0.0648,
1.1006, -0.2764, 1.1437, 2.3773],
[-0.3958, 0.5138, 0.0432, -0.0932, 2.2951, -1.3561, 0.4870, -1.4136,
1.0158, -1.5997, -2.5283, 0.5416, -0.0307, -1.3237, -0.1767, 1.9054,
1.5694, -0.5196, 0.3701, 1.1737],
[ 0.4174, 0.5138, -1.1092, -0.0051, -1.0940, -0.1167, 1.0324, -1.6913,
-0.0920, -0.4754, 0.2900, -0.9673, -2.0961, -0.1045, 1.0242, -0.9995,
1.1757, -0.4290, 1.8097, 0.9739],
[ 1.1494, 0.0153, 0.2669, -0.2239, -0.3777, 0.6096, 0.1383, -0.6071,
-0.9103, 0.7419, -0.2132, -0.0099, -1.1543, -0.0944, 1.1588, 0.7124,
-2.2216, -0.0356, 0.0506, 0.1203],
[ 0.9027, -0.2452, 0.3295, 1.3359, 0.5843, 0.5813, 0.2054, 0.1454,
1.4566, 0.4892, -0.5222, -0.6388, 0.4159, 0.8533, -0.8192, -1.3142,
1.6547, 0.9800, 0.4647, -0.4676]])
PotentialODE.f and PotentialODE.drift are identical. Under the hood, the following occurs:
def drift(self, y: torch.Tensor) -> torch.Tensor:
y = y.requires_grad_()
ψ = self._potential(y)
return self._gradient(ψ, y) * self._coef_drift
Wherein PotentialODE._potential(y) computes ψ = PotentialODE.mu(y). ψ is of shape: [y.shape[0], 1] or [n_samples x 1]. While the regular NeuralODE computes y(t) = net(y), directly, the next step for a PotentialODE occurs in PotentialODE._gradient(ψ, y):
y_hat = torch.autograd.grad(ψ, y, torch.ones_like(ψ), create_graph=True)[0]
We’ll skip self._coef_drift for now.
[4]:
y_f_hat = ODE.f(t=None, y=y)
print(y_f_hat)
tensor([[-0.0191, -0.0182, 0.0078, 0.0020, 0.0070, -0.0190, -0.0366, -0.0121,
-0.0267, -0.0194, 0.0034, -0.0013, -0.0185, -0.0411, -0.0280, 0.0304,
-0.0010, -0.0459, -0.0191, -0.0101],
[-0.0118, -0.0113, 0.0267, -0.0601, -0.0217, -0.0136, -0.0339, 0.0420,
0.0257, 0.0173, -0.0147, 0.0158, -0.0204, 0.0089, -0.0070, 0.0195,
-0.0142, -0.0115, 0.0208, -0.0027],
[ 0.0078, -0.0325, 0.0557, -0.0108, 0.0017, -0.0035, -0.0211, -0.0244,
-0.0189, -0.0022, -0.0195, -0.0373, 0.0397, -0.0341, -0.0472, 0.0457,
-0.0276, -0.0150, 0.0251, 0.0113],
[ 0.0117, -0.0229, 0.0043, 0.0080, -0.0022, -0.0083, -0.0038, -0.0119,
-0.0275, -0.0035, 0.0126, 0.0075, 0.0523, 0.0035, -0.0165, 0.0104,
0.0084, 0.0045, 0.0319, -0.0330],
[-0.0044, -0.0056, 0.0090, 0.0182, 0.0087, 0.0157, -0.0245, 0.0270,
-0.0402, -0.0136, -0.0249, 0.0182, -0.0473, 0.0016, 0.0184, 0.0178,
-0.0443, -0.0500, -0.0312, 0.0028]], grad_fn=<MmBackward0>)
[5]:
y_f_hat = ODE.drift(y=y)
print(y_f_hat)
tensor([[-0.0191, -0.0182, 0.0078, 0.0020, 0.0070, -0.0190, -0.0366, -0.0121,
-0.0267, -0.0194, 0.0034, -0.0013, -0.0185, -0.0411, -0.0280, 0.0304,
-0.0010, -0.0459, -0.0191, -0.0101],
[-0.0118, -0.0113, 0.0267, -0.0601, -0.0217, -0.0136, -0.0339, 0.0420,
0.0257, 0.0173, -0.0147, 0.0158, -0.0204, 0.0089, -0.0070, 0.0195,
-0.0142, -0.0115, 0.0208, -0.0027],
[ 0.0078, -0.0325, 0.0557, -0.0108, 0.0017, -0.0035, -0.0211, -0.0244,
-0.0189, -0.0022, -0.0195, -0.0373, 0.0397, -0.0341, -0.0472, 0.0457,
-0.0276, -0.0150, 0.0251, 0.0113],
[ 0.0117, -0.0229, 0.0043, 0.0080, -0.0022, -0.0083, -0.0038, -0.0119,
-0.0275, -0.0035, 0.0126, 0.0075, 0.0523, 0.0035, -0.0165, 0.0104,
0.0084, 0.0045, 0.0319, -0.0330],
[-0.0044, -0.0056, 0.0090, 0.0182, 0.0087, 0.0157, -0.0245, 0.0270,
-0.0402, -0.0136, -0.0249, 0.0182, -0.0473, 0.0016, 0.0184, 0.0178,
-0.0443, -0.0500, -0.0312, 0.0028]], grad_fn=<MmBackward0>)
Compute the potential, ψ of the observed state, y - i.e., (ψ(y)):
[6]:
y_hat_potential = ODE._potential(y=y)
print(y_hat_potential)
tensor([[-0.2161],
[-0.1079],
[-0.2329],
[-0.0970],
[-0.1882]], grad_fn=<MmBackward0>)