符号编程

之前的文章,我们介绍了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. 转*SqlSever查询某个表的列名称、说明、备注、注释,类型等

    @原文地址 关键部分如下: ------sqlserver 查询某个表的列名称.说明.备注.类型等 SELECT 表名 then d.name else '' end, 表说明 then isnull ...

  2. es6对象的扩展

    对象(object)是 JavaScript 最重要的数据结构之一. object 在es6中新增了很多便利的方法 在es6中允许直接写入变量和方法的名称直接作为对象的属性 let x =1 ,y=2 ...

  3. 制作docker基础镜像

    docker build -t tomcat:jre8-alpine . sudo docker tag 5a3 registry.cn-shenzhen.aliyuncs.com/ibase/tom ...

  4. 《Java从入门到精通》学习总结3

    1. 3种构成重载的条件: 参数类型不同.参数个数不同.参数顺序不同 只有返回值类型不同并不足以区分两个方法的重载. 2. import关键字除了导入包之外,还可以导入静态成员,这是JDK 5.0以上 ...

  5. leedcode算法解题思路

    1.两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个 ...

  6. Oracle lag()/lead() over()分析函数

    with tmp as(select '1' id ,'aa' name ,'22' age from dual union allselect '2' id ,'bb' name ,'20' age ...

  7. python+selenium,打开浏览器时报selenium.common.exceptions.WebDriverException: Message: 'chromedriver' executable needs to be in PATH

    有一年多没写web自动化了,今天搭建环境的时候报了一个常见错误,但是处理过程有点闹心,报错就是常见的找不到驱动<selenium.common.exceptions.WebDriverExcep ...

  8. vue实例的生命周期

    Vue实例的生命周期也就是这个实例从出生到死亡的过程,所以我在文档原图上把这个周期大致分为创建过程 | 运行过程 | 销毁过程三个阶段,不同结果或又分为一些小的阶段 在第一个阶段,创建阶段,会完成Vu ...

  9. 源码分析之groupcache之consistenthash

    很多时候读开源的库就是为自己准备大量的代码库,优秀开源项目代码质量比绝大部分人的代理质量都要高. 依赖的库 ```import ( "hash/crc32" "sort& ...

  10. C# 互通操作 (一)

    回顾一下自己学习的内容然后从互通的基础案例开始写起. 这次实现一个很简单的互通demo,就是 在unity里  在c#里调用windows窗体的MessageBox 消息提示 public class ...