目录

  • 数据生成器的功能是什么
  • fit_generator
  • Evaluation_generator
  • predict_generator
  • ImageDataGenerator类
  • 灵活的数据生成器
  • 可运行实例
  • 结论
  • ref:
  • 相关阅读:


重点介绍如何构建数据生成器以在Keras中加载和处理图像。

数据生成器的功能是什么

在Keras Model类中,有三种我们感兴趣的方法:fit_generatorvaluate_generatorpredict_generator。它们全部三个都需要数据生成器,但并非所有生成器都是平等创建的。
让我们看一下每种方法需要哪种生成器:

fit_generator

需要两个生成器,一个用于训练数据,另一个用于验证。幸运的是,它们两个都应该返回一个tupple(输入,目标),并且它们都可以是Sequence类的实例。

Evaluation_generator

此处的数据生成器具有与fit_generator中相同的要求,并且可以与训练生成器相同。

predict_generator

这里的生成器有点不同。它应该只返回输入。
考虑到这一点,让我们构建一些数据生成器。由于fit_generator中的生成器和evaluate_generator之间的相似性,我们将集中精力构建fit_generatorpredict_generator的数据生成器。

ImageDataGenerator类

ImageDataGenerator类在图像分类中非常有用。有多种使用此生成器的方法,具体取决于我们使用的方法,这里我们将重点介绍flow_from_directory采取的路径,该目录包含在子目录中排序的图像和图像增强参数。
让我们看一个例子:
我们将使用可从https://www.kaggle.com/c/dogs-vs-cats/data下载的数据集,其结构如下:

data/
    train/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...
    validation/
        dogs/
            dog001.jpg
            dog002.jpg
            ...
        cats/
            cat001.jpg
            cat002.jpg
            ...

首先,让我们导入所有必要的库,并创建具有图像增强功能的数据生成器。

from tensorflow.keras.preprocessing.image import ImageDataGenerator

train_datagen = ImageDataGenerator(
    rescale=1./255,
    shear_range=0.2,
    zoom_range=0.2,
    horizontal_flip=True)

test_datagen = ImageDataGenerator(rescale=1./255)

train_generator = train_datagen.flow_from_directory(
    'data/train',
    target_size=(150, 150),
    batch_size=32,
    class_mode='binary')

validation_generator = test_datagen.flow_from_directory(
    'data/validation',
    target_size=(150, 150),
    batch_size=32,
    class_mode='binary')

最后,创建一个模型并运行fit_generator方法。

model.fit_generator(
    train_generator,
    steps_per_epoch=2000,
    epochs=50,
    validation_data=validation_generator,
    validation_steps=800)

灵活的数据生成器

要构建自定义数据生成器,我们需要从 Sequence 类继承。让我们添加所需的参数。

class DataGenerator(Sequence):
    'Generates data for Keras'
    def __init__(self, list_IDs, labels, image_path, mask_path,
    to_fit=True, batch_size=32, dim=(256,256),
    n_channels=1, n_classes=10, shuffle=True):
        'Initialization'
        self.list_IDs = list_IDs
        self.labels = labels
        self.image_path = image_path
        self.mask_path = mask_path
        self.to_fit = to_fit
        self.batch_size = batch_size
        self.dim = dim
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.on_epoch_end()

Sequence类迫使我们实现两种方法:len__和__getitem。如果我们希望生成器在每个时期之后执行某些操作,我们还可以实现on_epoch_end方法。
__len__方法应返回每个时期的批次数。下面显示了一种可能的实现。

def __len__(self):
    'Denotes the number of batches per epoch'
    return int(np.floor(len(self.list_IDs) / self.batch_size))

如果shuffle = True,此示例中的on_epoch_end可以将训练的索引洗牌。但是,在每个epoch之后,我们可以运行任何逻辑。

def on_epoch_end(self):
    'Updates indexes after each epoch'
    self.indexes = np.arange(len(self.list_IDs))
    if self.shuffle == True:
        np.random.shuffle(self.indexes)

我们必须实现的第二种方法是__getitem__,它完全符合您的期望。如果我们预测的话,它应该返回一批图像和蒙版。可以通过将to_fit设置为True或False来控制。

def __getitem__(self, index):
    'Generate one batch of data'
    # Generate indexes of the batch
    indexes = self.indexes[index*self.batch_size: (index+1)*self.batch_size]
    
    # Find list of IDs
    list_IDs_temp = [self.list_IDs[k] for k in indexes]
    
    # Generate data
    X = self._generate_X(list_IDs_temp)
  
    if self.to_fit:
        y = self._generate_y(list_IDs_temp)
        return X, y
    else
        return X

def _generate_X(self, list_IDs_temp):
    'Generates data containing batch_size images'
    # Initialization
    X = np.empty((self.batch_size, *self.dim, self.n_channels))
    
    # Generate data
    for i, ID in enumerate(list_IDs_temp):
        # Store sample
        X[i,] = _load_grayscale_image(self.image_path + self.labels[ID])
        
    return X
 
def _generate_y(self, list_IDs_temp):
    'Generates data containing batch_size masks'
    y = np.empty((self.batch_size, *self.dim), dtype=int)
    
    # Generate data
    for i, ID in enumerate(list_IDs_temp):
        # Store sample
        y[i,] = _load_grayscale_image(self.mask_path + self.labels[ID])
        
    return y
 
def _load_grayscale_image(image_path):
    'Load grayscale image'
    img = cv2.imread(image_path)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    img = img / 255
    
    return img

整个数据生成器应与此类似:

import numpy as np
import cv2
from tensorflow.keras.utils import Sequence


class DataGenerator(Sequence):
    """Generates data for Keras
    Sequence based data generator. Suitable for building data generator for training and prediction.
    """
    def __init__(self, list_IDs, labels, image_path, mask_path,
                 to_fit=True, batch_size=32, dim=(256, 256),
                 n_channels=1, n_classes=10, shuffle=True):
        """Initialization
        :param list_IDs: list of all 'label' ids to use in the generator
        :param labels: list of image labels (file names)
        :param image_path: path to images location
        :param mask_path: path to masks location
        :param to_fit: True to return X and y, False to return X only
        :param batch_size: batch size at each iteration
        :param dim: tuple indicating image dimension
        :param n_channels: number of image channels
        :param n_classes: number of output masks
        :param shuffle: True to shuffle label indexes after every epoch
        """
        self.list_IDs = list_IDs
        self.labels = labels
        self.image_path = image_path
        self.mask_path = mask_path
        self.to_fit = to_fit
        self.batch_size = batch_size
        self.dim = dim
        self.n_channels = n_channels
        self.n_classes = n_classes
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        """Denotes the number of batches per epoch
        :return: number of batches per epoch
        """
        return int(np.floor(len(self.list_IDs) / self.batch_size))

    def __getitem__(self, index):
        """Generate one batch of data
        :param index: index of the batch
        :return: X and y when fitting. X only when predicting
        """
        # Generate indexes of the batch
        indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]

        # Find list of IDs
        list_IDs_temp = [self.list_IDs[k] for k in indexes]

        # Generate data
        X = self._generate_X(list_IDs_temp)

        if self.to_fit:
            y = self._generate_y(list_IDs_temp)
            return X, y
        else:
            return X

    def on_epoch_end(self):
        """Updates indexes after each epoch
        """
        self.indexes = np.arange(len(self.list_IDs))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)

    def _generate_X(self, list_IDs_temp):
        """Generates data containing batch_size images
        :param list_IDs_temp: list of label ids to load
        :return: batch of images
        """
        # Initialization
        X = np.empty((self.batch_size, *self.dim, self.n_channels))

        # Generate data
        for i, ID in enumerate(list_IDs_temp):
            # Store sample
            X[i,] = self._load_grayscale_image(self.image_path + self.labels[ID])

        return X

    def _generate_y(self, list_IDs_temp):
        """Generates data containing batch_size masks
        :param list_IDs_temp: list of label ids to load
        :return: batch if masks
        """
        y = np.empty((self.batch_size, *self.dim), dtype=int)

        # Generate data
        for i, ID in enumerate(list_IDs_temp):
            # Store sample
            y[i,] = self._load_grayscale_image(self.mask_path + self.labels[ID])

        return y

    def _load_grayscale_image(self, image_path):
        """Load grayscale image
        :param image_path: path to image to load
        :return: loaded image
        """
        img = cv2.imread(image_path)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
        img = img / 255
        return img

假设我们有两个目录,一个目录保存图像,另一个目录保存遮罩图像,并且每个图像都有一个具有相同名称的对应遮罩,以下代码将使用自定义数据生成器训练模型。

image_path = 'path to images'
mask_path = 'path to masks'

training_generator = DataGenerator(train_idx, labels, image_path, mask_path)
validation_generator = DataGenerator(val_idx, labels, image_path, mask_path)

# Design model
model = Sequential()
[...] # Architecture
model.compile()

# Train model on dataset
model.fit(training_generator, validation_data=validation_generator)

最后,如果我们要使用数据生成器进行预测,则应将to_fit设置为False并应调用predict_generator。

image_path = 'path to images'

pred_labels = [...] # list of image names

pred_generator = DataGenerator(pred_idx, pred_labels, image_path, to_fit=False)

pred = model.predict_generator(pred_generator)

可运行实例

import numpy as np
import keras
from keras.layers import Dense


class DataGenerator(keras.utils.Sequence):
    'Generates data for Keras'

    def __init__(self, train_data, test_data, batch_size=32, n_channels=1, shuffle=True):
        'Initialization'
        self.train_data = train_data
        self.test_data = test_data
        self.batch_size = batch_size
        self.n_channels = n_channels
        self.shuffle = shuffle
        self.on_epoch_end()

    def __len__(self):
        'Denotes the number of batches per epoch'
        return int(np.floor(len(self.train_data) / self.batch_size))

    def __getitem__(self, index):
        'Generate one batch of data'
        # Generate indexes of the batch
        indexes = self.indexes[index * self.batch_size:(index + 1) * self.batch_size]

        # Generate data
        # X, y = self.__data_generation(train_data_temp)

        X = self.train_data[indexes]
        y = self.test_data[indexes]

        return X, y

    def on_epoch_end(self):
        'Updates indexes after each epoch'
        self.indexes = np.arange(len(self.train_data))
        if self.shuffle == True:
            np.random.shuffle(self.indexes)


def gen_model():

    model = keras.models.Sequential()

    model.add(Dense(units=1, use_bias=False, input_shape=(1,)))  # 仅有的1个权重在这里

    return model


if __name__ == '__main__':

    # 数据比较简单,用 CPU 即可
    import os
    os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

    # Parameters
    params = {'batch_size': 10,
              'n_channels': 1,
              'shuffle': False}

    # Datasets
    # partition =  # IDs
    # labels =  # Labels
    x = {}
    x['train'] = np.arange(100, dtype='int32')
    x['validation'] = np.arange(100, 120, dtype='int32')
    y = {}
    y['train'] = -x['train']
    y['test'] = -x['train']


    # Generators
    training_generator = DataGenerator(x['train'], y['train'], **params)
    # validation_generator = DataGenerator(x['validation'], y['test'], **params)

    # Design model
    model = gen_model()
    model.compile(loss='mse', optimizer='adam')

    # model.fit(x['train'], y['train'], epochs=1000, batch_size=10, verbose=2)

    # Train model on dataset
    model.fit_generator(generator=training_generator,
                        # validation_data=validation_generator,
                        epochs=2000,
                        verbose=2,
                        workers=6,
                        )

    print(model.layers[0].get_weights())

结论

尽管Keras提供了数据生成器,但它们的功能有限。原因之一是每个任务都需要一个不同的数据加载器。有时每张图像都有一个遮罩,有时是几个,有时将遮罩保存为图像,有时将其编码,等等。
对于每个任务,我们可能都需要调整数据生成器,但结构将保持不变。