Keras多GPU训练指南
摘要:随着Keras(v2.0.8)最新版本的发布,使用多GPU 训练深度神经网络将变得非常容易,就跟调用函数一样简单!利用多GPU,能够获得准线性的提速。
Keras是我最喜欢的Python深度学习框架,特别是在图像分类领域。我在很多地方都使用到了Keras,包括生产系统、我自己的深度学习项目,以及PyImageSearch博客。
我的新书“基于Keras的深度学习计算机视觉”有三分之二的篇幅都跟这个框架有关。然而,在该框架过程中遇到的最大的一个问题就是执行多GPU训练。
但是,这个问题将不复存在!
随着Keras(v2.0.8)最新版本的发布,使用多GPU训练深度神经网络将变得非常容易,就跟调用函数一样简单!
如何使用Keras进行多GPU训练
当我第一次使用Keras的时候,我深深爱上了它的API。它简单而又优雅,类似于scikit-learn。但是,它又非常强大,能够实现并训练最先进的深度神经网络。
然后,对于Keras我最失望的的地方之一就是在多GPU环境中非常难用。如果你使用的是Theano,请忘记这一点,因为多GPU训练不会发生。虽然TensorFlow可以实现的,但需要编写大量的代码以及做出大量的调整来使你的网络支持多GPU训练。我喜欢在执行多GPU训练的时候使用mxnet作为Keras的后端,但它需要配置很多东西。
这所有的一切都随着Francorischollet公告的出现而发生了改变,使用TensorFlow作为后端的多GPU支持现在已经放到了Keras v2.0.8中。这个功劳很大程度上归功于kuza55和他们的karas-extra库。我使用这个多GPU功能已经有将近一年时间了,我非常高兴看到它成为Keras官方发布的一部分。
在这篇文章中,我将演示如何使用Keras、Python和深度学习来训练卷积神经网络进行图像分类。
MiniGoogLeNet深度学习架构
图1:MiniGoogLeNet架构是它的兄弟GoogLeNet/Inception的一个缩减版。在图1中,我们可以看到独立的convolution(左)、inception(中)和downsample(右)模块,下面则是由这些构建块构建而成的整个MiniGoogLeNet架构(底部)。我们将在本文后面的多GPU实验中使用MiniGoogLeNet架构。
MiniGoogLenet中的Inception模块是由Szegedy等人设计的原始Inception模块的一个变体。我最初是从ericjiang11和pluskid的推文中了解到“Miniception”模块的,他们的推文详细描述了该模块和相关的MiniGoogLeNet架构。
然后,我用Keras和Python实现了MiniGoogLeNet架构,并将其作为“基于Keras的深度学习计算机视觉”一书的一部分。
用Keras和多GPU训练深度神经网络
下面我们开始使用Keras和多GPU来训练深度学习网络。
在开始之前,请确保你的环境中已经安装了Keras 2.0.8(或更高版本):
创建一个新文件,将其命名为train.py,然后插入以下代码:
果你使用的是无外设服务器,则需要取消第3行和第4行上注释符来配置matplotlib后端。 这能让matplotlib图保存到磁盘。 如果你没有使用无外设服务器(即键盘、鼠标、显示器已连接系统),则无需去掉注释符。
在这段代码中,导入了该脚本所需的包。第7行从pyimagesearch模块导入MiniGoogLeNet。另一个需要注意的是第13行,我们导入了CIFAR10数据集。 这个帮助函数能让我们仅使用一行代码即可从磁盘加载CIFAR-10数据集。
现在我们来解析命令行参数:
我们在第20-25行使用argparse来解析一个必需的和一个可选的参数:
--output:训练完毕后输出图的路径。
--gpus:用于训练的GPU的个数。
在加载命令行参数后,为方便起见,我们将GPU数量保存在G中(第28行)
接着,我们要初始化用于配置训练过程的两个重要变量,然后定义poly_decay,这是一个学习速率调度函数,类似于caffeine多项式学习速率衰减:
设置NUM_EPOCHS = 70,这是训练数据通过网络(第32行)的迭代次数。我们还初始化了学习速率INIT_LR = 5e-3,这是我们在以前的试验中发现的值(第33行)。
这里,我们定义了poly_decay函数,这个函数类似于于Caffe的多项式学习速率衰减(第35-46行)。从本质上讲,这个函数更新了训练过程中的学习速度,在每次迭代之后能有效减少学习速率。power = 1.0将把衰减从多项式变为线性变化。
接下来,我们将加载训练和测试数据,并将图像数据从整数转换为浮点数:
接着对数据应用均值减法:
在第56行,计算了所有训练图像的平均值,然后在第57和58行,将训练和测试集中的每个图像减去这个平均值。
然后,执行“独热编码(one-hot encoding)”:
独热编码将分类标签从单个整数转换为向量,这样,就可以对其应用分类交叉熵损失函数。 我们已经在第61-63行考虑到了这一点。
接下来,创建一个数据增强器和一组回调函数:
在第67-69行,构建了图像生成器,用于数据扩充。数据扩充在是在训练过程中使用的一种方法,可通过对图像进行随机变换来随机改变图像。通过这些变换,网络将会持续看到增加的示例,这有助于网络更好地泛化到验证数据上,但同时也可能在训练集上表现得更差。 在多数情况下,这种权衡是值得的。
在第70行创建了一个回调函数,这使得学习速率在每次迭代之后发生衰减。请注意,函数名为poly_decay。
下面,我们来看看GPU变量:
如果GPU数量小于或等于1,则通过.build函数(第73-76行)来初始化model,否则在训练期间并行化模型:
在Keras中创建一个多GPU模型需要一些额外的代码,但不是很多!
首先,第84行,你会注意到我们已经指定使用CPU(而不是GPU)作为网络的上下文。为什么我们要用CPU呢?CPU可用于处理任何一种工作(比如在GPU内存上移动训练图像),而GPU本身则负责繁重的工作。在这种情况下,CPU将用于实例化基本模型。
然后,在第90行调用multi_gpu_model。 该函数将模型从CPU复制到所有的GPU上,从而获得单机多GPU的数据并行环境。
在训练网络图像的时候,训练任务将分批到每个GPU上执行。CPU将从每个GPU上获取梯度,然后执行梯度更新步骤。
然后,可以编译模型并启动训练过程了:
在第94行,我们构建了随机梯度下降(SGD)优化器。随后,我们用SGD优化器和分类交叉熵损失函数来编译模型。
现在,我们要开始训练网络了!
要启动训练,我们调用了model.fit_generator并提供了必要的参数。我们希望每个GPU上的批处理大小为64,这是由batch_size=64 * G指定的。训练将持续70此迭代(我们之前指定的)。梯度更新的结果将合并到CPU上,然后在整个训练过程中应用在每个GPU上。
现在训练和测试完成了,让我们画出损失/准确性曲线,以使整个训练过程可视化:
最后一段代码只是使用matplotlib来绘制训练/测试的损失和准确性曲线(第112-121行),然后将数据保存到磁盘上(第124行)。
Keras的多GPU运行结果
来看下我们努力的结果。我们先在一个GPU上进行训练以获得基线结果:
图2:在单个GPU上使用CIFAR-10对MiniGoogLeNet网络进行训练和测试的实验结果。在这个实验中,我在NVIDIA DevBox上使用了单个Titan X GPU进行了训练。 每一个迭代花了大概63秒,总训练时间为74分10秒。
然后,执行以下命令在四个Titan X GPU上进行训练:
图3:针对CIFAR10数据集在多GPU(4个Titan X GPU)上使用Keras和MiniGoogLeNet的训练结果。训练结果与单GPU的训练结果差不多,训练时间减少约75%。在这里,可以看到训练过程得到了准线性的提速:使用四个GPU,可以将每次迭代减少到16秒。整个网络的训练耗时19分3秒。
正如你所看到的,使用Keras和多个GPU来训练深度神经网络不仅简单而且高效!
注意:在这种情况下,单GPU实验获得的精度略高于多GPU。这是因为在训练任何一种随机机器学习模型的时候,都会出现一些差异。如果将这些结果平均一下,那么它们(几乎)是相同的。
总结
在今天的博文中,我们学到了如何使用多个GPU来训练基于Keras的深度神经网络。利用多个GPU,我们获得了准线性的提速。为了验证这一点,我们使用CIFAR-10数据集训练了MiniGoogLeNet。使用单个GPU,单次迭代时间为63秒,总训练时间为74分10秒。然而,使用Keras和Python在多GPU上训练的时候,单次迭代时间缩短到了16秒,总训练时间为19分03秒。
在Keras中启用多GPU训练就跟调用函数一样简单, 强烈建议你尽早使用多GPU进行训练。我猜想,在将来multi_gpu_model肯定会进一步得到改进,能让我们指定使用哪几个GPU进行训练,并最终实现多系统的训练。
文章原标题《How-To: Multi-GPU training with Keras, Python, and deep learning》,作者:Adrian Rosebrock,译者:夏天,审校:主题曲。
文章为简译,更为详细的内容,请查看原文
本文由阿里云云栖社区组织翻译。