Note
Go to the end to download the full example code or to run this example in your browser via Binder
Learning a Linear Elastic system
In this example, we train a DeepOperatorNetwork to learn the behavior of a linear elastic system.
In this example we use a deep operator network to approximate the solution to a linear elastic system. The dataset is provided by Goswami et al. [43] and our architecture closely follows their design. Using the dataset, we will
Construct a deep operator network
Load the training and testing data
Fit the network parameters to the training data
Plot the loss history
First, import the necessary modules and set the logging behavior of UQpy.
# torch imports
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader, random_split
import scipy.io as io
# UQpy imports
import logging
import UQpy.scientific_machine_learning as sml
logger = logging.getLogger("UQpy")
logger.setLevel(logging.INFO)
1. Construct a 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.
The branch and trunk networks can be defined using a torch.nn.Module or any subclass of it.
Here we use subclasses of torch.nn.Module to define the networks. Both classes use standard
torch activation functions and layers to define their operations.
class BranchNetwork(nn.Module):
"""Construct the branch network for a deep operator network"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fnn = nn.Sequential(nn.Linear(101, 100), nn.Tanh())
self.conv_layers = nn.Sequential(
nn.Conv2d(1, 16, (5, 5), padding="same"),
nn.AvgPool2d(2, 1, padding=0),
nn.Conv2d(16, 16, (5, 5), padding="same"),
nn.AvgPool2d(2, 1, padding=0),
nn.Conv2d(16, 16, (5, 5), padding="same"),
nn.AvgPool2d(2, 1, padding=0),
nn.Conv2d(16, 64, (5, 5), padding="same"),
nn.AvgPool2d(2, 1, padding=0),
)
self.dnn = nn.Sequential(
nn.Flatten(),
nn.Linear(64 * 6 * 6, 512),
nn.Tanh(),
nn.Linear(512, 512),
nn.Tanh(),
nn.Linear(512, 200),
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = self.fnn(x)
x = x.view(-1, 1, 10, 10)
x = self.conv_layers(x)
x = self.dnn(x)
return x.unsqueeze(1)
class TrunkNetwork(nn.Module):
"""Construct the trunk network for a deep operator network"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fnn = nn.Sequential(
nn.Linear(2, 128),
nn.Tanh(),
nn.Linear(128, 128),
nn.Tanh(),
nn.Linear(128, 128),
nn.Tanh(),
nn.Linear(128, 200),
nn.Tanh(),
)
self.x_min = torch.tensor([[0.0, 0.0]])
self.x_max = torch.tensor([[1.0, 1.0]])
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = 2.0 * (x - self.x_min) / (self.x_max - self.x_min) - 1.0
x = x.float()
x = self.fnn(x)
return x
branch_network = BranchNetwork()
trunk_network = TrunkNetwork()
model = sml.DeepOperatorNetwork(branch_network, trunk_network, 2)
2. Load the training and testing data
With the model constructed, we turn our attention to the training data.
Here we define a subclass of torch.nn.Dataset as outlined by the [torch documentation](https://pytorch.org/tutorials/beginner/data_loading_tutorial.html#dataset-class).
class ElasticityDataSet(Dataset):
"""Load the Elasticity dataset"""
def __init__(self, x, f_x, u_x, u_y):
self.x = x
self.f_x = f_x
self.u_x = u_x
self.u_y = u_y
def __len__(self):
return int(self.f_x.shape[0])
def __getitem__(self, i):
return self.x, self.f_x[i, :], (self.u_x[i, :, 0], self.u_y[i, :, 0])
elastic_data = io.loadmat('linear_elastic_data.mat')
train_dataset, test_dataset = random_split(ElasticityDataSet(
elastic_data['X'], elastic_data['F'], elastic_data['Ux'],
elastic_data['Uy']), [0.9, 0.1])
train_data = DataLoader(train_dataset,
batch_size=20,
shuffle=True,
)
test_data = DataLoader(test_dataset)
3. Fit the network parameters to the training data
All that’s left is to define a loss function, an optimizer, and run the Trainer. We use the Mean Squared Error loss and an Adam optimizer from torch. We assemble the model, optimizer, loss function, and training data with UQpy’s trainer to learn the model parameters.
class LossFunction(nn.Module):
def __init__(self, reduction: str = "mean", *args, **kwargs):
super().__init__(*args, **kwargs)
self.f = nn.MSELoss(reduction=reduction)
def forward(self, prediction, label):
return self.f(prediction[:, :, 0], label[0]) + self.f(
prediction[:, :, 1],
label[1],
)
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5)
trainer = sml.Trainer(model, optimizer, loss_function=LossFunction(), scheduler=scheduler)
trainer.run(train_data=train_data, test_data=test_data, epochs=50)
Finally, we plot the training history.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.semilogy(trainer.history["train_loss"], label="Train Loss")
ax.semilogy(trainer.history["test_loss"], label="Test Loss")
ax.set_title("DeepONet Training History")
ax.set(xlabel="Epoch", ylabel="MSE Loss")
ax.legend()
plt.show()
Total running time of the script: ( 0 minutes 0.000 seconds)