目錄
重點介紹如何構建數據生成器以在Keras中加載和處理圖像。
數據生成器的功能是什麼
在Keras Model類中,有三種我們感興趣的方法:fit_generator,valuate_generator和predict_generator。它們全部三個都需要數據生成器,但並非所有生成器都是平等創建的。
讓我們看一下每種方法需要哪種生成器:
fit_generator
需要兩個生成器,一個用於訓練數據,另一個用於驗證。幸運的是,它們兩個都應該返回一個tupple(輸入,目標),並且它們都可以是Sequence類的實例。
Evaluation_generator
此處的數據生成器具有與fit_generator中相同的要求,並且可以與訓練生成器相同。
predict_generator
這裏的生成器有點不同。它應該只返回輸入。
考慮到這一點,讓我們構建一些數據生成器。由於fit_generator中的生成器和evaluate_generator之間的相似性,我們將集中精力構建fit_generator和predict_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