Datagenerotor使いがKeras3へ移行するときメモ
はじめに
Kerasのサンプルコードのサイトに掲載されているコードの大半がバージョン3ベースへ移行していることもあり、Keras3を利用するべくTensorflow2.16をインストールしてみました。
Keras3のありがたみを探索する前に、まずは現在使っているTensorflow2-Kerasのコードを移行して動作確認をしようとしたところ、Datageneratorの周りでちょっとした変更が必要でした。
大きくは、モデルのfit
メソッドに直接Datageneratorを指定出来なくなった点があります。そしてtensorflow.data.Dataset
形式を指定する必要があります。ということは、tf.data.Dataset
形式でデータを作るようにデータハンドリングを修正すれば良いわけですが、それはそれで結構大掛かりな修正になるので、移行に際してはなるべくDatageneratorを活かしたい!という人もおられるかと思います。そのあたりの細かな話があまりドキュメントにも無いようですので、修正を行った記録をメモとして投稿します。
なお、Tensorflow2ではtf.data.Dataset
の利用が標準的と思います。こちらをお使いの場合は、この記事はほぼ該当しません。あくまで「Datagenerator使い」のためのメモです。
1. Datageneratorについて
ニューラルネットワークを訓練するとき、入力となる学習データが大量にあると、全てを配列に持ってしまうとメモリ不足になってしまうことが多々あります。このためKerasはデータを使う分だけメモリに置くための仕組みとしてDatageneratorというものがあります。
具体的には、次のようにモデルのFitを記載します。ここでは、DataGen()
がDatageneratorです。これはKeras2以前の書き方です。
model.fit(
x=DataGen(fl_lst, batch_size), # DataGen()というメソッドを記載する
steps_per_epoch=len_data//batch_size,
epochs=epochs
)
Datageneratorの中では、そのバッチサイズ分だけのデータがメモリに実体化されるように処理を記載して、yield
を使ってデータを渡すように記載します。
例えば、入力ファイル名のリストを定義しておき、バッチサイズ分の対象画像のみを読み取ってndarray
に変換します。ここでは、入力ファイルが3種類である例を示しています。
from PIL import Image
import numpy as np
def DataGen(fl_lst, tg_lst, batch_size):
imgx1_list, imgx2_list, imgx3_list = [], []. []
imgy_list = []
d_cnt = 0
while True: # 無限ループを作ります
f_cnt = 0
while data_cnt < batch_size : # バッチサイズに到達するまで繰り返す
"""
入力データ処理例:画像を読んでサイズを変更、配列に変換するなど
"""
imgx1 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize((256,256),Image.HAMMING)
imgx2 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize((128,128),Image.HAMMING)
imgx3 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize(( 64, 64),Image.HAMMING)
imgx1_list.append(np.asarray(imgx1[np.newaxis,:,:,:]))
imgx2_list.append(np.asarray(imgx2[np.newaxis,:,:,:]))
imgx3_list.append(np.asarray(imgx3[np.newaxis,:,:,:]))
"""
教師データに対応した処理
"""
label = tg_lst (略)
d_cnt += 1
f_cnt += 1
if d_cnt == batch_size:
"""
読み込んだデータの数がバッチサイズになったらyield関数でデータを渡す
"""
# バッチサイズ分concatenate
data_x1 = np.concatenate(imgx1_list, axis=0)
data_x2 = np.concatenate(imgx2_list, axis=0)
data_x3 = np.concatenate(imgx3_list, axis=0)
data_y = np.concatenate(label)
# yieldを使ってデータを戻す
yield [data_x1, data_x2, data_x3] , data_y
# 初期化
data_cnt = 0
imgx1_list, imgx2_list, imgx3_list = [], [], []
if f_cnt == len(fl_lst):
# ファイルリストの最後に来たら先頭に戻る
f_cnt == 0
このようにすることで、学習するバッチサイズ分のみがメモリに展開されるような処理とすることが出来ます。ただし実際には並列処理の関係で先読みしたりするため、メモリ上のサイズはもう少し大きくなるようです。
まとめるとKeras2では下記の書き方でした。
(1) fit
関数にdatageneratorを直接指定している。
(2) datageneratorでは、バッチサイズ分のデータを実体化して、yield
を使ってデータを返す。
以降では上記の変更について記載します。
2. Keras3での書き方
まずは、書き換え後にどうなるかです。
fit
メソッドに行く前に、output_signature
という属性を定義して、tf.data.Dataset
形式への変換を行います。fit
には変換してできたtf.data.Dataset
を指定します。
# output_signature という属性を記述する。
# 複数の入力データは( )で繋げる。
output_signature = (
( tf.TensorSpec(shape=(256,256,3), dtype=tf.float32),
tf.TensorSpec(shape=(128,128,3), dtype=tf.float32),
tf.TensorSpec(shape=( 64, 64,3), dtype=tf.float32)),
tf.TensorSpec(shape=(略), dtype=tf.int32)
)
# DataGen()とoutput_signatureから、from_generatorというメソッドを用いて
# tf.data.Dataset形式のデータを作成する
indataset = tf.data.Dataset.from_generator(
lambda: DataGen(fl_lst, tg_lst, batch_size),
output_signature=output_signature )
# fit関数にはindatasetを用いる
model.fit(
indataset.batch(batch_size), # tf.data.Dataset形式を使う
steps_per_epoch=len_data//batch_size,
epochs=epochs
)
Datagenerator側の処理としては、バッチサイズ分を返すのではなく、1回分のデータを返すように書きます。
加えて、output_signature
と一致するようなTensor
の型を明示します。
from PIL import Image
import numpy as np
def DataGen(fl_lst, tg_lst, batch_size):
imgx1_list, imgx2_list, imgx3_list = [], []. []
imgy_list = []
d_cnt = 0
while True: # 無限ループを作ります
f_cnt = 0
"""
入力データ処理例:画像を読んでサイズを変更、配列に変換するなど
"""
imgx1 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize((256,256),Image.HAMMING)
imgx2 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize((128,128),Image.HAMMING)
imgx3 = (
Image.open(fl_lst[f_cnt]).convert('RGB')
).resize(( 64, 64),Image.HAMMING)
"""
教師データに対応した処理
"""
label = tg_lst (略)
f_cnt += 1
if f_cnt == len(fl_lst):
# ファイルリストの最後に来たら先頭に戻る
f_cnt = 0
data_x1 = np.asarray(imgx1)
data_x2 = np.asarray(imgx2)
data_x3 = np.asarray(imgx3)
# yieldを使ってデータを戻す
yield
(data_x1.astype(np.float32),
data_x2.astype(np.float32),
data_x3.astype(np.float32)),
data_y.astype(np.int32)
少し細かく見ていきます。
2.1 fit
メソッドの部分に対する変更
Keras2では、fitメソッドにDataGeneratorを直接指定することが出来ましたが、Keras3ではtf.data.Dataset
型を指定する必要があります(tf.data.Dataset
形式でデータを作るように修正すれば良いわけですが、それはそれで結構大掛かりな修正になるので避けたい...という前提です)。
Datageneratorからtf.data.Dataset
を作るには、tf.data.Dataset.from_generator
を利用します。このときに、output_signature
という属性を指定する必要があります。
(1) output_signatureの書き方
# output_signature という属性を記述する。
# 複数の入力データは( )で繋げる。
output_signature = (
( tf.TensorSpec(shape=(256,256,3), dtype=tf.float32),
tf.TensorSpec(shape=(128,128,3), dtype=tf.float32),
tf.TensorSpec(shape=( 64, 64,3), dtype=tf.float32)),
tf.TensorSpec(shape=(略), dtype=tf.int32)
)
output_signature
は入力、出力のデータを組にして、形とtypeを明示して記載するのが基本です。
私のコードでは、元々入力データを3種類をひと組みにしていた部分を( )
で囲っています。Keras2ではリストとしていたのですが、output_signature
にはリストが指定できません。[]
で囲って指定するとエラーとなります。
(2) from_generatorの書き方
# DataGen()とoutput_signatureから、from_generatorというメソッドを用いて
# tf.data.Dataset形式のindatasetを作成する
indataset = tf.data.Dataset.from_generator(
lambda: DataGen(fl_lst, tg_lst, batch_size),
output_signature=output_signature )
メソッドとしてのDatagenerator、属性としてのoutput_signature
を指定して、tensorflow.data.Dataset.from_generator
によりtf.data.Dataset
形式に変換します。
なお私はDatageneratorをdefで定義していたので、直接指定すると関数とは見なされずエラーになりました。これを回避するために、lambda
を用いて関数に変換しています。
(3) fitメソッド本体の書き方
# fit関数にはindatasetを用いる
model.fit(
indataset.batch(batch_size), # tf.data.Dataset形式を使う
steps_per_epoch=len_data//batch_size,
epochs=epochs
)
fit
メソッドには、先ほど変換したtf.data.Dataset
のindataset
を指定します。
このとき、メソッドbatch
を用いてバッチサイズを指定します。
2.2 Datageneratorの部分に対する変更
Keras2では、バッチサイズ分のデータを実体化してyield
によってデータを返していました。
このため、入力・教師データ共に、返す配列の最初のdimension
はバッチの次元になります。
Keras3では、1個分のデータをyield
で返します。バッチの次元を作っておく必要もありません。
なお、データの配列タイプがoutput_signature
に対応する型になるように指定します。
def DataGen(fl_lst, tg_lst, batch_size):
(中略)
# yieldを使ってデータを戻す
yield
(data_x1.astype(np.float32),
data_x2.astype(np.float32),
data_x3.astype(np.float32)),
data_y.astype(np.int32)
また、複数の入力データがある場合は、Keras2ではリストにして、つまり[]
で囲って返していましたが、Keras3では()
で囲って返します。
3. まとめ
Keras3ではfit
メソッドでのdatageneratorの扱いが変わりました。
fit
メソッドでは、下記のような修正となります。
(1) Datageneratorを直接指定することは出来なくなった。tf.data.Dataset
形式のデータを、batch
メソッドによってバッチサイズを指定した上で使う。
(2) from_generator
というメソッドによって、従来のDatageneratorをtf.data.Dataset
に変換することができる。
(3) from_generator
には、detageneratorへの「関数」と、output_signature
という属性を指定する。
一方Datagenerator側では、以下を指定します。
(1) バッチサイズ分のデータではなく、1回分のデータをyield
で返すように書く。
fit
に指定したバッチサイズのデータが自動的に構成される。
(2) yield
で返す配列は、output_signature
で指定したTensorの型と対応した型を明示的に指定する。
なお、新規にKeras3のためのコードを書く場合には、最初からtf.data.Dataset
の流儀に沿ったデータの前処理を書いた方が良いでしょう。