Physics-informed Neural Networks (PINN)


By Prof. Seungchul Lee
http://iai.postech.ac.kr/
Industrial AI Lab at POSTECH

Table of Contents



1. Why Deep Learning Needs Physics?

Why do data-driven ‘black-box’ methods fail?

  • May output result that is physically inconsistent
  • Easy to learn spurious relationships that look good only on training and test data
    • Can lead to poor generalization outside the available data (out-of-sample prediction tasks)
  • Interpretability is absent
    • Discovering the mechanism of an underlying process is crucial for scientific advancements

Physics-Informed Neural Networks (PINNs)

  • Take full advantage of data science methods with the accumulated prior knowledge of scientific theories $\rightarrow$ Improve predictive performance
  • Integration of domain knowledge to overcome the issue of imbalanced data & data shortage

1.1. Taxonomy of Informed Deep Learning



1.2. Multilayer Feedforward Networks are Universal Approximators

  • The Universal Approximation Theorem
    • Neural Networks are capable of approximating any Borel measurable function
    • Neural Networks (1989)



1.3. Neural Networks for Solving Differential Equations

  • Neural Algorithm for Solving Differential Equations
    • Journal of Computational Physics (1990)
    • Neural minimization for finite difference equation



  • ANN for ODE and PDE
    • IEEE on Neural Networks (1998)



1.4. Journal of Computational Physics (2019)






1.5. Nature Reviews Physics (2021)




2. Architecture of Physics-informed Neural Networks (PINN)

  • NN as an universal function approximator
  • Given
    • Some measured data from initial and boundary conditions
    • ODE or PDE


$$ \frac{\partial u}{\partial t} - \lambda \frac{\partial^2 u}{\partial x^2} = 0 $$

  • Adding constraints for regularization
    • Regularized by physics, but matched with sparse data


$$ \begin{align*} \mathcal{L} &= \omega_{\text{data}} \mathcal{L}_{\text{data}} + \omega_{\text{PDE}} \mathcal{L}_{\text{PDE}}\\\\\\ \mathcal{L}_{\text{data}} &= \frac{1}{N_{\text{data}}} \sum \left(u(x,t) - u \right)^2 \\ \mathcal{L}_{\text{PDE}} &= \frac{1}{N_{\text{PDE}}} \sum \left(\frac{\partial u}{\partial t} - \lambda \frac{\partial^2 u}{\partial x^2} \right)^2 \end{align*} $$




3. Methond for Solving ODE with Neural Networks

3.1. Background

This is a result first due to Lagaris et.al from 1998. The idea is to solve differential equations using neural networks by representing the solution by a neural network and training the resulting network to satisfy the conditions required by the differential equation.

Consider a system of ordinary differential equations

$$ {u^\prime} = f(u,t) $$

with $t \in [0, 1]$ and a known initial condition $u(0) = u_0$.

To solve this, we approximate the solution by a neural network:

$$ {\text{NN}(t)} \approx {u(t)} $$


If $\text{NN}(t)$ was the true solution, then it would hold that $\text{NN}'(t) = f\left(\text{NN}(t),t \right)$ for all $t$. Thus we turn this condtion into our loss function. This motivates the loss function.


$${L(\omega)} = \sum_{i} \left(\frac{d \text{NN}(t_i)}{dt}-f\left(\text{NN}(t_i),t_i \right)\right)^2$$

The choice of $t_i$ could be done in many ways: it can be random, it can be a grid, etc. Anyways, when this loss function is minimized (gradients computed with standard reverse-mode automatic differentiation), then we have that $\frac{dNN(t_i)}{dt} \approx f(\text{NN}(t_i),t_i)$ and thus $\text{NN}(t)$ approximately solves the differential equation.

Note that we still have to handle the initial condition. One simple way to do this is to add an initial condition term to the cost function.


$${L(\omega)} = \sum_{i} \left(\frac{d \text{NN}(t_i)}{dt}-f(\text{NN}(t_i),t_i)\right)^2 + (\text{NN}(0)-u_0)^2 $$

where $\omega$ are the parameters that define the neural network $\text{NN}$ that approximates $u$. Thus this reduces down, once again, to simply finding weights which minimize a loss function !

3.2. Lab 1: Simple Example

  • Let's look at the ODE:


$$\frac{du}{dt} = \cos2\pi t$$

  • Initial condition:


$$u(0) = 1$$

  • The exact solution:


$$u(t) = \frac{1}{2\pi}\sin2\pi t + 1$$

  • Make a neural network and loss functions like below:



3.2.0. Setup

  • Using PyTorch
  • Setup
    • ipynb files are made up of the following versions
    • PyTorch 1.10.0
    • DeepXDE 1.2.0

3.2.1. Import Library

In [2]:
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
import random

3.2.2. Define Network and Hyper-parameters

In [4]:
NN = tf.keras.models.Sequential([
    tf.keras.layers.Input((1,)),
    tf.keras.layers.Dense(units = 32, activation = 'tanh'),
    tf.keras.layers.Dense(units = 1)
])

NN.summary()
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 dense_2 (Dense)             (None, 32)                64        
                                                                 
 dense_3 (Dense)             (None, 1)                 33        
                                                                 
=================================================================
Total params: 97
Trainable params: 97
Non-trainable params: 0
_________________________________________________________________
In [5]:
optm = tf.keras.optimizers.Adam(learning_rate = 0.001)

3.2.3. Define ODE System

  • ODE loss:


$$L_{\text{ODE}} = \frac{1}{n}\sum_{i=1}^{n} \left(\frac{d \text{NN}(t_i)}{dt}- \cos2\pi t_i\right)^2$$

  • Initial condition loss:


$$L_{IC} = \frac{1}{n}\sum_{i=1}^{n} \left({\text{NN}(0)}- 1\right)^2$$

  • Total loss:


$$L_{\text{Total}} = L_{\text{ODE}} + L_{\text{IC}}$$

In [6]:
def ode_system(t, net):
    t = t.reshape(-1,1)
    t = tf.constant(t, dtype = tf.float32)    
    t_0 = tf.zeros((1,1))
    one = tf.ones((1,1))   
    
    with tf.GradientTape() as tape:
        tape.watch(t)
                    
        u = net(t)
        u_t = tape.gradient(u, t)
    
    ode_loss = u_t - tf.math.cos(2*np.pi*t) 
    IC_loss = net(t_0) - one
    
    square_loss = tf.square(ode_loss) + tf.square(IC_loss)
    total_loss = tf.reduce_mean(square_loss)
    
    return total_loss

3.2.4. Train & Prediction

In [11]:
train_t = (np.random.rand(30)*2).reshape(-1, 1)
train_loss_record = []

for itr in range(6000):
    with tf.GradientTape() as tape:
        train_loss = ode_system(train_t, NN)
        train_loss_record.append(train_loss)
                
        grad_w = tape.gradient(train_loss, NN.trainable_variables)
        optm.apply_gradients(zip(grad_w, NN.trainable_variables))
    
    if itr % 1000 == 0:
        print(train_loss.numpy())
        
plt.figure(figsize = (10,8))
plt.plot(train_loss_record)
plt.show()
0.0018772273
0.00019943345
0.00015714265
0.00013077962
0.00010821897
9.131467e-05
In [12]:
test_t = np.linspace(0, 2, 100)

train_u = np.sin(2*np.pi*train_t)/(2*np.pi) + 1
true_u = np.sin(2*np.pi*test_t)/(2*np.pi) + 1
pred_u = NN.predict(test_t).ravel()

plt.figure(figsize = (10,8))
plt.plot(train_t, train_u, 'ok', label = 'Train')
plt.plot(test_t, true_u, '-k',label = 'True')
plt.plot(test_t, pred_u, '--r', label = 'Prediction')
plt.legend(fontsize = 15)
plt.xlabel('t', fontsize = 15)
plt.ylabel('u', fontsize = 15)
plt.show()

4. Lab 2: Solve Lab 1 Again using DeepXDE

4.1. DeepXDE

  • Using DeepXDE libray
  • DeepXDE is a useful library for solving forward and inverse partial differential equations (PDEs) via physics-informed neural network (PINN)
In [ ]:
# !pip install deepxde
  • Change DeepXDE backends
    • DeepXDE supports backends for TensorFlow 1.x, TensorFlow 2.x, and PyTorch. We will change it into Pytorch
In [ ]:
# from deepxde.backend.set_default_backend import set_default_backend
# set_default_backend("pytorch")

4.2. Problem Setup Again

  • ODE


$$\frac{du}{dt} = \cos2\pi t$$

  • Boundary condition:


$$u(0) = 1$$

  • The exact solution:


$$u(t) = \frac{1}{2\pi}\sin2\pi t + 1$$

4.2.1. Import Library

In [2]:
import torch
import deepxde as dde
import numpy as np
import matplotlib.pyplot as plt
Using backend: pytorch

4.2.2. Define ODE System

In [3]:
pi = torch.pi

def ode_system(t, u):
    du_t = dde.grad.jacobian(u, t)
    return du_t - torch.cos(2*pi*t)

4.2.3. Define Initial Condition

In [4]:
def boundary(t, on_initial):
    return on_initial and np.isclose(t[0], 0)

4.2.4. Define Geometry & Implement Initial Condition

In [5]:
geom = dde.geometry.TimeDomain(0, 2)

ic = dde.IC(geom, lambda t: 1, boundary)

# Reference solution to compute the error
def true_solution(t):
    return np.sin(2*np.pi*t)/(2*np.pi) + 1

data = dde.data.PDE(geom,
                    ode_system,
                    ic,
                    num_domain = 30,
                    num_boundary = 2,
                    solution = true_solution,
                    num_test = 100)

4.2.5. Define Network and Hyper-parameters

In [6]:
layer_size = [1] + [32] + [1]
activation = "tanh"
initializer = "Glorot uniform"

NN = dde.maps.FNN(layer_size, activation, initializer)
In [7]:
model = dde.Model(data, NN)
model.compile("adam", lr = 0.001)
Compiling model...
'compile' took 0.000517 s

4.2.6. Train & Prediction

In [8]:
losshistory, train_state = model.train(epochs = 6000)
dde.saveplot(losshistory, train_state, issave = False, isplot = True)
Training model...

Step      Train loss              Test loss               Test metric
0         [5.16e-01, 1.00e+00]    [5.11e-01, 1.00e+00]    []  
1000      [4.18e-01, 1.68e-04]    [4.49e-01, 1.68e-04]    []  
2000      [2.32e-01, 2.96e-05]    [2.29e-01, 2.96e-05]    []  
3000      [3.65e-02, 1.40e-07]    [3.57e-02, 1.40e-07]    []  
4000      [3.71e-03, 8.53e-10]    [2.95e-03, 8.53e-10]    []  
5000      [2.83e-03, 2.42e-07]    [1.99e-03, 2.42e-07]    []  
6000      [2.35e-03, 1.51e-10]    [1.60e-03, 1.51e-10]    []  

Best model at step 6000:
  train loss: 2.35e-03
  test loss: 1.60e-03
  test metric: []

'train' took 20.474403 s

In [1]:
%%javascript
$.getScript('https://kmahelona.github.io/ipython_notebook_goodies/ipython_notebook_toc.js')