Keras數據生成器以及如何使用它們

重點介紹如何構建數據生成器以在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)

結論

儘管Keras提供了數據生成器,但它們的功能有限。原因之一是每個任務都需要一個不同的數據加載器。有時每張圖像都有一個遮罩,有時是幾個,有時將遮罩保存爲圖像,有時將其編碼,等等。
對於每個任務,我們可能都需要調整數據生成器,但結構將保持不變。


ref:

文中代碼的github地址
Keras data generators and how to use them

相關閱讀:

閱讀以下的鏈接,你能夠更加清晰地理解。
姊妹文章:如何在Keras中使用數據生成器(data generators)的詳細示例

A detailed example of how to use data generators with Keras by Stanford

Keras Model class

Keras ImageDataGenerator class

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章