pinnx 0.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pinnx/__init__.py +42 -0
- pinnx/_trainer.py +511 -0
- pinnx/callbacks.py +574 -0
- pinnx/fnspace.py +367 -0
- pinnx/geometry/__init__.py +45 -0
- pinnx/geometry/base.py +662 -0
- pinnx/geometry/geometry_1d.py +192 -0
- pinnx/geometry/geometry_2d.py +1018 -0
- pinnx/geometry/geometry_3d.py +181 -0
- pinnx/geometry/geometry_nd.py +322 -0
- pinnx/geometry/pointcloud.py +97 -0
- pinnx/geometry/timedomain.py +175 -0
- pinnx/grad.py +370 -0
- pinnx/grad_test.py +16 -0
- pinnx/icbc/__init__.py +29 -0
- pinnx/icbc/base.py +49 -0
- pinnx/icbc/boundary_conditions.py +439 -0
- pinnx/icbc/initial_conditions.py +89 -0
- pinnx/metrics.py +117 -0
- pinnx/nn/__init__.py +34 -0
- pinnx/nn/base.py +41 -0
- pinnx/nn/convert.py +129 -0
- pinnx/nn/deeponet.py +336 -0
- pinnx/nn/deeponet_strategy.py +158 -0
- pinnx/nn/fnn.py +173 -0
- pinnx/nn/mionet.py +243 -0
- pinnx/nn/model.py +114 -0
- pinnx/problem/__init__.py +33 -0
- pinnx/problem/base.py +122 -0
- pinnx/problem/dataset_function.py +79 -0
- pinnx/problem/dataset_general.py +66 -0
- pinnx/problem/dataset_mf.py +148 -0
- pinnx/problem/dataset_quadruple.py +129 -0
- pinnx/problem/dataset_triple.py +139 -0
- pinnx/problem/fpde.py +745 -0
- pinnx/problem/ide.py +149 -0
- pinnx/problem/pde.py +471 -0
- pinnx/problem/pde_operator.py +432 -0
- pinnx/utils/__init__.py +6 -0
- pinnx/utils/_convert.py +70 -0
- pinnx/utils/_display.py +109 -0
- pinnx/utils/array_ops.py +48 -0
- pinnx/utils/external.py +372 -0
- pinnx/utils/internal.py +255 -0
- pinnx/utils/losses.py +81 -0
- pinnx/utils/sampler.py +80 -0
- pinnx/utils/sampling.py +1525 -0
- pinnx/utils/transformers.py +327 -0
- pinnx-0.0.3.dist-info/METADATA +162 -0
- pinnx-0.0.3.dist-info/RECORD +53 -0
- pinnx-0.0.3.dist-info/WHEEL +5 -0
- pinnx-0.0.3.dist-info/licenses/LICENSE +497 -0
- pinnx-0.0.3.dist-info/top_level.txt +1 -0
pinnx/__init__.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Copyright 2024 BrainX Ecosystem Limited. All Rights Reserved.
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
# ==============================================================================
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
__version__ = "0.0.3"
|
|
18
|
+
__version_info__ = tuple(map(int, __version__.split(".")))
|
|
19
|
+
|
|
20
|
+
__all__ = [
|
|
21
|
+
"callbacks",
|
|
22
|
+
"fnspace",
|
|
23
|
+
"geometry",
|
|
24
|
+
"grad",
|
|
25
|
+
"icbc",
|
|
26
|
+
"metrics",
|
|
27
|
+
"nn",
|
|
28
|
+
"problem",
|
|
29
|
+
"utils",
|
|
30
|
+
"Trainer",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
from . import callbacks
|
|
34
|
+
from . import fnspace
|
|
35
|
+
from . import geometry
|
|
36
|
+
from . import grad
|
|
37
|
+
from . import icbc
|
|
38
|
+
from . import metrics
|
|
39
|
+
from . import nn
|
|
40
|
+
from . import problem
|
|
41
|
+
from . import utils
|
|
42
|
+
from ._trainer import Trainer
|
pinnx/_trainer.py
ADDED
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
# Rewrite of the original file in DeepXDE: https://github.com/lululxvi/deepxde
|
|
2
|
+
# ==============================================================================
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from typing import Union, Sequence, Callable, Optional
|
|
7
|
+
|
|
8
|
+
import braintools
|
|
9
|
+
import brainstate
|
|
10
|
+
import brainunit as u
|
|
11
|
+
import jax.numpy as jnp
|
|
12
|
+
import jax.tree
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from . import metrics as metrics_module
|
|
16
|
+
from . import utils
|
|
17
|
+
from .callbacks import CallbackList, Callback
|
|
18
|
+
from .problem.base import Problem
|
|
19
|
+
from .utils._display import training_display
|
|
20
|
+
|
|
21
|
+
__all__ = [
|
|
22
|
+
"Trainer",
|
|
23
|
+
"TrainState",
|
|
24
|
+
"LossHistory",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Trainer:
|
|
29
|
+
"""
|
|
30
|
+
A ``Trainer`` trains a neural network on a ``Problem``.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
problem: ``pinnx.problem.Problem`` instance.
|
|
34
|
+
external_trainable_variables: A trainable ``brainstate.ParamState`` object or a list
|
|
35
|
+
of trainable ``brainstate.ParamState`` objects. The unknown parameters in the
|
|
36
|
+
physics systems that need to be recovered.
|
|
37
|
+
"""
|
|
38
|
+
__module__ = 'pinnx'
|
|
39
|
+
optimizer: braintools.optim.Optimizer # optimizer
|
|
40
|
+
problem: Problem # problem
|
|
41
|
+
params: brainstate.util.FlattedDict # trainable variables
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
problem: Problem,
|
|
46
|
+
external_trainable_variables: Union[brainstate.ParamState, Sequence[brainstate.ParamState]] = None,
|
|
47
|
+
batch_size: Optional[int] = None,
|
|
48
|
+
):
|
|
49
|
+
# the problem
|
|
50
|
+
self.problem = problem
|
|
51
|
+
assert isinstance(self.problem, Problem), "problem must be a Problem instance."
|
|
52
|
+
|
|
53
|
+
# the approximator
|
|
54
|
+
if self.problem.approximator is None:
|
|
55
|
+
raise ValueError("Problem must define an approximator before training.")
|
|
56
|
+
|
|
57
|
+
# parameters and external trainable variables
|
|
58
|
+
params = brainstate.graph.states(self.problem.approximator, brainstate.ParamState)
|
|
59
|
+
if external_trainable_variables is None:
|
|
60
|
+
external_trainable_variables = []
|
|
61
|
+
else:
|
|
62
|
+
if not isinstance(external_trainable_variables, list):
|
|
63
|
+
external_trainable_variables = [external_trainable_variables]
|
|
64
|
+
for i, var in enumerate(external_trainable_variables):
|
|
65
|
+
assert isinstance(var, brainstate.ParamState), ("external_trainable_variables must be a "
|
|
66
|
+
"list of ParamState instance.")
|
|
67
|
+
params[('external_trainable_variable', i)] = var
|
|
68
|
+
self.params = params
|
|
69
|
+
|
|
70
|
+
# other useful parameters
|
|
71
|
+
self.metrics = None
|
|
72
|
+
self.batch_size = batch_size
|
|
73
|
+
|
|
74
|
+
# training state
|
|
75
|
+
self.train_state = TrainState()
|
|
76
|
+
self.loss_history = LossHistory()
|
|
77
|
+
self.stop_training = False
|
|
78
|
+
|
|
79
|
+
@utils.timing
|
|
80
|
+
def compile(
|
|
81
|
+
self,
|
|
82
|
+
optimizer: braintools.optim.Optimizer,
|
|
83
|
+
metrics: Union[str, Sequence[str]] = None,
|
|
84
|
+
measture_train_step_compile_time: bool = False,
|
|
85
|
+
):
|
|
86
|
+
"""
|
|
87
|
+
Configures the trainer for training.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
optimizer: String name of an optimizer, or an optimizer class instance.
|
|
91
|
+
metrics: List of metrics to be evaluated by the trainer during training.
|
|
92
|
+
"""
|
|
93
|
+
print("Compiling trainer...")
|
|
94
|
+
|
|
95
|
+
# optimizer
|
|
96
|
+
assert isinstance(optimizer, braintools.optim.Optimizer), "optimizer must be an Optimizer instance."
|
|
97
|
+
self.optimizer = optimizer
|
|
98
|
+
self.optimizer.register_trainable_weights(self.params)
|
|
99
|
+
|
|
100
|
+
# metrics may use trainer variables such as self.net,
|
|
101
|
+
# and thus are instantiated after compile.
|
|
102
|
+
metrics = metrics or []
|
|
103
|
+
self.metrics = [metrics_module.get(m) for m in metrics]
|
|
104
|
+
|
|
105
|
+
def fn_outputs(training: bool, inputs):
|
|
106
|
+
with brainstate.environ.context(fit=training):
|
|
107
|
+
inputs = jax.tree.map(lambda x: u.math.asarray(x), inputs, is_leaf=u.math.is_quantity)
|
|
108
|
+
return self.problem.approximator(inputs)
|
|
109
|
+
|
|
110
|
+
def fn_outputs_losses(training, inputs, targets, **kwargs):
|
|
111
|
+
with brainstate.environ.context(fit=training):
|
|
112
|
+
# inputs
|
|
113
|
+
inputs = jax.tree.map(lambda x: u.math.asarray(x), inputs, is_leaf=u.math.is_quantity)
|
|
114
|
+
|
|
115
|
+
# outputs
|
|
116
|
+
outputs = self.problem.approximator(inputs)
|
|
117
|
+
|
|
118
|
+
# targets
|
|
119
|
+
if targets is not None:
|
|
120
|
+
targets = jax.tree.map(lambda x: u.math.asarray(x), targets, is_leaf=u.math.is_quantity)
|
|
121
|
+
|
|
122
|
+
# compute losses
|
|
123
|
+
if training:
|
|
124
|
+
losses = self.problem.losses_train(inputs, outputs, targets, **kwargs)
|
|
125
|
+
else:
|
|
126
|
+
losses = self.problem.losses_test(inputs, outputs, targets, **kwargs)
|
|
127
|
+
return outputs, losses
|
|
128
|
+
|
|
129
|
+
def fn_outputs_losses_train(inputs, targets, **aux):
|
|
130
|
+
return fn_outputs_losses(True, inputs, targets, **aux)
|
|
131
|
+
|
|
132
|
+
def fn_outputs_losses_test(inputs, targets, **aux):
|
|
133
|
+
return fn_outputs_losses(False, inputs, targets, **aux)
|
|
134
|
+
|
|
135
|
+
def fn_train_step(inputs, targets, **aux):
|
|
136
|
+
def _loss_fun():
|
|
137
|
+
losses = fn_outputs_losses_train(inputs, targets, **aux)[1]
|
|
138
|
+
return u.math.sum(u.math.asarray([loss.sum() for loss in jax.tree.leaves(losses)]))
|
|
139
|
+
|
|
140
|
+
grads = brainstate.transform.grad(_loss_fun, grad_states=self.params)()
|
|
141
|
+
self.optimizer.update(grads)
|
|
142
|
+
|
|
143
|
+
# Callables
|
|
144
|
+
self.fn_outputs = brainstate.transform.jit(fn_outputs, static_argnums=0)
|
|
145
|
+
self.fn_outputs_losses_train = brainstate.transform.jit(fn_outputs_losses_train)
|
|
146
|
+
self.fn_outputs_losses_test = brainstate.transform.jit(fn_outputs_losses_test)
|
|
147
|
+
self.fn_train_step = brainstate.transform.jit(fn_train_step)
|
|
148
|
+
|
|
149
|
+
if measture_train_step_compile_time:
|
|
150
|
+
t0 = time.time()
|
|
151
|
+
self._compile_training_step(self.batch_size)
|
|
152
|
+
t1 = time.time()
|
|
153
|
+
return self, t1 - t0
|
|
154
|
+
|
|
155
|
+
return self
|
|
156
|
+
|
|
157
|
+
@utils.timing
|
|
158
|
+
def train(
|
|
159
|
+
self,
|
|
160
|
+
iterations: int,
|
|
161
|
+
batch_size: int = None,
|
|
162
|
+
display_every: int = 1000,
|
|
163
|
+
disregard_previous_best: bool = False,
|
|
164
|
+
callbacks: Union[Callback, Sequence[Callback]] = None,
|
|
165
|
+
model_restore_path: str = None,
|
|
166
|
+
model_save_path: str = None,
|
|
167
|
+
measture_train_step_time: bool = False,
|
|
168
|
+
):
|
|
169
|
+
"""
|
|
170
|
+
Trains the trainer.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
iterations (Integer): Number of iterations to train the trainer, i.e., number
|
|
174
|
+
of times the network weights are updated.
|
|
175
|
+
batch_size: Integer, tuple, or ``None``.
|
|
176
|
+
|
|
177
|
+
- If you solve PDEs via ``pinnx.problem.PDE`` or ``pinnx.problem.TimePDE``, do not use `batch_size`,
|
|
178
|
+
and instead use `pinnx.callbacks.PDEPointResampler
|
|
179
|
+
<https://deepxde.readthedocs.io/en/latest/modules/deepxde.html#deepxde.callbacks.PDEPointResampler>`_,
|
|
180
|
+
see an `example <https://github.com/lululxvi/deepxde/blob/master/examples/pinn_forward/diffusion_1d_resample.py>`_.
|
|
181
|
+
- For DeepONet in the format of Cartesian product, if `batch_size` is an Integer,
|
|
182
|
+
then it is the batch size for the branch input;
|
|
183
|
+
if you want to also use mini-batch for the trunk net input,
|
|
184
|
+
set `batch_size` as a tuple, where the fist number is the batch size for the branch net input
|
|
185
|
+
and the second number is the batch size for the trunk net input.
|
|
186
|
+
display_every (Integer): Print the loss and metrics every this steps.
|
|
187
|
+
disregard_previous_best: If ``True``, disregard the previous saved best
|
|
188
|
+
trainer.
|
|
189
|
+
callbacks: List of ``pinnx.callbacks.Callback`` instances. List of callbacks
|
|
190
|
+
to apply during training.
|
|
191
|
+
model_restore_path (String): Path where parameters were previously saved.
|
|
192
|
+
model_save_path (String): Prefix of filenames created for the checkpoint.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
if measture_train_step_time:
|
|
196
|
+
t0 = time.time()
|
|
197
|
+
|
|
198
|
+
if self.metrics is None:
|
|
199
|
+
raise ValueError("Compile the trainer before training.")
|
|
200
|
+
|
|
201
|
+
# callbacks
|
|
202
|
+
callbacks = CallbackList(callbacks=[callbacks] if isinstance(callbacks, Callback) else callbacks)
|
|
203
|
+
callbacks.set_model(self)
|
|
204
|
+
|
|
205
|
+
# disregard previous best
|
|
206
|
+
if disregard_previous_best:
|
|
207
|
+
self.train_state.disregard_best()
|
|
208
|
+
|
|
209
|
+
# restore
|
|
210
|
+
if model_restore_path is not None:
|
|
211
|
+
self.restore(model_restore_path, verbose=1)
|
|
212
|
+
|
|
213
|
+
print("Training trainer...\n")
|
|
214
|
+
self.stop_training = False
|
|
215
|
+
|
|
216
|
+
# testing
|
|
217
|
+
self.train_state.set_data_train(*self.problem.train_next_batch(batch_size))
|
|
218
|
+
self.train_state.set_data_test(*self.problem.test())
|
|
219
|
+
self._test()
|
|
220
|
+
|
|
221
|
+
# training
|
|
222
|
+
callbacks.on_train_begin()
|
|
223
|
+
self._train(iterations, display_every, batch_size, callbacks)
|
|
224
|
+
callbacks.on_train_end()
|
|
225
|
+
|
|
226
|
+
# summary
|
|
227
|
+
print("")
|
|
228
|
+
training_display.summary(self.train_state)
|
|
229
|
+
if model_save_path is not None:
|
|
230
|
+
self.save(model_save_path, verbose=1)
|
|
231
|
+
|
|
232
|
+
if measture_train_step_time:
|
|
233
|
+
t1 = time.time()
|
|
234
|
+
return self, t1 - t0
|
|
235
|
+
return self
|
|
236
|
+
|
|
237
|
+
def _compile_training_step(self, batch_size=None):
|
|
238
|
+
# get data
|
|
239
|
+
self.train_state.set_data_train(*self.problem.train_next_batch(batch_size))
|
|
240
|
+
|
|
241
|
+
# train one batch
|
|
242
|
+
self.fn_train_step.compile(self.train_state.X_train,
|
|
243
|
+
self.train_state.y_train,
|
|
244
|
+
**self.train_state.Aux_train)
|
|
245
|
+
|
|
246
|
+
def _train(self, iterations, display_every, batch_size, callbacks):
|
|
247
|
+
for i in range(iterations):
|
|
248
|
+
callbacks.on_epoch_begin()
|
|
249
|
+
callbacks.on_batch_begin()
|
|
250
|
+
|
|
251
|
+
# get data
|
|
252
|
+
self.train_state.set_data_train(*self.problem.train_next_batch(batch_size))
|
|
253
|
+
|
|
254
|
+
# train one batch
|
|
255
|
+
self.fn_train_step(self.train_state.X_train, self.train_state.y_train, **self.train_state.Aux_train)
|
|
256
|
+
|
|
257
|
+
self.train_state.epoch += 1
|
|
258
|
+
self.train_state.step += 1
|
|
259
|
+
if self.train_state.step % display_every == 0 or i + 1 == iterations:
|
|
260
|
+
self._test()
|
|
261
|
+
|
|
262
|
+
callbacks.on_batch_end()
|
|
263
|
+
callbacks.on_epoch_end()
|
|
264
|
+
|
|
265
|
+
if self.stop_training:
|
|
266
|
+
break
|
|
267
|
+
|
|
268
|
+
def _test(self):
|
|
269
|
+
# evaluate the training data
|
|
270
|
+
(
|
|
271
|
+
self.train_state.y_pred_train,
|
|
272
|
+
self.train_state.loss_train,
|
|
273
|
+
) = self.fn_outputs_losses_train(
|
|
274
|
+
self.train_state.X_train,
|
|
275
|
+
self.train_state.y_train,
|
|
276
|
+
**self.train_state.Aux_train,
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# evaluate the test data
|
|
280
|
+
(
|
|
281
|
+
self.train_state.y_pred_test,
|
|
282
|
+
self.train_state.loss_test
|
|
283
|
+
) = self.fn_outputs_losses_test(
|
|
284
|
+
self.train_state.X_test,
|
|
285
|
+
self.train_state.y_test,
|
|
286
|
+
**self.train_state.Aux_test,
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# metrics
|
|
290
|
+
if isinstance(self.train_state.y_test, (list, tuple)):
|
|
291
|
+
self.train_state.metrics_test = [
|
|
292
|
+
m(self.train_state.y_test[i],
|
|
293
|
+
self.train_state.y_pred_test[i])
|
|
294
|
+
for m in self.metrics
|
|
295
|
+
for i in range(len(self.train_state.y_test))
|
|
296
|
+
]
|
|
297
|
+
else:
|
|
298
|
+
self.train_state.metrics_test = [
|
|
299
|
+
m(self.train_state.y_test,
|
|
300
|
+
self.train_state.y_pred_test)
|
|
301
|
+
for m in self.metrics
|
|
302
|
+
]
|
|
303
|
+
|
|
304
|
+
# history
|
|
305
|
+
self.train_state.update_best()
|
|
306
|
+
self.loss_history.append(
|
|
307
|
+
self.train_state.step,
|
|
308
|
+
self.train_state.loss_train,
|
|
309
|
+
self.train_state.loss_test,
|
|
310
|
+
self.train_state.metrics_test,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# check NaN
|
|
314
|
+
if (
|
|
315
|
+
jnp.isnan(jnp.asarray(jax.tree.leaves(self.train_state.loss_train))).any()
|
|
316
|
+
or jnp.isnan(jnp.asarray(jax.tree.leaves(self.train_state.loss_test))).any()
|
|
317
|
+
):
|
|
318
|
+
self.stop_training = True
|
|
319
|
+
|
|
320
|
+
# display
|
|
321
|
+
training_display(self.train_state)
|
|
322
|
+
|
|
323
|
+
def predict(
|
|
324
|
+
self,
|
|
325
|
+
xs,
|
|
326
|
+
operator: Optional[Callable] = None,
|
|
327
|
+
callbacks: Union[Callback, Sequence[Callback]] = None,
|
|
328
|
+
):
|
|
329
|
+
"""Generates predictions for the input samples. If `operator` is ``None``,
|
|
330
|
+
returns the network output, otherwise returns the output of the `operator`.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
xs: The network inputs. A Numpy array or a tuple of Numpy arrays.
|
|
334
|
+
operator: A function takes arguments (`neural_net`, `inputs`) and outputs a tensor. `inputs` and
|
|
335
|
+
`outputs` are the network input and output tensors, respectively. `operator` is typically
|
|
336
|
+
chosen as the PDE (used to define `pinnx.problem.PDE`) to predict the PDE residual.
|
|
337
|
+
callbacks: List of ``pinnx.callbacks.Callback`` instances. List of callbacks
|
|
338
|
+
to apply during prediction.
|
|
339
|
+
"""
|
|
340
|
+
xs = jax.tree.map(
|
|
341
|
+
lambda x: u.math.asarray(x, dtype=brainstate.environ.dftype()),
|
|
342
|
+
xs,
|
|
343
|
+
is_leaf=u.math.is_quantity
|
|
344
|
+
)
|
|
345
|
+
callbacks = CallbackList(callbacks=[callbacks] if isinstance(callbacks, Callback) else callbacks)
|
|
346
|
+
callbacks.set_model(self)
|
|
347
|
+
callbacks.on_predict_begin()
|
|
348
|
+
ys = self.fn_outputs(False, xs)
|
|
349
|
+
if operator is not None:
|
|
350
|
+
ys = operator(xs, ys)
|
|
351
|
+
callbacks.on_predict_end()
|
|
352
|
+
return ys
|
|
353
|
+
|
|
354
|
+
def save(self, save_path, verbose: int = 0):
|
|
355
|
+
"""Saves all variables to a disk file.
|
|
356
|
+
|
|
357
|
+
Args:
|
|
358
|
+
save_path (string): Prefix of filenames to save the trainer file.
|
|
359
|
+
verbose (int): Verbosity mode, 0 or 1.
|
|
360
|
+
|
|
361
|
+
Returns:
|
|
362
|
+
string: Path where trainer is saved.
|
|
363
|
+
"""
|
|
364
|
+
import braintools
|
|
365
|
+
|
|
366
|
+
# save path
|
|
367
|
+
save_path = f"{save_path}-{self.train_state.epoch}.msgpack"
|
|
368
|
+
|
|
369
|
+
# avoid the duplicate ParamState save
|
|
370
|
+
model = brainstate.graph.Dict(params=self.params, optimizer=self.optimizer)
|
|
371
|
+
|
|
372
|
+
checkpoint = brainstate.graph.states(model).to_nest()
|
|
373
|
+
braintools.file.msgpack_save(save_path, checkpoint)
|
|
374
|
+
|
|
375
|
+
if verbose > 0:
|
|
376
|
+
print(
|
|
377
|
+
"Epoch {}: saving trainer to {} ...\n".format(
|
|
378
|
+
self.train_state.epoch, save_path
|
|
379
|
+
)
|
|
380
|
+
)
|
|
381
|
+
return save_path
|
|
382
|
+
|
|
383
|
+
def restore(self, save_path, verbose: int = 0):
|
|
384
|
+
"""Restore all variables from a disk file.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
save_path (string): Path where trainer was previously saved.
|
|
388
|
+
verbose (int): Verbosity mode, 0 or 1.
|
|
389
|
+
"""
|
|
390
|
+
import braintools
|
|
391
|
+
if verbose > 0:
|
|
392
|
+
print("Restoring trainer from {} ...\n".format(save_path))
|
|
393
|
+
|
|
394
|
+
data = brainstate.graph.Dict(params=self.params, optimizer=self.optimizer)
|
|
395
|
+
|
|
396
|
+
checkpoint = brainstate.graph.states(data).to_nest()
|
|
397
|
+
braintools.file.msgpack_load(save_path, target=checkpoint)
|
|
398
|
+
|
|
399
|
+
def saveplot(
|
|
400
|
+
self,
|
|
401
|
+
issave: bool = True,
|
|
402
|
+
isplot: bool = True,
|
|
403
|
+
loss_fname: str = "loss.dat",
|
|
404
|
+
train_fname: str = "train.dat",
|
|
405
|
+
test_fname: str = "test.dat",
|
|
406
|
+
output_dir: str = None,
|
|
407
|
+
):
|
|
408
|
+
"""
|
|
409
|
+
Saves and plots the loss and metrics.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
issave: If ``True``, save the loss and metrics to files.
|
|
413
|
+
isplot: If ``True``, plot the loss and metrics.
|
|
414
|
+
loss_fname: Filename to save the loss.
|
|
415
|
+
train_fname: Filename to save the training metrics.
|
|
416
|
+
test_fname: Filename to save the test metrics.
|
|
417
|
+
output_dir: Directory to save the files.
|
|
418
|
+
"""
|
|
419
|
+
utils.saveplot(
|
|
420
|
+
self.loss_history,
|
|
421
|
+
self.train_state,
|
|
422
|
+
issave=issave,
|
|
423
|
+
isplot=isplot,
|
|
424
|
+
loss_fname=loss_fname,
|
|
425
|
+
train_fname=train_fname,
|
|
426
|
+
test_fname=test_fname,
|
|
427
|
+
output_dir=output_dir,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
class TrainState:
|
|
432
|
+
__module__ = 'pinnx'
|
|
433
|
+
|
|
434
|
+
def __init__(self):
|
|
435
|
+
self.epoch = 0
|
|
436
|
+
self.step = 0
|
|
437
|
+
|
|
438
|
+
# Current data
|
|
439
|
+
self.X_train = None
|
|
440
|
+
self.y_train = None
|
|
441
|
+
self.Aux_train = dict()
|
|
442
|
+
self.X_test = None
|
|
443
|
+
self.y_test = None
|
|
444
|
+
self.Aux_test = dict()
|
|
445
|
+
|
|
446
|
+
# Results of current step
|
|
447
|
+
# Train results
|
|
448
|
+
self.loss_train = None
|
|
449
|
+
self.y_pred_train = None
|
|
450
|
+
# Test results
|
|
451
|
+
self.loss_test = None
|
|
452
|
+
self.y_pred_test = None
|
|
453
|
+
self.y_std_test = None
|
|
454
|
+
self.metrics_test = None
|
|
455
|
+
|
|
456
|
+
# The best results correspond to the min train loss
|
|
457
|
+
self.best_step = 0
|
|
458
|
+
self.best_loss_train = np.inf
|
|
459
|
+
self.best_loss_test = np.inf
|
|
460
|
+
self.best_y = None
|
|
461
|
+
self.best_ystd = None
|
|
462
|
+
self.best_metrics = None
|
|
463
|
+
|
|
464
|
+
def set_data_train(self, X_train, y_train, *args):
|
|
465
|
+
self.X_train = X_train
|
|
466
|
+
self.y_train = y_train
|
|
467
|
+
if len(args) > 0:
|
|
468
|
+
assert len(args) == 1, "Auxiliary training data must be a single argument."
|
|
469
|
+
assert isinstance(args[0], dict), "Auxiliary training data must be a dictionary."
|
|
470
|
+
self.Aux_train = args[0]
|
|
471
|
+
|
|
472
|
+
def set_data_test(self, X_test, y_test, *args):
|
|
473
|
+
self.X_test = X_test
|
|
474
|
+
self.y_test = y_test
|
|
475
|
+
if len(args) > 0:
|
|
476
|
+
assert len(args) == 1, "Auxiliary test data must be a single argument."
|
|
477
|
+
assert isinstance(args[0], dict), "Auxiliary test data must be a dictionary."
|
|
478
|
+
self.Aux_test = args[0]
|
|
479
|
+
|
|
480
|
+
def update_best(self):
|
|
481
|
+
current_loss_train = jnp.sum(jnp.asarray(jax.tree.leaves(self.loss_train)))
|
|
482
|
+
if self.best_loss_train > current_loss_train:
|
|
483
|
+
self.best_step = self.step
|
|
484
|
+
self.best_loss_train = current_loss_train
|
|
485
|
+
self.best_loss_test = jnp.sum(jnp.asarray(jax.tree.leaves(self.loss_test)))
|
|
486
|
+
self.best_y = self.y_pred_test
|
|
487
|
+
self.best_ystd = self.y_std_test
|
|
488
|
+
self.best_metrics = self.metrics_test
|
|
489
|
+
|
|
490
|
+
def disregard_best(self):
|
|
491
|
+
self.best_loss_train = np.inf
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class LossHistory:
|
|
495
|
+
__module__ = 'pinnx'
|
|
496
|
+
|
|
497
|
+
def __init__(self):
|
|
498
|
+
self.steps = []
|
|
499
|
+
self.loss_train = []
|
|
500
|
+
self.loss_test = []
|
|
501
|
+
self.metrics_test = []
|
|
502
|
+
|
|
503
|
+
def append(self, step, loss_train, loss_test, metrics_test):
|
|
504
|
+
self.steps.append(step)
|
|
505
|
+
self.loss_train.append(loss_train)
|
|
506
|
+
if loss_test is None:
|
|
507
|
+
loss_test = self.loss_test[-1]
|
|
508
|
+
if metrics_test is None:
|
|
509
|
+
metrics_test = self.metrics_test[-1]
|
|
510
|
+
self.loss_test.append(loss_test)
|
|
511
|
+
self.metrics_test.append(metrics_test)
|