2020-12-11 kerasはmodel.fit_generatorを介してモデルをトレーニングします(メモリを節約します)

Kerasはmodel.fit_generatorを介してモデルをトレーニングします(メモリを節約します)

序文

少し前にモデルをトレーニングしたところ、トレーニングセットの数が多すぎて入力画像の寸法が大きすぎると、メモリがオーバーランしやすいことがわかりました。簡単な例として、サンプルが20,000の場合、入力画像の寸法は次のようになります。 224x224x3、float32ストレージを使用して、すべてのデータを一度にメモリにロードすると、合計20000x224x224x3x32bit / 8 = 11.2GBのメモリが必要になるため、すべてのデータセットを一度にロードする場合は、大量のメモリが必要になります。

keras fit関数を直接使用してモデルをトレーニングする場合は、すべてのトレーニングデータを渡す必要がありますが、幸い、データをバッチで読み取ってメモリを節約できるfit_generatorが提供されています。必要なのは実装することだけです。ジェネレーター。

1.fit_generator関数の概要

fit_generator(generator,

steps_per_epoch=None,

epochs=1,

verbose=1,

callbacks=None,

validation_data=None,

validation_steps=None,

class_weight=None,

max_queue_size=10,

workers=1,

use_multiprocessing=False,

shuffle=True,

initial_epoch=0)

パラメータ:

ジェネレーター:ジェネレーター、またはシーケンス(keras.utils.Sequence)オブジェクトのインスタンス。これが私たちの実装の焦点です。ジェネレータとシーケンスの2つの実装は後で紹介されます。

Steps_per_epoch:これは、データを生成するために各エポックでジェネレーターを実行する必要がある回数です。fit_generator関数にはbatch_sizeパラメーターがありません。steps_per_epochを介して実装されます。毎回生成されるデータはバッチであるため、steps_per_epochの値が渡されます。 (サンプル数/ batch_size)に設定します。ジェネレーターがシーケンスタイプの場合、このパラメーターはオプションであり、デフォルトでlen(generator)が使用されます。

エポック:トレーニングの反復回数。

詳細:0、1または2。ログ表示モード。0 =クワイエットモード、1 =プログレスバー、2 =ラウンドごとに1行

コールバック:トレーニング中に呼び出される一連のコールバック関数。

validate_data:ジェネレーターに似ていますが、これは検証に使用され、トレーニングには参加しません。

validate_steps:前のsteps_per_epochと同様です。

class_weight:クラスインデックス(整数)を重み(浮動小数点)値にマッピングするオプションの辞書。重み付き損失関数に使用されます(トレーニング中のみ)。これを使用して、過小評価されているクラスのサンプルに「もっと注意を払う」ようにモデルに指示できます。(このパラメーターはあまり使用されていないと思います)

max_queue_size:整数。ジェネレータキューの最大サイズ。デフォルトは10です。

ワーカー:整数。プロセスベースのマルチスレッドが使用されている場合、使用されるプロセスの最大数。指定しない場合、ワーカーはデフォルトで1になります。0の場合、ジェネレータはメインスレッドで実行されます。

use_multiprocessing:ブール値。Trueの場合、プロセスベースのマルチスレッドを使用します。デフォルトはFalseです。

シャッフル:各反復の前にバッチの順序をシャッフルするかどうか。これは、Sequence(keras.utils.Sequence)インスタンスでのみ使用できます。

initial_epoch:トレーニングを開始するラウンド(前のトレーニングを再開するのに役立ちます)

2.ジェネレータの実装

2.1ジェネレータの実装方法

サンプルコード:

from keras.models import Sequential
from keras.layers import Dense
import numpy as np
from PIL import Image


def process_x(path):
    img = Image.open(path)
    img = img.resize((96, 96))
    img = img.convert('RGB')
    img = np.array(img)

    img = np.asarray(img, np.float32) / 255.0
    # 也可以进行进行一些数据数据增强的处理
    return img


def generate_arrays_from_file(x_y):
    # x_y 是我们的训练集包括标签,每一行的第一个是我们的图片路径,后面的是图片标签

    global count
    batch_size = 8
    while 1:
        batch_x = x_y[(count - 1) * batch_size:count * batch_size, 0]
        batch_y = x_y[(count - 1) * batch_size:count * batch_size, 1:]

        batch_x = np.array([process_x(img_path) for img_path in batch_x])
        batch_y = np.array(batch_y).astype(np.float32)
        print("count:" + str(count))
        count = count + 1
        yield batch_x, batch_y


model = Sequential()
model.add(Dense(units=1000, activation='relu', input_dim=2))
model.add(Dense(units=2, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='sgd', metrics=['accuracy'])
count = 1
x_y = []
model.fit_generator(generate_arrays_from_file(x_y), steps_per_epoch=10, epochs=2, max_queue_size=1, workers=1)

上記のコードを理解する前に、まずyieldの使用法を理解する必要があります。

イールドキーワード:

まず、例を通して歩留まりの使用法を見てみましょう。

def foo():
    print("starting...")
    while True:
        res = yield 4
        print("res:", res)


g = foo()
print(next(g))
print("----------")
print(next(g))

動作結果:

starting...
4
----------
res: None
4

イールドのある関数はジェネレーターであり、関数ではありません。foo関数にyieldキーワードがあるため、foo関数は実行されません。代わりに、ジェネレーターのインスタンスが最初に取得されます。次の関数を初めて呼び出すと、foo関数が開始されます。最初にfooを実行します。次に、関数のprintメソッドはwhileループに入ります。ループがyieldに実行されると、yieldは実際にはreturnと同等であり、関数は4を返し、プログラムは停止します。したがって、next(g)への最初の呼び出しの出力は、最初の2行です。

次に、next(g)を再度呼び出すと、前回の実行で4が返され、resがランダムにNoneに割り当てられるため、前回停止した場所から実行を継続します。つまり、resの割り当て操作を実行します。 print( "res:"、res)を実行してres:Noneを出力し、再度ループしてyieldして4に戻ると、プログラムが停止します。

したがって、yieldキーワードの機能は、プログラムが最後に停止した場所から実行を継続できることです。これにより、プログラムをジェネレーターとして使用するときに、一度にデータを読み取ることによるメモリ不足の状況を回避できます。

上記のサンプルコードをご覧ください。

generate_arrays_from_file関数はジェネレーターであり、ループ内のデータのバッチを読み取り、データを処理して戻ります。x_yは、次の形式のように、パスとラベルを組み合わせた後のトレーニングセットです。

['data / img_4092.jpg''0''1''0''0''0']

フォーマットは、このようにする必要はなく、独自のフォーマットにすることもできます。対処方法は、独自のフォーマットに基づいて、process_xで処理します。ここでは、保存された画像パスであるため、process_x関数の主な機能は、画像を読み取って復元することです。画像のリアルタイムデータ拡張など、ここで実行する必要のある操作を定義することもできます。

2.2シーケンスを使用してジェネレータを実装する

サンプルコード:

class BaseSequence(Sequence):
    """
 基础的数据流生成器,每次迭代返回一个batch
 BaseSequence可直接用于fit_generator的generator参数
 fit_generator会将BaseSequence再次封装为一个多进程的数据流生成器
 而且能保证在多进程下的一个epoch中不会重复取相同的样本
 """

    def __init__(self, img_paths, labels, batch_size, img_size):
        # np.hstack在水平方向上平铺
        self.x_y = np.hstack((np.array(img_paths).reshape(len(img_paths), 1), np.array(labels)))
        self.batch_size = batch_size
        self.img_size = img_size

    def __len__(self):
        # math.ceil表示向上取整
        # 调用len(BaseSequence)时返回,返回的是每个epoch我们需要读取数据的次数
        return math.ceil(len(self.x_y) / self.batch_size)

    def preprocess_img(self, img_path):
        img = Image.open(img_path)
        resize_scale = self.img_size[0] / max(img.size[:2])
        img = img.resize((self.img_size[0], self.img_size[0]))
        img = img.convert('RGB')
        img = np.array(img)

        # 数据归一化
        img = np.asarray(img, np.float32) / 255.0
        return img

    def __getitem__(self, idx):
        batch_x = self.x_y[idx * self.batch_size: (idx + 1) * self.batch_size, 0]
        batch_y = self.x_y[idx * self.batch_size: (idx + 1) * self.batch_size, 1:]
        batch_x = np.array([self.preprocess_img(img_path) for img_path in batch_x])
        batch_y = np.array(batch_y).astype(np.float32)
        print(batch_x.shape)
        return batch_x, batch_y

    # 重写的父类Sequence中的on_epoch_end方法,在每次迭代完后调用。
    def on_epoch_end(self):
        # 每次迭代后重新打乱训练集数据
        np.random.shuffle(self.x_y)

上記のコードでは、__ len__と__getitem__は書き直されたマジックメソッドです。len(BaseSequence)関数を呼び出すと__len __が呼び出されます。ここでは、(サンプルの総数/ batch_size)を返します。 fit_generatorにsteps_per_epochパラメーターを入力します。__getitem__を使用すると、オブジェクトで反復関数を実装できるため、BaseSequenceオブジェクトをfit_generatorに渡した後、ジェネレーターを継続的に実行することでデータを周期的に読み取ることができます。

getitemの役割を説明する例を挙げてください。

class Animal:
    def __init__(self, animal_list):
        self.animals_name = animal_list

    def __getitem__(self, index):
        return self.animals_name[index]


animals = Animal(["dog", "cat", "fish"])
for animal in animals:
    print(animal)

出力結果:

dog
cat
fish

また、Sequenceクラスを使用すると、複数のプロセスの場合に、各エポックのサンプルが1回だけトレーニングされるようになります。

降伏方法を参照してください

 

 

おすすめ

転載: blog.csdn.net/qingfengxd1/article/details/111032641