0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Datagenerator使いがKeras 3へ移行するときメモ

Posted at

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_Keras2.py

model.fit(

    x=DataGen(fl_lst, batch_size), # DataGen()というメソッドを記載する
 
    steps_per_epoch=len_data//batch_size,
    epochs=epochs
)

Datageneratorの中では、そのバッチサイズ分だけのデータがメモリに実体化されるように処理を記載して、yieldを使ってデータを渡すように記載します。
例えば、入力ファイル名のリストを定義しておき、バッチサイズ分の対象画像のみを読み取ってndarrayに変換します。ここでは、入力ファイルが3種類である例を示しています。

datagenerator_Keras2.py
 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を指定します。

model_fit_Keras3.py

# 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の型を明示します。

datagenerator_Keras3.py
 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_Keras3.py

# 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の書き方

from_generator_Keras3

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

# fit関数にはindatasetを用いる

model.fit(

    indataset.batch(batch_size), # tf.data.Dataset形式を使う
 
    steps_per_epoch=len_data//batch_size,
    epochs=epochs
)

fitメソッドには、先ほど変換したtf.data.Datasetindatasetを指定します。
このとき、メソッドbatchを用いてバッチサイズを指定します。

2.2 Datageneratorの部分に対する変更

Keras2では、バッチサイズ分のデータを実体化してyieldによってデータを返していました。
このため、入力・教師データ共に、返す配列の最初のdimensionはバッチの次元になります。

Keras3では、1個分のデータをyieldで返します。バッチの次元を作っておく必要もありません。

なお、データの配列タイプがoutput_signatureに対応する型になるように指定します。

generator_Keras3
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の流儀に沿ったデータの前処理を書いた方が良いでしょう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?