CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

机器之心發表於2017-07-28

MXNet 現已廣泛應用於生產環境中,並且因為其執行速度而飽受讚譽。現在,MXNet 有了十分重要的新介面 Gluon,MXNet 可以透過它令科研工作變得更加簡單。本文將簡要介紹同時具備命令式執行和符號式執行的框架 Gluon,並且為讀者介紹李沐博士在 CVPR 2017 上所做的 MXNet/Gluon 教程。機器之心也將對使用 Gluon 構建卷積神經網路和實現平行計算的過程與優勢這兩部分內容進行展開。更詳細的內容請檢視李沐博士的 Github。

教程地址:https://github.com/mli/cvpr17

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

在本教程中,我們將體驗如何使用 Gluon 來實現各種各樣的演算法。我們將在本教程中細細體會每一個概念,並且還不需要深度學習背景。讀者朋友也可以使用筆記本按照以下的介紹文件嘗試使用 Gluon。此外,讀者也將體驗到 Gluon 命令式地開發和符號式地部署,這是非常高效和便捷的方式。

下面是本教程的文件和 PPT 資源。

  • 什麼是 Gluon?為什麼我們要使用 Gluon?:https://github.com/mli/cvpr17/blob/master/gluon_part1.pdf

第一部分:

  • n 維陣列文件:http://gluon.mxnet.io/P01-C02-ndarray.html
  • 自動微分文件:http://gluon.mxnet.io/P01-C05-autograd.html
  • 線性迴歸:http://gluon.mxnet.io/P02-C01-linear-regression-scratch.html(文件一),http://gluon.mxnet.io/P02-C02-linear-regression-gluon.html(文件二)
  • 卷積神經網路:http://gluon.mxnet.io/P04-C01-cnn-scratch.html(文件一),http://gluon.mxnet.io/P04-C02-cnn-gluon.html(文件二)

第二部分:

  • 混合命令(hybridizing imperative)和符號程式設計文件:http://gluon.mxnet.io/P14-C05-hybridize.html
  • MXNet 後端引擎幻燈片:https://github.com/mli/cvpr17/blob/master/gluon_part2.pdf
  • 多 GPU 和多機器幻燈片:https://github.com/mli/cvpr17/blob/master/gluon_part3.pdf
  • 使用多 GPU 訓練模型:http://gluon.mxnet.io/P14-C02-multiple-gpus-scratch.html(文件一),http://gluon.mxnet.io/P14-C03-multiple-gpus-gluon.html(文件二)
  • 使用多機器訓練模型文件:http://gluon.mxnet.io/P14-C03-multiple-gpus-gluon.html

以上所有文件筆記都是可執行的,我們可以按照以下指示安裝本教程。同時機器之心也將在文章後面介紹怎樣使用 Gluon 構建卷積神經網路和並行運算。


執行本教程

每一個教程的文件都是使用 Jupyter notebook 構建的,因此它們都是可編輯和可執行的。現在假定大家已經安裝了 Python,除此之外,我們還需要 Jupyter 和最新版本的 MXNet。下面的命令可以使用 pip 安裝這三個庫:

# optional: update pip to the newest version
sudo pip install --upgrade pip
# install jupyter
pip install jupyter --user
# install the nightly built mxnet
pip install mxnet --pre --user

預設的 MXNet 包只支援 CPU,但我們有一些教程需要呼叫 GPU。如果我們有可用的 GPU,並且安裝了 CUDA 7.5 或 8.0,那麼我們就可以使用 pip 安裝 GPU 支援包:

pip install mxnet-cu75 --pre --user  # for CUDA 7.5
pip install mxnet-cu80 --pre --user  # for CUDA 8.0

現在我們就可以獲取原始碼並執行它們:

git clone https://github.com/zackchase/mxnet-the-straight-dope/
cd mxnet-the-straight-dope
jupyter notebook

最後的命令是執行 Jupyter notebook,然後我們就可以編輯和執行本教程了。

什麼是 Gluon,為什麼使用它?

在進入教程之前,我們需要先了解什麼是 Gluon。如下圖所示,我們可以看到各大主流框架的時間順序,其中上半部分如 Chainer、Pytorch 和 MXNet 可以稱為命令式(imperative)框架,下半部分可以稱為符號式(symbolic)框架。我們能注意到,MXNet 同時具有命令式和符號式的特點。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

如下所示,MXNet 在實現殘差網路和 Adam 最佳化演算法時所採用的程式碼。我們可以看到 MXNet 在定義殘差網路時使用的是符號式的執行,我們需要像呼叫函式那樣確定每一個引數而完整地定義一個神經網路。而 MXNet 在實現 Adam 最佳化演算法時,我們可以看到它使用的是命令式的執行進行張量計算。但 MXNet 還不夠優秀,我們需要更加強大的 Gluon。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

因此我們可以從下圖看到,Gluon 結合了命令式的框架 Pytorch、Chainer 和符號式的框架 Keras。它將更為強大,可以令科研更加輕鬆。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

我們可以從下圖檢視 Gluon 的程式碼形式,前面一段可以看出 Gluon 使用的是符號式的執行構建神經網路,後面一部分構建 Softmax 交叉熵損失函式、計算梯度和執行反向傳播等都十分簡潔。net.hybridize() 函式可以將命令式執行轉換為符號式執行。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

總地來說符號式的執行更高效和辨析便攜,但是很難使用,而命令式的執行更靈活卻可能比較慢。但是在 Gluon 中,我們可以使用命令式的執行開發模型,使用符號式的執行部署模型。因此,Gluon 將同時具備兩種方法的優點,並且可以更加高效地應用在科研研究和產品中。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

Gluon 中的卷積神經網路

現在我們看一下如何使用 gluon 來簡潔的表示一個卷積神經網路。

from __future__ import print_function
import mxnet as mx
from mxnet import nd, autograd
from mxnet import gluon
import numpy as np
mx.random.seed(1)

設定環境

ctx = mx.gpu()

抓取 MNIST 資料集

mnist = mx.test_utils.get_mnist()
batch_size = 64
train_data = mx.io.NDArrayIter(mnist["train_data"], mnist["train_label"], batch_size, shuffle=True)
test_data = mx.io.NDArrayIter(mnist["test_data"], mnist["test_label"], batch_size, shuffle=True)

定義一個卷積神經網路

如果要改變模型,你僅需要對這些程式碼行進行操作,現在讓我們使用 gluon.nn 填加一對卷積層。

net = gluon.nn.Sequential()
with net.name_scope():
    net.add(gluon.nn.Conv2D(channels=20, kernel_size=5, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    net.add(gluon.nn.Conv2D(channels=50, kernel_size=5, activation='relu'))
    net.add(gluon.nn.MaxPool2D(pool_size=2, strides=2))
    net.add(gluon.nn.Flatten())
    net.add(gluon.nn.Dense(500, activation="relu"))
    net.add(gluon.nn.Dense(10))

引數初始化

net.collect_params().initialize(mx.init.Xavier(magnitude=2.24), ctx=ctx)

Softmax 交叉熵損失函式

loss = gluon.loss.SoftmaxCrossEntropyLoss()

最佳化器

trainer = gluon.Trainer(net.collect_params(), 'sgd', {'learning_rate': .1})

編寫評估迴圈以計算精度

def evaluate_accuracy(data_iterator, net):
    numerator = 0.
    denominator = 0.
    data_iterator.reset()
    for i, batch in enumerate(data_iterator):
        data = batch.data[0].as_in_context(ctx)
        label = batch.label[0].as_in_context(ctx)
        label_one_hot = nd.one_hot(label, 10)
        output = net(data)
        predictions = nd.argmax(output, axis=1)
        numerator += nd.sum(predictions == label)
        denominator += data.shape[0]
    return (numerator / denominator).asscalar()

訓練迴圈

epochs = 10

for e in range(epochs):
    train_data.reset()
    moving_loss = 0.
    for i, batch in enumerate(train_data):
        data = batch.data[0].as_in_context(ctx)
        label = batch.label[0].as_in_context(ctx)
        with autograd.record():
            output = net(data)
            cross_entropy = loss(output, label)
        cross_entropy.backward()
        trainer.step(data.shape[0])

        moving_loss = .99 * moving_loss + .01 * nd.mean(cross_entropy).asscalar()

    test_accuracy = evaluate_accuracy(test_data, net)
    train_accuracy = evaluate_accuracy(train_data, net)
    print("Epoch %d. Loss: %e, Train_acc %.4f, Test_acc %.4f" % (e, moving_loss, train_accuracy, test_accuracy))
Epoch 0. Loss: 7.431280e-02, Train_acc 0.9664, Test_acc 0.9627
Epoch 1. Loss: 4.358178e-02, Train_acc 0.9845, Test_acc 0.9816
Epoch 2. Loss: 3.054833e-02, Train_acc 0.9913, Test_acc 0.9878
Epoch 3. Loss: 2.258651e-02, Train_acc 0.9942, Test_acc 0.9896
Epoch 4. Loss: 1.622117e-02, Train_acc 0.9954, Test_acc 0.9900
Epoch 5. Loss: 1.204737e-02, Train_acc 0.9958, Test_acc 0.9900
Epoch 6. Loss: 9.149311e-03, Train_acc 0.9963, Test_acc 0.9896
Epoch 7. Loss: 7.570230e-03, Train_acc 0.9962, Test_acc 0.9892
Epoch 8. Loss: 5.724863e-03, Train_acc 0.9978, Test_acc 0.9900
Epoch 9. Loss: 4.022369e-03, Train_acc 0.9984, Test_acc 0.9912

結論

你可能會注意到,透過使用 gluon,我們可以讓程式碼在 CPU 或 GPU 上執行的更快。這很大程度上是因為 gluon 可以調入已用 C++ 寫好的高度最佳化層。

多 GPU 和多機器訓練

如下圖所示是一個用 2 塊 GPU 實現 2 層神經網路的範例。可以看到,為這個兩層神經網路編寫並行程式是比較麻煩的,更何況對於一個動輒幾百層的神經網路來說呢?

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

如下圖所示,應用 MXNet 的相應方式編寫序列程式,就可以實現並行化的執行,這是十分便捷的。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

下圖是資料並行化的概述過程:

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

當使用多臺機器進行分散式計算時,使用者不再需要大量改寫程式碼。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

運用階層式引數伺服器可將運算擴充套件到多臺 GPU 機器:

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

下圖是相關實驗的設定情況:

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

如下圖所示是多臺機器的相關擴充套件性結果。

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

下圖是用不同數量 GPU 完成 top-1 accuracy 的相關時間情況對比:

CVPR 2017李沐介紹MXNet新介面Gluon:高效支援命令式與符號式程式設計

相關文章