モデルの保存と復元

View on TensorFlow.org Run in Google Colab View source on GitHub Download notebook

モデルは訓練中にも、訓練が終わったあとも保存できます。このことは、長い訓練時間を掛けなくても、やめたところから再開できるということを意味します。モデルが保存可能であることは、あなたが作ったモデルを他の人と共有できるということでもあります。研究結果であるモデルや手法を公開する際、機械学習の実務家はほとんど次のものを共有します。

  • モデルを構築するプログラム
  • 学習済みモデルの重みあるいはパラメータ

このデータを共有することで、他の人がモデルだどの様に動作するかを理解したり、新しいデータに試してみたりすることが容易になります。

注意:信頼できないプログラムには気をつけましょう。TensorFlowのモデルもプログラムです。詳しくは、Using TensorFlow Securelyを参照してください。

オプション

TensorFlowのモデルを保存する方法は、使っているAPIによって異なります。このガイドはTensorFlowのモデルを構築し訓練するためのハイレベルなAPIであるtf.kerasを使っています。この他のアプローチについては、TensorFlowの Save and Restore ガイド、あるいは、Saving in eagerを参照してください。

設定

インストールとインポート

TensorFlowと依存関係のライブラリをインストールし、インポートします。

pip install -q pyyaml h5py  # HDF5フォーマットでモデルを保存するために必要
import os

import tensorflow as tf
from tensorflow import keras

print(tf.version.VERSION)
2.3.0

サンプルデータセットの取得

ここでは、モデルを訓練し重みの保存をデモするために、 MNIST dataset を使います。デモの実行を速くするため、最初の1,000件のサンプルだけを使います。

(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

モデルの定義

重みの保存と読み込みのデモを行うための簡単なモデルを定義しましょう。

# 短いシーケンシャルモデルを返す関数
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


# 基本的なモデルのインスタンスを作成
model = create_model()

# モデルの構造を表示
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
_________________________________________________________________

訓練中にチェックポイントを保存する

主な用途は訓練の途中あるいは終了後にチェックポイントを自動的に保存することです。こうすることにより、再び訓練を行うことなくモデルを使用することができ、また、訓練が中断された場合に、中止したところから再開できます。

tf.keras.callbacks.ModelCheckpointがこれを行うためのコールバックです。このコールバックにはチェックポイントを構成するためのいくつかの引数があります。

チェックポイントコールバックの使い方

訓練の途中でのみ重みを保存するよう tf.keras.callbacks.ModelCheckpoint コールバックを作成します。

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

# チェックポイントコールバックを作る
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, 
                                                 save_weights_only=True,
                                                 verbose=1)

# 新しいコールバックを用いるようモデルを訓練
model.fit(train_images, 
          train_labels,  
          epochs=10,
          validation_data=(test_images,test_labels),
          callbacks=[cp_callback])  # 訓練にコールバックを渡す

# オプティマイザの状態保存についての警告が表示されるかもしれません。
# これらの警告は(このノートブックで発生する同様な警告を含めて)
# 古い用法を非推奨にするためのもので、無視して構いません。
Epoch 1/10
29/32 [==========================>...] - ETA: 0s - loss: 1.2312 - accuracy: 0.6444
Epoch 00001: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 8ms/step - loss: 1.1866 - accuracy: 0.6550 - val_loss: 0.7670 - val_accuracy: 0.7670
Epoch 2/10
30/32 [===========================>..] - ETA: 0s - loss: 0.4359 - accuracy: 0.8844
Epoch 00002: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 5ms/step - loss: 0.4266 - accuracy: 0.8880 - val_loss: 0.5315 - val_accuracy: 0.8370
Epoch 3/10
30/32 [===========================>..] - ETA: 0s - loss: 0.2973 - accuracy: 0.9156
Epoch 00003: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.2916 - accuracy: 0.9170 - val_loss: 0.4916 - val_accuracy: 0.8460
Epoch 4/10
30/32 [===========================>..] - ETA: 0s - loss: 0.2169 - accuracy: 0.9458
Epoch 00004: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.2155 - accuracy: 0.9470 - val_loss: 0.4472 - val_accuracy: 0.8600
Epoch 5/10
30/32 [===========================>..] - ETA: 0s - loss: 0.1554 - accuracy: 0.9635
Epoch 00005: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1554 - accuracy: 0.9630 - val_loss: 0.4123 - val_accuracy: 0.8700
Epoch 6/10
30/32 [===========================>..] - ETA: 0s - loss: 0.1302 - accuracy: 0.9719
Epoch 00006: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.1369 - accuracy: 0.9710 - val_loss: 0.4198 - val_accuracy: 0.8580
Epoch 7/10
30/32 [===========================>..] - ETA: 0s - loss: 0.0901 - accuracy: 0.9854
Epoch 00007: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0951 - accuracy: 0.9840 - val_loss: 0.4089 - val_accuracy: 0.8720
Epoch 8/10
31/32 [============================>.] - ETA: 0s - loss: 0.0697 - accuracy: 0.9929
Epoch 00008: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0694 - accuracy: 0.9930 - val_loss: 0.4033 - val_accuracy: 0.8740
Epoch 9/10
29/32 [==========================>...] - ETA: 0s - loss: 0.0488 - accuracy: 0.9968
Epoch 00009: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0478 - accuracy: 0.9970 - val_loss: 0.4114 - val_accuracy: 0.8670
Epoch 10/10
31/32 [============================>.] - ETA: 0s - loss: 0.0377 - accuracy: 0.9980
Epoch 00010: saving model to training_1/cp.ckpt
32/32 [==============================] - 0s 4ms/step - loss: 0.0377 - accuracy: 0.9980 - val_loss: 0.4137 - val_accuracy: 0.8730

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

この結果、エポックごとに更新される一連のTensorFlowチェックポイントファイルが作成されます。

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

訓練していない新しいモデルを作ります。重みだけからモデルを復元する場合には、元のモデルと同じアーキテクチャのモデルが必要です。モデルのアーキテクチャが同じであるため、モデルの異なるインスタンスであっても重みを共有することができるのです。

訓練していない全く新しいモデルを作り、テストデータセットで評価します。訓練をしていないモデルは偶然のレベル(正解率10%以下)の性能しか無いはずです。

model = create_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.3364 - accuracy: 0.0700
Untrained model, accuracy:  7.00%

次に、チェックポイントから重みをロードし、再び評価します。

model.load_weights(checkpoint_path)
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 0.4137 - accuracy: 0.8730
Restored model, accuracy: 87.30%

チェックポイントコールバックのオプション

このコールバックには、チェックポイントに一意な名前をつけたり、チェックポイントの頻度を調整するためのオプションがあります。

新しいモデルを訓練し、5エポックごとに一意な名前のチェックポイントを保存します。

# ファイル名に(`str.format`を使って)エポック数を埋め込む
checkpoint_path = "training_2/cp-{epoch:04d}.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)

# 5エポックごとにモデルの重みを保存するコールバックを作成
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    filepath=checkpoint_path, 
    verbose=1, 
    save_weights_only=True,
    period=5)

# 新しいモデルのインスタンスを作成
model = create_model()

# `checkpoint_path` フォーマットで重みを保存
model.save_weights(checkpoint_path.format(epoch=0))

# 新しいコールバックを使い、モデルを訓練
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.

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 0x7f15ed8bdef0>

次に、出来上がったチェックポイントを確認し、最後のものを選択します。

 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'

注意:デフォルトのtensorflowフォーマットは、直近の5つのチェックポイントのみを保存します。

テストのため、モデルをリセットし最後のチェックポイントをロードします。

# 新しいモデルのインスタンスを作成
model = create_model()

# 先ほど保存した重みを読み込み
model.load_weights(latest)

# モデルを再評価
loss, acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 0.4959 - accuracy: 0.8720
Restored model, accuracy: 87.20%

これらのファイルは何?

上記のコードでは、重みだけをバイナリでcheckpoint形式の一連のファイルに保存します。チェックポイントには、次のものが含まれます。

  • 1つ以上のモデルの重みの断片
  • どの重みがどの断片に保存されているかを示すインデックスファイル

1台のマシンだけでモデルの訓練を行っている場合には、.data-00000-of-00001のようなサフィックスのついたファイルが1つだけ作成されます。

手動で重みを保存する

上記では重みをモデルにロードする方法を見ました。

手動で重みを保存するのも同じ様に簡単です。Model.save_weights メソッドを使います。

# 重みの保存
model.save_weights('./checkpoints/my_checkpoint')

# 新しいモデルのインスタンスを作成
model = create_model()

# 重みの復元
model.load_weights('./checkpoints/my_checkpoint')

# モデルの評価
loss,acc = model.evaluate(test_images,  test_labels, verbose=2)
print("Restored model, accuracy: {:5.2f}%".format(100*acc))
32/32 - 0s - loss: 0.4959 - accuracy: 0.8720
Restored model, accuracy: 87.20%

モデル全体の保存

model.saveを呼ぶことで、モデルのアーキテクチャや重み、訓練の設定を単一のファイル/フォルダに保存できます。これにより、オリジナルの Python コード (*) にアクセスすることなしにモデルを使えるように、モデルをエクスポートできます。オプティマイザーの状態も復旧されるため、中断した箇所から訓練を再開できます。

モデル全体を2つの異なるファイルフォーマット (SavedModelHDF5) に保存できます。TF2.x のデフォルトのファイルフォーマットは SavedModel フォーマットであることには注意が必要でしょう。ですが、モデルを HDF5 フォーマットで保存することもできます。モデル全体を保存に関するより詳細について、以降で述べていきます。

完全に動作するモデルを保存することはとても便利です。それを TensorFlow.js (Saved Model, HDF5) で読み込んで、ブラウザ上で訓練や実行することもできますし、TensorFlow Lite (Saved Model, HDF5) を用いてモバイルデバイス上で実行できるよう変換することもできます。

*カスタムのオブジェクト (e.g. クラスを継承したモデルやレイヤー) は保存やロードを行うとき、特別な注意を必要とします。以降の カスタムオブジェクトの保存 を参照してください。

SavedModel フォーマットとして

SavedModel フォーマットはモデルをシリアライズする方法の一つです。このフォーマットで保存されたモデルを tf.keras.models.load_model を用いてリストアすることができます。また、TensorFlow Serving との互換性もあります。SavedModel ガイド は SavedModel をサービング/検査する方法の詳細を記しています。この章の以降では、モデルをサービングし、リストアするためのステップについて概要を示します。

# 新しいモデルのインスタンスを作成して訓練
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# モデル全体を SavedModel として保存
!mkdir -p saved_model
model.save('saved_model/my_model') 
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 1.1399 - accuracy: 0.6710
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4052 - accuracy: 0.8930
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2844 - accuracy: 0.9280
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2185 - accuracy: 0.9520
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1531 - accuracy: 0.9650
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

SavedModel フォーマットはバイナリ形式の protobuf と TensorFlow checkpoint を含んだディレクトリです。SavedModel ディレクトリの中を確認しましょう。

# my_model ディレクトリ
ls saved_model

# assets フォルダと saved_model.pb, variables フォルダが含まれる
ls saved_model/my_model
my_model
assets  saved_model.pb  variables

SavedModel を読み込んで新しい Keras モデルを作成します。

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

# アーキテクチャを確認
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
_________________________________________________________________

リストアされたモデルは元のモデルと同じ引数を用いてコンパイルされます。読み込んだモデルを用いて評価と予測を行ってみましょう。

# リストアされたモデルを評価
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.4491 - accuracy: 0.0900
Restored model, accuracy:  9.00%
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.
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.
(1000, 10)

HDF5ファイルとして

Keras は HDF5 の標準に従ったベーシックな保存形式も提供します。

# 新しいモデルのインスタンスを作成して訓練
model = create_model()
model.fit(train_images, train_labels, epochs=5)

# HDF5 ファイルにモデル全体を保存
# 拡張子 '.h5' はモデルが HDF5 で保存されているということを暗示する
model.save('my_model.h5') 
Epoch 1/5
32/32 [==============================] - 0s 2ms/step - loss: 1.1808 - accuracy: 0.6540
Epoch 2/5
32/32 [==============================] - 0s 2ms/step - loss: 0.4232 - accuracy: 0.8880
Epoch 3/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2781 - accuracy: 0.9300
Epoch 4/5
32/32 [==============================] - 0s 2ms/step - loss: 0.2127 - accuracy: 0.9550
Epoch 5/5
32/32 [==============================] - 0s 2ms/step - loss: 0.1538 - accuracy: 0.9700

保存したファイルを使ってモデルを再作成します。

# 同じモデルを読み込んで、重みやオプティマイザーを含むモデル全体を再作成
new_model = tf.keras.models.load_model('my_model.h5')

# モデルのアーキテクチャを表示
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
_________________________________________________________________

正解率を検査します。

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.4245 - accuracy: 0.0850
Restored model, accuracy:  8.50%

Keras はモデルのアーキテクチャを検査して保存します。このテクニックでは次のすべてのものを保存します。

  • 重みの値
  • モデルのアーキテクチャ
  • モデルの訓練の設定 (コンパイル時に渡したもの)
  • もしあれば、オプティマイザーとその状態 (これは中断したところからの訓練の再開を可能にします)

Keras は v1.x (tf.compat.v1.train にあります) のオプティマイザーを保存できません。これらは checkpoint と互換性がないためです。v1.x のオプティマイザーでは、オプティマイザーの状態を読み込ませてモデルを再度コンパイルする必要があります。

カスタムオブジェクトの保存

SavedModel を利用している場合、このセクションは読み飛ばしてかまいません。HDF5 と SavedModel の重要な差異は、モデルのアーキテクチャを保存する際に、HDF5 ではオブジェクトの設定を用いるのに対し、SavedModelでは演算グラフを保存する点です。なので、SavedModel はモデルのサブクラスであるカスタムオブジェクトやカスタムレイヤーを、オリジナルのコードを必要とせずに保存できます。

カスタムオブジェクトを HDF5 で保存する際には、次の事項を実行しなければいけません。

  1. get_config オブジェクトを実装する必要があります、from_config クラスメソッドの実装はオプションです
    • get_config(self) は JSON にシリアライズ可能なパラメーターを格納したディクショナリを返します。これはオブジェクトの再作成に必要なものです
    • from_config(cls, config)get_config の返り値から新しいオブジェクトを生成します。デフォルトでは、この関数は入力をイニシャライザの kwargs として扱います (return cls(**config))
  2. モデルのロード時にオブジェクトを custom_objects 引数に渡します。この引数は文字列のクラス名と Python のクラスとを対応付けるディクショナリになっている必要があります (例: tf.keras.models.load_model(path, custom_objects={'CustomLayer': CustomLayer}))

カスタムオブジェクトや 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.