使用Keras訓練具有多個GPU的深度神經網絡(照片來源:Nor-Tech.com)。
摘要
在今天的博客文章中,我們學習了如何使用多個GPU來訓練基於Keras的深度神經網絡。
使用多個GPU使我們能夠獲得准線性加速。
為了驗證這一點,我們在CIFAR-10數據集上訓練了MiniGoogLeNet。
使用單個GPU,我們能夠獲得63秒的時間段,總訓練時間為74分10秒。
然而,通過使用Keras和Python的多GPU訓練,我們將訓練時間減少到16秒,總訓練時間為19m3s。
使用Keras啟用多GPU培訓就像單個函數調用一樣簡單 - 我建議您盡可能使用多GPU培訓。在未來,我想象 multi_gpu_model 將演變,讓我們進一步定制專門其中的GPU應該用於訓練,最終實現多系統的訓練也是如此。
方法:使用Keras,Python和深度學習進行多GPU培訓
當我第一次開始使用Keras時,我愛上了API。它簡單而優雅,類似於scikit-learn。然而它非常強大,能夠實施和訓練最先進的深度神經網絡。
然而,我對Keras的最大挫折之一是在多GPU環境中使用它可能有點不重要。
如果您使用Theano,請忘掉它 - 多GPU培訓不會發生。
TensorFlow是一種可能性,但它可能需要大量的樣板代碼和調整才能使您的網絡使用多個GPU進行訓練。
我更喜歡在執行多GPU培訓時使用mxnet后端(甚至是mxnet庫直接)到Keras,但這引入了更多的配置來處理。
所有這一切都與改變弗朗索瓦CHOLLET宣布,使用TensorFlow后端多GPU的支持,現在在烤到Keras V2.0.9。大部分功勞歸功於@ kuza55和他們的keras-extras回購。
我已經使用並測試了這個多GPU功能已近一年了,我非常高興看到它作為官方Keras發行版的一部分。
在今天博客文章的剩余部分中,我將演示如何使用Keras,Python和深度學習訓練卷積神經網絡進行圖像分類。
MiniGoogLeNet深度學習架構
圖1: MiniGoogLeNet架構是它的大兄弟GoogLeNet / Inception的一個小版本。圖片來自@ ericjang11和@pluskid。
在上面的圖1中,我們可以看到單個卷積(左),初始(中)和下采樣(右)模塊,然后是從這些構建塊構建的整體MiniGoogLeNet架構(底部)。我們將在本文后面的多GPU實驗中使用MiniGoogLeNet架構。
MiniGoogLenet中的Inception模塊是Szegedy等人設計的原始Inception模塊的變體。
我首先從@ ericjang11和@pluskid的推文中了解了這個“Miniception”模塊,它們可以很好地可視化模塊和相關的MiniGoogLeNet架構。
在做了一些研究后,我發現這張圖片來自張等人的2017年出版物“ 理解深度學習需要重新思考泛化”。
然后我開始在Keras + Python中實現MiniGoogLeNet架構 - 我甚至將它作為使用Python進行計算機視覺深度學習的一部分。
對MiniGoogLeNet Keras實現的全面審查超出了本博文的范圍,因此如果您對網絡的工作原理(以及如何編碼)感興趣,請參閱我的書。
否則,您可以使用此博客文章底部的“下載”部分下載源代碼。
使用Keras和多個GPU訓練深度神經網絡
讓我們繼續使用Keras和多個GPU開始培訓深度學習網絡。
首先,您需要確保 在虛擬環境中安裝和更新Keras 2.0.9(或更高版本)(我們 在本書中使用名為dl4cv的虛擬環境 ):
1
2
|
$
workon
dl4cv
$
pip
install
--
upgrade
keras
|
從那里,打開一個新文件,將其命名為 train .py ,並插入以下代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
# 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數據集。
現在讓我們解析命令行參數:
19
20
21
22
23
24
25
26
27
28
|
# 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"
]
|
我們使用 argparse 解析一個需要和一個可選的參數線20-25:
- - 輸出 :訓練完成后輸出圖的路徑。
- - gpus :用於培訓的GPU數量。
加載命令行參數后, 為方便起見,我們將GPU的數量存儲為 G(第28行)。
從那里,我們初始化用於配置我們的訓練過程的兩個重要變量,然后定義 poly_decay ,一個等同於Caffe的多項式學習速率衰減的學習速率調度函數:
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
|
# 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行)。本質上,此功能可在訓練期間更新學習速率,並在每個時期后有效地減少學習速度。設置 功率= 1.0 會將衰減從多項式更改為線性。
接下來我們將加載我們的訓練+測試數據並將圖像數據從整數轉換為浮點數:
48
49
50
51
52
53
|
# 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"
)
|
從那里我們對數據應用平均減法:
55
56
57
58
|
# apply mean subtraction to the data
mean
=
np
.
mean
(
trainX
,
axis
=
0
)
trainX
-=
mean
testX
-=
mean
|
在 第56行,我們計算所有訓練圖像的平均值,然后是 第57和58行,其中我們從訓練和測試集中的每個圖像中減去平均值。
然后,我們執行“one-hot encoding”,這是我在本書中更詳細討論的編碼方案:
60
61
62
63
|
# convert the labels from integers to vectors
lb
=
LabelBinarizer
(
)
trainY
=
lb
.
fit_transform
(
trainY
)
testY
=
lb
.
transform
(
testY
)
|
單熱編碼將分類標簽從單個整數轉換為向量,因此我們可以應用分類交叉熵損失函數。我們已經在第61-63行處理了這個問題 。
接下來,我們創建一個數據增強器和一組回調:
65
66
67
68
69
70
|
# 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行,我們構建了用於數據增強的圖像生成器。
數據增強覆蓋在里面詳細執業捆綁的深度學習計算機視覺與Python ; 然而,暫時理解這是一種在訓練過程中使用的方法,我們通過對它們進行隨機變換來隨機改變訓練圖像。
由於這些改變,網絡不斷地看到增強的示例 - 這使得網絡能夠更好地概括驗證數據,同時可能在訓練集上表現更差。在大多數情況下,這些權衡是值得的。
我們在第70行創建了一個回調函數, 它允許我們的學習率在每個時代之后衰減 - 注意我們的函數名稱 poly_decay 。
我們接下來檢查GPU變量:
72
73
74
75
76
|
# 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,我們 通過使用 初始化 模型。構建 函數(第73-76行),否則我們將在訓練期間並行化模型:
78
79
80
81
82
83
84
85
86
87
88
89
90
|
# 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獲得梯度,然后執行梯度更新步驟。
然后我們可以編譯我們的模型並啟動培訓過程:
92
93
94
95
96
97
98
99
100
101
102
103
104
105
|
# 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優化器和分類的交叉熵損失函數編譯模型。
我們現在准備訓練網絡了!
為了啟動培訓過程,我們打電話給 模型。fit_generator 並提供必要的參數。
我們希望每個GPU上的批量大小為64,因此由batch_size = 64 * G 指定 。
我們的培訓將持續70個時期(我們之前指定)。
梯度更新的結果將在CPU上組合,然后在整個訓練過程中應用於每個GPU。
現在培訓和測試已經完成,讓我們繪制損失/准確度,以便我們可以看到培訓過程:
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
|
# 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行)。
如果您想了解有關培訓過程(以及內部工作原理)的更多信息,請參閱使用Python進行計算機視覺深度學習。
Keras多GPU結果
讓我們檢查一下我們辛勤工作的結果。
首先,使用本文底部的“下載”部分從本課程中獲取代碼。然后,您就可以按照結果進行操作
讓我們在單個GPU上訓練以獲得基線:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$
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上使用Keras在CIFAR-10上訓練和測試MiniGoogLeNet網絡架構的實驗結果。
對於這個實驗,我在我的NVIDIA DevBox上使用單個Titan X GPU進行了訓練。每個時期花費約63秒,總訓練時間為74分10秒。
然后我執行以下命令來訓練 我的所有四個Titan X GPU:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
$
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數據集上使用Keras和MiniGoogLeNet的多GPU培訓結果(4個Titan X GPU)。訓練結果類似於單GPU實驗,而訓練時間減少了約75%。
在這里你可以看到訓練中的准線性加速:使用四個GPU,我能夠將每個時期減少到只有 16秒。整個網絡在19分3秒內完成了培訓 。
正如您所看到的,不僅可以輕松地訓練具有Keras和多個GPU的深度神經網絡 ,它也是 高效的!
注意:在這種情況下,單GPU實驗獲得的精度略高於多GPU實驗。在訓練任何隨機機器學習模型時,會有一些差異。如果你要在數百次運行中平均這些結果,它們將(大致)相同。