符号编程

之前的文章,我们介绍了NDArray模块,它是MXNet中处理数据的核心模块,我们可以使用NDArray完成非常丰富的数学运算。实际上,我们完全可以使用NDArray来定义神经网络,这种方式我们称它为命令式的编程风格,它的优点是编写简单直接,方便调试。像下面我们就定义了一个两层的神经网络,它包含了一个全连接层,和一个relu的激活层。

import mxnet as mx
import mxnet.ndarray as nd def net(X, w, b):
z = nd.FullyConnected(data=X, weight=w, bias=b, num_hidden=128)
out = nd.Activation(data=z, act_type='relu')
return out

既然如此,我们为什么不用NDArray来完成所有事情呢?我们想像一下,如果我们要将我们上面定义的模型保存下来,使用C++ API来实际运行呢,没有非常直接的方法,我们只能根据Python的代码结构来找到对应的c++ api的定义。

MXNet提供了Sybmol API,主要用于符号编程。符号编程不像是命令式编程语句一条一条的执行,我们会首先定义一个计算图来描述整个计算过程,整个计算的输入、输出以及中间结果都是先通过占位符来表示,我们可以编译计算图来生成一个实际的函数,生成的函数可以直接对NDArray进行计算。这样看来,MXNet的Sybmol API有点像Caffe中使用的protobuf格式的网络配置文件,所以我们也很容易将使用Symbol API定义的网络模型保存到磁盘,再通过其他语言的api来读取,方便部署。

符号编程另外一个优势是,我们可以对整个计算图描述的计算过程进行优化,因为在编译计算图的时候,整个计算过程都已经定义完成,我们更加了解每个计算步骤之间的依赖关系,以及一些中间变量的生命周期,这方便我们对操作进行并行化,对一些中间变量使用原地存储来节省内存。

使用NDArray的好处:

  1. 简单直观
  2. 方便编程,可以整合在一些控制流中(for loop, if-else condition,...)以及一些库相互调用(numpy)。
  3. 方便按步调试

使用Symbol的好处:

  1. 提供了丰富的运算
  2. 方便对计算图进行保存和可视化
  3. 通过编译模块可以优化,并行计算与节约存储空间

Sybmol中的基本组成

在MXNet的Sybmol API中,我们可以通过operators把Symobls和Symbols组成在一起,形成计算图。这些计算图可以是很简单的算术运算,也可以形成一个神经网络。每一种operator都接收若干的输入的变量,然后输出一些变量,这些变量我们都用符号来表示。

下面我们代码演示了如果定义一个最简单的加法的计算图:

a = mx.sym.var('a')
b = mx.sym.var('b')
c = a + b
(a, b, c)
mx.viz.plot_network(c)

从上面我们可以看到,使用mx.sym.var来定义一个符号,同时需要指定符号的名称。但是在第三条语句中,我们使用了+这个operator来连接符号a和符号b,它的输出为符号c,符号c并没有显式的指定一个名称,它的名称是自动生成且惟一的。从输出中,我们可以看出是_plus0

上面我们使用了+操作符,Sybmol模块定义了丰富的操作符,NDArray支持的运算在Symbol中基本都支持:

d = a * b
e = mx.sym.dot(a, b)
f = mx.sym.reshape(d + e, shape=(1,4))
g = mx.sym.broadcast_to(f, shape=(2,4))
mx.viz.plot_network(g)

更复杂的operator

除了上面介绍的那些基本的操作运算(*,+,reshape)外,Symbol还提供了丰富的神经网络的层的操作,下面的例子显示了,使用Symbol模块的一些高级的operator来构建一个高层的神经网络。

net = mx.sym.var('data')
net = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=net, name='relu1', act_type='relu')
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net = mx.sym.Activation(data=net, name='relu2', act_type='relu')
net = mx.sym.SoftmaxOutput(data=net,name='out')
mx.viz.plot_network(net, shape={'data':(28,28)})

mx.sym.FullyConnected这样的operator接收符号变量作为输入,同时这个操作本身内部是带有参数的,我们通过接口的一些参数来指定。最后的net我们也可以看成是接入了一组参数的一个函数,这个函数需要参数我们可以用下面的方法列出来:

net.list_arguments()

更复杂的组合

针对深度学习中一些常见的层,MXNet在Symbol模块中都直接做好了优化封装。同时针对于各种不同的需要,我们也可以用Python来定义我们新的operator。

除了像上面那边一层一层向前的组装我们的Sybmol外,我们还可以对多个复杂的Symbol进行组合,形成结构更加复杂的Symbol。

data = mx.sym.var('data')
net1 = mx.sym.FullyConnected(data=data, name='fc1', num_hidden=10)
net2 = mx.sym.var('data')
net2 = mx.sym.FullyConnected(data=net2, name='fc2', num_hidden=10)
# net2就像一个函数一样,接收Symbol net1作为输入
composed = net2(data=net1, name='composed')
mx.viz.plot_network(composed)

通过用前缀管理模块来管理Symbol模块参数的名称

当我们要构建一个更大的network时,通常一些Symbol我们希望有一个共同的命名前缀。那么我们就可以使用MXNet的Prefix NameManager来处理:

data = mx.sym.var('data')
net = mx.sym.FullyConnected(data=data, name='fc1', num_hidden=10)
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net.list_arguments()
data = mx.sym.var('data')
with mx.name.Prefix('layer1'):
net = mx.sym.FullyConnected(data=data, name='fc1', num_hidden=10)
net = mx.sym.FullyConnected(data=net, name='fc2', num_hidden=10)
net.list_arguments()

深度网络的模块化构建方法

当我们在构建大的神经网络结构的时候,比如Google Inception Network,它的层很多,如果我们一层一层的构建那将是一个非常烦索的工作,但是实际上这些网络结构是由非常多结构类似的小网络组合而成的,我们可以模块化的来构建。

在Google Inception network中,其中有一个非常基本的结构就是卷积->BatchNorm->Relu,我们可以把这个部分的构建写成一个小的构建函数:

def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0, 0), name=None, suffix=''):
conv = mx.sym.Convolution(data=data, num_filter=num_filter, kernel=kernel,
stride=stride, pad=pad, name='conv_{}{}'.format(name, suffix))
bn = mx.sym.BatchNorm(data=conv, name='bn_{}{}'.format(name, suffix))
act = mx.sym.Activation(data=bn, act_type='relu', name='relu_{}{}'.format(name, suffix))
return act prev = mx.sym.Variable(name="Previous Output")
conv_comp = ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2, 2))
shape = {"Previous Output" : (128, 3, 28, 28)}
mx.viz.plot_network(symbol=conv_comp, shape=shape)

接下来我们就可以用ConvFactory来构建一个inception module了,它是Google Inception大的网络建构的基础。

def InceptionFactoryA(data, num_1x1, num_3x3red, num_3x3, num_d3x3red, num_d3x3,
pool, proj, name):
# 1x1
c1x1 = ConvFactory(data=data, num_filter=num_1x1, kernel=(1, 1), name=('%s_1x1' % name))
# 3x3 reduce + 3x3
c3x3r = ConvFactory(data=data, num_filter=num_3x3red, kernel=(1, 1), name=('%s_3x3' % name), suffix='_reduce')
c3x3 = ConvFactory(data=c3x3r, num_filter=num_3x3, kernel=(3, 3), pad=(1, 1), name=('%s_3x3' % name))
# double 3x3 reduce + double 3x3
cd3x3r = ConvFactory(data=data, num_filter=num_d3x3red, kernel=(1, 1), name=('%s_double_3x3' % name), suffix='_reduce')
cd3x3 = ConvFactory(data=cd3x3r, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_0' % name))
cd3x3 = ConvFactory(data=cd3x3, num_filter=num_d3x3, kernel=(3, 3), pad=(1, 1), name=('%s_double_3x3_1' % name))
# pool + proj
pooling = mx.sym.Pooling(data=data, kernel=(3, 3), stride=(1, 1), pad=(1, 1), pool_type=pool, name=('%s_pool_%s_pool' % (pool, name)))
cproj = ConvFactory(data=pooling, num_filter=proj, kernel=(1, 1), name=('%s_proj' % name))
# concat
concat = mx.sym.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat' % name)
return concat
prev = mx.sym.Variable(name="Previous Output")
in3a = InceptionFactoryA(prev, 64, 64, 64, 64, 96, "avg", 32, name="in3a")
mx.viz.plot_network(symbol=in3a, shape=shape)

把多个Symbol组合在一起

上面示例中所有构建的Symbol都是串行向下,有一个输入,一个输出的。但在神经网络中,尤其是设计loss的时候,我们需要将多个loss layer作为输出,这时我们可以使用Symbol模块提供的Group功能,将多个输出组合起来。

net = mx.sym.Variable('data')
fc1 = mx.sym.FullyConnected(data=net, name='fc1', num_hidden=128)
net = mx.sym.Activation(data=fc1, name='relu1', act_type="relu")
out1 = mx.sym.SoftmaxOutput(data=net, name='softmax')
out2 = mx.sym.LinearRegressionOutput(data=net, name='regression')
group = mx.sym.Group([out1, out2])
print(group.list_outputs())
mx.viz.plot_network(symbol=group)

Symbol输出shape与type的推断

Symbol只是我们定义好的一个计算图,它本身内部并没有操作任何实际的数据。但我们也可以从这个计算图获取相当多的信息,比如这个网络的输入输出,参数,状态,以及输出的形状和数据类型等。

arg_name = c.list_arguments()  # get the names of the inputs
out_name = c.list_outputs() # get the names of the outputs
# infers output shape given the shape of input arguments
arg_shape, out_shape, _ = c.infer_shape(a=(2,3), b=(2,3))
# infers output type given the type of input arguments
arg_type, out_type, _ = c.infer_type(a='float32', b='float32')
{'input' : dict(zip(arg_name, arg_shape)),
'output' : dict(zip(out_name, out_shape))}
{'input' : dict(zip(arg_name, arg_type)),
'output' : dict(zip(out_name, out_type))}

绑定数据并运行

如果要使得我们之前定义的计算图能够完成计算的功能,我们必须给计算图喂入对应的数据,也就是Symbol的所有自由变量。我们可以使用bind方法,它接收一个contxt参数和一个dict参数,dict的元素都是变量名及对应的NDArry组成的一个pair。

ex = c.bind(ctx=mx.cpu(), args={'a' : mx.nd.ones([2,3]),
'b' : mx.nd.ones([2,3])})
ex.forward()
print('number of outputs = %d\nthe first output = \n%s' % (
len(ex.outputs), ex.outputs[0].asnumpy()))

我们同时可以使用GPU数据进行绑定:

gpu_device=mx.gpu() # Change this to mx.cpu() in absence of GPUs.

ex_gpu = c.bind(ctx=gpu_device, args={'a' : mx.nd.ones([3,4], gpu_device)*2,
'b' : mx.nd.ones([3,4], gpu_device)*3})
ex_gpu.forward()
ex_gpu.outputs[0].asnumpy()

对于神经网络来说,一个更加常用的模式就是使用simple_bind

保存与加载

在我们序列化一个NDArray对象时,我们序列化的是面的的tensor数据,我们直接把这些数据以二进制的格式保存到磁盘。但是Symbol是一个计算图,它包含了一连串的操作,我们只是使用最终的输出来表示整个计算图。当我们序列化一个计算图时,我们也是对它的输入Sybmol进行序列化,我们保存为json格式,方向阅读与修改。

print(group.tojson())
group.save('symbol-group.json')
group2 = mx.sym.load('symbol-group.json')
group.tojson() == group2.tojson()

自定义Symbol

在MXNet中,为了更好的性能,大部分的operators都是用C++实现的,比如mx.sym.Convolutionmx.sym.Reshape。MXNet同时允许用户用Python自己写了一些新的operator,这部分的内容可以参考:How to create new operator

类型转换

MXNet在默认的情况下使用float32作为所有operator的操作类型。但是为了最大化程序的运行性能,我们可以使用低精度的数据类型。比如:在Nvidia TeslaPascal(P100)上可以使用FP16,在GTX Pascal GPUS(GTX 1080)上可以使用INT8

我们可以使用mx.sym.cast操作也进行数据类型的转换:

a = mx.sym.Variable('data')
b = mx.sym.cast(data=a, dtype='float16')
arg, out, _ = b.infer_type(data='float32')
print({'input':arg, 'output':out}) c = mx.sym.cast(data=a, dtype='uint8')
arg, out, _ = c.infer_type(data='int32')
print({'input':arg, 'output':out})

参考资料

  1. Symbol - Neural network graphs and auto-differentiation
  2. Deep Learning Programming Style
  3. Symbol in Picture

使用Sybmol模块来构建神经网络的更多相关文章

  1. TFLearn构建神经网络

    TFLearn构建神经网络 Building the network TFLearn lets you build the network by defining the layers. Input ...

  2. maven多模块项目构建

    描述 一个大的企业级项目通常跨越了数十万行代码,牵涉了数十或数百软件人员的努力.如果开发者在同一个项目下开   发,那么项目的管理.构建将会变得很难控制.因此设计人员会将项目划分为多个模块,多个模块独 ...

  3. 使用pytorch构建神经网络的流程以及一些问题

    使用PyTorch构建神经网络十分的简单,下面是我总结的PyTorch构建神经网络的一般过程以及我在学习当中遇到的一些问题,期望对你有所帮助. PyTorch构建神经网络的一般过程 下面的程序是PyT ...

  4. Tensorflow BatchNormalization详解:2_使用tf.layers高级函数来构建神经网络

    Batch Normalization: 使用tf.layers高级函数来构建神经网络 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 吴恩达deeplearningai课程 课程笔 ...

  5. Maven环境下多模块项目构建

    Maven环境下多模块项目构建 一.新建项目 1.建立我们的父模块par 2.建立我们的子模块dao层 3.建立我们的子模块service层 4.建立我们的子模块web层 5.全部配置完成后,怎么把我 ...

  6. 使用 Visual Studio 2015 + Python3.6 + tensorflow 构建神经网络时报错:'utf-8' codec can't decode byte 0xcc in position 78: invalid continuation byte

    使用 Visual Studio 2015 + Python3.6 + tensorflow 构建神经网络时报错:'utf-8' codec can't decode byte 0xcc in pos ...

  7. 使用PyTorch构建神经网络以及反向传播计算

    使用PyTorch构建神经网络以及反向传播计算 前一段时间南京出现了疫情,大概原因是因为境外飞机清洁处理不恰当,导致清理人员感染.话说国外一天不消停,国内就得一直严防死守.沈阳出现了一例感染人员,我在 ...

  8. 利用Module模块把构建的神经网络跑起来

    训练一个神经网络往往只需要简单的几步: 准备训练数据 初始化模型的参数 模型向往计算与向后计算 更新模型参数 设置相关的checkpoint 如果上述的每个步骤都需要我们写Python的代码去一步步实 ...

  9. ue4 模块的构建和加载

    ue4的代码是模块的形式来组织 在源码层面,一个包含*.build.cs的目录就是一个模块 这个目录里的文件在编译后都会被链接在一起,比如一个静态库lib,或者一个动态库dll. 不管是哪种形式,都需 ...

随机推荐

  1. redis集群搭建及设置账户(转)

    Redis集群搭建以及为集群设置密码 介绍安装环境与版本 用两台虚拟机模拟6个节点,一台机器3个节点,创建出3 master.3 salve 环境. redis 采用 redis-3.2.4 版本. ...

  2. “XmlDocumentationProvider”不实现接口成员“IDocumentationProvider.GetDocumentation(HttpControllerDescriptor)”错误的解决方案

    这东西就是这样,会的不难,难的不会.以前我配置过 WebAPI 的 HelpPage 功能,第一步先安装:Microsoft.AspNet.WebAPi.HelpPage,第二步安装:WebApiTe ...

  3. Mysql数据库性能优化(一)

    参考 http://www.jb51.net/article/82254.htm 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才需要 ...

  4. Git上传代码的步骤

    1.git status 列出来所有修改的文件2.git add 所有的文件列表,或者git add -A,添加所有文件到Add列表 3.git reset --hard 取最新的git标签 4.gi ...

  5. 第一次面试经历(hr面)

    经过介绍,我有幸去到一家国际背景的广告公司面试前端开发实习生.收到的邮件是复试通知,看来我已经跳过了第一轮面试. 来到hr请我进了一个小间坐下里填求职书,里面有各种个人信息,有兴趣爱好,有工作经历,以 ...

  6. playframework 一步一步来 之 日志 (三)

    在paly中自定义配置logback,也很简单,只需在conf folder下添一个application-logger.xml或者logger.xml就行了.(出处:"If you cre ...

  7. vue.js中axios的封装

    基于前文所述,axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,它有很多优秀的特性,例如拦截请求和响应.取消请求.转换json.客户端防御XSRF等. 如果还对a ...

  8. 安装composer Failed to decode zlib stream 问题解决方法

    https://getcomposer.org/download/ 页面下载最新版本 composer.phar 放到php.exe 页面下.创建一个.bat文件,存入下面内容 @ECHO OFF p ...

  9. 浅谈如何获取机器的memory和CPU信息

    最近做了一个项目,需要获取机器的CPU和memory的使用情况.花了一些时间网上搜索了一下,自己也做了些测试.总结下来,基本上2种方式:一种是用WMI(2种),另一种是用Performance cou ...

  10. 如何选择合适的PHP版本

    PHP版本很多,包括32位64位以及线程安全与非线程安全在内的php版本多达几百个,应该如何选择PHP版本呢 PHP32和64的选择和区别 32bit的php的整型数据最大最小正负2GB左右(0x7F ...