使用Sybmol模块来构建神经网络
符号编程
在之前的文章,我们介绍了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的好处:
- 简单直观
- 方便编程,可以整合在一些控制流中(for loop, if-else condition,...)以及一些库相互调用(numpy)。
- 方便按步调试
使用Symbol的好处:
- 提供了丰富的运算
- 方便对计算图进行保存和可视化
- 通过编译模块可以优化,并行计算与节约存储空间
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.Convolution和mx.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})
参考资料
- Symbol - Neural network graphs and auto-differentiation
- Deep Learning Programming Style
- Symbol in Picture
使用Sybmol模块来构建神经网络的更多相关文章
- TFLearn构建神经网络
TFLearn构建神经网络 Building the network TFLearn lets you build the network by defining the layers. Input ...
- maven多模块项目构建
描述 一个大的企业级项目通常跨越了数十万行代码,牵涉了数十或数百软件人员的努力.如果开发者在同一个项目下开 发,那么项目的管理.构建将会变得很难控制.因此设计人员会将项目划分为多个模块,多个模块独 ...
- 使用pytorch构建神经网络的流程以及一些问题
使用PyTorch构建神经网络十分的简单,下面是我总结的PyTorch构建神经网络的一般过程以及我在学习当中遇到的一些问题,期望对你有所帮助. PyTorch构建神经网络的一般过程 下面的程序是PyT ...
- Tensorflow BatchNormalization详解:2_使用tf.layers高级函数来构建神经网络
Batch Normalization: 使用tf.layers高级函数来构建神经网络 觉得有用的话,欢迎一起讨论相互学习~Follow Me 参考文献 吴恩达deeplearningai课程 课程笔 ...
- Maven环境下多模块项目构建
Maven环境下多模块项目构建 一.新建项目 1.建立我们的父模块par 2.建立我们的子模块dao层 3.建立我们的子模块service层 4.建立我们的子模块web层 5.全部配置完成后,怎么把我 ...
- 使用 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 ...
- 使用PyTorch构建神经网络以及反向传播计算
使用PyTorch构建神经网络以及反向传播计算 前一段时间南京出现了疫情,大概原因是因为境外飞机清洁处理不恰当,导致清理人员感染.话说国外一天不消停,国内就得一直严防死守.沈阳出现了一例感染人员,我在 ...
- 利用Module模块把构建的神经网络跑起来
训练一个神经网络往往只需要简单的几步: 准备训练数据 初始化模型的参数 模型向往计算与向后计算 更新模型参数 设置相关的checkpoint 如果上述的每个步骤都需要我们写Python的代码去一步步实 ...
- ue4 模块的构建和加载
ue4的代码是模块的形式来组织 在源码层面,一个包含*.build.cs的目录就是一个模块 这个目录里的文件在编译后都会被链接在一起,比如一个静态库lib,或者一个动态库dll. 不管是哪种形式,都需 ...
随机推荐
- Analysis Services 中的服务器属性配置
Analysis Services 中的服务器属性配置: https://docs.microsoft.com/zh-cn/sql/analysis-services/server-propertie ...
- 作业四:个人项目-小学四则运算之JAVA版
作业的要求来自于:https://edu.cnblogs.com/campus/gzcc/GZCC-16SE2/homework/2186 代码所在的github远程仓库的地址:https://git ...
- 《JAVA程序设计》第五周总结
第五周学习总结 本周目录: 第六章知识梳理 本周学习时遇到的问题和解决过程 上周错题与解析 码云链接 感想 第六章知识梳理: 接口:使用关键字interface定义,分为接口声明和接口体. inter ...
- 如何打开用eclipse没有.project文件的Java工程
大概分为以下7个步骤,具体如下: 1.在你的eclipse下新建一个与你想要打开的源代码项目同名的一个项目(我这举例叫myweb). 2.在电脑磁盘中找到刚刚新建的项目,把它生成的.project文件 ...
- create-react-app创建项目并用git上传至GitHub及展示预览效果
1.在本地中创建一个项目所在的文件夹 2.npm -g create-react-app 3.在此文件夹下 create-react-app react-demo (项目名) 4.cd react-d ...
- 手绘raft算法
手绘raft算法 互联网技术窝 2019-04-07 12:06:05 在现实的分布式系统中,不能可能保证集群中的每一台机器都是100%可用可靠的,集群中的任何机器都可能发生宕机.网络连接等问题导致集 ...
- 真机测试没有问题,but上线后安装在手机上crash。也有无法打包的情况。
图片格式不对,把16位or p3图片改为8位sRGB图片 9.0以前不支持 解决步骤: a.找到导出项目的ipa文件修改后缀名.ipa为.zip后直接解压该文件: b.找到出问题的图片,直接把该图片 ...
- nodeJS接入微信公众平台开发
一.写在前面的话 Node.js是一个开放源代码.跨平台的JavaScript语言运行环境,采用Google开发的V8运行代码,使用事件驱动.非阻塞和异步输入输出模型等技术来提高性能,可优化应用程 ...
- C++重载Level蓝图
一.从ALevelScriptActor派生自己的类,添加功能并编译. 二.在编辑器中打开level blueprint,然后class default选项卡中,在细节面板中Parent class选 ...
- MongoDB学习记录(四) - MongoDB的"增查改删"操作之"改"
更新文档主要有以下几种方法: db.collection.updateOne(filter, update, options) db.collection.updateMany(filter, upd ...