Note
Go to the end to download the full example code or to run this example in your browser via Binder
Learning the 1D Integral Operator
In this example, we train a Deep Operator Network to learn the operator \(\mathcal{L}f(x) = \int f(x) dx\).
In this example we will approximate the integral operator \(\mathcal{L}f(x) = \int f(x) dx\) Using a Deep Operator Network. This example
Generates training data from a stochastic process
Defines the architecture of a Deep Operator Network
Fits the network parameters to the training data
Visualizes the results
First, import the necessary modules.
# Default imports
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
# UQpy imports
import UQpy.scientific_machine_learning as sml
from UQpy.stochastic_process import SpectralRepresentation
1. Generate Training Data
We generate random functions using the stochastic process module in UQpy.
The stochastic process is sampled at 100 points over using the function srm_samples.
We denote these input functions \(f\). They are sampled at 100 sensing points \(x\)
on the interval \([0, 1]\).
The operator \(\mathcal{L}f(x) = \int f(x) dx\) is approximated using the function operator to
numerically approximate the integral of \(f\) using the cumulative summation.
The class DONDataSet formats the dataset for training. It does not compute any new information.
def srm_samples(n_samples: int) -> tuple[torch.Tensor, torch.Tensor]:
"""Sample a 1D Gaussian process using the Spectral Representation Method
:param n_samples: Number of samples. Each sample is one row
:return: time, samples
"""
max_time = 1
n_time = 100
max_frequency = 8 * np.pi
n_frequency = 32
delta_time = max_time / n_time
delta_frequency = max_frequency / n_frequency
frequency = np.linspace(0, max_frequency - delta_frequency, num=n_frequency)
time = np.linspace(0, max_time - delta_time, num=n_time)
power_spectrum = np.exp(-2 * frequency**2)
srm = SpectralRepresentation(
n_samples, power_spectrum, delta_time, delta_frequency, n_time, n_frequency
)
return (
torch.tensor(time, dtype=torch.float).reshape(-1, 1),
torch.tensor(srm.samples, dtype=torch.float).squeeze(),
)
def operator(x: torch.Tensor, u_x: torch.Tensor) -> torch.Tensor:
"""Numerically approximate the integral operator
:param x: Sensing points at which the function :math:`u` is evalated
:param u_x: Evaluations of the function :math:`u` at points :math:`x`
:return: Cumulative sum of ``u_x``
"""
return torch.cumsum(u_x, axis=1) * (x[1] - x[0])
class DONDataSet(Dataset):
"""Format the data for UQpy Trainer"""
def __init__(self, x, u_x, gu_x):
self.x = x
self.u_x = u_x
self.gu_x = gu_x
def __len__(self):
return int(self.x.shape[0])
def __getitem__(self, i):
return self.x, self.u_x[i, :], self.gu_x[i, :]
2. Initialize Deep Operator Network
A Deep Operator Network is defined using the branch network, that encodes information about the domain \(x\),
and the trunk network, that encodes information about the transformation \(\mathcal{L}:f \mapsto \int f\).
This network maps a 1D function sampled at 100 points.
The width of each hidden layer in the network is given in width and the final width of the branch
and trunk networks before they are combined is given in final_width.
The branch and trunk networks can be defined using a torch.nn.Module or any subclass of it.
Here we use the torch.nn.Sequential class to define the networks.
n_points = 100
n_dimension = 1
width = 20
final_width = 100 # Number of nodes on output of branch and trunk network
branch_network = nn.Sequential(
nn.Linear(n_points, width),
nn.ReLU(),
nn.Linear(width, width),
nn.ReLU(),
nn.Linear(width, final_width),
)
trunk_network = nn.Sequential(
nn.Linear(n_dimension, width),
nn.ReLU(),
nn.Linear(width, width),
nn.ReLU(),
nn.Linear(width, final_width),
nn.ReLU(),
)
model = sml.DeepOperatorNetwork(branch_network, trunk_network)
3. Train the network on the data
The Trainer class organizes the model, data, and a pytorch optimization algorithm to learn the model parameters.
x, f_x = srm_samples(300)
Lf_x = operator(x, f_x)
train_data = DataLoader(DONDataSet(x, f_x, Lf_x), batch_size=20, shuffle=True)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
trainer = sml.Trainer(model, optimizer)
trainer.run(train_data=train_data, epochs=1_000, tolerance=1e-6)
4. Visualize the results
Finally, we plot the loss history and the predictions made by the optimized network.
# Plot loss history
train_loss = trainer.history["train_loss"].detach().numpy()
fig, ax = plt.subplots()
ax.semilogy(train_loss)
ax.set_title("Training Loss of Deep Operator Network")
ax.set(xlabel="Epoch", ylabel="Loss")
# Plot deep operator network prediction and exact solution
colors = ("tab:blue", "tab:orange", "tab:green")
x_plot = x.detach().numpy().squeeze()
fig, ax = plt.subplots()
for i in range(3):
prediction = model(x, f_x[i, :])
ax.plot(
x_plot,
Lf_x[i, :].detach().numpy(),
label=f"$g_{i}:=\mathcal{{L}}f_{i} (x)$",
color=colors[i],
linestyle="dashed",
)
ax.plot(
x_plot,
prediction.detach().numpy(),
label=f"DoN $\hat{{g}}_{i}$",
color=colors[i],
)
ax.set_title("Deep Operator Network Predictions")
ax.legend()
plt.show()
Total running time of the script: ( 0 minutes 0.000 seconds)