Keras多GPU训练指南
更多深度文章,請關注:https://yq.aliyun.com/cloud
Keras是我最喜歡的Python深度學習框架,特別是在圖像分類領域。我在很多地方都使用到了Keras,包括生產(chǎn)系統(tǒng)、我自己的深度學習項目,以及PyImageSearch博客。
我的新書“基于Keras的深度學習計算機視覺”有三分之二的篇幅都跟這個框架有關。然而,在該框架過程中遇到的最大的一個問題就是執(zhí)行多GPU訓練。
但是,這個問題將不復存在!
隨著Keras(v2.0.8)最新版本的發(fā)布,使用多GPU 訓練深度神經(jīng)網(wǎng)絡將變得非常容易,就跟調用函數(shù)一樣簡單!
如何使用Keras進行多GPU訓練
當我第一次使用Keras的時候,我深深愛上了它的API。它簡單而又優(yōu)雅,類似于scikit-learn。但是,它又非常強大,能夠實現(xiàn)并訓練最先進的深度神經(jīng)網(wǎng)絡。
然后,對于Keras我最失望的的地方之一就是在多GPU環(huán)境中非常難用。如果你使用的是Theano,請忘記這一點,因為多GPU訓練不會發(fā)生。雖然TensorFlow可以實現(xiàn)的,但需要編寫大量的代碼以及做出大量的調整來使你的網(wǎng)絡支持多GPU訓練。我喜歡在執(zhí)行多GPU訓練的時候使用mxnet作為Keras的后端,但它需要配置很多東西。
這所有的一切都隨著Fran?oisChollet公告的出現(xiàn)而發(fā)生了改變,使用TensorFlow作為后端的多GPU支持現(xiàn)在已經(jīng)放到了Keras v2.0.8中。這個功勞很大程度上歸功于@kuza55和他們的keras-extras庫。我使用這個多GPU功能已經(jīng)有將近一年時間了,我非常高興看到它成為Keras官方發(fā)布的一部分。
在這篇文章中,我將演示如何使用Keras、Python和深度學習來訓練卷積神經(jīng)網(wǎng)絡進行圖像分類。要獲取相關代碼,請訪問這個網(wǎng)頁中的“Downloads”章節(jié)。
MiniGoogLeNet深度學習架構
圖1:MiniGoogLeNet架構是它的兄弟GoogLeNet/Inception的一個縮減版。
在圖1中,我們可以看到獨立的convolution(左)、inception(中)和downsample(右)模塊,下面則是由這些構建塊構建而成的整個MiniGoogLeNet架構(底部)。我們將在本文后面的多GPU實驗中使用MiniGoogLeNet架構。
MiniGoogLenet中的Inception模塊是由Szegedy等人設計的原始Inception模塊的一個變體。我最初是從@ericjang11和@pluskid的推文中了解到“Miniception”模塊的,他們的推文詳細描述了該模塊和相關的MiniGoogLeNet架構。
然后,我用Keras和Python實現(xiàn)了MiniGoogLeNet架構,并將其作為“基于Keras的深度學習計算機視覺”一書的一部分。
對MiniGoogLeNet Keras實現(xiàn)的完整描述已經(jīng)超出了本文的討論范圍,如果你對此感興趣的話,請閱讀我這本書。
用Keras和多GPU訓練深度神經(jīng)網(wǎng)絡
下面我們開始使用Keras和多GPU來訓練深度學習網(wǎng)絡。
在開始之前,請確保你的環(huán)境中已經(jīng)安裝了Keras 2.0.8(或更高版本):
$ workon dl4cv $ pip install --upgrade keras創(chuàng)建一個新文件,將其命名為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圖保存到磁盤。 如果你沒有使用無外設服務器(即鍵盤、鼠標、顯示器已連接系統(tǒng)),則無需去掉注釋符。
在這段代碼中,導入了該腳本所需的包。第7行從pyimagesearch模塊導入MiniGoogLeNet。另一個需要注意的是第13行,我們導入了CIFAR10數(shù)據(jù)集。 這個幫助函數(shù)能讓我們僅使用一行代碼即可從磁盤加載CIFAR-10數(shù)據(jù)集。
現(xiàn)在我們來解析命令行參數(shù):
# 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來解析一個必需的和一個可選的參數(shù):
- --output:訓練完畢后輸出圖的路徑。
- --gpus:用于訓練的GPU的個數(shù)。
在加載命令行參數(shù)后,為方便起見,我們將GPU數(shù)量保存在G中(第28行)
接著,我們要初始化用于配置訓練過程的兩個重要變量,然后定義poly_decay,這是一個學習速率調度函數(shù),類似于Caffe多項式學習速率衰減:
# definine the total number of epochs to train for along with the # initial learning rate NUM_EPOCHS = 70 INIT_LR = 5e-3def poly_decay(epoch):# initialize the maximum number of epochs, base learning rate,# and power of the polynomialmaxEpochs = NUM_EPOCHSbaseLR = INIT_LRpower = 1.0# compute the new learning rate based on polynomial decayalpha = baseLR * (1 - (epoch / float(maxEpochs))) ** power# return the new learning ratereturn alpha設置NUM_EPOCHS = 70,這是訓練數(shù)據(jù)通過網(wǎng)絡(第32行)的迭代次數(shù)。我們還初始化了學習速率INIT_LR = 5e-3,這是我們在以前的試驗中發(fā)現(xiàn)的值(第33行)。
這里,我們定義了poly_decay函數(shù),這個函數(shù)類似于于Caffe的多項式學習速率衰減(第35-46行)。從本質上講,這個函數(shù)更新了訓練過程中的學習速度,在每次迭代之后能有效減少學習速率。 power = 1.0將把衰減從多項式變?yōu)?strong>線性變化。
接下來,我們將加載訓練和測試數(shù)據(jù),并將圖像數(shù)據(jù)從整數(shù)轉換為浮點數(shù):
# 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")接著對數(shù)據(jù)應用均值減法:
# apply mean subtraction to the data mean = np.mean(trainX, axis=0) trainX -= mean testX -= mean在第56行,計算了所有訓練圖像的平均值,然后在第57和58行,將訓練和測試集中的每個圖像減去這個平均值。
然后,執(zhí)行“獨熱編碼(one-hot encoding)”:
# convert the labels from integers to vectors lb = LabelBinarizer() trainY = lb.fit_transform(trainY) testY = lb.transform(testY)獨熱編碼將分類標簽從單個整數(shù)轉換為向量,這樣,就可以對其應用分類交叉熵損失函數(shù)。 我們已經(jīng)在第61-63行考慮到了這一點。
接下來,創(chuàng)建一個數(shù)據(jù)增強器和一組回調函數(shù):
# 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行,構建了圖像生成器,用于數(shù)據(jù)擴充。數(shù)據(jù)擴充在是在訓練過程中使用的一種方法,可通過對圖像進行隨機變換來隨機改變圖像。通過這些變換,網(wǎng)絡將會持續(xù)看到增加的示例,這有助于網(wǎng)絡更好地泛化到驗證數(shù)據(jù)上,但同時也可能在訓練集上表現(xiàn)得更差。 在多數(shù)情況下,這種權衡是值得的。
在第70行創(chuàng)建了一個回調函數(shù),這使得學習速率在每次迭代之后發(fā)生衰減。請注意,函數(shù)名為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數(shù)量小于或等于1,則通過.build函數(shù)(第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 CPUwith tf.device("/cpu:0"):# initialize the modelmodel = MiniGoogLeNet.build(width=32, height=32, depth=3,classes=10)# make the model parallelmodel = multi_gpu_model(model, gpus=G)在Keras中創(chuàng)建一個多GPU模型需要一些額外的代碼,但不是很多!
首先,第84行,你會注意到我們已經(jīng)指定使用CPU(而不是GPU)作為網(wǎng)絡的上下文。為什么我們要用CPU呢?CPU可用于處理任何一種工作(比如在GPU內存上移動訓練圖像),而GPU本身則負責繁重的工作。在這種情況下,CPU將用于實例化基本模型。
然后,在第90行調用multi_gpu_model。 該函數(shù)將模型從CPU復制到所有的GPU上,從而獲得單機多GPU的數(shù)據(jù)并行環(huán)境。
在訓練網(wǎng)絡圖像的時候,訓練任務將分批到每個GPU上執(zhí)行。CPU將從每個GPU上獲取梯度,然后執(zhí)行梯度更新步驟。
然后,可以編譯模型并啟動訓練過程了:
# 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)優(yōu)化器。隨后,我們用SGD優(yōu)化器和分類交叉熵損失函數(shù)來編譯模型。
現(xiàn)在,我們要開始訓練網(wǎng)絡了!
要啟動訓練,我們調用了model.fit_generator并提供了必要的參數(shù)。我們希望每個GPU上的批處理大小為64,這是由batch_size=64 * G指定的。訓練將持續(xù)70此迭代(我們之前指定的)。梯度更新的結果將合并到CPU上,然后在整個訓練過程中應用在每個GPU上。
現(xiàn)在訓練和測試完成了,讓我們畫出損失/準確性曲線,以使整個訓練過程可視化:
# 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行),然后將數(shù)據(jù)保存到磁盤上(第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網(wǎng)絡進行訓練和測試的實驗結果。
在這個實驗中,我在NVIDIA DevBox上使用了單個Titan X GPU進行了訓練。 每一個迭代花了大概63秒,總訓練時間為74分10秒。
然后,執(zhí)行以下命令在四個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數(shù)據(jù)集在多GPU(4個Titan X GPU)上使用Keras和MiniGoogLeNet的訓練結果。訓練結果與單GPU的訓練結果差不多,訓練時間減少約75%。
在這里,可以看到訓練過程得到了準線性的提速:使用四個GPU,可以將每次迭代減少到16秒。整個網(wǎng)絡的訓練耗時19分3秒。
正如你所看到的,使用Keras和多個GPU來訓練深度神經(jīng)網(wǎng)絡不僅簡單而且高效!
注意:在這種情況下,單GPU實驗獲得的精度略高于多GPU。這是因為在訓練任何一種隨機機器學習模型的時候,都會出現(xiàn)一些差異。如果將這些結果平均一下,那么它們(幾乎)是相同的。
總結
在今天的博文中,我們學到了如何使用多個GPU來訓練基于Keras的深度神經(jīng)網(wǎng)絡。利用多個GPU,我們獲得了準線性的提速。為了驗證這一點,我們使用CIFAR-10數(shù)據(jù)集訓練了MiniGoogLeNet。使用單個GPU,單次迭代時間為63秒,總訓練時間為74分10秒。然而,使用Keras和Python在多GPU上訓練的時候,單次迭代時間縮短到了16秒,總訓練時間為19分03秒。
在Keras中啟用多GPU訓練就跟調用函數(shù)一樣簡單, 強烈建議你盡早使用多GPU進行訓練。我猜想,在將來multi_gpu_model肯定會進一步得到改進,能讓我們指定使用哪幾個GPU進行訓練,并最終實現(xiàn)多系統(tǒng)的訓練。
文章原標題《How-To: Multi-GPU training with Keras, Python, and deep learning》,作者:Adrian Rosebrock,譯者:夏天,審校:主題曲。
文章為簡譯,更為詳細的內容,請查看原文
本文由北郵@愛可可-愛生活老師推薦,阿里云云棲社區(qū)組織翻譯。
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的Keras多GPU训练指南的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: nagios全攻略(一)----准备阶段
- 下一篇: zabbix监控windows进程