點擊上方「AI有道」,選擇「星標」公眾號
重磅乾貨,第一時間送達
作者: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(或更高版本):
這裡,新建一個文件並命名為train.py,然後插入下面的代碼:
# 設置matplotlib後端,這樣子數字可以保存在後端(如果你使用的是headless server,請取消注釋下面的行)# import matplotlib# matplotlib.use("Agg") # 導入必要的包from pyimagesearch.minigooglenet import MiniGoogLeNetfrom sklearn.preprocessing import LabelBinarizerfrom keras.preprocessing.image import ImageDataGeneratorfrom keras.callbacks import LearningRateSchedulerfrom keras.utils.training_utils import multi_gpu_modelfrom keras.optimizers import SGDfrom keras.datasets import cifar10import matplotlib.pyplot as pltimport tensorflow as tfimport numpy as npimport argparse如果你使用的是headless server,則需要通過取消注釋行來配置2-3行的matplotlib後端。這樣可以將matplotlib圖保存到磁碟。如果你沒有使用headless server(即,你的鍵盤+滑鼠+顯示器插入系統,則可以將線條注釋掉)。
這裡,我們導入這個腳本所需的包。
第6行從我的pyimagesearch模塊導入MiniGoogLeNet。
另一個值得注意的是12行的導入了CIFAR10數據集。這個輔助函數將使我們導入CIFAR-10數據集。
現在讓我們解析命令行參數:
# 構建解析參數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:
# 定義要訓練的周期數以及初始學習率NUM_EPOCHS = 70INIT_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會將衰減從多項式變為線性。
接下來我們將加載我們的訓練+測試數據並將圖像數據從整數轉換為浮點數:
# 加載訓練和測試數據,將圖像從整數轉換為浮點數print("[INFO] loading CIFAR-10 data...")((trainX, trainY), (testX, testY)) = cifar10.load_data()trainX = trainX.astype("float")testX = testX.astype("float")這裡,將對數據應用平均值相減:
mean = np.mean(trainX, axis=0)trainX -= meantestX -= mean計算所有訓練圖像的平均值,然後從訓練和測試集中的每個圖像中減去平均值。
然後執行獨熱編碼(one-hot encoding):
# 構造用於數據增強的圖像生成器並構造一系列的回調函數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變量:
# 檢測我們是否只使用一個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行),否則我們將在訓練期間並行化模型:
# 否則,我們正在使用多個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獲得梯度,然後執行梯度更新步驟。
然後我們可以編譯我們的模型並啟動訓練過程:
# 初始化優化器和模型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。
既然訓練和測試已經完成,讓我們畫出損失/準確率圖,以便可視化整個訓練過程。
# 獲取歷史對象字典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):
$ python3 train.py [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.5983Epoch 2/70 - 63s - loss: 1.0279 - acc: 0.6361 - val_loss: 0.9844 - val_acc: 0.6472Epoch 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.9069Epoch 69/70 - 63s - loss: 0.0348 - acc: 0.9898 - val_loss: 0.3593 - val_acc: 0.9080Epoch 70/70 - 63s - loss: 0.0340 - acc: 0.9900 - val_loss: 0.3583 - val_acc: 0.9065Using TensorFlow backend. real 74m10.603suser 131m24.035ssys 11m52.143s
圖2 在單個GPU上使用Keras在CIFAR-10上訓練和測試MiniGoogLeNet網絡架構的實驗結果
對於這個實驗,我在我的NVIDIA DevBox上使用單個Titan X GPU進行了訓練。每個時期花費約63秒,總訓練時間為74分10秒。
然後我執行以下命令來訓練我的所有四個Titan X GPU:
$ python3 train.py [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.5026Epoch 2/70 - 16s - loss: 1.2814 - acc: 0.5356 - val_loss: 1.1252 - val_acc: 0.5998Epoch 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.8852Epoch 69/70 - 16s - loss: 0.1605 - acc: 0.9466 - val_loss: 0.3604 - val_acc: 0.8863Epoch 70/70 - 16s - loss: 0.1569 - acc: 0.9487 - val_loss: 0.3603 - val_acc: 0.8877Using TensorFlow backend. real 19m3.318suser 104m3.270ssys 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應該用於訓練,最終還能實現多系統訓練。
重磅!AI 有道學術交流群成立啦
掃描下方二維碼,添加 AI有道小助手微信,可申請入林軒田機器學習群(數字 1)、吳恩達 deeplearning.ai 學習群(數字 2)。一定要備註:入哪個群(1 或 2 或 1+2)+ 地點 + 學校/公司 + 暱稱。例如:1+上海+復旦+小牛。
長按掃碼,申請入群
(添加人數較多,請耐心等待)
最新 AI 乾貨,我在看