Keras3 にしたら古いモデルが読み込めなくなった話

## 背景
`Ubuntu` を `22.04` から `24.04` に更新して、以前作成したトレーニング済みモデルをロードしようとしたら、読み込めなくなったので、その試行錯誤と解決方法をまとめる。


## 動作確認環境
• `LinuxOS Ubuntu 24.04.1 LTS`
• `Python 3.12.3`


## 免責事項
その前に、このスクリプトの実行は自己責任でお願いします。本スクリプトを実行することにより生じるいかなる問題に関しましても、筆者は一切責任を負いません。予めご了承ください。


## `from tensorflow.keras.callbacks import ModelCheckpoint` でチェックポイントを登録しようとしたら失敗した。

```Python
from tensorflow.keras.callbacks import ModelCheckpoint

checkpoint = ModelCheckpoint(filepath='MODELS/model{epoch:02d}')

ValueError: The filepath provided must end in `.keras` (Keras model format). Received: filepath=MODELS/model{epoch:02d}
```

どうやら、Keras3から、チェックポイントの登録、モデルの保存には、 `.keras` を付けなければいけなくなったようだ。


## `pip freeze` で `keras` と `tensorflow` のバージョンを調べる

```Python
(venv) % pip freeze

keras==3.5.0
tensorflow==2.17.0
```

確かに、 `Keras` のバージョンが `3.5.0` に上がっている。


## 以前の `Tensorflow` のバージョン `2.15.1` のインストールを試みる

```Python
(venv) % deactivate
% rm -rf venv
% python -m venv venv
% source venv/bin/activate
(venv) % pip install tensorflow==2.15.1

ERROR: Could not find a version that satisfies the requirement tensorflow==2.15.1 (from versions: 2.16.0rc0, 2.16.1, 2.16.2, 2.17.0rc0, 2.17.0rc1, 2.17.0)
ERROR: No matching distribution found for tensorflow==2.15.1
```

`2.16.0rc0` 以上でないとインストールできないようだ。


## 取り敢えずチェックポイントに拡張子 `.keras` を付けてトレーニングしてみる

取り敢えず拡張子 `.keras` 付きのモデルが作成された。

```Python
checkpoint = ModelCheckpoint(filepath='MODELS/model{epoch:02d}.keras')

history = model.fit(x_train, y_train, epochs=1, batch_size=32, validation_split=0.2, callbacks=[checkpoint])
```

`Keras3` ではトレーニング終了後 `MODELS/model01.keras` という `zip` 形式のトレーニング済みモデルが作成されたようだ。中身を見てみる。

```
model01.keras
├─ config.json
├─ metadata.json
└─ model.weights.h5
```

`Keras2` 以前のトレーニング済みモデルと中身が異なっている。因みに以前のトレーニング済みモデルはこんな感じ。

```
model01
├+ assets
│  └─ 空
├─ keras_metadata.pb
├─ saved_model.pb
└+ variables
   ├─ variables.data-00000-of-00001
   └─ variables.index
```


## 拡張子 `.keras` をつけてロードしてみる

```Python
from tensorflow import keras

model = keras.models.load_model('MODELS/model01.keras')

TypeError: Could not locate class 'Bias'. Make sure custom classes are decorated with `@keras.saving.register_keras_serializable()`.

During handling of the above exception, another exception occurred:

TypeError: <class 'keras.src.models.sequential.Sequential'> could not be deserialized properly. Please ensure that components that are Python object instances (layers, models, etc.) returned by `get_config()` are explicitly deserialized in the model's `from_config()` method.
```

カスタムクラス `Bias` が配置されていないというエラー。
もう一つが、 `get_config()` で返されたコンポーネントが `from_config()` で逆シリアライズ化されていないというエラー。


## カスタムクラスが配置されていないということなので、トレーニング済みモデルをロードする前にモデルを定義・コンパイルする

```Python
from tensorflow import keras
from tensorflow.keras import layers

class Bias(keras.layers.Layer):
    # 省略  

model = keras.Sequential()
model.add(Bias(""" 省略 """))
model.compile(""" 省略 """)
```


## コンパイルしたモデルに、チェックポイントで保存したウェイトをロードする

```Python
import shutil

shutil.unpack_archive('MODELS/model01.keras', 'MODELS/model01')

shutil.ReadError: Unknown archive format 'MODELS/model01.keras'
```

`shutil.unpack_archive` では、拡張子 `zip` 以外のファイルを解凍できないようだ。
`zipfile.ZipFile` に変更してみる。

```Python
import zipfile

with zipfile.ZipFile('MODELS/model01.keras') as zf:
    zf.extractall('MODELS/model01')
```

無事に `MODELS` フォルダの下に `model01` フォルダが作成され、その配下にモデルデータが解凍された。

```
MODELS
└+ model01
   ├─ config.json
   ├─ metadata.json
   └─ model.weights.h5
```

`zf.extractall` の解凍先を `MODELS` にすると `MODELS` フォルダの直下に、 `config.json` 、 `metadata.json` 、 `model.weights.h5` が解凍されてしまう。

```Python
with zipfile.ZipFile('MODELS/model01.keras') as zf:
    zf.extractall('MODELS')
```

```
MODELS
├─ config.json
├─ metadata.json
└─ model.weights.h5
```

モデルが複数個存在する場合、上書きされてしまうので、モデル毎に別フォルダで管理するのが無難。
解凍が終わったら、 `model.weights.h5` のウェイトをロードして、モデルのフォルダ毎削除。

```Python
import shutil

model.load_weights('MODELS/model01/model.weights.h5')
shutil.rmtree('MODELS/model01')
```

別にモデルのフォルダを削除する必要はないが、 `MODELS/model01.keras` があるので、 `MODELS/model01` を置いておく必要はない。
`MODELS` フォルダの直下に解凍しても良いが、後始末が面倒なので、モデル毎のフォルダを作成し、そのフォルダ毎削除するのが無難。


## 余談、ウェイトをロードして生成した `Keras3` 形式のモデルから、 `Keras2` 形式のモデルを生成できる

```Python
model.export('MODELS/model01')
```

トレーニング済みウェイトをロードして生成した `Keras3` 形式のモデルから `model.export` を使用すると、 `Keras2` 形式のモデルを生成できるが、 `Keras2` 形式のモデルを生成したところで、 `Keras3` では使い道がない。


## ロードしたウェイトでトレーニング

```Python
checkpoint = ModelCheckpoint(filepath='MODELS/model{epoch:02d}.keras')

history = model.fit(x_train, y_train, initial_epoch=1, epochs=2, batch_size=32, validation_split=0.2, callbacks=[checkpoint]
```

チェックポイントを定義し、無事に途中からでもトレーニングに成功。チェックポイントを定義していることで、 `Keras3` 形式のトレーニング済みウェイトが生成される。


## Keras2 と Keras3の比較
• トレーニング済みモデルをロードする時 `Keras2` では、 `keras.models.load_model` でモデルをロードできていたが、 `Keras3` ではトレーニング済みモデルをロードできなくなった。 `Keras3` でもトレーニング済みモデルをロードできるかもしれないが、その方法がわからない。
• 代わりに `Keras3` では、モデルを再定義・コンパイルし、そのモデルにトレーニング済みウェイトをロード `load_weights('モデルのパス/model.weights.h5')` することで、トレーニング済みモデルを生成するような感じになっている。
• `Keras2` では、トレーニングを途中から再開する際、トレーニング済みモデルを読み込めば、トレーニングを途中から再開できたが、 `Keras3` では、トレーニングを途中から再開する場合は、毎回モデルの定義・コンパイルを行い、そのモデルにトレーニング済みウェイトを読み込まなければならない。


## 最後に

今回 `Ubuntu` の最新 `LTS` 版がリリースされてから約半年が経ち、 `22.04` から `24.04` に更新してみたところ、以前 `Tensorflow` でトレーニングしたトレーニング済みモデルが読み込めないという問題に遭遇しました。
長年ソフトウェア開発に身を置いていますが、 `Keras` に限らずモジュールが更新されると今まで動いていたモジュールが使えなくなるということがよくあります。ソフトウェア開発をする上で、この問題は永遠のテーマと言えます。
こういう問題に遭遇すると、問題解決のために、あっという間に時間が過ぎてしまいます。何とかしてもらいたいところです。
最後まで、この記事を読んでいただき、ありがとうございました。