前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何使用keras,python和深度学习进行多GPU训练

如何使用keras,python和深度学习进行多GPU训练

作者头像
AI算法与图像处理
发布2019-08-23 15:16:52
3.3K0
发布2019-08-23 15:16:52
举报

作者:pyimagesearch 原文链接:https://www.pyimagesearch.com/2017/10/30/how-to-multi-gpu-training-with-keras-python-and-deep-learning/ 编译:AI算法与图像处理

内容简介

Keras简单而优雅,类似于scikit-learn。然而,它非常强大,能够实施和训练最先进的深度神经网络。

然而,我们对keras最感到受挫的一个原因,是在多GPU环境下使用,因为这是非常重要的。

如果你使用Theano,请忽略它——多GPU训练,这并不会发生。

TensorFlow还是有使用的可能性,但它可能需要大量的样板代码和调整才能是你的网络使用多个GPU进行训练。

在使用多GPU训练的时,我更喜欢用mxnet后端(或甚至直接是mxnet库)而不是keras,但这会引入更多配置进行处理。

随着François Chollet’s宣布tensorflow后端对多GPU的支持已经融入到keras v2.0.9时,所有这一切都发生了改变。大部分功劳归功于 kuza55(ID)和他们的keras-extras回购。

我已经使用并测试了这个多GPU功能近一年,我非常高兴能将它视为官方keras发行版的一部分。

在今天文章的其他部分中,我将演示如何使用keras,python和深度学习训练图像分类的CNN。

MiniGoogLeNet 深度学习框架

图1:MiniGoogLeNet架构是它的大兄弟GoogLeNet / Inception的一个小版本。

在上面的图1中,我们可以看到单个卷积(左),初始(中)和下采样(右)模块,然后是从这些模块来构建MiniGoogLeNet架构(底部)。我们将在本文后面的多GPU实验中使用MiniGoogLeNet架构。

在MiniGoogLeNet中的Inception是由Szegedy 等人设计的,是原始Inception模块的变体。

我首先从@ericjang11 和 @pluskid 的推文中了解了这个“Miniception”模块,它们精美地可视化模块和相关的MiniGoogLeNet架构。

在做了一些研究后,我发现这张图片来自张等人2017的文章https://arxiv.org/abs/1611.03530

然后我开始在keras和python中应用MiniGoogLe架构——甚至使用python进行计算机视觉深度学习这本书的一部分。

对MiniGoogLeNet实现全面的复习超出了本文的范围,因此如果你对网络的工作原理(以及如何编码)感兴趣,可以参阅这本书https://www.pyimagesearch.com/deep-learning-computer-vision-python-book/

使用keras和多GPU训练一个深层神经网络

首先确保在环境中安装和更新keras 2.09(或更高版本):

代码语言:javascript
复制
pip3 install --upgrade keras

这里,新建一个文件并命名为train.py,然后插入下面的代码:

代码语言:javascript
复制
# 设置matplotlib后端,这样子数字可以保存在后端(如果你使用的是headless server,请取消注释下面的行)
# import matplotlib
# matplotlib.use("Agg")
 
# 导入必要的包
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

如果你使用的是headless server,则需要通过取消注释行来配置2-3行的matplotlib后端。这样可以将matplotlib图保存到磁盘。如果你没有使用headless server(即,你的键盘+鼠标+显示器插入系统,则可以将线条注释掉)。

这里,我们导入这个脚本所需的包。

第6行从我的pyimagesearch模块导入MiniGoogLeNet。

另一个值得注意的是12行的导入了CIFAR10数据集。这个辅助函数将使我们导入CIFAR-10数据集。

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

代码语言:javascript
复制
# 构建解析参数
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())
 
# 获取GPU的数量并将其存储在一个传输变量中
G = args["gpus"]

我们使用argparse去解析一个必要参数和一个可选参数:

  • --output:训练完成后的输出图的路径
  • --gpus:用于训练的gpu数量

加载命令行参数后,为了方便起见,我们将GPU的数量存储为G(10行)。

这里,我们初始化用于配置我们的训练过程的两个重要遍历,然后定义poly_decay,一个等同于caff的多项式学习速率衰减的学习率调度函数https://stackoverflow.com/questions/30033096/what-is-lr-policy-in-caffe:

代码语言:javascript
复制
# 定义要训练的周期数以及初始学习率
NUM_EPOCHS = 70
INIT_LR = 5e-3
 
def poly_decay(epoch):
  # 初始化最大周期数,基本学习率和多项式的幂次
  maxEpochs = NUM_EPOCHS
  baseLR = INIT_LR
  power = 1.0
 
  # 根据多项式衰减计算新的学习率
  alpha = baseLR * (1 - (epoch / float(maxEpochs))) ** power
 
  # 返回新的学习率
  return alpha

我们设置 NUM_EPOCHS=70——这是我们训练数据将要传递给网络的次数(周期)

初始化学习率INIT_LR=5e-3,这是在之前的试验中发现的值

这里定义poly_decay函数,它相当于Caffe的多项式学习速率衰减。本质上,此功能可在训练期间更新学习率,并在每个时期后有效减少学习率。设置power=1.0会将衰减从多项式变为线性。

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

代码语言:javascript
复制
# 加载训练和测试数据,将图像从整数转换为浮点数
print("[INFO] loading CIFAR-10 data...")
((trainX, trainY), (testX, testY)) = cifar10.load_data()
trainX = trainX.astype("float")
testX = testX.astype("float")

这里,将对数据应用平均值相减:

代码语言:javascript
复制
mean = np.mean(trainX, axis=0)
trainX -= mean
testX -= mean

计算所有训练图像的平均值,然后从训练和测试集中的每个图像中减去平均值。

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

代码语言:javascript
复制
# 构造用于数据增强的图像生成器并构造一系列的回调函数
aug = ImageDataGenerator(width_shift_range=0.1,
  height_shift_range=0.1, horizontal_flip=True,
  fill_mode="nearest")
callbacks = [LearningRateScheduler(poly_decay)]

第2行构造用于数据增强的图像生成器。

由于这些改变,网络不断地看到增强的示例 - 这使得网络能够更好地概括验证数据,同时可能在训练集上表现更差。在大多数情况下,这些权衡是值得的。

我们在第5行创建了一个回调函数,它允许我们的学习速率在每个周期后衰减 - 注意我们的函数名称poly_decay。

我们接下来检查GPU变量:

代码语言:javascript
复制
# 检测我们是否只使用一个GPU进行编译
if G <= 1:
  print("[INFO] training with 1 GPU...")
  model = MiniGoogLeNet.build(width=32, height=32, depth=3,
    classes=10)

如果GPU计数小于或等于1,我们通过.build函数初始化模型(第2-5行),否则我们将在训练期间并行化模型:

代码语言:javascript
复制
# 否则,我们正在使用多个GPU进行编译
else:
  print("[INFO] training with {} GPUs...".format(G))
 
  # 我们将在* every * GPU上存储模型的副本,然后将CPU上的渐变更新结果组合在一起
  with tf.device("/cpu:0"):
    # 初始化模型
    model = MiniGoogLeNet.build(width=32, height=32, depth=3,
      classes=10)
  
  # 是模型并行
  model = multi_gpu_model(model, gpus=G)

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

首先,您将在第6行注意到我们已指定使用CPU(而不是GPU)作为网络上下文。

为什么我们需要CPU?

CPU负责处理任何开销(例如在GPU内存上移动和移动训练图像),而GPU本身则负担繁重。

在这种情况下,CPU实例化基本模型。

然后我们可以在第12行调用multi_gpu_model。这个函数将模型从CPU复制到我们所有的GPU,从而获得一个机,多个GPU数据并行性。

在训练我们的网络时,图像将被批量分配到每个GPU。CPU将从每个GPU获得梯度,然后执行梯度更新步骤。

然后我们可以编译我们的模型并启动训练过程:

代码语言:javascript
复制
# 初始化优化器和模型
print("[INFO] compiling model...")
opt = SGD(lr=INIT_LR, momentum=0.9)
model.compile(loss="categorical_crossentropy", optimizer=opt,
  metrics=["accuracy"])
 
# 训练网络
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)  

第3行构建了一个随机梯度下降(SGD)优化器。

随后,我们使用SGD优化器和分类的交叉熵损失函数编译模型。

现在准备训练网络了!

为了启动训练过程,我们调用model.fit_generator函数并提供必要的参数。

我们制定每个GPU上的batch大小64,因此batch_size=64*G

我们训练将持续70个周期(前面已经制定)。

梯度更新的结果将在CPU上组合,然后在整个训练过程中应用与每个GPU。

既然训练和测试已经完成,让我们画出损失/准确率图,以便可视化整个训练过程。

代码语言:javascript
复制
# 获取历史对象字典
H = H.history
 
# 绘制训练的loss和准确率的图
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()
 
# 保存图
plt.savefig(args["output"])
plt.close()

最后一块仅使用matplotlib绘制训练/测试的loss和准确率的曲线图(6-15行),然后将曲线图保存到磁盘中(18行)。

keras多GPU训练结果

让我们检查一下辛勤的劳动成果。

首先,使用附带链接中的代码。然后,可以按照结果进行操作。

利用单个GPU进行训练以获取基准线(baseline):

代码语言:javascript
复制
$ python3 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上使用Keras在CIFAR-10上训练和测试MiniGoogLeNet网络架构的实验结果

对于这个实验,我在我的NVIDIA DevBox上使用单个Titan X GPU进行了训练。每个时期花费约63秒,总训练时间为74分10秒。

然后我执行以下命令来训练我的所有四个Titan X GPU:

代码语言:javascript
复制
$ python3 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数据集上使用Keras和MiniGoogLeNet的多GPU培训结果(4个Titan X GPU)。训练结果类似于单GPU实验,而训练时间减少了约75%。

在这里你可以看到训练中的准线性加速:使用四个GPU,我能够将每个时期减少到仅16秒。整个网络在19分3秒内完成了训练。

正如你所看到的,不仅可以轻松地使用Keras和多个GPU训练深度神经网络,它也是高效的!

注意:在这种情况下,单GPU实验获得的精度略高于多GPU实验。在训练任何随机机器学习模型时,会有一些差异。如果你要在数百次运行中平均这些结果,它们将(大致)相同。

总结

在今天的博客文章中,我们学习了如何使用多个GPU来训练基于Keras的深度神经网络。

使用多个GPU使我们能够获得准线性加速。

为了验证这一点,我们在CIFAR-10数据集上训练了MiniGoogLeNet。

使用单个GPU,我们能够获得63秒的时间段,总训练时间为74分10秒。

然而,通过使用Keras和Python的多GPU训练,我们将训练时间减少到16秒,总训练时间为19m3s。

使用Keras启用多GPU培训就像单个函数调用一样简单 - 我建议尽可能使用多GPU培训。在未来我想象multi_gpu_model将会发展并允许我们进一步定制哪些GPU应该用于训练,最终还能实现多系统训练。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-08-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI算法与图像处理 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 内容简介
  • MiniGoogLeNet 深度学习框架
  • 使用keras和多GPU训练一个深层神经网络
  • keras多GPU训练结果
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档