MXNet: A flexible and efficient library for deep learning.
這是MXNet的官網(wǎng)介紹泉手,“MXNet是靈活且高效的深度學(xué)習(xí)庫(kù)”。
歡迎Follow我的GitHub:https://github.com/SpikeKing
MXNet是主流的三大深度學(xué)習(xí)框架之一:
- TensorFlow:Google支持拓售,其簡(jiǎn)化版是Keras;
- PyTorch:Facebook支持,其工業(yè)版是Caffe2;
- MXNet:中立发魄,Apache孵化器項(xiàng)目,也被AWS選為官方DL平臺(tái)俩垃;
MXNet的優(yōu)勢(shì)是励幼,其開(kāi)發(fā)者之一李沐,是中國(guó)人????口柳,在MXNet的推廣中具有語(yǔ)言優(yōu)勢(shì)(漢語(yǔ))苹粟,有利于國(guó)內(nèi)開(kāi)發(fā)者的學(xué)習(xí)。同時(shí)跃闹,推薦李沐錄制的教學(xué)視頻嵌削,非常不錯(cuò)。
MXNet的高層接口是Gluon望艺,Gluon同時(shí)支持靈活的動(dòng)態(tài)圖和高效的靜態(tài)圖苛秕,既保留動(dòng)態(tài)圖的易用性,也具有靜態(tài)圖的高性能找默,這也是官網(wǎng)介紹的flexible和efficient的出處艇劫。同時(shí),MXNet還具備大量學(xué)術(shù)界的前沿算法惩激,方便移植至工業(yè)界店煞。希望MXNet團(tuán)隊(duì)再接再勵(lì),在深度學(xué)習(xí)框架的競(jìng)賽中咧欣,位于前列浅缸。
因此轨帜,掌握 MXNet/Gluon 很有必要魄咕。
本文以深度學(xué)習(xí)的多層感知機(jī)(Multilayer Perceptrons)為算法基礎(chǔ),數(shù)據(jù)集選用MNIST蚌父,介紹MXNet的工程細(xì)節(jié)哮兰。
本文的源碼:https://github.com/SpikeKing/gluon-tutorial
數(shù)據(jù)集
在虛擬環(huán)境(Virtual Env)中毛萌,直接使用pip安裝MXNet即可:
pip install mxnet
如果下載速度較慢,推薦使用阿里云的pypi源:
-i http://mirrors.aliyun.com/pypi/simple --trusted-host mirrors.aliyun.com
MNIST就是著名的手寫(xiě)數(shù)字識(shí)別庫(kù)喝滞,其中包含0至9等10個(gè)數(shù)字的手寫(xiě)體阁将,圖片大小為28*28的灰度圖,目標(biāo)是根據(jù)圖片識(shí)別正確的數(shù)字右遭。
MNIST庫(kù)在MXNet中被封裝為MNIST類做盅,數(shù)據(jù)存儲(chǔ)于.mxnet/datasets/mnist
中。如果下載MNIST數(shù)據(jù)較慢窘哈,可以選擇到MNIST官網(wǎng)下載吹榴,放入mnist文件夾中即可。在MNIST類中:
- 參數(shù)
train
:是否為訓(xùn)練數(shù)據(jù)滚婉,其中true是訓(xùn)練數(shù)據(jù)图筹,false是測(cè)試數(shù)據(jù); - 參數(shù)
transform
:數(shù)據(jù)的轉(zhuǎn)換函數(shù)让腹,lambda表達(dá)式远剩,轉(zhuǎn)換數(shù)據(jù)和標(biāo)簽為指定的數(shù)據(jù)類型;
源碼:
# 參數(shù)train
if self._train:
data, label = self._train_data, self._train_label
else:
data, label = self._test_data, self._test_label
# 參數(shù)transform
if self._transform is not None:
return self._transform(self._data[idx], self._label[idx])
return self._data[idx], self._label[idx]
在MXNet中骇窍,數(shù)據(jù)加載類被封裝成DataLoader類瓜晤,迭代器模式,迭代輸出與批次數(shù)相同的樣本集像鸡。在DataLoader中活鹰,
- 參數(shù)
dataset
:數(shù)據(jù)源,如MNIST只估; - 參數(shù)
batch_size
:訓(xùn)練中的批次數(shù)量志群,在迭代中輸出指定數(shù)量的樣本; - 參數(shù)
shuffle
:是否洗牌蛔钙,即打亂數(shù)據(jù)锌云,一般在訓(xùn)練時(shí)需要此操作。
迭代器的測(cè)試吁脱,每次輸出樣本個(gè)數(shù)(第1維)與指定的批次數(shù)量相同:
for data, label in train_data:
print(data.shape) # (64L, 28L, 28L, 1L)
print(label.shape) # (64L,)
break
在load_data()
方法中桑涎,輸出訓(xùn)練和測(cè)試數(shù)據(jù),數(shù)據(jù)類型是0~1(灰度值除以255)的浮點(diǎn)數(shù)兼贡,標(biāo)簽類型也是浮點(diǎn)數(shù)攻冷。
具體實(shí)現(xiàn):
def load_data(self):
def transform(data, label):
return data.astype(np.float32) / 255., label.astype(np.float32)
train_data = DataLoader(MNIST(train=True, transform=transform),
self.batch_size, shuffle=True)
test_data = DataLoader(MNIST(train=False, transform=transform),
self.batch_size, shuffle=False)
return train_data, test_data
模型
網(wǎng)絡(luò)模型使用MXNet中Gluon的樣式:
- 創(chuàng)建
Sequential()
序列,Sequential是全部操作單元的容器遍希; - 添加全連接單元Dense等曼,參數(shù)units是輸出單元的個(gè)數(shù),參數(shù)activation是激活函數(shù);
- 初始化參數(shù):
- init是數(shù)據(jù)來(lái)源禁谦,Normal類即正態(tài)分布胁黑,sigma是正態(tài)分布的標(biāo)準(zhǔn)差;
- ctx是上下文州泊,表示訓(xùn)練中參數(shù)更新使用CPU或GPU丧蘸,如mx.cpu();
Gluon的Sequential類與其他的深度學(xué)習(xí)框架類似遥皂,通過(guò)有序地連接不同的操作單元力喷,組成不同的網(wǎng)絡(luò)結(jié)構(gòu),每一層只需設(shè)置輸出的維度演训,輸入維度通過(guò)上一層傳遞冗懦,轉(zhuǎn)換矩陣在內(nèi)部自動(dòng)計(jì)算。
實(shí)現(xiàn):
def model(self):
num_hidden = 64
net = gluon.nn.Sequential()
with net.name_scope():
net.add(gluon.nn.Dense(units=num_hidden, activation="relu"))
net.add(gluon.nn.Dense(units=num_hidden, activation="relu"))
net.add(gluon.nn.Dense(units=self.num_outputs))
net.collect_params().initialize(init=mx.init.Normal(sigma=.1), ctx=self.model_ctx)
print(net) # 展示模型
return net
其中仇祭,net.name_scope()
為Sequential中的操作單元自動(dòng)添加名稱披蕉。
模型可視化
直接使用print(),打印模型結(jié)構(gòu)乌奇,如print(net)
:
Sequential(
(0): Dense(None -> 64, Activation(relu))
(1): Dense(None -> 64, Activation(relu))
(2): Dense(None -> 10, linear)
)
或没讲,使用稍復(fù)雜的jupyter繪制模型,安裝jupyter包(Python 2.x):
pip install ipython==5.3.0
pip install jupyter==1.0.0
啟動(dòng)jupyter服務(wù)礁苗,訪問(wèn)http://localhost:8888/
:
jupyter notebook
新建Python 2
文件爬凑,編寫(xiě)繪制網(wǎng)絡(luò)的代碼。代碼的樣式是试伙,在已有模型之后嘁信,添加“繪制邏輯”,調(diào)用plot_network()
即可繪圖疏叨。如果替換Sequential類為HybridSequential類潘靖,可以提升繪制效率,不替換也不會(huì)影響繪制效果
網(wǎng)絡(luò)模型和繪制邏輯:
import mxnet as mx
from mxnet import gluon
num_hidden = 64
net = gluon.nn.HybridSequential()
with net.name_scope():
net.add(gluon.nn.Dense(num_hidden, activation="relu"))
net.add(gluon.nn.Dense(num_hidden, activation="relu"))
net.add(gluon.nn.Dense(10))
# 繪制邏輯
net.hybridize()
net.collect_params().initialize()
x = mx.sym.var('data')
sym = net(x)
mx.viz.plot_network(sym)
效果圖:
訓(xùn)練
在訓(xùn)練前蚤蔓,加載數(shù)據(jù)卦溢,創(chuàng)建網(wǎng)絡(luò)。
train_data, test_data = self.load_data() # 訓(xùn)練和測(cè)試數(shù)據(jù)
net = self.model() # 模型
接著秀又,創(chuàng)建交叉熵的接口softmax_cross_entropy
单寂,創(chuàng)建訓(xùn)練器trainer
。
訓(xùn)練器的參數(shù)包含:網(wǎng)絡(luò)中參數(shù)吐辙、優(yōu)化器宣决、優(yōu)化器的參數(shù)等。
epochs = 10
smoothing_constant = .01
num_examples = 60000
softmax_cross_entropy = gluon.loss.SoftmaxCrossEntropyLoss() # 交叉熵
trainer = gluon.Trainer(params=net.collect_params(),
optimizer='sgd',
optimizer_params={'learning_rate': smoothing_constant})
循環(huán)epoch訓(xùn)練網(wǎng)絡(luò)模型:
- 從迭代器
train_data
源中昏苏,獲取批次數(shù)據(jù)和標(biāo)簽: - 指定數(shù)據(jù)和標(biāo)簽的執(zhí)行環(huán)境ctx是CPU或GPU尊沸,同時(shí)展開(kāi)數(shù)據(jù)為1行昵时;
- 自動(dòng)梯度計(jì)算
autograd.record()
,網(wǎng)絡(luò)預(yù)測(cè)數(shù)據(jù)椒丧,輸出output,計(jì)算交叉熵loss救巷; - 對(duì)于loss反向傳播求導(dǎo)壶熏,設(shè)置訓(xùn)練器trainer的步驟為批次數(shù);
- 在
cumulative_loss
中浦译,累加每個(gè)批次的損失loss棒假,計(jì)算全部損失; - 在訓(xùn)練一次epoch之后精盅,計(jì)算測(cè)試和訓(xùn)練數(shù)據(jù)的準(zhǔn)確率accuracy帽哑;
不斷循環(huán),直至執(zhí)行完成全部epochs為止叹俏。
訓(xùn)練的實(shí)現(xiàn):
for e in range(epochs):
cumulative_loss = 0 # 累積的
for i, (data, label) in enumerate(train_data):
data = data.as_in_context(self.model_ctx).reshape((-1, 784)) # 數(shù)據(jù)
label = label.as_in_context(self.model_ctx) # 標(biāo)簽
with autograd.record(): # 梯度
output = net(data) # 輸出
loss = softmax_cross_entropy(output, label) # 輸入和輸出計(jì)算loss
loss.backward() # 反向傳播
trainer.step(data.shape[0]) # 設(shè)置trainer的step
cumulative_loss += nd.sum(loss).asscalar() # 計(jì)算全部損失
test_accuracy = self.__evaluate_accuracy(test_data, net)
train_accuracy = self.__evaluate_accuracy(train_data, net)
print("Epoch %s. Loss: %s, Train_acc %s, Test_acc %s" %
(e, cumulative_loss / num_examples, train_accuracy, test_accuracy))
在預(yù)測(cè)接口evaluate_accuracy()
中:
- 創(chuàng)建準(zhǔn)確率Accuracy類acc妻枕,用于統(tǒng)計(jì)準(zhǔn)確率;
- 迭代輸出批次的數(shù)據(jù)和標(biāo)簽粘驰;
- 預(yù)測(cè)數(shù)據(jù)不同類別的概率屡谐,選擇最大概率(argmax)做為類別;
- 通過(guò)
acc.update()
更新準(zhǔn)確率蝌数;
最終返回準(zhǔn)確率的值愕掏,即acc的第2維acc[1]
,而acc的第1維acc[0]
是acc的名稱顶伞。
def __evaluate_accuracy(self, data_itertor, net):
acc = mx.metric.Accuracy() # 準(zhǔn)確率
for i, (data, label) in enumerate(data_iterator):
data = data.as_in_context(self.model_ctx).reshape((-1, 784))
label = label.as_in_context(self.model_ctx)
output = net(data) # 預(yù)測(cè)結(jié)果
predictions = nd.argmax(output, axis=1) # 類別
acc.update(preds=predictions, labels=label) # 更新概率和標(biāo)簽
return acc.get()[1] # 第1維是數(shù)據(jù)名稱饵撑,第2維是概率
效果:
Epoch 0. Loss: 1.2743850797812144, Train_acc 0.846283333333, Test_acc 0.8509
Epoch 1. Loss: 0.46071574948628746, Train_acc 0.884366666667, Test_acc 0.8892
Epoch 2. Loss: 0.37149955205917357, Train_acc 0.896466666667, Test_acc 0.9008
Epoch 3. Loss: 0.3313815038919449, Train_acc 0.908366666667, Test_acc 0.9099
Epoch 4. Loss: 0.30456133014361064, Train_acc 0.915966666667, Test_acc 0.9172
Epoch 5. Loss: 0.2827877395868301, Train_acc 0.919466666667, Test_acc 0.9214
Epoch 6. Loss: 0.2653073514064153, Train_acc 0.925433333333, Test_acc 0.9289
Epoch 7. Loss: 0.25018166739145914, Train_acc 0.92965, Test_acc 0.9313
Epoch 8. Loss: 0.23669789231618246, Train_acc 0.933816666667, Test_acc 0.9358
Epoch 9. Loss: 0.22473177655935286, Train_acc 0.934716666667, Test_acc 0.9337
GPU
對(duì)于深度學(xué)習(xí)而言,使用GPU可以加速網(wǎng)絡(luò)的訓(xùn)練過(guò)程唆貌,MXNet同樣支持使用GPU訓(xùn)練網(wǎng)絡(luò)滑潘。
檢查服務(wù)器的Cuda版本,命令:nvcc --version
锨咙,用于確定下載MXNet的GPU版本众羡。
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Sun_Sep__4_22:14:01_CDT_2016
Cuda compilation tools, release 8.0, V8.0.44
則,當(dāng)前服務(wù)器的Cuda版本是8.0蓖租。
將MXNet由CPU版本轉(zhuǎn)為GPU版本粱侣,卸載mxnet
,安裝mxnet-cu80
蓖宦。
pip uninstall mxnet
pip install mxnet-cu80
當(dāng)安裝完成GPU版本之后齐婴,在Python Console中,執(zhí)行如下代碼稠茂,確認(rèn)MXNet的GPU庫(kù)可以使用柠偶。
>>> import mxnet as mx
>>> a = mx.nd.ones((2, 3), mx.gpu())
>>> b = a * 2 + 1
>>> b.asnumpy()
array([[ 3., 3., 3.],
[ 3., 3., 3.]], dtype=float32)
檢查GPU數(shù)量情妖,命令:nvidia-smi
:
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.26 Driver Version: 375.26 |
|-------------------------------+----------------------+----------------------+
| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |
|===============================+======================+======================|
| 0 TITAN X (Pascal) Off | 0000:02:00.0 Off | N/A |
| 28% 49C P2 84W / 250W | 12126MiB / 12189MiB | 25% Default |
+-------------------------------+----------------------+----------------------+
| 1 TITAN X (Pascal) Off | 0000:03:00.0 Off | N/A |
| 24% 39C P2 57W / 250W | 12126MiB / 12189MiB | 33% Default |
+-------------------------------+----------------------+----------------------+
| 2 TITAN X (Pascal) Off | 0000:83:00.0 Off | N/A |
| 25% 41C P2 58W / 250W | 12126MiB / 12189MiB | 37% Default |
+-------------------------------+----------------------+----------------------+
| 3 TITAN X (Pascal) Off | 0000:84:00.0 Off | N/A |
| 23% 31C P2 53W / 250W | 11952MiB / 12189MiB | 2% Default |
+-------------------------------+----------------------+----------------------+
則,當(dāng)前服務(wù)器的GPU數(shù)量是4诱担。
設(shè)置參數(shù)環(huán)境ctx為GPU的列表毡证,即[mx.gpu(0), mx.gpu(1), ...]
。
GPU_COUNT = 4
ctx = [mx.gpu(i) for i in range(GPU_COUNT)]
在網(wǎng)絡(luò)net中使用GPU初始化initialize()參數(shù)params蔫仙,然后創(chuàng)建trainer訓(xùn)練器料睛。
net = self.model() # 模型
net.collect_params().initialize(init=mx.init.Normal(sigma=.1), ctx=ctx)
smoothing_constant = .01
trainer = gluon.Trainer(params=net.collect_params(),
optimizer='sgd',
optimizer_params={'learning_rate': smoothing_constant})
循環(huán)執(zhí)行10個(gè)epoch訓(xùn)練模型,train_data
和valid_data
是迭代器摇邦,每次輸出一個(gè)batch樣本集恤煞。在train_batch()
中,依次傳入批次數(shù)據(jù)batch施籍、GPU環(huán)境列表ctx居扒、網(wǎng)絡(luò)net和訓(xùn)練器trainer;在valid_batch()
中丑慎,與訓(xùn)練類似喜喂,只是不傳訓(xùn)練器trainer。
epochs = 10
for e in range(epochs):
start = time()
for batch in train_data:
self.train_batch(batch, ctx, net, trainer)
nd.waitall() # 等待所有異步的任務(wù)都終止
print('Epoch %d, training time = %.1f sec' % (e, time() - start))
correct, num = 0.0, 0.0
for batch in valid_data:
correct += self.valid_batch(batch, ctx, net)
num += batch[0].shape[0]
print('\tvalidation accuracy = %.4f' % (correct / num))
具體分析批次訓(xùn)練方法train_batch()
:
- 輸入batch是數(shù)據(jù)和標(biāo)簽的集合竿裂,索引0表示數(shù)據(jù)夜惭,索引1表示標(biāo)簽。
- 根據(jù)GPU的數(shù)量铛绰,拆分?jǐn)?shù)據(jù)data與標(biāo)簽label诈茧,每個(gè)GPU對(duì)應(yīng)不同的數(shù)據(jù);
- 每組數(shù)據(jù)和標(biāo)簽捂掰,分別反向傳播backward()更新網(wǎng)絡(luò)net的參數(shù)敢会;
- 設(shè)置訓(xùn)練器trainer的步驟step為批次數(shù)
batch_size
;
多個(gè)GPU是相互獨(dú)立的这嚣,因此鸥昏,當(dāng)使用多個(gè)GPU訓(xùn)練模型時(shí),需要注意不同GPU之間的數(shù)據(jù)融合姐帚。
實(shí)現(xiàn)如下:
@staticmethod
def train_batch(batch, ctx, net, trainer):
# split the data batch and load them on GPUs
data = gluon.utils.split_and_load(batch[0], ctx) # 列表
label = gluon.utils.split_and_load(batch[1], ctx) # 列表
# compute gradient
GluonFirst.forward_backward(net, data, label)
# update parameters
trainer.step(batch[0].shape[0])
@staticmethod
def forward_backward(net, data, label):
loss = gluon.loss.SoftmaxCrossEntropyLoss()
with autograd.record():
losses = [loss(net(X), Y) for X, Y in zip(data, label)] # loss列表
for l in losses: # 每個(gè)loss反向傳播
l.backward()
具體分析批次驗(yàn)證方法valid_batch()
:
- 將全部驗(yàn)證數(shù)據(jù)吏垮,都運(yùn)行于一個(gè)GPU中,即ctx[0]罐旗;
- 網(wǎng)絡(luò)net預(yù)測(cè)數(shù)據(jù)data的類別概率膳汪,再轉(zhuǎn)換為具體類別argmax();
- 將全部預(yù)測(cè)正確的樣本進(jìn)行匯總九秀,獲得總的正確樣本數(shù)遗嗽;
實(shí)現(xiàn)如下:
@staticmethod
def valid_batch(batch, ctx, net):
data = batch[0].as_in_context(ctx[0])
pred = nd.argmax(net(data), axis=1)
return nd.sum(pred == batch[1].as_in_context(ctx[0])).asscalar()
除了訓(xùn)練部分,GPU的數(shù)據(jù)加載和網(wǎng)絡(luò)模型都與CPU一致鼓蜒。
訓(xùn)練GPU模型痹换,需要連接遠(yuǎn)程服務(wù)器征字,上傳工程。如果無(wú)法使用Git傳輸娇豫,則推薦使用RsyncOSX匙姜,非常便捷的文件同步工具:
在遠(yuǎn)程服務(wù)器中,將工程的依賴庫(kù)安裝至虛擬環(huán)境中冯痢,注意需要使用MXNet的GPU版本mxnet-cu80
氮昧,接著,執(zhí)行模型訓(xùn)練系羞。
以下是GPU版本的模型輸出結(jié)果:
Epoch 5, training time = 13.7 sec
validation accuracy = 0.9277
Epoch 6, training time = 13.9 sec
validation accuracy = 0.9284
Epoch 7, training time = 13.8 sec
validation accuracy = 0.9335
Epoch 8, training time = 13.7 sec
validation accuracy = 0.9379
Epoch 9, training time = 14.4 sec
validation accuracy = 0.9402
當(dāng)遇到如下警告??時(shí):
only 4 out of 12 GPU pairs are enabled direct access.
It may affect the performance. You can set MXNET_ENABLE_GPU_P2P=0 to turn it off
關(guān)閉MXNET_ENABLE_GPU_P2P
即可,不影響正常的訓(xùn)練過(guò)程霸琴。
export MXNET_ENABLE_GPU_P2P=0
至此 MXNet/Gluon 的工程設(shè)計(jì)椒振,已經(jīng)全部完成,從數(shù)據(jù)集梧乘、模型澎迎、訓(xùn)練、GPU四個(gè)部分剖析MXNet的實(shí)現(xiàn)細(xì)節(jié)选调,MXNet的各個(gè)環(huán)節(jié)設(shè)計(jì)的非常巧妙夹供,也與其他框架類似,容易上手仁堪。實(shí)例雖小哮洽,“五臟俱全”,為繼續(xù)學(xué)習(xí)MXNet框架弦聂,起到拋磚引玉的作用鸟辅。
OK, that's all! Enjoy it!