您的位置:首页 > 理论基础 > 计算机网络

MXNet官方文档教程(6):神经网络图

2017-02-21 22:48 585 查看
符号教程
除了张量计算接口NDArray,MXNet中另一个主要的对象就是Symbol,位于mxnet.symbol(缩写mxnet.sym)中。一个符号代表一个多输出的符号表达式。他们由运算符复合而成,例如简单的矩阵运算(如“+”),或者一个神经网络层(如卷积层)。一个操作符可以获得多个输入变量,并提供至少一个输出变量,并拥有内部变量。一个变量可以是空的(我们可以在之后为其赋值),也可以是一个其他符号的输出。

 

符号组成

基本操作符

以下的例子复合了一个简单的表达式“a+b”。我们首先使用mx.sym.Variable创建占位符a和b及其名称,然后用操作符“+”构造期望的符号。在新建时如果名字字符串没有给定,MXNet会自动为符号生产一个独一无二的名字,如c的例子所示。

import mxnet as mx
a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
c = a+ b
(a, b, c)

输出

(<Symbol a>, <Symbol b>, <Symbol _plus0>)


大多数NDArray操作符可以被应用于Symbol,例如:

# elemental wise times

d= a * b 

# matrix multiplication

e= mx.sym.dot(a, b)  

# reshape

f= mx.sym.Reshape(d+e, shape=(1,4)) 

# broadcast

g= mx.sym.broadcast_to(f, shape=(2,4)) 

mx.viz.plot_network(symbol=g)


 

基本神经网络

除了基本的操作符,Symbol拥有丰富的神经网络层集。以下代码构造了一个两层的全连接层,然后通过给定输入数据大小实例化该结构。

# Output may vary

net= mx.sym.Variable('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.SoftmaxOutput(data=net, name='out')

mx.viz.plot_network(net, shape={'data':(100,200)})


 


深度网络的模块化构建

对于深度网络,例如Google Inception,当有大量的层时,一个一个地构建层会十分痛苦。对于这些网络,我们通常模块化其构建。以Google Inception为例,我们首先定义一个制造函数来将卷积层,批标准化层和Relu激活层捆绑在一起:

# Output may vary

def ConvFactory(data, num_filter, kernel, stride=(1,1), pad=(0,0), name=None, suffix=''):

   conv = mx.symbol.Convolution(data=data, num_filter=num_filter, kernel=kernel, stride=stride, pad=pad, name='conv_%s%s'%(name, suffix))

   bn = mx.symbol.BatchNorm(data=conv, name='bn_%s%s'%(name, suffix))

   act = mx.symbol.Activation(data=bn, act_type='relu', name='relu_%s%s'%(name, suffix))

   return act

prev= mx.symbol.Variable(name="Previos Output")

conv_comp= ConvFactory(data=prev, num_filter=64, kernel=(7,7), stride=(2,2))

shape= {"Previos Output" : (128,3,28,28)}

mx.viz.plot_network(symbol=conv_comp, shape=shape)


然后我们定义一个构建基于ConvFactory的Inception模型的函数:

# @@@ AUTOTEST_OUTPUT_IGNORED_CELL

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.symbol.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.symbol.Concat(*[c1x1, c3x3, cd3x3, cproj], name='ch_concat_%s_chconcat'% name)

   return concat

prev= mx.symbol.Variable(name="Previos Output")

in3a= InceptionFactoryA(prev, 64,64,64,64,96,"avg",32, name="in3a")

mx.viz.plot_network(symbol=in3a, shape=shape)


最终我们可以通过改变多inception模型获得整个网络。

 


多符号组合

为了使用多损失层构建网络,我们可以使用mxnet.sym.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])
group.list_outputs()

输出

['softmax_output','regression_output']

 

 

与NDArray关系

如我们目前所见,在MXNet中,Symbol与NDArray都提供多维数组操作符,例如c=a+b。有时使用者会困惑到底应该使用哪一个。在此我们简短地阐明两者不同,更多细节参见:Programming
Models for Deep Learning。

NDArray提供了类命令式编程的接口:其中,计算被一条语句一条语句地定值。而相比之下,Symbol更加接近于声明式编程:其中,我们首先声明计算,然后再用数据定值。

同样的例子包括正则表达式和SQL。

 

NDArray优点:

简便。
方便与其他语言特性(循环、分支)和库(numpy,…)配合。
易于逐步debug。

Symbol优点:

提供几乎NDArray所有的功能,例如+,*和reshape。
提供了大量的神经网络相关操作符,例如卷积、激活和BatchNorm。
提供了自动微分。
易于构建和操作复杂的计算,例如深度神经网络。
易于保存、读取和可视化。
后端易于优化计算和存储的使用。

 

 

符号操作

Symbol与NDArray的一大不同就是,我们首先声明计算,然后绑定数据来运行。

在此部分,我们介绍直接操作符号的函数。但注意,它们大部分都被mx.module完美地包装了起来。所以,即便跳过本节也无伤大雅。

 

形状接口

对于每个符号,我们可以询问其输入(或者参数)和输出。我们也可以通过给定输入大小来获得输出大小,这有易于存储空间申请

arg_name = c.list_arguments() # get
the names of the inputs
out_name = c.list_outputs()   # get
the names ofthe outputs
arg_shape, out_shape,_ = c.infer_shape(a=(2,3),
b=(2,3)) 
{'input' :dict(zip(arg_name,
arg_shape)),
 'output' :dict(zip(out_name,
out_shape))}

输出

{'input': {'a':(2L, 3L), 'b': (2L, 3L)},
 'output': {'_plus0_output': (2L, 3L)}}

绑定数据与定值

我们构建的符号c声明了应该运行的计算。为了为其定值,我们需要首先用数据确定参数,也就是自由变量。我们可以使用bind方法来完成。该方法接受设备上下文和一个将自由变量名称映射到NDArray的字典作为参数,然后返回一个执行器。执行器为提供forward方法来定值和归属outputs以获取所有结果。

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

输出

number of outputs =1
the first output =
[[ 2.  2.  2.]
 [ 2. 2.  2.]]

我们在GPU上用不同的的数据计算同一个符号:

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

输出

array([[ 5.,  5.,  5.,  5.],

      [ 5.,  5.,  5.,  5.],

      [ 5.,  5.,  5.,  5.]], dtype=float32)


保存和载入

类似于NDArray,我们可以使用pickle模块序列号Symbol或者直接使用save和load。与NDArray选择的二进制格式不同,Symbol使用可读性更强的json格式来序列化。tojson方法返回json字符串。

print(c.tojson())

c.save('symbol-c.json')

c2= mx.symbol.load('symbol-c.json')

c.tojson()== c2.tojson()


输出

{

  "nodes": [

   {

     "op": "null",

      "name": "a",

      "inputs": []

   },

    {

     "op": "null",

      "name": "b",

      "inputs": []

   },

    {

     "op": "elemwise_add",

      "name": "_plus0",

      "inputs": [[0, 0, 0], [1, 0, 0]]

   }

  ],

  "arg_nodes": [0, 1],

  "node_row_ptr": [0, 1, 2, 3],

  "heads": [[2, 0, 0]],

  "attrs": {"mxnet_version": ["int", 901]}

}

 

 

 

 

 

True


 

自定义符号

大部分操作符例如mx.sym.Convolution和mx.symReshape为了更好的性能使用C++实现。MXNet也支持用户用任何前端语言例如Python撰写新的操作符。这经常使开发和调试更加简便。

为了用Python实现一个操作符,我们只需要定义两个计算方法forward和backward和一些查询属性的方法,例如list_arguments和infer_shape。

NDArray是forward和backward方法默认的参数类型。因此我们通常用NDArray操作符来实现计算。当然为例展示MXNet的灵活性,我们将演示使用NumPy来实现一个softmax层的过程。即便基于NumPy的操作符只能运行于CPU且失去一些可应用于NDArray的优化,但是可以享受NumPy提供的丰富的方法功能。

 

我们首先建立一个mx.operator.CustomOp的子类,然后定义forward和backward。

class Softmax(mx.operator.CustomOp):

    def forward(self, is_train, req, in_data, out_data, aux):

       x = in_data[0].asnumpy()

       y = np.exp(x- x.max(axis=1).reshape((x.shape[0],1)))

       y /= y.sum(axis=1).reshape((x.shape[0],1))

       self.assign(out_data[0], req[0], mx.nd.array(y))

 

    def backward(self, req, out_grad, in_data, out_data, in_grad, aux):

       l = in_data[1].asnumpy().ravel().astype(np.int)

       y = out_data[0].asnumpy()

       y[np.arange(l.shape[0]), l] -=1.0

       self.assign(in_grad[0], req[0], mx.nd.array(y))


此处我们使用asnumpy将NDArray输入转换为numpy.ndarray。然后根据req的取值(“重写”或“加上”)使用CustomOp.assign来将结果赋回mxnet.NDArray。

 

之后我们创建一个mx.operator.CustomOpProp的子类来查询属性。

# register this operator into MXNet by name "softmax"

@mx.operator.register("softmax")

class SoftmaxProp(mx.operator.CustomOpProp):

    def __init__(self):

       # softmax is a loss layer so we don’t need gradient input

       # from layers above.

        super(SoftmaxProp,self).__init__(need_top_grad=False)

   

    def list_arguments(self):

       return ['data','label']

 

    def list_outputs(self):

       return ['output']

 

    def infer_shape(self, in_shape):

       data_shape = in_shape[0]

       label_shape = (in_shape[0][0],)

       output_shape = in_shape[0]

       return [data_shape, label_shape], [output_shape], []

 

    def create_operator(self, ctx, shapes, dtypes):

       return Softmax()


最后,我们使用mx.sym.Custom和注册名称来使用这个操作符。

net= mx.symbol.Custom(data=prev_input, op_type='softmax')


 

 

高级用法

类型转换

MXNet默认使用32位float类型。有时我们为了更好的正确率—性能权衡,想要使用低精度的数据类型。例如,英伟达Tesla
Pascal GPUs(如P100)使用16位浮点的性能提升,以及GTX
Pascal GPUs(如GTX1080)使用8位整型的速度更快。

 

我们可以使用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})


输出

{'input': [<type 'numpy.float32'>], 'output': [<type 'numpy.float16'>]}

{'input': [<type 'numpy.int32'>], 'output': [<type 'numpy.uint8'>]}


 

变量共享

有时我们想要共享多个不同符号中的内容。这可以直接通过用同一个数组绑定这些符号来实现。

a= mx.sym.Variable('a')

b= mx.sym.Variable('b')

c= mx.sym.Variable('c')

d= a + b * c

 

data= mx.nd.ones((2,3))*2

ex= d.bind(ctx=mx.cpu(), args={'a':data,'b':data,'c':data})

ex.forward()

ex.outputs[0].asnumpy()


输出

array([[ 6.,  6.,  6.],

      [ 6.,  6.,  6.]], dtype=float32)


 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息