Menyimpan dan memuat model

Liht di TensorFlow.org Jalankan di Google Colab Lihat kode di GitHub Unduh notebook

Progres dari model dapat disimpan ketika proses training dan setelah training. Ini berarti sebuah model dapat melanjutkan proses training dengan kondisi yang sama dengan ketika proses training sebelumnya dihentikan dan dapat menghindari waktu training yang panjng. Menyimpan juga berarti Anda dapat membagikan model Anda dan orang lain dapat membuat ulang proyek Anda. Ketika mempublikasikan hasil riset dan teknik dari suatu model, kebanyakan praktisi machine learning membagikan:

  • kode untuk membuat model, dan
  • berat, atau parameter, dari sebuah model

Membagikan data ini akan membantu orang lain untuk memahami bagaimana model bekerja dan mencoba model tersebut dengan data yang baru.

Perhatian: Hati-hati dengan kode yang tidak dapat dipercaya—model-model TensorFlow adalah kode. Lihat Menggunakan TensorFlow dengan aman untuk lebih detail.

Opsi

Terdapat beberapa cara untuk menyimpan model TensorFlow—bergantung kepada API yang Anda gunakan. Panduan ini menggunakan tf.keras, sebuah API tingkat tinggi yang digunakan untuk membangun dan melatih model di TensorFlow. Untuk pendekatan lainnya, lihat panduan Tensorflow Simpan dan Restorasi atau Simpan sesuai keinginan.

Pengaturan

Instal dan import

Install dan import TensorFlow dan beberapa dependency:

try:
  # %tensorflow_version only exists in Colab.
  %tensorflow_version 2.x
except Exception:
  pass

!pip install -q pyyaml h5py  # Required to save models in HDF5 format
WARNING: You are using pip version 20.2.2; however, version 20.2.3 is available.
You should consider upgrading via the '/tmpfs/src/tf_docs_env/bin/python -m pip install --upgrade pip' command.

from __future__ import absolute_import, division, print_function, unicode_literals

import os

import tensorflow as tf
from tensorflow import keras

print(tf.version.VERSION)
2.3.0

Memperoleh dataset

Untuk menunjukan bagaimana cara untuk menyimpan dan memuat berat dari model, Anda akan menggunakan Dataset MNIST. Untuk mempercepat operasi ini, gunakan hanya 1000 data pertama:

(train_images, train_labels), (test_images, test_labels) = tf.keras.datasets.mnist.load_data()

train_labels = train_labels[:1000]
test_labels = test_labels[:1000]

train_images = train_images[:1000].reshape(-1, 28 * 28) / 255.0
test_images = test_images[:1000].reshape(-1, 28 * 28) / 255.0
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step

Mendefinisikan sebuah model

Mulai dengan membangun sebuah model sekuensial sederhana:

# Define a simple sequential model
def create_model():
  model = tf.keras.models.Sequential([
    keras.layers.Dense(512, activation='relu', input_shape=(784,)),
    keras.layers.Dropout(0.2),
    keras.layers.Dense(10, activation='softmax')
  ])

  model.compile(optimizer='adam',
                loss='sparse_categorical_crossentropy',
                metrics=['accuracy'])

  return model

# Create a basic model instance
model = create_model()

# Display the model's architecture
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                (None, 512)               401920    
_________________________________________________________________
dropout (Dropout)            (None, 512)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Menyimpan cek poin ketika proses training

You can use a trained model without having to retrain it, or pick-up training where you left off—in case the training process was interrupted. The tf.keras.callbacks.ModelCheckpoint callback allows to continually save the model both during and at the end of training.

Anda dapat menggunakan model terlatih tanpa harus melatihnya kembali, atau melanjutkan proses training di titik di mana proses training sebelumnya berhenti. Callback tf.keras.callbacks.ModelCheckpoint memungkinkan sebuah model untuk disimpan ketika dan setelah proses training dilakukan.

Penggunaan callback cek poin

Buat sebuah callback tf.keras.callbacks.ModelCheckpoint yang menyimpan berat hanya ketika proses training berlangsung:

checkpoint_path = "training_1/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Create a callback that saves the model's weights
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
                                                 save_weights_only=True,
                                                 verbose=1)

# Train the model with the new callback
model.fit(train_images, 
          train_labels,  
          epochs=10,
          validation_data=(test_images,test_labels),
          callbacks=[cp_callback])  # Pass callback to training

# This may generate warnings related to saving the state of the optimizer.
# These warnings (and similar warnings throughout this notebook)
# are in place to discourage outdated usage, and can be ignored.
Epoch 1/10
30/32 [===========================>..] - ETA: 0s - loss: 1.2039 - accuracy: 0.6479
Epoch 00001: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 8ms/step - loss: 1.1789 - accuracy: 0.6550 - val_loss: 0.7509 - val_accuracy: 0.7680
Epoch 2/10
32/32 [==============================] - ETA: 0s - loss: 0.4260 - accuracy: 0.8810
Epoch 00002: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.4260 - accuracy: 0.8810 - val_loss: 0.5580 - val_accuracy: 0.8160
Epoch 3/10
30/32 [===========================>..] - ETA: 0s - loss: 0.3025 - accuracy: 0.9104
Epoch 00003: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.2981 - accuracy: 0.9120 - val_loss: 0.4835 - val_accuracy: 0.8410
Epoch 4/10
31/32 [============================>.] - ETA: 0s - loss: 0.2118 - accuracy: 0.9486
Epoch 00004: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.2106 - accuracy: 0.9490 - val_loss: 0.4706 - val_accuracy: 0.8470
Epoch 5/10
31/32 [============================>.] - ETA: 0s - loss: 0.1634 - accuracy: 0.9627
Epoch 00005: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1641 - accuracy: 0.9620 - val_loss: 0.4228 - val_accuracy: 0.8550
Epoch 6/10
31/32 [============================>.] - ETA: 0s - loss: 0.1118 - accuracy: 0.9758
Epoch 00006: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1123 - accuracy: 0.9760 - val_loss: 0.4340 - val_accuracy: 0.8610
Epoch 7/10
31/32 [============================>.] - ETA: 0s - loss: 0.0908 - accuracy: 0.9849
Epoch 00007: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0904 - accuracy: 0.9850 - val_loss: 0.4191 - val_accuracy: 0.8570
Epoch 8/10
31/32 [============================>.] - ETA: 0s - loss: 0.0635 - accuracy: 0.9919
Epoch 00008: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0632 - accuracy: 0.9920 - val_loss: 0.4128 - val_accuracy: 0.8620
Epoch 9/10
31/32 [============================>.] - ETA: 0s - loss: 0.0553 - accuracy: 0.9950
Epoch 00009: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0551 - accuracy: 0.9950 - val_loss: 0.4105 - val_accuracy: 0.8650
Epoch 10/10
31/32 [============================>.] - ETA: 0s - loss: 0.0390 - accuracy: 0.9990
Epoch 00010: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0388 - accuracy: 0.9990 - val_loss: 0.4074 - val_accuracy: 0.8740

<tensorflow.python.keras.callbacks.History at 0x7f5985823e48>

This creates a single collection of TensorFlow checkpoint files that are updated at the end of each epoch:

ls {checkpoint_dir}
checkpoint  cp.ckpt.data-00000-of-00001  cp.ckpt.index

Create a new, untrained model. When restoring a model from weights-only, you must have a model with the same architecture as the original model. Since it's the same model architecture, you can share weights despite that it's a different instance of the model.

Now rebuild a fresh, untrained model, and evaluate it on the test set. An untrained model will perform at chance levels (~10% accuracy):

# Create a basic model instance
model = create_model()

# Evaluate the model
loss, acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Untrained model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 2.3442 - accuracy: 0.1380
Untrained model, accuracy: 13.80%

Then load the weights from the checkpoint and re-evaluate:

# Loads the weights
model.load_weights(checkpoint_path)

# Re-evaluate the model
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 0.4074 - accuracy: 0.8740
Restored model, accuracy: 87.40%

Checkpoint callback options

The callback provides several options to provide unique names for checkpoints and adjust the checkpointing frequency.

Train a new model, and save uniquely named checkpoints once every five epochs:

# Include the epoch in the file name (uses `str.format`)
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# Create a callback that saves the model's weights every 5 epochs
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_path, 
    verbose=1, 
    save_weights_only=True,
    period=5)

# Create a new model instance
model = create_model()

# Save the weights using the `checkpoint_path` format
model.save_weights(checkpoint_path.format(epoch=0))

# Train the model with the new callback
model.fit(train_images, 
              train_labels,
              epochs=50, 
              callbacks=[cp_callback],
              validation_data=(test_images,test_labels),
              verbose=0)
WARNING:tensorflow:`period` argument is deprecated. Please use `save_freq` to specify the frequency in number of batches seen.
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.iter
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_1
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_2
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/guide/checkpoint#loading_mechanics for details.

Epoch 00005: saving model to training_2/cp-0005.ckpt

Epoch 00010: saving model to training_2/cp-0010.ckpt

Epoch 00015: saving model to training_2/cp-0015.ckpt

Epoch 00020: saving model to training_2/cp-0020.ckpt

Epoch 00025: saving model to training_2/cp-0025.ckpt

Epoch 00030: saving model to training_2/cp-0030.ckpt

Epoch 00035: saving model to training_2/cp-0035.ckpt

Epoch 00040: saving model to training_2/cp-0040.ckpt

Epoch 00045: saving model to training_2/cp-0045.ckpt

Epoch 00050: saving model to training_2/cp-0050.ckpt

<tensorflow.python.keras.callbacks.History at 0x7f59d56cce80>

Sekarang, lihat hasil cek poin dan pilih yang terbaru:

ls {checkpoint_dir}
checkpoint            cp-0025.ckpt.index
cp-0000.ckpt.data-00000-of-00001  cp-0030.ckpt.data-00000-of-00001
cp-0000.ckpt.index        cp-0030.ckpt.index
cp-0005.ckpt.data-00000-of-00001  cp-0035.ckpt.data-00000-of-00001
cp-0005.ckpt.index        cp-0035.ckpt.index
cp-0010.ckpt.data-00000-of-00001  cp-0040.ckpt.data-00000-of-00001
cp-0010.ckpt.index        cp-0040.ckpt.index
cp-0015.ckpt.data-00000-of-00001  cp-0045.ckpt.data-00000-of-00001
cp-0015.ckpt.index        cp-0045.ckpt.index
cp-0020.ckpt.data-00000-of-00001  cp-0050.ckpt.data-00000-of-00001
cp-0020.ckpt.index        cp-0050.ckpt.index
cp-0025.ckpt.data-00000-of-00001

latest = tf.train.latest_checkpoint(checkpoint_dir)
latest
'training_2/cp-0050.ckpt'

Catatan: secara default format tensorflow hanya menyimpan 5 cek poin terbaru.

Untuk tes, reset model dan muat cek poin terakhir:

# Create a new model instance
model = create_model()

# Load the previously saved weights
model.load_weights(latest)

# Re-evaluate the model
loss, acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 0.4764 - accuracy: 0.8710
Restored model, accuracy: 87.10%

Apa sajakah file-file ini?

Kode di atas menyimpan berat dari model ke sebuah kumpulan cek poin-file yang hanya berisikan berat dari model yan sudah dilatih dalam format biner. Cek poin terdiri atas:

  • Satu atau lebih bagian (shard) yang berisi berat dari model Anda.
  • Sebuah file index yang mengindikasikan suatu berat disimpan pada shard yang mana.

Jika Anda hanya melakukan proses training dari sebuah model pada sebuah komputer, Anda akan hanya memiliki satu shard dengan sufiks .data-00000-of-00001

Menyimpan berat secara manual

Anda telah melihat bagaimana caranya memuat berat yang telah disimpan sebelumnya menjadi model. Menyimpannya secara manual dapat dilakukan dengan mudah dengan method Model.save_weights. Secara default, tf.keras—dan save_weights menggunakan format TensorFlow cek poin dengan ekstensi .ckpt (menyimpan dalam format HDF5 dengan ekstensi .h5 dijelaskan dalam panduan ini Menyimpan dan serialisasi model):

# Save the weights
model.save_weights('./checkpoints/my_checkpoint')

# Create a new model instance
model = create_model()

# Restore the weights
model.load_weights('./checkpoints/my_checkpoint')

# Evaluate the model
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.iter
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_1
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.beta_2
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.decay
WARNING:tensorflow:Unresolved object in checkpoint: (root).optimizer.learning_rate
WARNING:tensorflow:A checkpoint was restored (e.g. tf.train.Checkpoint.restore or tf.keras.Model.load_weights) but not all checkpointed values were used. See above for specific issues. Use expect_partial() on the load status object, e.g. tf.train.Checkpoint.restore(...).expect_partial(), to silence these warnings, or use assert_consumed() to make the check explicit. See https://www.tensorflow.org/guide/checkpoint#loading_mechanics for details.
32/32 - 0s - loss: 0.4764 - accuracy: 0.8710
Restored model, accuracy: 87.10%

Menyimpan keseluruhan model

Gunakan model.save untuk menyimpan arsitektur dari model, berat, dan konfigurasi training dalam satu file/folder. Hal ini menyebabkan Anda dapat melakukan ekspor dari suatu model sehingga model tersebut dapat digunakan tanpa harus mengakses kode Python secara langsung*. Karena kondisi optimizer dipulihkan, Anda dapat melanjutkan proses training tepat ketika proses training sebelumnya ditinggalkan.

Meneyimpan sebuah model fungsional sangat berguna—Anda dapat memuatnya di TensorFlow.js HDF5, Saved Model) dan kemudian melatih dan menggunakan model tersebut di web browser, atau mengubahnya sehingga dapat beroperasi di perangkat mobile menggunakan TensorFlw Lite HDF5, Saved Model)

*Objek-objek custom (model subkelas atau layer) membutuhkan perhatian lebih ketika proses disimpan atau dimuat. Lihat bagian Penyimpanan objek custom di bawah.

Format HDF5

Keras menyediakan format penyimpanan menggunakan menggunakan HDF5

# Create and train a new model instance.
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# Save the entire model to a HDF5 file.
# The '.h5' extension indicates that the model shuold be saved to HDF5.
model.save('my_model.h5') 
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 1.1695 - accuracy: 0.6640
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4276 - accuracy: 0.8820
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2828 - accuracy: 0.9310
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2016 - accuracy: 0.9590
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1581 - accuracy: 0.9640

Sekarang, buat ulang model dari file tersebut:

# Recreate the exact same model, including its weights and the optimizer
new_model = tf.keras.models.load_model('my_model.h5')

# Show the model architecture
new_model.summary()
Model: "sequential_5"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_10 (Dense)             (None, 512)               401920    
_________________________________________________________________
dropout_5 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_11 (Dense)             (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Cek akurasi dari model:

loss, acc = new_model.evaluate(test_images,  test_labels, verbose=2)
print('Restored model, accuracy: {:5.2f}%'.format(100*acc))
32/32 - 0s - loss: 0.4310 - accuracy: 0.0890
Restored model, accuracy:  8.90%

Teknik ini menyimpan semuanya:

  • Nilai berat
  • Konfigurasi model (arsitektur)
  • konfigurasi dari optimizer

Keras menyimpan model dengan cara menginspeksi arsitekturnya. Saat ini, belum bisa menyimpan optimizer TensorFlow (dari tf.train). Ketika menggunakannya, Anda harus mengkompilasi kembali model setelah dimuat, dan Anda akan kehilangan kondisi dari optimizer.

Format SavedModel

Format SavedModel adalah suatu cara lainnya untuk melakukan serialisasi model. Model yang disimpan dalam format ini dapat direstorasi menggunakan tf.keras.models.load_model dan kompatibel dengan TensorFlow Serving. Panduan SavedModel menjelaskan detail bagaimana untuk menyediakan/memeriksa SavedModel. Kode di bawah ini mengilustrasikan langkah-langkah yang dilakukan untuk menyimpan dan memuat kembali model.

# Create and train a new model instance.
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# Save the entire model as a SavedModel.
!mkdir -p saved_model
model.save('saved_model/my_model') 
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 1.1342 - accuracy: 0.6850
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4186 - accuracy: 0.8790
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2816 - accuracy: 0.9350
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2114 - accuracy: 0.9500
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1429 - accuracy: 0.9730
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Model.state_updates (from tensorflow.python.keras.engine.training) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.6/site-packages/tensorflow/python/training/tracking/tracking.py:111: Layer.updates (from tensorflow.python.keras.engine.base_layer) is deprecated and will be removed in a future version.
Instructions for updating:
This property should not be used in TensorFlow 2.0, as updates are applied automatically.
INFO:tensorflow:Assets written to: saved_model/my_model/assets

Format SavedModel merupakan direktori yang berisi sebuah protobuf binary dan sebuah cek poin TensorFlow. Mememiksa direktori dari model tersimpan:

# my_model directory
ls saved_model

# Contains an assets folder, saved_model.pb, and variables folder.
ls saved_model/my_model
my_model
assets  saved_model.pb  variables

Muat ulang Keras model yang baru dari model tersimpan:

new_model = tf.keras.models.load_model('saved_model/my_model')

# Check its architecture
new_model.summary()
Model: "sequential_6"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_12 (Dense)             (None, 512)               401920    
_________________________________________________________________
dropout_6 (Dropout)          (None, 512)               0         
_________________________________________________________________
dense_13 (Dense)             (None, 10)                5130      
=================================================================
Total params: 407,050
Trainable params: 407,050
Non-trainable params: 0
_________________________________________________________________

Model yang sudah terestorasi dikompilasi dengan argument yang sama dengan model asli. Coba lakukan evaluasi dan prediksi menggunakan model tersebut:

# Evaluate the restored model
loss, acc = new_model.evaluate(test_images,  test_labels, verbose=2)
print('Restored model, accuracy: {:5.2f}%'.format(100*acc))

print(new_model.predict(test_images).shape)
32/32 - 0s - loss: 0.4346 - accuracy: 0.0820
Restored model, accuracy:  8.20%
(1000, 10)

Menyimpan objek custom

Apabila Anda menggunakan format SavedModel, Anda dapat melewati bagian ini. Perbedaan utama antara HDF5 dan SavedModel adalah HDF5 menggunakan konfigurasi objek untuk menyimpan arsitektur dari model, sementara SavedModel menyimpan execution graph. Sehingga, SavedModel dapat menyimpan objek custom seperti model subkelas dan layer custom tanpa membutuhkan kode yang asli.

Untuk menyimpan objek custom dalam bentuk HDF5, Anda harus melakukan hal sebagai berikut:

  1. Mendefinisikan sebuah method get_config pada objek Anda, dan mendefinisikan classmethod (opsional) from_config.
    • get_config(self) mengembalikan JSON-serializable dictionary dari parameter-parameter yang dibutuhkan untuk membuat kembali objek.
    • from_config(cls, config) menggunakan dan mengembalikan konfigurasi dari get_config untuk membuat objek baru. Secara default, fungsi ini menggunakan konfigurasi teresbut untuk menginisialisasi kwargs (return cls(**config)).
  2. Gunakan objek tersebut sebagai argumen dari custom_objects ketika memuat model. Argumen tersebut harus merupakan sebuah dictionary yang memetakan string dari nama kelas ke class Python. Misalkan tf.keras.models.load_model(path, custom_objects={'CustomLayer': CustomLayer})

Lihat tutorial Menulis layers and models from awal untuk contoh dari objek custom dan get_config.

# MIT License
#
# Copyright (c) 2017 François Chollet
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
# to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
# and/or sell copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.