关于如何使用Keras中的data generator的具体例子
动机
如果你训练需要使用大规模数据集的模型的话,大概常会遇到一个问题:过大的数据集无法同时加载到内存中,然而模型的性能提升又不得不使用大规模数据集,所以如何在训练过程中调用体积庞大的数据集是一个非常现实的问题。
在这片博文中,将要介绍一种数据集逐批生成方法,来实时多核生成数据集并且可以立刻交付给你的深度学习模型
Tutorial
使用generator之前
在读这篇文章之前,你的Keras脚本文件可能看起来是这样:
import numpy as np
from keras.models import Sequential
# Load entire dataset
X, y = np.load('some_training_set_with_labels.npy')
# Design model
model = Sequential()
[...] # Your architecture
model.compile()
# Train model on your dataset
model.fit(x=X, y=y)
这篇文章就是要讲如何改变这种一次性加载所有数据集的方式,来解决由于数据集过大内存不足的问题。
接下来,我们来写一个generator解决以上问题,此外,这些代码也可以作为你创建自已的项目的一个很好的框架,你可以直接ctrl+C&V以下代码并根据自身情况做修改。
Notations
在开始之前,我们先看一下几个在处理大规模数据集时非常有用的小技巧。
ID:Python string,指代数据集的名称
你可以用以下框架来给你的数据集及标签命名:
- 创建一个名为
partition
的字典:
partition['train']
: training IDs 的列表
partition['validation']
: validation IDs 的列表 - 为每一个ID创建一个名为
labels
的字典,以labels[ID]
命名
例如,我们的训练集中包括id-1
、id-2
和id-3
,以及相对应的标签0
、1
和2
,以及包含一个id-4
及1
的训练集,在这种情况下,在Python中partition
及labels
变量如下:>>> partition {'train': ['id-1', 'id-2', 'id-3'], 'validation': ['id-4']}
>>> labels {'id-1': 0, 'id-2': 1, 'id-3': 2, 'id-4': 1}
并且,出于使工程模块化的原因,我们将Keras中几个类定义在不同的文件中,所以文件夹看起来大概是这样:
folder/
├── my_classes.py
├── keras_script.py
└── data/
其中,data/
中默认是你的数据集的所在文件夹。
本篇tutorial中的code尽可能实现普适性,以便于你应用在自己的工程中。
Data generator
现在我们开始进入正题,如何在你的keras模型中使用Data generator。
首先,我们需要定义一个类的初始化函数,该类继承了keras.utils.Sequence
特性以便我们使用一些非常好的功能,例如multi processing
def __init__(self, list_IDs, labels, batch_size=32, dim=(32,32,32), n_channels=1,
n_classes=10, shuffle=True):
'Initialization'
self.dim = dim
self.batch_size = batch_size
self.labels = labels
self.list_IDs = list_IDs
self.n_channels = n_channels
self.n_classes = n_classes
self.shuffle = shuffle
self.on_epoch_end()
这里,每当训练一回合的开始同时也是上一回合的结束,on_epoch_end
方法就会被触发。如果shuffle
被设置为True的话,我们在不同迭代的回合中将会得到一个打乱顺序的同批数据集。
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)
打乱同批喂给模型的数据集的次序对于提升模型的稳健性还是非常有用的。
接下来,是data generator的核心方法:生成多批次的数据集,负责这一项任务的private method叫做:_data_generation
def __data_generation(self, list_IDs_temp):
'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)
# Initialization
X = np.empty((self.batch_size, *self.dim, self.n_channels))
y = np.empty((self.batch_size), dtype=int)
# Generate data
for i, ID in enumerate(list_IDs_temp):
# Store sample
X[i,] = np.load('data/' + ID + '.npy')
# Store class
y[i] = self.labels[ID]
return X, keras.utils.to_categorical(y, num_classes=self.n_classes)
在生成数据的过程中,这一段代码从相应的ID.npy
文件中读取Numpy数组,因为这段代码适用于多核操作,所以你可以做一些更复杂的操作(例如基于源文件计算)而不用训练过程中由于数据生成导致的瓶颈问题。
此外,需要注意的是,我们使用Keras自带的keras.utils.to_categorical
函数将我们数值类型的标签存储为二进制形式(例如,如果有6类的话,转化为[0 0 1 0 0])。
接下来这部分我们将这三部分整合到一起,每一次调用请求返回一个batch,batch的大小通过以下函数计算:
def __len__(self):
'Denotes the number of batches per epoch'
return int(np.floor(len(self.list_IDs) / self.batch_size))
接下来,当某个相应的batch被调用时,生成器通过执行_getitem_
方法来返回batch
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, y = self.__data_generation(list_IDs_temp)
return X, y
完整的代码如下:
import numpy as np
import keras
class DataGenerator(keras.utils.Sequence):
'Generates data for Keras'
def __init__(self, list_IDs, labels, batch_size=32, dim=(32,32,32), n_channels=1,
n_classes=10, shuffle=True):
'Initialization'
self.dim = dim
self.batch_size = batch_size
self.labels = labels
self.list_IDs = list_IDs
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 int(np.floor(len(self.list_IDs) / 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]
# Find list of IDs
list_IDs_temp = [self.list_IDs[k] for k in indexes]
# Generate data
X, y = self.__data_generation(list_IDs_temp)
return X, y
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 __data_generation(self, list_IDs_temp):
'Generates data containing batch_size samples' # X : (n_samples, *dim, n_channels)
# Initialization
X = np.empty((self.batch_size, *self.dim, self.n_channels))
y = np.empty((self.batch_size), dtype=int)
# Generate data
for i, ID in enumerate(list_IDs_temp):
# Store sample
X[i,] = np.load('data/' + ID + '.npy')
# Store class
y[i] = self.labels[ID]
return X, keras.utils.to_categorical(y, num_classes=self.n_classes)
Keras Script
接下来就是如何将以上代码具体应用在我们的Keras模型中:
import numpy as np
from keras.models import Sequential
from my_classes import DataGenerator
# Parameters
params = {'dim': (32,32,32),
'batch_size': 64,
'n_classes': 6,
'n_channels': 1,
'shuffle': True}
# Datasets
partition = # IDs
labels = # Labels
# Generators
training_generator = DataGenerator(partition['train'], labels, **params)
validation_generator = DataGenerator(partition['validation'], labels, **params)
# Design model
model = Sequential()
[...] # Architecture
model.compile()
# Train model on dataset
model.fit_generator(generator=training_generator,
validation_data=validation_generator,
use_multiprocessing=True,
workers=6)
如上所示,我们调用fit_generator
而非fit
方法,接下来Keras会处理一切。
我们的这种实现方式可以使用fit_generator
中的multi processing
设定,线程个数由n_workers
定义。
总结
完成!接下来就可以通过以下命令来执行该文件:
python3 keras_script.py
你将会看到,在训练阶段,数据由多个CPU并行生成后直接输入模型。
原文出处:https://stanford.edu/~shervine/blog/keras-how-to-generate-data-on-the-fly