Keras多GPU训练指南

更多深度文章,请关注:https://yq.aliyun.com/cloud

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的后端,但它需要配置很多东西。

这所有的一切都随着FrançoisChollet公告的出现而发生了改变,使用TensorFlow作为后端的多GPU支持现在已经放到了Keras v2.0.8中。这个功劳很大程度上归功于@kuza55和他们的keras-extras库。我使用这个多GPU功能已经有将近一年时间了,我非常高兴看到它成为Keras官方发布的一部分。

在这篇文章中,我将演示如何使用Keras、Python和深度学习来训练卷积神经网络进行图像分类。要获取相关代码,请访问这个网页中的“Downloads”章节。

MiniGoogLeNet深度学习架构


图1:MiniGoogLeNet架构是它的兄弟GoogLeNet/Inception的一个缩减版。

图1中,我们可以看到独立的convolution(左)、inception(中)和downsample(右)模块,下面则是由这些构建块构建而成的整个MiniGoogLeNet架构(底部)。我们将在本文后面的多GPU实验中使用MiniGoogLeNet架构。

MiniGoogLenet中的Inception模块是由Szegedy等人设计的原始Inception模块的一个变体。我最初是从@ericjang11@pluskid的推文中了解到“Miniception”模块的,他们的推文详细描述了该模块和相关的MiniGoogLeNet架构。

然后,我用Keras和Python实现了MiniGoogLeNet架构,并将其作为“基于Keras的深度学习计算机视觉”一书的一部分。

对MiniGoogLeNet Keras实现的完整描述已经超出了本文的讨论范围,如果你对此感兴趣的话,请阅读我这本书

用Keras和多GPU训练深度神经网络

下面我们开始使用Keras和多GPU来训练深度学习网络。

在开始之前,请确保你的环境中已经安装了Keras 2.0.8(或更高版本):

$ workon dl4cv
$ pip install --upgrade keras

创建一个新文件,将其命名为train.py,然后插入以下代码:

# set the matplotlib backend so figures can be saved in the background
# (uncomment the lines below if you are using a headless server)
# import matplotlib
# matplotlib.use("Agg")

# import the necessary packages
from pyimagesearch.minigooglenet import MiniGoogLeNet
from sklearn.preprocessing import LabelBinarizer
from keras.preprocessing.image import ImageDataGenerator
from keras.callbacks import LearningRateScheduler
from keras.utils.training_utils import multi_gpu_model
from keras.optimizers import SGD
from keras.datasets import cifar10
import matplotlib.pyplot as plt
import tensorflow as tf
import numpy as np
import argparse

如果你使用的是无外设服务器,则需要取消第3行和第4行上注释符来配置matplotlib后端。 这能让matplotlib图保存到磁盘。 如果你没有使用无外设服务器(即键盘、鼠标、显示器已连接系统),则无需去掉注释符。

在这段代码中,导入了该脚本所需的包。第7行pyimagesearch模块导入MiniGoogLeNet。另一个需要注意的是第13行,我们导入了CIFAR10数据集。 这个帮助函数能让我们仅使用一行代码即可从磁盘加载CIFAR-10数据集。

现在我们来解析命令行参数:

# construct the argument parse and parse the arguments
ap = argparse.ArgumentParser()
ap.add_argument("-o", "--output", required=True,
    help="path to output plot")
ap.add_argument("-g", "--gpus", type=int, default=1,
    help="# of GPUs to use for training")
args = vars(ap.parse_args())

# grab the number of GPUs and store it in a conveience variable
G = args["gpus"]

我们在第20-25行使用argparse来解析一个必需的和一个可选的参数:

  • --output:训练完毕后输出图的路径。
  • --gpus:用于训练的GPU的个数。

在加载命令行参数后,为方便起见,我们将GPU数量保存在G中(第28行

接着,我们要初始化用于配置训练过程的两个重要变量,然后定义poly_decay,这是一个学习速率调度函数,类似于Caffe多项式学习速率衰减


# definine the total number of epochs to train for along with the
# initial learning rate
NUM_EPOCHS = 70
INIT_LR = 5e-3

def poly_decay(epoch):
    # initialize the maximum number of epochs, base learning rate,
    # and power of the polynomial
    maxEpochs = NUM_EPOCHS
    baseLR = INIT_LR
    power = 1.0

    # compute the new learning rate based on polynomial decay
    alpha = baseLR  (1 - (epoch / float(maxEpochs))) * power

    # return the new learning rate
    return alpha

设置NUM_EPOCHS = 70,这是训练数据通过网络(第32行)的迭代次数。我们还初始化了学习速率INIT_LR = 5e-3,这是我们在以前的试验中发现的值(第33行)。

这里,我们定义了poly_decay函数,这个函数类似于于Caffe的多项式学习速率衰减(第35-46行)。从本质上讲,这个函数更新了训练过程中的学习速度,在每次迭代之后能有效减少学习速率。 power = 1.0将把衰减从多项式变为线性变化。

接下来,我们将加载训练和测试数据,并将图像数据从整数转换为浮点数:

# load the training and testing data, converting the images from
# integers to floats
print("[INFO] loading CIFAR-10 data...")
((trainX, trainY), (testX, testY)) = cifar10.load_data()
trainX = trainX.astype("float")
testX = testX.astype("float")

接着对数据应用均值减法:

# apply mean subtraction to the data
mean = np.mean(trainX, axis=0)
trainX -= mean
testX -= mean

第56行,计算了所有训练图像的平均值,然后在第57和58行,将训练和测试集中的每个图像减去这个平均值。

然后,执行“独热编码(one-hot encoding)”:

# convert the labels from integers to vectors
lb = LabelBinarizer()
trainY = lb.fit_transform(trainY)
testY = lb.transform(testY)

独热编码将分类标签从单个整数转换为向量,这样,就可以对其应用分类交叉熵损失函数。 我们已经在第61-63行考虑到了这一点。

接下来,创建一个数据增强器和一组回调函数:

# construct the image generator for data augmentation and construct
# the set of callbacks
aug = ImageDataGenerator(width_shift_range=0.1,
    height_shift_range=0.1, horizontal_flip=True,
    fill_mode="nearest")
callbacks = [LearningRateScheduler(poly_decay)]

第67-69行,构建了图像生成器,用于数据扩充。数据扩充在是在训练过程中使用的一种方法,可通过对图像进行随机变换来随机改变图像。通过这些变换,网络将会持续看到增加的示例,这有助于网络更好地泛化到验证数据上,但同时也可能在训练集上表现得更差。 在多数情况下,这种权衡是值得的。

第70行创建了一个回调函数,这使得学习速率在每次迭代之后发生衰减。请注意,函数名为poly_decay

下面,我们来看看GPU变量:

# check to see if we are compiling using just a single GPU
if G <= 1:
    print("[INFO] training with 1 GPU...")
    model = MiniGoogLeNet.build(width=32, height=32, depth=3,
        classes=10)

如果GPU数量小于或等于1,则通过.build函数(第73-76行)来初始化model,否则在训练期间并行化模型:

# otherwise, we are compiling using multiple GPUs
else:
    print("[INFO] training with {} GPUs...".format(G))

    # we'll store a copy of the model on every GPU and then combine
    # the results from the gradient updates on the CPU
    with tf.device("/cpu:0"):
        # initialize the model
        model = MiniGoogLeNet.build(width=32, height=32, depth=3,
            classes=10)

    # make the model parallel
    model = multi_gpu_model(model, gpus=G)

在Keras中创建一个多GPU模型需要一些额外的代码,但不是很多!

首先,第84行,你会注意到我们已经指定使用CPU(而不是GPU)作为网络的上下文。为什么我们要用CPU呢?CPU可用于处理任何一种工作(比如在GPU内存上移动训练图像),而GPU本身则负责繁重的工作。在这种情况下,CPU将用于实例化基本模型。

然后,在第90行调用multi_gpu_model。 该函数将模型从CPU复制到所有的GPU上,从而获得单机多GPU的数据并行环境。

在训练网络图像的时候,训练任务将分批到每个GPU上执行。CPU将从每个GPU上获取梯度,然后执行梯度更新步骤。

然后,可以编译模型并启动训练过程了:

# initialize the optimizer and model
print("[INFO] compiling model...")
opt = SGD(lr=INIT_LR, momentum=0.9)
model.compile(loss="categorical_crossentropy", optimizer=opt,
    metrics=["accuracy"])

# train the network
print("[INFO] training network...")
H = model.fit_generator(
    aug.flow(trainX, trainY, batch_size=64 * G),
    validation_data=(testX, testY),
    steps_per_epoch=len(trainX) // (64 * G),
    epochs=NUM_EPOCHS,
    callbacks=callbacks, verbose=2)

第94行,我们构建了随机梯度下降(SGD)优化器。随后,我们用SGD优化器和分类交叉熵损失函数来编译模型。

现在,我们要开始训练网络了!

要启动训练,我们调用了model.fit_generator并提供了必要的参数。我们希望每个GPU上的批处理大小为64,这是由batch_size=64 * G指定的。训练将持续70此迭代(我们之前指定的)。梯度更新的结果将合并到CPU上,然后在整个训练过程中应用在每个GPU上。

现在训练和测试完成了,让我们画出损失/准确性曲线,以使整个训练过程可视化:

# grab the history object dictionary
H = H.history

# plot the training loss and accuracy
N = np.arange(0, len(H["loss"]))
plt.style.use("ggplot")
plt.figure()
plt.plot(N, H["loss"], label="train_loss")
plt.plot(N, H["val_loss"], label="test_loss")
plt.plot(N, H["acc"], label="train_acc")
plt.plot(N, H["val_acc"], label="test_acc")
plt.title("MiniGoogLeNet on CIFAR-10")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend()

# save the figure
plt.savefig(args["output"])
plt.close()

最后一段代码只是使用matplotlib来绘制训练/测试的损失和准确性曲线(第112-121行),然后将数据保存到磁盘上(第124行)。

Keras的多GPU运行结果

来看下我们努力的结果。我们先在一个GPU上进行训练以获得基线结果:

$ python train.py --output single_gpu.png
[INFO] loading CIFAR-10 data...
[INFO] training with 1 GPU...
[INFO] compiling model...
[INFO] training network...
Epoch 1/70
 - 64s - loss: 1.4323 - acc: 0.4787 - val_loss: 1.1319 - val_acc: 0.5983
Epoch 2/70
 - 63s - loss: 1.0279 - acc: 0.6361 - val_loss: 0.9844 - val_acc: 0.6472
Epoch 3/70
 - 63s - loss: 0.8554 - acc: 0.6997 - val_loss: 1.5473 - val_acc: 0.5592
...
Epoch 68/70
 - 63s - loss: 0.0343 - acc: 0.9898 - val_loss: 0.3637 - val_acc: 0.9069
Epoch 69/70
 - 63s - loss: 0.0348 - acc: 0.9898 - val_loss: 0.3593 - val_acc: 0.9080
Epoch 70/70
 - 63s - loss: 0.0340 - acc: 0.9900 - val_loss: 0.3583 - val_acc: 0.9065
Using TensorFlow backend.

real    74m10.603s
user    131m24.035s
sys     11m52.143s


图2:在单个GPU上使用CIFAR-10对MiniGoogLeNet网络进行训练和测试的实验结果。

在这个实验中,我在NVIDIA DevBox上使用了单个Titan X GPU进行了训练。 每一个迭代花了大概63秒,总训练时间为74分10秒

然后,执行以下命令在四个Titan X GPU上进行训练:

$ python train.py --output multi_gpu.png --gpus 4
[INFO] loading CIFAR-10 data...
[INFO] training with 4 GPUs...
[INFO] compiling model...
[INFO] training network...
Epoch 1/70
 - 21s - loss: 1.6793 - acc: 0.3793 - val_loss: 1.3692 - val_acc: 0.5026
Epoch 2/70
 - 16s - loss: 1.2814 - acc: 0.5356 - val_loss: 1.1252 - val_acc: 0.5998
Epoch 3/70
 - 16s - loss: 1.1109 - acc: 0.6019 - val_loss: 1.0074 - val_acc: 0.6465
...
Epoch 68/70
 - 16s - loss: 0.1615 - acc: 0.9469 - val_loss: 0.3654 - val_acc: 0.8852
Epoch 69/70
 - 16s - loss: 0.1605 - acc: 0.9466 - val_loss: 0.3604 - val_acc: 0.8863
Epoch 70/70
 - 16s - loss: 0.1569 - acc: 0.9487 - val_loss: 0.3603 - val_acc: 0.8877
Using TensorFlow backend.

real    19m3.318s
user    104m3.270s
sys     7m48.890s


图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,译者:夏天,审校:主题曲。

文章为简译,更为详细的内容,请查看原文

本文由北邮@爱可可-爱生活老师推荐,阿里云组织翻译。

时间: 2024-10-24 17:17:03

Keras多GPU训练指南的相关文章

GPU训练的快速大规模分布式扩展-GPU多机多卡Machine Learning Middleware

在其他同学的文章中已经介绍过了,阿里新的自动语音识别系统的第一个落地点,被选定在客服电话语音识别上. 这个落地项目非常难,首先就在于我们面对的语音数据非常多样化:比如各种随意的对话.不完整的句子.各种话题以及各种传输差异和环境噪声.面对如此复杂的语音数据,我们后端的语音识别声学模型就一定要尽可能的覆盖各种可能的场景,包括各种对话.各种声道.各种噪音甚至各种口音,而要覆盖这些场景,就要求我们用海量的数据来训练语音识别声学模型.面对如此海量的数据,如何快速有效的.迭代式的训练语音识别声学模型.不断调

&amp;amp;lt;《cuda 并行程序设计 gpu编程指南》书本的代码,这本书的例子代码,急求!!

问题描述 <<cuda 并行程序设计 gpu编程指南>书本的代码,这本书的例子代码,急求!! 希望大神们能帮我找找,真的很需要,不甚感激!!..........

Keras新手“入坑”指南

  对于所有想要开始深度学习的人来说,有很多神经网络框架.库和api等都是非常重要的.可是-为什么要用Keras呢? Keras是一种高级的神经网络API,它运行在许多底层库之上,这些库被用作后端,包括TensorFlow.Theano.CNTK和PlaidML等.Keras代码是可移植的,这意味着你可以使用Keras 实现一个神经网络,然后使用Theano作为一个备份,再指定后端在TensorFlow上运行,并且不需要对代码进行进一步的更改.数据科学家和机器学习专家Charles Martin

深度学习将会变革NLP中的中文分词

雷锋网(公众号:雷锋网)按:本文转自ResysChina高翔,文章主要介绍了1)区分中文分词的方法:2)用深度学习的方法来解决中文分词的好处及其具体应用. 现有分词介绍 自然语言处理(NLP,Natural Language Processing)是一个信息时代最重要的技术之一,简单来讲,就是让计算机能够理解人类语言的一种技术.在其中,分词技术是一种比较基础的模块.对于英文等拉丁语系的语言而言,由于词之间有空格作为词边际表示,词语一般情况下都能简单且准确的提取出来.而中文日文等文字,除了标点符号

阿里云GPU云服务器TensorFlow单机多卡训练性能实践

1 背景 2015年11月9日,Google发布深度学习框架TensorFlow.Google表示,TensorFlow在设计上尤其针对克服其第一代深度学习框架DistBelief 的短板,灵活.更通用.易使用.更快,而且完全开源.在短短的一年时间内,在GitHub上,TensorFlow就成为了最流行的深度学习项目. 本文将介绍TensorFlow在阿里云GPU云服务器上的单机性能表现,并对单机多卡的训练性能调优给出了一些建议. 2 使用卷积神经网络进行图像分类 卷积神经网络(Convolut

【前沿】何恺明大神ICCV2017最佳论文Mask R-CNN的Keras/TensorFlow/Pytorch 代码实现

我们提出了一个概念上简单.灵活和通用的用于目标实例分割(object instance segmentation)的框架.我们的方法能够有效地检测图像中的目标,同时还能为每个实例生成一个高质量的分割掩码(segmentation mask).这个方面被称为 Mask R-CNN,是在 Faster R-CNN 上的扩展--在其已有的用于边界框识别的分支上添加了一个并行的用于预测目标掩码的分支.Mask R-CNN 的训练很简单,仅比 Faster R-CNN 多一点计算开销,运行速度为 5 fp

TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比

Google 近日发布了 TensorFlow 1.0 候选版,这第一个稳定版将是深度学习框架发展中的里程碑的一步.自 TensorFlow 于 2015 年底正式开源,距今已有一年多,这期间 TensorFlow 不断给人以惊喜.在这一年多时间,TensorFlow 已从初入深度学习框架大战的新星,成为了几近垄断的行业事实标准. 主流深度学习框架对比 深度学习研究的热潮持续高涨,各种开源深度学习框架也层出不穷,其中包括 TensorFlow.Caffe.Keras.CNTK.Torch7.MX

如何为TensorFlow和PyTorch自动选择空闲GPU,解决抢卡争端

使用 git clone https://github.com/QuantumLiu/tf_gpu_manager 把manager.py放到你训练的目录就行. 直接使用with gm.auto_choice()自动选择设备进行接下来代码块的操作. import tensorflow as tf  from manager import GPUManager  from keras.layers LSTM  gm=GPUManager() with gm.auto_choice():      

GPU加速深度学习

1. 背景 一年半以前,AlphaGo完胜李世乭的围棋赛让深度学习(Deep Learning)这个名词家喻户晓,再度掀起人工智能的新一波热潮.其实深度学习背后的神经网络基础理论早在上世纪50年代就已提出,经过几起几落的发展,到了21世纪初,多层神经网络算法也日趋成熟.深度学习理论早在十多年以前就有重要突破,为何直到近年才出现爆发.这不得不提到2012年的一场竞赛. 2012年,Geoffrey E. Hinton(与Yann LeCun 和Yoshua Bengio并称为深度学习三驾马车)的弟