为了更快地训练模型,在CPU上填充/生成批次并在GPU上并行运行模型的训练似乎是一个好习惯。为此,可以使用Python编写一个生成器类,该类继承Sequence
类。
以下是文档的链接: https://www.tensorflow.org/api_docs/python/tf/keras/utils/Sequence
文档指出的重要内容是:
Sequence
是进行多处理的更安全方法。这种结构 保证网络在每个样本上仅训练一次 生成器不是这样的时代。
它给出了一个简单的代码示例,如下所示:
from skimage.io import imread
from skimage.transform import resize
import numpy as np
import math
# Here, `x_set` is list of path to the images
# and `y_set` are the associated classes.
class CIFAR10Sequence(Sequence):
def __init__(self, x_set, y_set, batch_size):
self.x, self.y = x_set, y_set
self.batch_size = batch_size
def __len__(self):
return math.ceil(len(self.x) / self.batch_size)
def __getitem__(self, idx):
batch_x = self.x[idx * self.batch_size:(idx + 1) *
self.batch_size]
batch_y = self.y[idx * self.batch_size:(idx + 1) *
self.batch_size]
return np.array([
resize(imread(file_name), (200, 200))
for file_name in batch_x]), np.array(batch_y)
在我看来,在模型中理想地要做的是创建此生成器类的实例并将其提供给fit_generator(...)
函数。
gen = CIFAR10Sequence(x_set, y_set, batch_size)
# Train the model
model.fit_generator(generator=gen,
use_multiprocessing=True,
workers=6)
以下是Keras文档的引文:
使用
keras.utils.Sequence
保证顺序并保证 使用时每个历元的每个输入的单一用法use_multiprocessing=True
。
在这种形状下,我认为此设置是线程安全的。 问题1):我的假设正确吗?
一个令人困惑的事情是,在Windows 10上参数use_multiprocessing
可能未设置为True。似乎只能在Linux上将其设置为True。 (我不知道其他平台上的情况如何。)但是workers
参数仍然可以设置为大于0的值。
让我们看一下这两个参数的定义:
workers:
整数。使用时最多可启动的进程数 基于进程的线程。如果未指定,则worker将默认为1。 0,将在主线程上执行生成器。
use_multiprocessing:
布尔值。如果为True,则使用基于进程的线程。如果 未指定,use_multiprocessing将默认为False。注意 因为此实现依赖于多处理,所以您不应 将不可拾取的参数传递给生成器,因为它们无法传递 轻松进行子进程。
因此,通过使用workers
参数,似乎有可能创建多个过程来独立于use_multiprocessing
是否为True来加快训练速度。
如果要使用继承Sequence
的生成器类(在Windows 10上为 ),则必须将use_multiprocessing
设置为False,如下所示:
gen = CIFAR10Sequence(x_set, y_set, batch_size)
# Train the model
model.fit_generator(generator=gen,
use_multiprocessing=False, # CHANGED
workers=6)
由于worker = 6,这里仍然有多个进程在运行。
问题2):此设置仍然是线程安全的吗?或者在将use_multiprocessing
参数设置为False之后,线程安全特性现在丢失了吗?根据文档,我无法弄清楚。
问题3)仍与此主题相关...当以这种方式进行训练时,CPU生成数据并在GPU上进行训练,如果所训练的模型较浅,由于GPU一直在等待来自CPU的数据,因此GPU的使用率最终非常低,CPU的使用率显着提高。在这种情况下,是否有办法利用一些GPU资源进行数据生成?
答案 0 :(得分:2)
在看过这篇文章的人中,似乎没有人给出最终答案,因此我想给出对我有用的答案。由于该领域缺乏文档,我的答案可能缺少一些相关细节。请随时添加我在这里没有提及的更多信息。
貌似,在 Windows 中不支持使用Python编写继承Sequence
类的生成器类。 (您似乎可以使其在Linux上运行。)要使其运行,您需要设置参数use_multiprocessing=True
(使用类方法)。但是如上所述,它在Windows上不起作用,因此您必须将use_multiprocessing
设置为False
(在Windows上)。
但是,这并不意味着多重处理在Windows上不起作用。即使您设置了use_multiprocessing=False
,使用以下设置运行代码时仍可以支持多处理,只需将workers
参数设置为任何大于1的值即可。
示例:
history = \
merged_model.fit_generator(generator=train_generator,
steps_per_epoch=trainset_steps_per_epoch,
epochs=300,
verbose=1,
use_multiprocessing=False,
workers=3,
max_queue_size=4)
在这一点上,让我们再次记住Keras文档:
使用keras.utils.Sequence保证顺序并保证 使用时每个时期每个输入的单一使用 use_multiprocessing = True。
据我了解,如果为use_multiprocessing=False
,则生成器不再是线程安全的,这使得编写继承Sequence
的 generator类变得困难。
要解决此问题,我自己编写了一个生成器,并手动将其设为线程安全。这是伪代码示例:
import tensorflow as tf
import threading
class threadsafe_iter:
"""Takes an iterator/generator and makes it thread-safe by
serializing call to the `next` method of given iterator/generator.
"""
def __init__(self, it):
self.it = it
self.lock = threading.Lock()
def __iter__(self):
return self
def __next__(self): # Py3
return next(self.it)
#def next(self): # Python2 only
# with self.lock:
# return self.it.next()
def threadsafe_generator(f):
"""A decorator that takes a generator function and makes it thread-safe.
"""
def g(*a, **kw):
return threadsafe_iter(f(*a, **kw))
return g
@threadsafe_generator
def generate_data(tfrecord_file_path_list, ...):
dataset = tf.data.TFRecordDataset(tfrecord_file_path_list)
# example proto decode
def _parse_function(example_proto):
...
return batch_data
# Parse the record into tensors.
dataset = dataset.map(_parse_function)
dataset = dataset.shuffle(buffer_size=100000)
# Repeat the input indefinitly
dataset = dataset.repeat()
# Generate batches
dataset = dataset.batch(batch_size)
# Create an initializable iterator
iterator = dataset.make_initializable_iterator()
# Get batch data
batch_data = iterator.get_next()
iterator_init_op = iterator.make_initializer(dataset)
with tf.Session() as sess:
sess.run(iterator_init_op)
while True:
try:
batch_data = sess.run(batch_data)
except tf.errors.OutOfRangeError:
break
yield batch_data
好吧,可以这样进行讨论是否真的很优雅,但是似乎运行得很好。
总结:
use_multiprocessing
设置为False
。Sequence
的生成器类。 (我猜这是一个Tensorflow / Keras问题)。workers
设置为大于1的数字。重要说明::在此设置中,生成器在CPU上运行,培训在GPU上完成。我可以观察到的一个问题是,如果您正在训练的模型足够浅,则GPU的利用率仍然很低,而CPU利用率却很高。如果模型较浅并且数据集足够小,那么将所有数据存储在内存中并在GPU上运行所有数据是一个不错的选择。它应该大大加快培训速度。如果出于任何原因想要同时使用CPU和GPU,我的建议是尝试使用Tensorflow的tf.data API,该API可显着加快数据预处理和批处理的速度。如果生成器仅使用Python编写,则GPU会一直等待数据以继续训练。可以说有关Tensorflow / Keras文档的所有内容,但这确实是高效的代码!
任何对API有更全面的了解的人,请看这篇文章,如果我误解了任何东西或者API已更新甚至在Windows上也解决了问题,请随时在这里纠正我。
答案 1 :(得分:1)
我有一个建议的“改进的”解决方案,可能会让其他人感兴趣。请注意,这是基于我对Tensorflow 1.15的经验(我尚未使用版本2)。
在Windows上安装wsl
版本2,在此处在Linux环境(例如Ubuntu)中安装Tensorflow,然后将use_multiprocessing
设置为True
以将其保存到工作。
注意: 仅适用于Windows 10版本1903,内部版本18362或更高版本的Windows Subshell for Linux(WSL)2。 请确保升级Windows Update中的Windows版本以使其正常工作。
请参见Install Tensorflow-GPU on WSL2
对于multitasking
和multithreading
(即parallelism
和concurrency
),我们必须考虑两个操作:
forking
=父进程创建其自身(子级)的副本,该副本具有其使用的所有内存段的精确副本spawning
=父进程创建了一个不共享其内存的全新子进程,并且父进程必须等待子进程完成才能继续 Linux支持forking
,但Windows不支持。 Windows仅支持spawning
。
使用use_multiprocessing=True
时Windows挂起的原因是因为Python threading
模块将spawn
用于Windows。因此,父进程将永远等待子进程完成,因为父进程无法将其内存转移给子进程,因此子进程不知道该怎么做。
答案2: 它不是threadsafe
。在Windows上,如果您曾经尝试使用数据生成器或序列,则可能看到这样的错误
ValueError: Using a generator with use_multiprocessing=True is not supported on Windows
(no marshalling of generators across process boundaries). Instead, use single
thread/process or multithreading.
marshalling
的意思是“将对象的内存表示形式转换为适合传输的数据格式”。错误是说,与使用fork
的Linux不同,use_multiprocessing=True
在Windows上不起作用,因为它使用spawn`并且无法将其数据传输到子线程。
此时,您可能会问自己:
“等等……Python全局解释器锁(GIL)怎么样?”。如果Python一次只允许一个线程运行,为什么它甚至具有threading
模块,为什么?我们在Tensorflow中关心吗?!”
答案在于CPU-bound tasks
和I/O-bound tasks
之间的区别:
CPU-bound tasks
=等待数据处理的那些人I/O-bound tasks
=等待其他进程的输入或输出(即数据传输)的那些人在编程中,当我们说两个任务是concurrent
时,是指它们可以在重叠的时间内启动,运行和完成。当我们说它们是parallel
时,意味着它们实际上是在同时运行。
因此,GIL阻止线程并行运行 ,但不能并行运行 。这对Tensorflow很重要的原因是因为 concurrency 都是关于I / O操作(数据传输)的。 Tensorflow中良好的数据流管道应尽量设置为concurrent
,这样在与CPU,GPU和/或RAM之间来回传输数据时,就不会有延迟时间,并且训练可以更快地完成。 (而不是让线程坐等直到它从其他地方获取数据,我们可以让它执行图像预处理或其他方法直到数据返回。)
GIL
是用Python制作的,因为Python中的所有内容都是对象。 (这就是为什么您可以使用“愚蠢/魔术”方法来做“怪异”事情的原因,例如(5).__add__(3)
获得8
注意::在上文中,5
是5.
,因此float
周围需要加上括号,因此我们需要使用括号来利用运算顺序。
Python通过计算对单个对象的所有引用来处理内存和垃圾回收。当计数变为0时,Python删除对象。如果两个线程试图同时访问同一对象,或者一个线程比另一个线程完成得更快,则可以得到race condition
,对象将被“随机”删除。我们可以在每个线程上放置一个lock
,但是我们将无法阻止deadlocks
。
Guido认为parallel
线程执行的丢失(以及我自己,尽管这当然可以争论)是很小的损失,因为我们仍然保持I / O并发操作,并且任务仍然可以在parallel
中运行在不同的CPU内核(即multiprocessing
)上运行它们。因此,这是(原因之一)Python同时具有threading
和multiprocessing
模块。
现在返回threadsafe
。在执行concurrent
/ parallel
任务时,您必须注意其他事项。两个大的是:
race conditions
-每次运行程序时,运算所花的时间并不完全相同(为什么timeit
会在多次运行中求平均值)。由于线程会根据运行在不同的时间完成,因此每次运行都会得到不同的结果。
deadlock
-如果两个线程试图同时访问相同的内存,则会出现错误。为防止这种情况,我们在线程中添加了lock
或mutex
(互斥),以防止其他线程在运行时访问同一内存。但是,如果两个线程需要访问相同的内存而被锁定,并且每个线程都依赖于另一个线程才能执行,则程序将挂起。
之所以提出这一点,是因为Tensorflow需要能够pickle
Python对象以使代码运行更快。 (pickling
将对象和数据转换为字节码,就像将整个程序的源代码转换为Windows上的exe
一样)。 Tensorflow Iterator.__init__()
方法锁定线程并包含一个threading.Lock()
def __init__(self, n, batch_size, shuffle, seed):
...
self.lock = threading.Lock()
...
问题是Python无法在Windows上pickle
线程锁定对象(即Windows无法marshall
线程锁定到child
线程)。
如果您尝试使用生成器并将其传递给fit_generator
,则会收到错误消息(请参见GitHub Issue #10842
TypeError: can't pickle _thread.lock objects
因此,尽管use_multiprocessing=True
在Linux上是线程安全的,但在Windows上却不是。
解决方案::2020年6月左右,Microsoft推出了适用于Linux的Windows Subshell版本2(wsl
)。这很重要,因为它启用了GPU硬件加速。版本1“仅”是Windows NT和Linux之间的驱动程序,而wsl
现在实际上是内核。因此,您现在可以在Windows上安装Linux,在命令提示符下打开bash shell,(最重要的是)访问硬件。因此,现在可以在tensorflow-gpu
上安装wsl
。此外,您现在可以使用fork
。
**因此,我建议
wsl
版本2并添加所需的Linux环境tensorflow-gpu
Linux环境中的虚拟环境中安装wsl
use_multiprocessing=True
看看是否有效。** 注意:我尚未测试此功能以验证其是否有效,但是据我所知,我相信它应该起作用。
此后,回答问题3 应该很简单,只需调整并行性和并行性即可,我建议使用TensorflowDev 2018 Summit视频Training Performance: A user’s guide to converge faster来了解如何做到这一点。