Discovery Model Example¶
Here we will attempt to learn the parameters provided in this example (i.e. .0001 and 5.0) from the data provided. We consider that data, in this case, to be “experimental” data, however we do know it to be high-fidelity data from a simulation of the AC PDE system.
First, we present the whole script to train a discovery model, then we will break it apart and examine the major chunks.
Full Example¶
# Put params into a list
params = [tf.Variable(0.0, dtype=tf.float32), tf.Variable(0.0, dtype=tf.float32)]
# Define f_model, note the `vars` argument. Inputs must follow this order!
def f_model(u_model, var, x, t):
u = u_model(tf.concat([x, t], 1))
u_x = tf.gradients(u, x)
u_xx = tf.gradients(u_x, x)
u_t = tf.gradients(u, t)
c1 = var[0] # tunable param 1
c2 = var[1] # tunable param 2
f_u = u_t - c1 * u_xx + c2 * u * u * u - c2 * u
return f_u
# Import data, same data as Raissi et al
data = scipy.io.loadmat('AC.mat')
t = data['tt'].flatten()[:, None]
x = data['x'].flatten()[:, None]
Exact = data['uu']
Exact_u = np.real(Exact)
# generate all combinations of x and t
X, T = np.meshgrid(x, t)
X_star = np.hstack((X.flatten()[:, None], T.flatten()[:, None]))
u_star = Exact_u.T.flatten()[:, None]
x = X_star[:, 0:1]
t = X_star[:, 1:2]
print(np.shape(x))
# append to a list for input to model.fit
X = [x, t]
# define MLP depth and layer width
layer_sizes = [2, 128, 128, 128, 128, 1]
# initialize, compile, train model
model = DiscoveryModel()
model.compile(layer_sizes, f_model, X, u_star, params)
# train loop
model.fit(tf_iter=10000)
Let’s break this apart and look at its pieces.
Defining parameters and f_model
for estimation¶
First we define the tf.Variable
objects for the parameters and the new f_model
. Note that the structure and syntax is largely the same as the CollocationSolverND
example, with a few notable exceptions.
# Put params into a list
params = [tf.Variable(0.0, dtype=tf.float32), tf.Variable(0.0, dtype=tf.float32)]
# Define f_model, note the `vars` argument. Inputs must follow this order!
def f_model(u_model, var, x, t):
u = u_model(tf.concat([x, t], 1))
u_x = tf.gradients(u, x)
u_xx = tf.gradients(u_x, x)
u_t = tf.gradients(u, t)
c1 = var[0] # tunable param 1
c2 = var[1] # tunable param 2
f_u = u_t - c1 * u_xx + c2 * u * u * u - c2 * u
return f_u
Above, we can see that the parameters must be tf.Variables,
initialized as above, with a tf.float32
data type.
You must initialize as many of these as there are parameters to estimate. Those variables
must then be added to a list
for training at a later step. Here we initialize the parameters to 0.0
to start. As a heuristic, this
works well since the \(u\) network also needs to get somewhat close to a 0.0
residual solution before the trianing of the
parameters can really start to take root.
Concurrently, we generate the new f_model
. As discussed earlier, the new f_model
contains an additional input from its
CollocationSolverND
cousin - the var
input. This input is where the list
of
tf.Variables
goes. Inside the f_model
definition, that list is then partitioned out piecewise into the PDE. This allows the
tensorflow tracing to reach into the f_model
function and backpropagate against those values, resulting in training of the parameters
as well as the \(u\) network itself.
Importing data and generating input¶
# Import data, same data as Raissi et al
data = scipy.io.loadmat('AC.mat')
t = data['tt'].flatten()[:, None]
x = data['x'].flatten()[:, None]
Exact = data['uu']
Exact_u = np.real(Exact)
# generate all combinations of x and t
X, T = np.meshgrid(x, t)
X_star = np.hstack((X.flatten()[:, None], T.flatten()[:, None]))
u_star = Exact_u.T.flatten()[:, None]
x = X_star[:, 0:1]
t = X_star[:, 1:2]
# append to a list for input to model.fit
X = [x, t]
In this case, the input x
and t
sequences were held in the file. These took a similar form to np.linspace
objects, i.e. were vectors of even spacing across the x
and t
dimensions, independently. Therefore, we needed to generate all possible combinations
of x
and t
to use these. If you are in need of a multidimensional meshgrid generator (past 2D) that returns a list of all possible combinations
of np.linepace
type arrays, check out this github gist to get the input in the format tensordiffeq requires. This multimesh generation is
included in tdq base and is available by combining the function multimesh
with flatten_and_stack
, both in tensordiffeq.utils
. Note that you need
an X, u_sol
pair for each of your data points. So, if you have a 1D (with time) problem, then you need an input pair that of the form [x,t]
and
a target u_sol
value. Essentially, we are performing supervised learning of the parameters, therefore we need some target value for each input coordinate in
the domain where we have data available.
Defining the network and training¶
Next we define the layer_size
, similar to the CollocationSolverND
example
# define MLP depth and layer width
layer_sizes = [2, 128, 128, 128, 128, 1]
Finally, we can compile with all the parameters we defined above and begin training!
# initialize, compile, train model
model = DiscoveryModel()
model.compile(layer_sizes, f_model, X, u_star, params)
# train loop
model.fit(tf_iter=10000)