11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

MXNet API 入门 —第 2 篇

  • 2017-07-04
  • 本文字数:2907 字

    阅读完需:约 10 分钟

第 1 篇文章中,我们介绍了一些有关 MXNet 的基础知识,并介绍了 NDArray API(简而言之:NDArrays 可用于存储数据、参数等信息)。

在介绍过数据的存储后,本文将谈谈 MXNet 定义计算步骤的方式。

计算步骤?是说代码吗?

这个问题很棒!我们是否都学过“程序 = 数据结构 + 代码”?NDArrays 是数据结构,那么接下来需要写代码了!

没错,是该这样做。我们需要明确定义所有步骤,随后针对数据按顺序运行。这也叫“指令式编程(Imperative programming)”,Fortran、Pascal、C、C++ 等都是这样做的,这么做也没什么错。

然而神经网络从本质上来说是一种并行的怪兽:在特定的技术层中,所有输出都可同步计算。每个层也可以并行运行。因此为了获得最佳性能,我们必须使用多线程或其他类似机制自行实现并行处理。具体做法大概都知道,而就算写出了恰当的代码,如果数据规模或网络布局不断变化,如何确保能可复用地进行?

好在还有其他备选方案。

数据流编程

数据流编程(Dataflow programming)”是一种定义并行运算的灵活方法,这种方法中,数据可通过 Graph* 的方式流动。Graph 定义了运算顺序,即数据是要按顺序运算或并行运算。每个运算都是一种黑匣子:我们只需要为其定义输入和输出,无需制定具体的行为。

按照传统的计算机科学思路来看,这似乎很不靠谱,但实际上神经网络就是通过这种方式定义的:输入的数据流进行一系列叫做“层(Layer)”的有序操作,每一层可以并行运行指令。

说的差不多了,一起看一个例子吧。我们可以通过下列方式将 E 定义为 (A*B) + (C*D)。

复制代码
E = (A*B) + (C*D)

A、B、C、D 具体是什么目前暂不重要,它们实际上是符号(Symbol)

无论输入了什么内容(整数、向量、矩阵等),这个 Graph 可以告诉我们如何通过计算获得输出值,但前提是必须定义了“+”和“*”操作。

这个 Graph 还可以告诉我们 (A*B) 和 (C*D) 可以并行运算。

当然,MXNet 会通过这些信息进行优化。

Symbol API

至此已经了解到这些东西为何叫做符号(Symbol)(显而易见嘛!)接下来一起看看如何为上述例子编写代码。

复制代码
>>> import mxnet as mx
>>> a = mx.symbol.Variable('A')
>>> b = mx.symbol.Variable('B')
>>> c = mx.symbol.Variable('C')
>>> d = mx.symbol.Variable('D')
>>> e = (a*b)+(c*d)

看到了吗?上述代码完全是有效的。我们可以直接向 E 指派结果,而无需知道 A、B、C、D 分别是什么。继续吧。

复制代码
>>> (a,b,c,d)
(<Symbol A>, <Symbol B>, <Symbol C>, <Symbol D>)
>>> e
<Symbol _plus1>
>>> type(e)
<class 'mxnet.symbol.Symbol'>

A、B、C、D 是我们明确声明的符号。但 E 略有不同,它也是符号,但实际上它是“+”运算的结果。接下来进一步看看 E。

复制代码
>>> e.list_arguments()
['A', 'B', 'C', 'D']
>>> e.list_outputs()
['_plus1_output']
>>> e.get_internals().list_outputs()
['A', 'B', '_mul0_output', 'C', 'D', '_mul1_output', '_plus1_output']

从上述代码可以知道:

  • E 取决于变量 A、B、C、D,
  • 用于计算 E 的是一次求和操作,
  • E 实际上就是 (a*b)+(c*d) 的结果。

当然,通过使用符号,我们能做的远远不止“+”和“*”。与 NDArrays 类似,还可以定义很多不同类型的运算(数学、格式等)。详细信息可以参阅 API 文档

至此我们已经了解了如何定义计算步骤。接下来看看如何将其应用给实际数据。

NDArray 与 Symbol 的绑定

将 Symbol 定义的计算步骤应用给 NDArray 中存储的数据,需要一种名为“绑定(Binding)”的操作,例如将一个 NDArray 分配给 Graph 的每个输入变量。

继续用上面的例子来看。在这里我们将“A”设置为 1,“B”为 2,“C”为 3,“D”为 4,因此我创建了 4 个包含单一整数的 NDArray。

复制代码
>>> import numpy as np
>>> a_data = mx.nd.array([1], dtype=np.int32)
>>> b_data = mx.nd.array([2], dtype=np.int32)
>>> c_data = mx.nd.array([3], dtype=np.int32)
>>> d_data = mx.nd.array([4], dtype=np.int32)

随后将每个 NDArray 绑定给对应的 Symbol。这里请注意,还需要选择用于执行该操作的上下文(CPU 或 GPU)。

复制代码
>>> executor=e.bind(mx.cpu(), {'A':a_data, 'B':b_data, 'C':c_data, 'D':d_data})
>>> executor
<mxnet.executor.Executor object at 0x10da6ec90>

接着需要让输入的数据流经 Graph 并获得结果:这是通过 forward() 函数实现的。由于一个 Graph 可以包含多个输出,因此该函数可以返回 NDArrays 组成的数组。这里我们只有一个输出,即“14”这个值,这个值当然与 (1*2)+(3*4) 的运算结果是相等的。

复制代码
>>> e_data = executor.forward()
>>> e_data
[<NDArray 1 @cpu(0)>]
>>> e_data[0]
<NDArray 1 @cpu(0)>
>>> e_data[0].asnumpy()
array([14], dtype=int32)

接下来我们将这个 Graph 应用给四个 1000x1000 矩阵,这些矩阵中填充了介于 0 和 1 之间的随机浮点数。为此我们只需要定义新的输入数据,绑定和计算过程是完全相同的

复制代码
>>> a_data = mx.nd.uniform(low=0, high=1, shape=(1000,1000))
>>> b_data = mx.nd.uniform(low=0, high=1, shape=(1000,1000))
>>> c_data = mx.nd.uniform(low=0, high=1, shape=(1000,1000))
>>> d_data = mx.nd.uniform(low=0, high=1, shape=(1000,1000))
>>> executor=e.bind(mx.cpu(), {'A':a_data, 'B':b_data, 'C':c_data, 'D':d_data})
>>> e_data = executor.forward()
>>> e_data
[<NDArray 1000x1000 @cpu(0)>]
>>> e_data[0]
<NDArray 1000x1000 @cpu(0)>
>>> e_data[0].asnumpy()
array([[ 0.89252722, 0.46442914, 0.44864511, ..., 0.08874825,
0.83029556, 1.15613985],
[ 0.10265817, 0.22077513, 0.36850023, ..., 0.36564362,
0.98767519, 0.57575727],
[ 0.24852338, 0.6468209 , 0.25207704, ..., 1.48333383,
0.1183901 , 0.70523977],
...,
[ 0.85037285, 0.21420079, 1.21267629, ..., 0.35427764,
0.43418071, 1.12958288],
[ 0.14908466, 0.03095067, 0.19960476, ..., 1.13549757,
0.22000578, 0.16202438],
[ 0.47174677, 0.19318949, 0.05837669, ..., 0.06060726,
1.01848066, 0.48173574]], dtype=float32)

很酷吧!这种数据和计算之间明确的区分使得我们可以在不同环节同时获得最佳效果

  • 我们可以使用自己已经很熟悉的指令式编程模式加载和准备数据,甚至可以在这个过程中使用外部库(整个过程和传统变成方式完全相同)。
  • 计算过程则使用符号式编程方式进行,借此 MXNet 不仅可以实现代码与数据的解耦,而且可以随着 Graph 的优化实现并行执行。

本文的内容就是这些。下篇文章将介绍 Module API,随后将开始训练并使用神经网络!

后续内容:

- 第 3 篇:Module API

- 第 4 篇:使用预训练模型进行图片分类(Inception v3)

- 第 5 篇:进一步了解预训练模型(VGG16 和 ResNet-152)

- 第 6 篇:通过树莓派进行实时物体检测(并让它讲话!)

作者 Julien Simon 阅读英文原文 An introduction to the MXNet API?—?part 2


感谢杜小芳对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-07-04 17:093465
用户头像

发布了 283 篇内容, 共 94.6 次阅读, 收获喜欢 54 次。

关注

评论

发布
暂无评论
发现更多内容

测试管理和领导力秘诀,12+ BAT 大厂测试经理的干货经验汇总

测吧(北京)科技有限公司

测试

预约直播 | 流批一体机器学习算法平台Alink介绍及应用

阿里云大数据AI技术

深度学习

仅靠一文便火爆全网!开源阿里绝密Java面试笔记:霸榜GitHub

Geek_0c76c3

Java 数据库 开源 程序员 开发

墨天轮沙龙 | 宝兰德詹年科 :基础软件中间件,让业务人员更好专注业务逻辑的实现

墨天轮

数据库 基础软件 中间件 消息中间件 数据库中间件

测试人生 | 毕业 2 年,涨薪 100%,从创业小团队到某中厂测试开发(附面试真题)

测吧(北京)科技有限公司

测试

干货 | Pytest 结合 Allure 生成测试报告

测吧(北京)科技有限公司

测试

测试面试 | Python 算法与数据结构面试题系列二(附答案)

测吧(北京)科技有限公司

测试

基于 JMeter 完成 Dubbo 接口的测试

测吧(北京)科技有限公司

测试

干货 | Dubbo 接口测试原理及多种方法实践总结

测吧(北京)科技有限公司

测试

阿里P8爆款《SpringBoot+vue全栈开发实战项目》笔记太香了

Geek_0c76c3

Java 数据库 开源 架构 开发

技术分享 | 测试平台开发-前端开发之Vue router路由设计

测吧(北京)科技有限公司

测试

实战 | 电商业务的性能测试(一): 必备基础知识

测吧(北京)科技有限公司

测试

实战演示 H5 性能分析

测吧(北京)科技有限公司

测试右移之logstash完整配置实例

测吧(北京)科技有限公司

测试

测试开发基础 | Python 算法与数据结构面试题系列一(附答案)

测吧(北京)科技有限公司

测试

干货 | 通用 api 封装实战,带你深入理解 PO

测吧(北京)科技有限公司

测试

测试面试 | 一道大厂算法面试真题,你能答上来吗?(附答案)

测吧(北京)科技有限公司

测试

面试官:说说你对事件循环的理解

CoderBin

JavaScript 前端 Promise Vue 3 10月月更

OneFlow的大模型分片保存和加载策略

OneFlow

机器学习 深度学习 分布式

凭借一份“面试真经pdf”,我四面字节跳动,拿下1-2级offer

Geek_0c76c3

Java 数据库 开源 架构 开发

Rust vs C++ 深度比较

俞凡

c++ rust

干货 | 实战演练基于加密接口测试测试用例设计

测吧(北京)科技有限公司

测试

openGauss社区七月运作报告

openGauss

观测云正式加入openGauss社区

openGauss

空间数据库开源路,超图+openGauss风起禹贡

openGauss

成长计划校园极客秀|基于OpenHarmony的智能阳台

OpenHarmony开发者社区

OpenHarmony

测试左移之Sonarqube maven项目分析

测吧(北京)科技有限公司

测试

MXNet API入门 —第2篇_语言 & 开发_Julien Simon_InfoQ精选文章