動機
您是否曾經不得不加載一個非常消耗內存的數據集,以至於希望魔術能夠無縫地解決這一問題?大型數據集正日益成爲我們生活的一部分,因爲我們能夠利用數量不斷增長的數據。
我們必須謹記,在某些情況下,即使是最先進的配置也沒有足夠的內存空間來像以前那樣處理數據。這就是爲什麼我們需要找到其他有效地完成該任務的方法的原因。在此博客文章中,我們將向您展示如何實時在多個內核上生成數據集並將其立即饋送到您的深度學習模型。
本教程中使用的框架是Python的高級軟件包Keras提供的框架,可以在TensorFlow或Theano的GPU安裝之上使用。
講解
以前的情況
在閱讀本文之前,您的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)
本文全部涉及更改一次加載整個數據集的方式。確實,此任務可能會引起問題,因爲所有訓練樣本可能無法同時放入內存中。
爲了做到這一點,讓我們深入研究如何構建適合此情況的數據生成器的逐步方法。順便說一句,以下代碼是用於您自己的項目的良好框架;您可以複製/粘貼以下代碼,並相應地填充空白。
小提示
在開始之前,我們先看一些組織技巧,這些技巧在處理大型數據集時特別有用。
讓ID
使用Python
字符串來標識給定的數據集樣本。跟蹤樣本及其標籤的一種好方法是採用以下框架:
創建一個字典partition
收集索引數據:
- partition[‘train’]:train ID列表
- partition[‘validation’]:validation ID列表
創建一個字典labels
,其中每個ID數據集的相關標籤由給出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/
是包含數據集的文件夾。
最後,需要注意的是,本教程中的代碼是針對通用和最少的,因此您可以輕鬆地將其調整爲自己的數據集。
數據產生器
現在,讓我們詳細介紹如何設置Python類DataGenerator
,該類將用於將實時數據饋入Keras模型。
首先,讓我們編寫該類的初始化函數。我們使後者繼承了屬性,keras.utils.Sequence
以便可以利用諸如多重處理之類的出色功能。
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()
我們將有關數據的相關信息作爲參數,例如維度大小(例如,長度爲32的卷dim=(32,32,32)
),通道數,類數,批處理大小,或決定是否要在生成時改組數據。我們還存儲重要信息,例如標籤以及我們希望在每次通過時生成的ID列表。
在此,該方法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)
調整將示例 fed to 分類器的順序很有幫助,這樣,各個時期之間的批處理看起來就不會相似。這樣做最終將使我們的模型更強大。
生成過程最核心的另一種方法是完成最關鍵的工作:生成一批數據。調用負責此任務的私有方法__data_generation
,並將目標批處理的ID列表作爲參數。
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)
在數據生成期間,此代碼從其對應的文件中讀取每個示例的NumPy數組ID.npy
。由於我們的代碼是多核友好的,因此請注意,您可以執行更復雜的操作(例如,從源文件進行計算),而不必擔心數據生成成爲訓練過程中的瓶頸。
另外,請注意,我們使用Keras keras.utils.to_categorical
函數將存儲的數字標籤轉換y
爲[0 0 1 0 0 0]
適合分類的二進制形式(例如,在6類問題中,第三個標籤對應於)。
現在是我們將所有這些組件組合在一起的部分。每個調用都請求一個介於0和批次總數之間的批次索引,其中在__len__
方法中指定了後者。
def __len__(self):
'Denotes the number of batches per epoch'
return int(np.floor(len(self.list_IDs) / self.batch_size))
通常的做法是將此值設置爲
這樣該模型每個時期最多可以看到一次訓練樣本。
現在,當調用與給定索引相對應的批處理時,生成器將執行__getitem__
生成該方法的方法。
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腳本
現在,我們必須相應地修改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)
正如你所看到的,我們從所謂model
的fit_generator
方法,而不是fit
,我們剛剛給我們的訓練發生器的參數之一。Keras負責其餘的工作!
請注意,我們的實現允許使用的multiprocessing
參數fit_generator
,其中指定的線程數是workers
並行生成批處理的線程數。足夠多的工作人員可以確保有效地管理CPU計算,即瓶頸確實是神經網絡在GPU上進行的向前和向後操作(而不是數據生成)。
結論
就是這個!您現在可以使用以下命令運行Keras腳本
python3 keras_script.py
和你會看到,在訓練階段期間,數據被並行地由CPU生成,然後直接喂入到GPU。
你可以找到這個戰略上的一個具體的例子應用的完整例子GitHub上,其中的代碼數據的生成以及對Keras腳本可用。
A detailed example of how to use data generators with Keras