Salvataggio e caricamento di modelli

Visualizza su TensorFlow.org Esegui in Google Colab Visualizza il sorgente su GitHub Scarica il notebook

I cambiamenti dei modelli durante-edopo-l'addestramento possono essere salvati. Ciò significa che un modello può riprendere da dove aveva lasciato, ed evitare lunghi tempi di addestramento. Il salvataggio significa anche che potete condividere il vostro modello ed altri possono ricreare il vostro lavoro. La maggioranza di chi lavora nel machine learning, quando pubblica modelli e tecniche di ricerca, condivide:

  • il codice per creare il modello, and
  • i trained weights, o i parametri, per il modello

Condividere questi dati aiuta gli altri a capire come lavora il modello e provarlo in autonomia con nuovi dati.

Attenzione: Siate cauti con il codice non fidato—i modelli TensorFlow sono codice. Per i dettagli vedere Uso sicuro di TensorFlow.

Opzioni

Ci sono diversi modi per salvare i modelli TensorFlow—a seconda delle API che state usando. Questa guida usa tf.keras, un'API di alto livello per costruire e addestrare modelli in TensorFlow. Per altri approcci, vedere la guida Salvataggio e Ripristino di TensorFlow o Salvataggio come eager.

Setup

Installazioni e importazioni

Installare e importare TensorFlow e dipendenze:

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

Procuriamoci un dataset di esempio

Per dimostrare come salvare e caricare weights, usiamo il dataset MNIST. Per velocizzare il processo, usiamo i primi 1000 esempi:

(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

Definiamo un modello

Cominciamo costruendo un semplice modello sequenziale:

# 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)
  ])

  model.compile(optimizer='adam',
                loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
                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
_________________________________________________________________

Salvare i punti di controllo durante l'addestramento

Potete usare un modello addestrato senza doverlo addestrare di nuovo, o riprendere l'addestramento da dove avevate lasciato, nel caso il processo di addestramento sia stato interrotto. La callback tf.keras.callbacks.ModelCheckpoint permette di salvare il modello continuamente sia durante sia alla fine dell'addestramento.

Utilizzo della callback Checkpoint

Creiamo una callback tf.keras.callbacks.ModelCheckpoint che salvi i weights solo durante l'addestramento:

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
27/32 [========================>.....] - ETA: 0s - loss: 1.2340 - accuracy: 0.6586
Epoch 00001: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 9ms/step - loss: 1.1394 - accuracy: 0.6860 - val_loss: 0.7377 - val_accuracy: 0.7700
Epoch 2/10
27/32 [========================>.....] - ETA: 0s - loss: 0.4473 - accuracy: 0.8715
Epoch 00002: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 5ms/step - loss: 0.4302 - accuracy: 0.8750 - val_loss: 0.5219 - val_accuracy: 0.8360
Epoch 3/10
28/32 [=========================>....] - ETA: 0s - loss: 0.2768 - accuracy: 0.9319
Epoch 00003: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 5ms/step - loss: 0.2838 - accuracy: 0.9300 - val_loss: 0.5095 - val_accuracy: 0.8280
Epoch 4/10
27/32 [========================>.....] - ETA: 0s - loss: 0.2420 - accuracy: 0.9387
Epoch 00004: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 5ms/step - loss: 0.2371 - accuracy: 0.9390 - val_loss: 0.4644 - val_accuracy: 0.8470
Epoch 5/10
28/32 [=========================>....] - ETA: 0s - loss: 0.1618 - accuracy: 0.9721
Epoch 00005: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1591 - accuracy: 0.9720 - val_loss: 0.4370 - val_accuracy: 0.8530
Epoch 6/10
27/32 [========================>.....] - ETA: 0s - loss: 0.1298 - accuracy: 0.9734
Epoch 00006: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1274 - accuracy: 0.9740 - val_loss: 0.4355 - val_accuracy: 0.8540
Epoch 7/10
28/32 [=========================>....] - ETA: 0s - loss: 0.1018 - accuracy: 0.9777
Epoch 00007: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1001 - accuracy: 0.9780 - val_loss: 0.4214 - val_accuracy: 0.8650
Epoch 8/10
27/32 [========================>.....] - ETA: 0s - loss: 0.0644 - accuracy: 0.9954
Epoch 00008: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0664 - accuracy: 0.9960 - val_loss: 0.4138 - val_accuracy: 0.8640
Epoch 9/10
28/32 [=========================>....] - ETA: 0s - loss: 0.0585 - accuracy: 0.9955
Epoch 00009: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0574 - accuracy: 0.9960 - val_loss: 0.4213 - val_accuracy: 0.8660
Epoch 10/10
28/32 [=========================>....] - ETA: 0s - loss: 0.0398 - accuracy: 0.9978
Epoch 00010: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 5ms/step - loss: 0.0404 - accuracy: 0.9980 - val_loss: 0.4216 - val_accuracy: 0.8670

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

Ciò crea un'unica collezione di file con i punti di controllo di TensorFlow che vengono salvati alla fine di ogni epoca:

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

Creiamo un nuovo modello, non addestrato. Quando ripristinate un modello dai soli weight, dovete avere un modello con la stessa architettura dell'originale. Dato che l'architettura del modello è la stessa, potete condividere gli weight anche se si tratta di una diversa istanza del modello.

Ora ricostruiamo una nuova istanza, non addestrata, del modello e valutiamola sull'insieme di test. Un modello non addestrato avrà prestazioni di livello basso (~10% di accuratezza):

# 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.4034 - accuracy: 0.0710
Untrained model, accuracy:  7.10%

Ora carichiamo i pesi dal punto di controllo e valutiamo di nuovo:

# 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.4216 - accuracy: 0.8670
Restored model, accuracy: 86.70%

Opzioni di callback del punto di controllo

La callback mette a disposizione diverse opzioni per dare un nome univoco ai punti di controllo e regolare la loro frequenza.

Addestriamo un nuovo modello, e salviamo i checkpoint con un nome univoco ogni cinque epoche:

# 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 0x7fe38d8c31d0>

Ora, guardiamo i punti di controllo che abbiamo ottenuto e prendiamo l'ultimo:

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'

Notare: che il formato standard di tensorflow salva solo i 5 punti di controllo più recenti.

Per verificare, riavviamo il modello e carichiamo il checkpoint più recente:

# 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.4850 - accuracy: 0.8730
Restored model, accuracy: 87.30%

Cosa sono questi file?

Il codice di cui sopra memorizza i pesi in una collezione di file formattati come checkpoint-che contengono solo i pesi addestrati in un formato binario. I checkpoint contengono:

  • Uno o più blocchi che contengono i pesi del nostro modello.
  • Un file indice che indica quali pesi sono memorizzati in un determinato blocco.

Se state solo addestrando un modello su una singola macchina, avrete un blocco con il suffisso: .data-00000-of-00001

Salvare i pesi manualmente

Avete visto come caricare i pesi in un modello. E' semplice salvarli manualmente con il metodo Model.save_weights. Per default, in particolare-tf.keras—and save_weights —usa il formato TensorFlow checkpoint con estensione .ckpt (il salvataggio in HDF5 con estensione .h5 è trattato nella guidaSalvare e serializzare i modelli):

# 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.4850 - accuracy: 0.8730
Restored model, accuracy: 87.30%

Salvare l'intero modello

Per salvare l'architettura di un modello, i pesi, e la configurazione di addestramento in un singolo file/cartella, occorre chiamare model.save. Ciò vi permette di esportare un modello in modo che possa essere usato senza accedere al codice* Python originale. Dato che viene ripristinato lo stato dell'ottimizzatore, potrete riprendere l'addestramento esattamente da dove l'avevate lasciato.

Un intero modello può essere salvato in due formati di file diversi (SavedModel e HDF). Occorre notare che il formato TensodrFlow SavedModel è il default in TF2.x. Comunque, un modello può essere salvato anche in formato HDF5. Maggiori dettagli sul salvataggio dei modelli nei due formati sono dati in seguito.

Il salvataggio di un modello completamente funzionante è molto utile—lo potete caricare in un TensorFlow.js (HDF5, Modello Salvato) e poi addestrarlo ed eseguirlo in un browser web, o convertirlo per eseguirlo su un dispositivo mobile usando TensorFlow Lite (HDF5, Modello Salvato)

*Oggetti personalizzati (es. modelli specializzati o livelli) richiedono particolare attenzione durante il salvataggio e il caricamento. Vedere la sezione Salvataggio di oggetti personalizzati più sotto

formato HDF5

Keras fornisce un formato base di salvataggio che usa lo standard 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 should be saved to HDF5.
model.save('my_model.h5') 
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 1.2001 - accuracy: 0.6630
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4343 - accuracy: 0.8810
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2939 - accuracy: 0.9280
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2183 - accuracy: 0.9440
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1606 - accuracy: 0.9620

Ora, ri-creiamo il modello dal file:

# 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
_________________________________________________________________

E controlliamo la sua accuratezza:

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.4208 - accuracy: 0.0880
Restored model, accuracy:  8.80%

Questa tecnica salva ogni cosa:

  • I valori dei pesi
  • L'architettura del modello
  • La configurazione di addestramento del modello (ciò che avete passato al compilatore)
  • L'ottimizzatore ed il suo stato, se ce n'è uno (questo è ciò che vi permette di riprendere l'addestramento da dove l'avete lasciato)

Keras salva i modelli ispezionando l'architettura. Al momento, esso non è in grado di salvare gli ottimizzatori 'v1.x' (da tf.compat.v1.train) in quanto essi non sono compatibili con i punti di controllo. Quando li doveste usare, avrete bisogno di ri-compilare il modello dopo il caricamento, e perderete lo stato dell'ottimizzatore.

formato SavedModel

Il formato SavedModel è un'altro modo di serializzare i modelli. I modelli salvati in questo formato possono essere ripristinati usando tf.keras.models.load_model e sono compatibili con TensorFlow Serving. La guida SavedModel scende nei dettagli su come utilizzare/ispezionare il SavedModel. La sezione che segue illustra i passi di salvataggio e recupero del modello.

# 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.1166 - accuracy: 0.6840
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4198 - accuracy: 0.8880
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2705 - accuracy: 0.9300
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1963 - accuracy: 0.9590
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1523 - accuracy: 0.9700
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

Il formato SavedModel è una directory che contiene un binario protobuf ed un checkpoint Tensorflow. Osserviamo la directory del modello salvato:

# 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

Carichiamo un nuovo modello Keras dal modello salvato:

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
_________________________________________________________________

Il modello ripristinato viene compilato con gli stessi argomenti del modello originale. Proviamo ad eseguire, valutare e predire con il modello caricato:

# 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.4227 - accuracy: 0.0880
Restored model, accuracy:  8.80%
(1000, 10)

Salvataggio di oggetti personalizzati

Se usate il formato SavedModel potete saltare questa sezione. La differenza chiave tra HDF5 e SavedModel è che HDF5 usa le gli oggetti configs per salvare l'architettura del modello, mentre SavedModel salva il grafo di esecuzione. Così, i SavedModel sono in grado di salvare oggetti personalizzati come modelli derivati per specializzazione e livelli personalizzati senza bisogno del codice originale.

Per salvare oggetti personalizzati in HDF5, dovete:

  1. Definire un metodo get_config nel vostro oggetto, e and facoltativamente un classmethod from_config.
    • get_config(self) ritorna un dizionario JSON-serializzabile dei parametri necessari per ri-creare l'oggetto.
    • from_config(cls, config) usa il config restituito da get_config per creare un nuovo oggetto. Per, questa funzione userà il config come kwargs di inizializzazioone (return cls(**config)).
  2. Passare l'oggetto come argomento custom_objects al caricamento del modello. L'argomento deve essere un dizionario che mappi la stringa del nome della classe name nella classe Python. Es. tf.keras.models.load_model(path, custom_objects={'CustomLayer': CustomLayer})

Vedere il tutorial Scrivere livelli e modelli da zero per esempi di oggetti personalizzati e 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.