Optimizing the training Open in Colab

This notebooks covers more details on tweaking and optimizing the training process.

# install konda
!pip -q install konda

import konda
konda.install()

# accept Anaconda Terms of Service
!conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/main
!conda tos accept --override-channels --channel https://repo.anaconda.com/pkgs/r

# create the environment
!konda create -q -n pinn_env python=3.10 -y
!konda activate pinn_env

# install PiNN & download the QM9 dataset
!konda run "pip -q install 'tensorflow[and-cuda]==2.15.1' 'numpy<2'"
!konda run "pip -q install git+https://github.com/Teoroo-CMC/PiNN"
!mkdir -p /tmp/dsgdb9nsd && curl -sSL https://ndownloader.figshare.com/files/3195389 | tar xj -C /tmp/dsgdb9nsd
%%writefile imports.py

import sys
import os, warnings
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import tensorflow as tf

import timeit
from glob import glob
from pinn.io import load_qm9, sparse_batch
from pinn.networks.pinet import PiNet
from pinn.utils import get_atomic_dress

os.environ['CUDA_VISIBLE_DEVICES'] = ''
index_warning = 'Converting sparse IndexedSlices'
warnings.filterwarnings('ignore', index_warning)
Overwriting imports.py

Optimizing the pipeline

Caching

Caching stores the decoded dataset in the memory.

%%writefile -a imports.py

# For the purpose of testing, we use only 1000 samples from QM9
filelist = glob('/tmp/dsgdb9nsd/*.xyz')[:1000]
dataset = lambda: load_qm9(filelist)
Appending to imports.py

!rm -f run.py
!cat imports.py >> run.py
%%writefile -a run.py

ds = dataset().repeat().apply(sparse_batch(100))
tensors = ds.as_numpy_iterator()
for i in range(10):
    next(tensors) # "Warm up" the graph

print(f"{timeit.timeit(lambda: next(tensors), number=100) / 100 * 1e3:.1f} ms per iteration, average of 100")
sys.exit()
Appending to run.py

!konda run "python run.py"
38.8 ms per iteration, average of 100

This speed indicates the IO limit of our current setting.

Now let's cache the dataset to the memory.

!rm -f run_cached.py
!cat imports.py >> run_cached.py
%%writefile -a run_cached.py

ds = dataset().cache().repeat().apply(sparse_batch(100))
tensors = ds.as_numpy_iterator()
for i in range(10):
    next(tensors) # "Warm up" the graph
print(f"{timeit.timeit(lambda: next(tensors), number=100) / 100 * 1e3:.1f} μs per iteration, average of 100")
sys.exit()
Appending to run_cached.py

!konda run "python run_cached.py"
0.5 μs per iteration, average of 100

Preprocessing

You might also see a notable difference in the performance with and without preprocessing. This is especially helpful when you are training with GPUs.

!rm -f run.py
!cat imports.py >> run.py
%%writefile -a run.py

pinet = PiNet()
ds = dataset().cache().repeat().apply(sparse_batch(100))
tensors = ds.as_numpy_iterator()
for i in range(10):
    pinet(next(tensors)) # "Warm up" the graph

print(f"{timeit.timeit(lambda: next(tensors), number=100) / 100 * 1e3:.1f} ms per per iteration, average of 100")
sys.exit()
Appending to run.py

!konda run "python run.py"
0.5 ms per per iteration, average of 100

!rm -f run_preprocessed.py
!cat imports.py >> run_preprocessed.py
%%writefile -a run_preprocessed.py

pinet = PiNet()
ds = dataset().cache().repeat().apply(sparse_batch(100)).map(pinet.preprocess)
tensors = ds.as_numpy_iterator()
for i in range(10):
    next(tensors) # "Warm up" the graph
print(f"{timeit.timeit(lambda: next(tensors), number=100) / 100 * 1e3:.1f} μs per iteration, average of 100")
sys.exit()
Appending to run_preprocessed.py

!konda run "python run_preprocessed.py"
6.1 μs per iteration, average of 100

You can even cache the preprocessed data.

!rm -f run_preprocessed_cached.py
!cat imports.py >> run_preprocessed_cached.py
%%writefile -a run_preprocessed_cached.py

pinet = PiNet()
ds = dataset().apply(sparse_batch(100)).map(pinet.preprocess).cache().repeat()
tensors = ds.as_numpy_iterator()
for i in range(10):
    next(tensors) # "Warm up" the graph

print(f"{timeit.timeit(lambda: next(tensors), number=100) / 100 * 1e3:.1f} μs per iteration")
sys.exit()
Appending to run_preprocessed_cached.py

!konda run "python run_preprocessed_cached.py"
0.4 μs per iteration

Atomic dress

Scaling and aligning the labels can enhance the performance of the models, and avoid numerical instability. For datasets like QM9, we can assign an atomic energy to each atom according to their elements to approximate the total energy. This can be done by a simple linear regression. We provide a simple tool to generate such "atomic dresses".

!rm -f atomic_dress.py
!cat imports.py >> atomic_dress.py
%%writefile -a atomic_dress.py

import numpy as np
import json

filelist = glob('/tmp/dsgdb9nsd/*.xyz')
dataset = lambda: load_qm9(filelist, splits={'train':8, 'test':2})
dress, error = get_atomic_dress(dataset()['train'],[1,6,7,8,9])

with open("dress.json", "w") as f:
  json.dump(dress, f)
np.save("error.npy", error)
Appending to atomic_dress.py

#adding a dress to the whole QM9 dataset. running this cell might take some time.
!konda run "python atomic_dress.py"

Applying the atomic dress converts the QM9 energies to a "normal" distribution. It also gives us some ideas about the relative distribution of energies, and how much our neural network improves from the naive guess of the atomic dress.

After applying the atomic dress, it turns out that the distribution of our training set is only about 0.05 Hartree, or 30 kcal/mol.

import json
import numpy as np
import matplotlib.pyplot as plt

with open("dress.json", "r") as f:
  dress = json.load(f)
error = np.load("error.npy")

plt.hist(error,50)
dress
{'1': -0.6038332153108412,
 '6': -38.0739293568195,
 '7': -54.74922497481819,
 '8': -75.22547418419636,
 '9': -99.86669179905425}
No description has been provided for this image

Training with the optimized pipeline

%%writefile training.py

from glob import glob
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
import tensorflow as tf

from pinn.io import load_qm9, sparse_batch
from pinn.networks.pinet import PiNet
from pinn.utils import get_atomic_dress
from pinn import get_model, get_network

filelist = glob('/tmp/dsgdb9nsd/*.xyz')
dataset = lambda: load_qm9(filelist, splits={'train':8, 'test':2})
dress, error = get_atomic_dress(dataset()['train'],[1,6,7,8,9])
Overwriting training.py

%%writefile -a training.py

params = {'model_dir': '/tmp/PiNet_QM9_pipeline',
          'network': {
              'name': 'PiNet',
              'params': {
                  'atom_types':[1, 6, 7, 8, 9],
              },
          },
          'model': {
              'name': 'potential_model',
              'params': {
                  'learning_rate': 1e-3, # Relatively large learning rate
                  'e_scale': 627.5, # Here we scale the model to kcal/mol
                  'e_dress': dress
              }
          }
         }

# The logging behavior of estimator can be controlled here
config = tf.estimator.RunConfig(log_step_count_steps=500)

# Preprocessing the datasets
model = get_model(params, config=config)

# If you are pre-processing the dataset in the training script,
# the preprocessing layer will occupy the namespace of the network
# resulting unexpected names in the ckpts and errors durning prediction
# To avoid this, wrap your preprocessing function with a name_scope.
# This will not be a problem if you save a preprocessed dataset
def pre_fn(tensors):
    with tf.name_scope("PRE") as scope:
        network = get_network(model.params['network'])
        tensors = network.preprocess(tensors)
    return tensors

train = lambda: dataset()['train'].apply(sparse_batch(100)).map(pre_fn).cache().repeat().shuffle(100)
test = lambda: dataset()['test'].apply(sparse_batch(100))

# Running specs
train_spec = tf.estimator.TrainSpec(input_fn=train, max_steps=1e4)
eval_spec = tf.estimator.EvalSpec(input_fn=test, steps=100)
Appending to training.py

%%writefile -a training.py

tf.estimator.train_and_evaluate(model, train_spec, eval_spec)
Appending to training.py

!rm -rf /tmp/PiNet_QM9_pipeline
!konda run "python training.py"
INFO\:tensorflow\:Not using Distribute Coordinator.
INFO\:tensorflow\:Running training and evaluation locally (non-distributed).
INFO\:tensorflow\:Start train and evaluate loop. The evaluate will happen after every checkpoint. Checkpoint frequency is determined based on RunConfig arguments: save\_checkpoints\_steps None or save\_checkpoints\_secs 600.
INFO\:tensorflow\:Calling model\_fn.
12112 trainable vaiabless, training with float32 precision.
INFO\:tensorflow\:Done calling model\_fn.
INFO\:tensorflow\:Create CheckpointSaverHook.
INFO\:tensorflow\:Graph was finalized.
INFO\:tensorflow\:Running local\_init\_op.
INFO\:tensorflow\:Done running local\_init\_op.
INFO\:tensorflow\:Calling checkpoint listeners before saving checkpoint 0...
INFO\:tensorflow\:Saving checkpoints for 0 into /tmp/PiNet\_QM9\_pipeline/model.ckpt.
INFO\:tensorflow\:Calling checkpoint listeners after saving checkpoint 0...
INFO\:tensorflow\:loss = 1608.7036, step = 0
INFO\:tensorflow\:global\_step/sec: 11.2424
INFO\:tensorflow\:loss = 309.28052, step = 500 (44.477 sec)
INFO\:tensorflow\:global\_step/sec: 11.6739
INFO\:tensorflow\:loss = 147.40509, step = 1000 (42.830 sec)
INFO\:tensorflow\:global\_step/sec: 25.8236
INFO\:tensorflow\:loss = 115.164055, step = 1500 (19.362 sec)
INFO\:tensorflow\:global\_step/sec: 26.4694
INFO\:tensorflow\:loss = 126.90699, step = 2000 (18.894 sec)
INFO\:tensorflow\:global\_step/sec: 26.1443
INFO\:tensorflow\:loss = 103.33997, step = 2500 (19.120 sec)
INFO\:tensorflow\:global\_step/sec: 26.1268
INFO\:tensorflow\:loss = 96.97985, step = 3000 (19.137 sec)
INFO\:tensorflow\:global\_step/sec: 25.9872
INFO\:tensorflow\:loss = 107.959435, step = 3500 (19.241 sec)
INFO\:tensorflow\:global\_step/sec: 26.0982
INFO\:tensorflow\:loss = 83.18972, step = 4000 (19.158 sec)
INFO\:tensorflow\:global\_step/sec: 26.2075
INFO\:tensorflow\:loss = 70.3028, step = 4500 (19.080 sec)
INFO\:tensorflow\:global\_step/sec: 25.9199
INFO\:tensorflow\:loss = 84.25394, step = 5000 (19.289 sec)
INFO\:tensorflow\:global\_step/sec: 26.4121
INFO\:tensorflow\:loss = 129.86829, step = 5500 (18.930 sec)
INFO\:tensorflow\:global\_step/sec: 25.8288
INFO\:tensorflow\:loss = 132.20454, step = 6000 (19.359 sec)
INFO\:tensorflow\:global\_step/sec: 26.261
INFO\:tensorflow\:loss = 69.64721, step = 6500 (19.038 sec)
INFO\:tensorflow\:global\_step/sec: 26.1977
INFO\:tensorflow\:loss = 62.85822, step = 7000 (19.086 sec)
INFO\:tensorflow\:global\_step/sec: 26.0748
INFO\:tensorflow\:loss = 69.52461, step = 7500 (19.176 sec)
INFO\:tensorflow\:global\_step/sec: 26.3489
INFO\:tensorflow\:loss = 93.84022, step = 8000 (18.975 sec)
INFO\:tensorflow\:global\_step/sec: 25.3495
INFO\:tensorflow\:loss = 97.3127, step = 8500 (19.724 sec)
INFO\:tensorflow\:global\_step/sec: 25.7534
INFO\:tensorflow\:loss = 43.729958, step = 9000 (19.416 sec)
INFO\:tensorflow\:global\_step/sec: 26.3466
INFO\:tensorflow\:loss = 41.565964, step = 9500 (18.977 sec)
INFO\:tensorflow\:Calling checkpoint listeners before saving checkpoint 10000...
INFO\:tensorflow\:Saving checkpoints for 10000 into /tmp/PiNet\_QM9\_pipeline/model.ckpt.
INFO\:tensorflow\:Calling checkpoint listeners after saving checkpoint 10000...
INFO\:tensorflow\:Calling model\_fn.
INFO\:tensorflow\:Done calling model\_fn.
INFO\:tensorflow\:Starting evaluation at 2021-05-31T15:01:59
INFO\:tensorflow\:Graph was finalized.
INFO\:tensorflow\:Restoring parameters from /tmp/PiNet\_QM9\_pipeline/model.ckpt-10000
INFO\:tensorflow\:Running local\_init\_op.
INFO\:tensorflow\:Done running local\_init\_op.
INFO\:tensorflow\:Evaluation [10/100]
INFO\:tensorflow\:Evaluation [20/100]
INFO\:tensorflow\:Evaluation [30/100]
INFO\:tensorflow\:Evaluation [40/100]
INFO\:tensorflow\:Evaluation [50/100]
INFO\:tensorflow\:Evaluation [60/100]
INFO\:tensorflow\:Evaluation [70/100]
INFO\:tensorflow\:Evaluation [80/100]
INFO\:tensorflow\:Evaluation [90/100]
INFO\:tensorflow\:Evaluation [100/100]
INFO\:tensorflow\:Inference Time : 10.84179s
INFO\:tensorflow\:Finished evaluation at 2021-05-31-15:02:10
INFO\:tensorflow\:Saving dict for global step 10000: METRICS/E\_LOSS = 71.01845, METRICS/E\_MAE = 5.8880224, METRICS/E\_RMSE = 8.427245, global\_step = 10000, loss = 71.01845
INFO\:tensorflow\:Saving 'checkpoint\_path' summary for global step 10000: /tmp/PiNet\_QM9\_pipeline/model.ckpt-10000
INFO\:tensorflow\:Loss for final step: 82.67876.
({'METRICS/E_LOSS': 71.01845, 'METRICS/E_MAE': 5.8880224, 'METRICS/E_RMSE': 8.427245, 'loss': 71.01845, 'global_step': 10000}, [])

<>:4: SyntaxWarning: invalid escape sequence '\:'
<>:4: SyntaxWarning: invalid escape sequence '\:'
/tmp/ipykernel_43351/3722440700.py:4: SyntaxWarning: invalid escape sequence '\:'
  print("INFO\:tensorflow\:Not using Distribute Coordinator.\nINFO\:tensorflow\:Running training and evaluation locally (non-distributed).\nINFO\:tensorflow\:Start train and evaluate loop. The evaluate will happen after every checkpoint. Checkpoint frequency is determined based on RunConfig arguments: save\_checkpoints\_steps None or save\_checkpoints\_secs 600.\nINFO\:tensorflow\:Calling model\_fn.\n12112 trainable vaiabless, training with float32 precision.\nINFO\:tensorflow\:Done calling model\_fn.\nINFO\:tensorflow\:Create CheckpointSaverHook.\nINFO\:tensorflow\:Graph was finalized.\nINFO\:tensorflow\:Running local\_init\_op.\nINFO\:tensorflow\:Done running local\_init\_op.\nINFO\:tensorflow\:Calling checkpoint listeners before saving checkpoint 0...\nINFO\:tensorflow\:Saving checkpoints for 0 into /tmp/PiNet\_QM9\_pipeline/model.ckpt.\nINFO\:tensorflow\:Calling checkpoint listeners after saving checkpoint 0...\nINFO\:tensorflow\:loss = 1608.7036, step = 0\nINFO\:tensorflow\:global\_step/sec: 11.2424\nINFO\:tensorflow\:loss = 309.28052, step = 500 (44.477 sec)\nINFO\:tensorflow\:global\_step/sec: 11.6739\nINFO\:tensorflow\:loss = 147.40509, step = 1000 (42.830 sec)\nINFO\:tensorflow\:global\_step/sec: 25.8236\nINFO\:tensorflow\:loss = 115.164055, step = 1500 (19.362 sec)\nINFO\:tensorflow\:global\_step/sec: 26.4694\nINFO\:tensorflow\:loss = 126.90699, step = 2000 (18.894 sec)\nINFO\:tensorflow\:global\_step/sec: 26.1443\nINFO\:tensorflow\:loss = 103.33997, step = 2500 (19.120 sec)\nINFO\:tensorflow\:global\_step/sec: 26.1268\nINFO\:tensorflow\:loss = 96.97985, step = 3000 (19.137 sec)\nINFO\:tensorflow\:global\_step/sec: 25.9872\nINFO\:tensorflow\:loss = 107.959435, step = 3500 (19.241 sec)\nINFO\:tensorflow\:global\_step/sec: 26.0982\nINFO\:tensorflow\:loss = 83.18972, step = 4000 (19.158 sec)\nINFO\:tensorflow\:global\_step/sec: 26.2075\nINFO\:tensorflow\:loss = 70.3028, step = 4500 (19.080 sec)\nINFO\:tensorflow\:global\_step/sec: 25.9199\nINFO\:tensorflow\:loss = 84.25394, step = 5000 (19.289 sec)\nINFO\:tensorflow\:global\_step/sec: 26.4121\nINFO\:tensorflow\:loss = 129.86829, step = 5500 (18.930 sec)\nINFO\:tensorflow\:global\_step/sec: 25.8288\nINFO\:tensorflow\:loss = 132.20454, step = 6000 (19.359 sec)\nINFO\:tensorflow\:global\_step/sec: 26.261\nINFO\:tensorflow\:loss = 69.64721, step = 6500 (19.038 sec)\nINFO\:tensorflow\:global\_step/sec: 26.1977\nINFO\:tensorflow\:loss = 62.85822, step = 7000 (19.086 sec)\nINFO\:tensorflow\:global\_step/sec: 26.0748\nINFO\:tensorflow\:loss = 69.52461, step = 7500 (19.176 sec)\nINFO\:tensorflow\:global\_step/sec: 26.3489\nINFO\:tensorflow\:loss = 93.84022, step = 8000 (18.975 sec)\nINFO\:tensorflow\:global\_step/sec: 25.3495\nINFO\:tensorflow\:loss = 97.3127, step = 8500 (19.724 sec)\nINFO\:tensorflow\:global\_step/sec: 25.7534\nINFO\:tensorflow\:loss = 43.729958, step = 9000 (19.416 sec)\nINFO\:tensorflow\:global\_step/sec: 26.3466\nINFO\:tensorflow\:loss = 41.565964, step = 9500 (18.977 sec)\nINFO\:tensorflow\:Calling checkpoint listeners before saving checkpoint 10000...\nINFO\:tensorflow\:Saving checkpoints for 10000 into /tmp/PiNet\_QM9\_pipeline/model.ckpt.\nINFO\:tensorflow\:Calling checkpoint listeners after saving checkpoint 10000...\nINFO\:tensorflow\:Calling model\_fn.\nINFO\:tensorflow\:Done calling model\_fn.\nINFO\:tensorflow\:Starting evaluation at 2021-05-31T15:01:59\nINFO\:tensorflow\:Graph was finalized.\nINFO\:tensorflow\:Restoring parameters from /tmp/PiNet\_QM9\_pipeline/model.ckpt-10000\nINFO\:tensorflow\:Running local\_init\_op.\nINFO\:tensorflow\:Done running local\_init\_op.\nINFO\:tensorflow\:Evaluation [10/100]\nINFO\:tensorflow\:Evaluation [20/100]\nINFO\:tensorflow\:Evaluation [30/100]\nINFO\:tensorflow\:Evaluation [40/100]\nINFO\:tensorflow\:Evaluation [50/100]\nINFO\:tensorflow\:Evaluation [60/100]\nINFO\:tensorflow\:Evaluation [70/100]\nINFO\:tensorflow\:Evaluation [80/100]\nINFO\:tensorflow\:Evaluation [90/100]\nINFO\:tensorflow\:Evaluation [100/100]\nINFO\:tensorflow\:Inference Time : 10.84179s\nINFO\:tensorflow\:Finished evaluation at 2021-05-31-15:02:10\nINFO\:tensorflow\:Saving dict for global step 10000: METRICS/E\_LOSS = 71.01845, METRICS/E\_MAE = 5.8880224, METRICS/E\_RMSE = 8.427245, global\_step = 10000, loss = 71.01845\nINFO\:tensorflow\:Saving 'checkpoint\_path' summary for global step 10000: /tmp/PiNet\_QM9\_pipeline/model.ckpt-10000\nINFO\:tensorflow\:Loss for final step: 82.67876.\n({'METRICS/E_LOSS': 71.01845, 'METRICS/E_MAE': 5.8880224, 'METRICS/E_RMSE': 8.427245, 'loss': 71.01845, 'global_step': 10000}, [])")

Monitoring

It's recommended to monitor the training with Tensorboard instead of the stdout here.
Try tensorboard --logdir /tmp

Parallelization with tf.Estimator

The estimator api makes it extremely easy to train on multiple GPUs.

# suppose you have two cards
distribution = tf.distribute.MirroredStrategy(["GPU:0", "GPU:1"])
config = tf.estimator.RunConfig(train_distribute=distribution)

Conclusions

Congratulations! You can now train atomic neural networks with state-of-the-art accuracy and speed.

But there's more. With PiNN, the components of ANNs are modulized. Read the following notebooks to see how you can build your own ANN.

« Previous
Next »