Fixing tutorials grammar (#242)

* grammar check and sparse rephrasing
* rst created
* meta copyright adjusted
This commit is contained in:
Giuseppe Alessio D'Inverno
2024-03-05 10:43:34 +01:00
committed by GitHub
parent 15136e13f8
commit b10e02103b
23 changed files with 272 additions and 237 deletions

View File

@@ -105,7 +105,7 @@
"f(x, y) = [\\sin(\\pi x) \\sin(\\pi y), -\\sin(\\pi x) \\sin(\\pi y)] \\quad (x,y)\\in[0,1]\\times[0,1]\n",
"$$\n",
"\n",
"using a batch size of one."
"using a batch size equal to 1."
]
},
{
@@ -130,14 +130,14 @@
"# points in the mesh fixed to 200\n",
"N = 200\n",
"\n",
"# vectorial 2 dimensional function, number_input_fileds=2\n",
"number_input_fileds = 2\n",
"# vectorial 2 dimensional function, number_input_fields=2\n",
"number_input_fields = 2\n",
"\n",
"# 2 dimensional spatial variables, D = 2 + 1 = 3\n",
"D = 3\n",
"\n",
"# create the function f domain as random 2d points in [0, 1]\n",
"domain = torch.rand(size=(batch_size, number_input_fileds, N, D-1))\n",
"domain = torch.rand(size=(batch_size, number_input_fields, N, D-1))\n",
"print(f\"Domain has shape: {domain.shape}\")\n",
"\n",
"# create the functions\n",
@@ -146,7 +146,7 @@
"f2 = - torch.sin(pi * domain[:, 1, :, 0]) * torch.sin(pi * domain[:, 1, :, 1])\n",
"\n",
"# stacking the input domain and field values\n",
"data = torch.empty(size=(batch_size, number_input_fileds, N, D))\n",
"data = torch.empty(size=(batch_size, number_input_fields, N, D))\n",
"data[..., :-1] = domain # copy the domain\n",
"data[:, 0, :, -1] = f1 # copy first field value\n",
"data[:, 1, :, -1] = f1 # copy second field value\n",
@@ -174,7 +174,7 @@
"1. `domain`: square domain (the only implemented) $[0,1]\\times[0,5]$. The minimum value is always zero, while the maximum is specified by the user\n",
"2. `start`: start position of the filter, coordinate $(0, 0)$\n",
"3. `jump`: the jumps of the centroid of the filter to the next position $(0.1, 0.3)$\n",
"4. `direction`: the directions of the jump, with `1 = right`, `0 = no jump`,`-1 = left` with respect to the current position\n",
"4. `direction`: the directions of the jump, with `1 = right`, `0 = no jump`, `-1 = left` with respect to the current position\n",
"\n",
"**Note**\n",
"\n",
@@ -188,9 +188,9 @@
"source": [
"### Filter definition\n",
"\n",
"Having defined all the previous blocks we are able to construct the continuous filter.\n",
"Having defined all the previous blocks, we are now able to construct the continuous filter.\n",
"\n",
"Suppose we would like to get an ouput with only one field, and let us fix the filter dimension to be $[0.1, 0.1]$."
"Suppose we would like to get an output with only one field, and let us fix the filter dimension to be $[0.1, 0.1]$."
]
},
{
@@ -220,7 +220,7 @@
" }\n",
"\n",
"# creating the filter \n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,\n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fields,\n",
" output_numb_field=1,\n",
" filter_dim=filter_dim,\n",
" stride=stride)"
@@ -242,7 +242,7 @@
"outputs": [],
"source": [
"# creating the filter + optimization\n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,\n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fields,\n",
" output_numb_field=1,\n",
" filter_dim=filter_dim,\n",
" stride=stride,\n",
@@ -254,7 +254,7 @@
"id": "f99c290e",
"metadata": {},
"source": [
"Let's try to do a forward pass"
"Let's try to do a forward pass:"
]
},
{
@@ -310,7 +310,7 @@
" return self.model(x)\n",
"\n",
"\n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,\n",
"cConv = ContinuousConvBlock(input_numb_field=number_input_fields,\n",
" output_numb_field=1,\n",
" filter_dim=filter_dim,\n",
" stride=stride,\n",
@@ -380,7 +380,7 @@
"id": "7f076010",
"metadata": {},
"source": [
"Let's now build a simple classifier. The MNIST dataset is composed by vectors of shape `[batch, 1, 28, 28]`, but we can image them as one field functions where the pixels $ij$ are the coordinate $x=i, y=j$ in a $[0, 27]\\times[0,27]$ domain, and the pixels value are the field values. We just need a function to transform the regular tensor in a tensor compatible for the continuous filter:"
"Let's now build a simple classifier. The MNIST dataset is composed by vectors of shape `[batch, 1, 28, 28]`, but we can image them as one field functions where the pixels $ij$ are the coordinate $x=i, y=j$ in a $[0, 27]\\times[0,27]$ domain, and the pixels values are the field values. We just need a function to transform the regular tensor in a tensor compatible for the continuous filter:"
]
},
{
@@ -478,7 +478,7 @@
"id": "4374c15c",
"metadata": {},
"source": [
"Let's try to train it using a simple pytorch training loop. We train for juts 1 epoch using Adam optimizer with a $0.001$ learning rate."
"Let's try to train it using a simple pytorch training loop. We train for just 1 epoch using Adam optimizer with a $0.001$ learning rate."
]
},
{
@@ -556,7 +556,7 @@
"id": "47fa3d0e",
"metadata": {},
"source": [
"Let's see the performance on the train set!"
"Let's see the performance on the test set!"
]
},
{
@@ -595,7 +595,7 @@
"id": "25cf2878",
"metadata": {},
"source": [
"As we can see we have very good performance for having traing only for 1 epoch! Nevertheless, we are still using structured data... Let's see how we can build an autoencoder for unstructured data now."
"As we can see we have very good performance for having trained only for 1 epoch! Nevertheless, we are still using structured data... Let's see how we can build an autoencoder for unstructured data now."
]
},
{
@@ -876,7 +876,7 @@
"id": "206141f9",
"metadata": {},
"source": [
"As we can see the two are really similar! We can compute the $l_2$ error quite easily as well:"
"As we can see, the two solutions are really similar! We can compute the $l_2$ error quite easily as well:"
]
},
{
@@ -916,7 +916,7 @@
"source": [
"### Filter for upsampling\n",
"\n",
"Suppose we have already the hidden dimension and we want to upsample on a differen grid with more points. Let's see how to do it:"
"Suppose we have already the hidden representation and we want to upsample on a differen grid with more points. Let's see how to do it:"
]
},
{
@@ -946,7 +946,7 @@
"input_data2[0, 0, :, -1] = torch.sin(pi *\n",
" grid2[:, 0]) * torch.sin(pi * grid2[:, 1])\n",
"\n",
"# get the hidden dimension representation from original input\n",
"# get the hidden representation from original input\n",
"latent = net.encoder(input_data)\n",
"\n",
"# upsample on the second input_data2\n",
@@ -996,13 +996,13 @@
"id": "465cbd16",
"metadata": {},
"source": [
"### Autoencoding at different resolution\n",
"In the previous example we already had the hidden dimension (of original input) and we used it to upsample. Sometimes however we have a more fine mesh solution and we simply want to encode it. This can be done without retraining! This procedure can be useful in case we have many points in the mesh and just a smaller part of them are needed for training. Let's see the results of this:"
"### Autoencoding at different resolutions\n",
"In the previous example we already had the hidden representation (of the original input) and we used it to upsample. Sometimes however we could have a finer mesh solution and we would simply want to encode it. This can be done without retraining! This procedure can be useful in case we have many points in the mesh and just a smaller part of them are needed for training. Let's see the results of this:"
]
},
{
"cell_type": "code",
"execution_count": 24,
"execution_count": null,
"id": "75ed28f5",
"metadata": {},
"outputs": [
@@ -1034,7 +1034,7 @@
"input_data2[0, 0, :, -1] = torch.sin(pi *\n",
" grid2[:, 0]) * torch.sin(pi * grid2[:, 1])\n",
"\n",
"# get the hidden dimension representation from more fine mesh input\n",
"# get the hidden representation from finer mesh input\n",
"latent = net.encoder(input_data2)\n",
"\n",
"# upsample on the second input_data2\n",

View File

@@ -1,20 +1,21 @@
#!/usr/bin/env python
# coding: utf-8
# # Tutorial 4: continuous convolutional filter
# # Tutorial: Unstructured convolutional autoencoder via continuous convolution
# In this tutorial, we will show how to use the Continuous Convolutional Filter, and how to build common Deep Learning architectures with it. The implementation of the filter follows the original work [**A Continuous Convolutional Trainable Filter for Modelling Unstructured Data**](https://arxiv.org/abs/2210.13416).
# In this tutorial, we will show how to use the Continuous Convolutional Filter, and how to build common Deep Learning architectures with it. The implementation of the filter follows the original work [*A Continuous Convolutional Trainable Filter for Modelling Unstructured Data*](https://arxiv.org/abs/2210.13416).
# First of all we import the modules needed for the tutorial, which include:
#
# * `ContinuousConv` class from `pina.model.layers` which implements the continuous convolutional filter
# * `PyTorch` and `Matplotlib` for tensorial operations and visualization respectively
# First of all we import the modules needed for the tutorial:
# In[1]:
import torch
import matplotlib.pyplot as plt
from pina.problem import AbstractProblem
from pina.solvers import SupervisedSolver
from pina.trainer import Trainer
from pina import Condition, LabelTensor
from pina.model.layers import ContinuousConvBlock
import torchvision # for MNIST dataset
from pina.model import FeedForward # for building AE and MNIST classification
@@ -54,7 +55,7 @@ from pina.model import FeedForward # for building AE and MNIST classification
# f(x, y) = [\sin(\pi x) \sin(\pi y), -\sin(\pi x) \sin(\pi y)] \quad (x,y)\in[0,1]\times[0,1]
# $$
#
# using a batch size of one.
# using a batch size equal to 1.
# In[2]:
@@ -65,14 +66,14 @@ batch_size = 1
# points in the mesh fixed to 200
N = 200
# vectorial 2 dimensional function, number_input_fileds=2
number_input_fileds = 2
# vectorial 2 dimensional function, number_input_fields=2
number_input_fields = 2
# 2 dimensional spatial variables, D = 2 + 1 = 3
D = 3
# create the function f domain as random 2d points in [0, 1]
domain = torch.rand(size=(batch_size, number_input_fileds, N, D-1))
domain = torch.rand(size=(batch_size, number_input_fields, N, D-1))
print(f"Domain has shape: {domain.shape}")
# create the functions
@@ -81,7 +82,7 @@ f1 = torch.sin(pi * domain[:, 0, :, 0]) * torch.sin(pi * domain[:, 0, :, 1])
f2 = - torch.sin(pi * domain[:, 1, :, 0]) * torch.sin(pi * domain[:, 1, :, 1])
# stacking the input domain and field values
data = torch.empty(size=(batch_size, number_input_fileds, N, D))
data = torch.empty(size=(batch_size, number_input_fields, N, D))
data[..., :-1] = domain # copy the domain
data[:, 0, :, -1] = f1 # copy first field value
data[:, 1, :, -1] = f1 # copy second field value
@@ -104,7 +105,7 @@ print(f"Filter input data has shape: {data.shape}")
# 1. `domain`: square domain (the only implemented) $[0,1]\times[0,5]$. The minimum value is always zero, while the maximum is specified by the user
# 2. `start`: start position of the filter, coordinate $(0, 0)$
# 3. `jump`: the jumps of the centroid of the filter to the next position $(0.1, 0.3)$
# 4. `direction`: the directions of the jump, with `1 = right`, `0 = no jump`,`-1 = left` with respect to the current position
# 4. `direction`: the directions of the jump, with `1 = right`, `0 = no jump`, `-1 = left` with respect to the current position
#
# **Note**
#
@@ -112,9 +113,9 @@ print(f"Filter input data has shape: {data.shape}")
# ### Filter definition
#
# Having defined all the previous blocks we are able to construct the continuous filter.
# Having defined all the previous blocks, we are now able to construct the continuous filter.
#
# Suppose we would like to get an ouput with only one field, and let us fix the filter dimension to be $[0.1, 0.1]$.
# Suppose we would like to get an output with only one field, and let us fix the filter dimension to be $[0.1, 0.1]$.
# In[3]:
@@ -130,7 +131,7 @@ stride = {"domain": [1, 1],
}
# creating the filter
cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,
cConv = ContinuousConvBlock(input_numb_field=number_input_fields,
output_numb_field=1,
filter_dim=filter_dim,
stride=stride)
@@ -142,14 +143,14 @@ cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,
# creating the filter + optimization
cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,
cConv = ContinuousConvBlock(input_numb_field=number_input_fields,
output_numb_field=1,
filter_dim=filter_dim,
stride=stride,
optimize=True)
# Let's try to do a forward pass
# Let's try to do a forward pass:
# In[5]:
@@ -182,7 +183,7 @@ class SimpleKernel(torch.nn.Module):
return self.model(x)
cConv = ContinuousConvBlock(input_numb_field=number_input_fileds,
cConv = ContinuousConvBlock(input_numb_field=number_input_fields,
output_numb_field=1,
filter_dim=filter_dim,
stride=stride,
@@ -231,7 +232,7 @@ test_loader = DataLoader(train_data, batch_size=batch_size,
sampler=SubsetRandomSampler(subsample_train_indices))
# Let's now build a simple classifier. The MNIST dataset is composed by vectors of shape `[batch, 1, 28, 28]`, but we can image them as one field functions where the pixels $ij$ are the coordinate $x=i, y=j$ in a $[0, 27]\times[0,27]$ domain, and the pixels value are the field values. We just need a function to transform the regular tensor in a tensor compatible for the continuous filter:
# Let's now build a simple classifier. The MNIST dataset is composed by vectors of shape `[batch, 1, 28, 28]`, but we can image them as one field functions where the pixels $ij$ are the coordinate $x=i, y=j$ in a $[0, 27]\times[0,27]$ domain, and the pixels values are the field values. We just need a function to transform the regular tensor in a tensor compatible for the continuous filter:
# In[8]:
@@ -300,7 +301,7 @@ class ContinuousClassifier(torch.nn.Module):
net = ContinuousClassifier()
# Let's try to train it using a simple pytorch training loop. We train for juts 1 epoch using Adam optimizer with a $0.001$ learning rate.
# Let's try to train it using a simple pytorch training loop. We train for just 1 epoch using Adam optimizer with a $0.001$ learning rate.
# In[10]:
@@ -336,7 +337,7 @@ for epoch in range(1): # loop over the dataset multiple times
running_loss = 0.0
# Let's see the performance on the train set!
# Let's see the performance on the test set!
# In[11]:
@@ -357,7 +358,7 @@ print(
f'Accuracy of the network on the 1000 test images: {(correct / total):.3%}')
# As we can see we have very good performance for having traing only for 1 epoch! Nevertheless, we are still using structured data... Let's see how we can build an autoencoder for unstructured data now.
# As we can see we have very good performance for having trained only for 1 epoch! Nevertheless, we are still using structured data... Let's see how we can build an autoencoder for unstructured data now.
# ## Building a Continuous Convolutional Autoencoder
#
@@ -463,7 +464,7 @@ class Decoder(torch.nn.Module):
# Very good! Notice that in the `Decoder` class in the `forward` pass we have used the `.transpose()` method of the `ContinuousConvolution` class. This method accepts the `weights` for upsampling and the `grid` on where to upsample. Let's now build the autoencoder! We set the hidden dimension in the `hidden_dimension` variable. We apply the sigmoid on the output since the field value is between $[0, 1]$.
# In[14]:
# In[17]:
class Autoencoder(torch.nn.Module):
@@ -482,42 +483,32 @@ class Autoencoder(torch.nn.Module):
out = self.decoder(weights, grid)
return out
net = Autoencoder()
# Let's now train the autoencoder, minimizing the mean square error loss and optimizing using Adam.
# Let's now train the autoencoder, minimizing the mean square error loss and optimizing using Adam. We use the `SupervisedSolver` as solver, and the problem is a simple problem created by inheriting from `AbstractProblem`. It takes approximately two minutes to train on CPU.
# In[15]:
# In[19]:
# setting the seed
torch.manual_seed(seed)
# define the problem
class CircleProblem(AbstractProblem):
input_variables = ['x', 'y', 'f']
output_variables = input_variables
conditions = {'data' : Condition(input_points=LabelTensor(input_data, input_variables), output_points=LabelTensor(input_data, output_variables))}
# optimizer and loss function
optimizer = torch.optim.Adam(net.parameters(), lr=0.001)
criterion = torch.nn.MSELoss()
max_epochs = 150
# define the solver
solver = SupervisedSolver(problem=CircleProblem(), model=net, loss=torch.nn.MSELoss())
for epoch in range(max_epochs): # loop over the dataset multiple times
# zero the parameter gradients
optimizer.zero_grad()
# forward + backward + optimize
outputs = net(input_data)
loss = criterion(outputs[..., -1], input_data[..., -1])
loss.backward()
optimizer.step()
# print statistics
if epoch % 10 ==9:
print(f'epoch [{epoch + 1}/{max_epochs}] loss [{loss.item():.2}]')
# train
trainer = Trainer(solver, max_epochs=150, accelerator='cpu', enable_model_summary=False) # we train on CPU and avoid model summary at beginning of training (optional)
trainer.train()
# Let's visualize the two solutions side by side!
# In[16]:
# In[20]:
net.eval()
@@ -538,9 +529,9 @@ plt.tight_layout()
plt.show()
# As we can see the two are really similar! We can compute the $l_2$ error quite easily as well:
# As we can see, the two solutions are really similar! We can compute the $l_2$ error quite easily as well:
# In[17]:
# In[21]:
def l2_error(input_, target):
@@ -554,9 +545,9 @@ print(f'l2 error: {l2_error(input_data[0, 0, :, -1], output[0, 0, :, -1]):.2%}')
# ### Filter for upsampling
#
# Suppose we have already the hidden dimension and we want to upsample on a differen grid with more points. Let's see how to do it:
# Suppose we have already the hidden representation and we want to upsample on a differen grid with more points. Let's see how to do it:
# In[18]:
# In[22]:
# setting the seed
@@ -568,7 +559,7 @@ input_data2[0, 0, :, :-1] = grid2
input_data2[0, 0, :, -1] = torch.sin(pi *
grid2[:, 0]) * torch.sin(pi * grid2[:, 1])
# get the hidden dimension representation from original input
# get the hidden representation from original input
latent = net.encoder(input_data)
# upsample on the second input_data2
@@ -589,16 +580,16 @@ plt.show()
# As we can see we have a very good approximation of the original function, even thought some noise is present. Let's calculate the error now:
# In[19]:
# In[23]:
print(f'l2 error: {l2_error(input_data2[0, 0, :, -1], output[0, 0, :, -1]):.2%}')
# ### Autoencoding at different resolution
# In the previous example we already had the hidden dimension (of original input) and we used it to upsample. Sometimes however we have a more fine mesh solution and we simply want to encode it. This can be done without retraining! This procedure can be useful in case we have many points in the mesh and just a smaller part of them are needed for training. Let's see the results of this:
# ### Autoencoding at different resolutions
# In the previous example we already had the hidden representation (of the original input) and we used it to upsample. Sometimes however we could have a finer mesh solution and we would simply want to encode it. This can be done without retraining! This procedure can be useful in case we have many points in the mesh and just a smaller part of them are needed for training. Let's see the results of this:
# In[20]:
# In[ ]:
# setting the seed
@@ -610,7 +601,7 @@ input_data2[0, 0, :, :-1] = grid2
input_data2[0, 0, :, -1] = torch.sin(pi *
grid2[:, 0]) * torch.sin(pi * grid2[:, 1])
# get the hidden dimension representation from more fine mesh input
# get the hidden representation from finer mesh input
latent = net.encoder(input_data2)
# upsample on the second input_data2
@@ -635,4 +626,10 @@ print(
# ## What's next?
#
# We have shown the basic usage of a convolutional filter. In the next tutorials we will show how to combine the PINA framework with the convolutional filter to train in few lines and efficiently a Neural Network!
# We have shown the basic usage of a convolutional filter. There are additional extensions possible:
#
# 1. Train using Physics Informed strategies
#
# 2. Use the filter to build an unstructured convolutional autoencoder for reduced order modelling
#
# 3. Many more...