选自Github
机器之心编译
参与:Smith、蒋思源
MXNet 现已广泛应用于生产环境中,并且因为其运行速度而饱受赞誉。现在,MXNet 有了十分重要的新接口 Gluon,MXNet 可以通过它令科研工作变得更加简单。本文将简要介绍同时具备命令式执行和符号式执行的接口 Gluon,并且为读者介绍李沐博士在 CVPR 2017 上所做的 MXNet/Gluon 教程。机器之心也将对使用 Gluon 构建卷积神经网络和实现并行计算的过程与优势这两部分内容进行展开。更详细的内容请查看李沐博士的 Github。
教程地址:https://github.com/mli/cvpr17
在本教程中,我们将体验如何使用 Gluon 来实现各种各样的算法。我们将在本教程中细细体会每一个概念,并且无需深度学习背景。读者朋友也可以使用笔记本按照以下的介绍文档尝试使用 Gluon。此外,读者也将体验到 Gluon 命令式地开发和符号式地部署,这是非常高效和便捷的方式。
下面是本教程的文档和 PPT 资源。
第一部分:
第二部分:
以上所有文档笔记都是可运行的,我们可以按照以下指示安装本教程。同时机器之心也将在文章后面介绍怎样使用 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。如下图所示,我们可以看到各大主流框架的时间顺序,其中上半部分如 Chainer、Pytorch 和 MXNet 可以称为命令式(imperative)框架,下半部分可以称为符号式(symbolic)框架。我们能注意到,MXNet 同时具有命令式和符号式的特点。
如下所示,MXNet 在实现残差网络和 Adam 优化算法时所采用的代码。我们可以看到 MXNet 在定义残差网络时使用的是符号式的执行,我们需要像调用函数那样确定每一个参数而完整地定义一个神经网络。而 MXNet 在实现 Adam 优化算法时,我们可以看到它使用的是命令式的执行进行张量计算。但 MXNet 还不够优秀,我们需要更加强大的 Gluon。
因此我们可以从下图看到,Gluon 结合了命令式的框架 Pytorch、Chainer 和符号式的框架 Keras。它将更为强大,可以令科研更加轻松。
我们可以从下图查看 Gluon 的代码形式,前面一段可以看出 Gluon 使用的是符号式的执行构建神经网络,后面一部分构建 Softmax 交叉熵损失函数、计算梯度和执行反向传播等都十分简洁。net.hybridize() 函数可以将命令式执行转换为符号式执行。
总地来说符号式的执行更高效和便携,但是很难使用,而命令式的执行更灵活却可能比较慢。但是在 Gluon 中,我们可以使用命令式的执行开发模型,使用符号式的执行部署模型。因此,Gluon 将同时具备两种方法的优点,并且可以更加高效地应用在科研研究和产品中。
下面,我们分别从Gluon中的卷积神经网络和并行计算详细介绍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.9627Epoch 1. Loss: 4.358178e-02, Train_acc 0.9845, Test_acc 0.9816Epoch 2. Loss: 3.054833e-02, Train_acc 0.9913, Test_acc 0.9878Epoch 3. Loss: 2.258651e-02, Train_acc 0.9942, Test_acc 0.9896Epoch 4. Loss: 1.622117e-02, Train_acc 0.9954, Test_acc 0.9900Epoch 5. Loss: 1.204737e-02, Train_acc 0.9958, Test_acc 0.9900Epoch 6. Loss: 9.149311e-03, Train_acc 0.9963, Test_acc 0.9896Epoch 7. Loss: 7.570230e-03, Train_acc 0.9962, Test_acc 0.9892Epoch 8. Loss: 5.724863e-03, Train_acc 0.9978, Test_acc 0.9900Epoch 9. Loss: 4.022369e-03, Train_acc 0.9984, Test_acc 0.9912
结论
你可能会注意到,通过使用 gluon,我们可以让代码在 CPU 或 GPU 上运行的更快。这很大程度上是因为 gluon 可以调入已用 C++ 写好的高度优化层。
如下图所示是一个用 2 块 GPU 实现 2 层神经网络的范例。可以看到,为这个两层神经网络编写并行程序是比较麻烦的,更何况对于一个动辄几百层的神经网络来说呢?
如下图所示,应用 MXNet 的相应方式编写串行程序,就可以实现并行化的运行,这是十分便捷的。
下图是数据并行化的概述过程:
当使用多台机器进行分布式计算时,用户不再需要大量改写代码。
运用阶层式参数服务器可将运算扩展到多台 GPU 机器:
下图是相关实验的设置情况:
如下图所示是多台机器的相关扩展性结果。
下图是用不同数量 GPU 完成 top-1 精确度的相关时间情况对比:
本文为机器之心编译,转载请联系本公众号获得授权。