MXNet学习3——Symbol

概要

本节介绍MXNet中的Symbol(模块)。Symbol是MXNet中另一个重要的概念,可以通过 mxnet.symbol 或者 mxnet.sym 使用。一个symbol表示一个具有多输出的符号表达式,表达式由多个运算组成,运算可以是简单的矩阵和(“+”),也可以是一个神经网络层(比如卷积层)。一个运算可以有多个输入,产生一个或多个输出,同时也可以由隐变量。变量可以是一个等待绑定参数值的自由符号,也可以是其他symbol的某一个输出值。

MXNet中重要概念NDArray与numpy类似,操作上也比较简单就不单独说明了,之后代码中遇到会提及。另外,本节代码跳过了官方tutorial中存储部分以及卷积神经网络部分,有些说明部分没有摘录,感兴趣请参考官方教程

正文

基础操作

import mxnet as mx
###构造一个简单的表达式 a+b
a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
###c没有人为指定名称,MXNet会自动命名一个不重复的名称,注意不一定是"C"
c = a + b

###下方的操作类似numpy
# 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).view()

MXNet学习3——Symbol_第1张图片

基础神经网络

除了上述基本的操作之外,Symbol还有一系列神经网络层,下面的代码构造了一个两层全连接的神经网络

###以下神经网络比较简单,就不单独说明了,其中relu是激活函数
# 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)}).view()

组合多个Symbol

如果想构造具有多种输出的神经网络,可以使用mxnet.sym.Group将多个组合在一起,比如以下softmax和线性回归

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与Symbol的区别

事实上MXNet的Symbol和NDArray很类似(NDArray和numpy类似),两者都提供了多维数组的操作,比如c=a+b,若想详细区分请点这里看官网说明,以下给出简单的区分。

NDArray 中的计算是一行一行向下顺序执行的,每一个参数都会参与当前操作的计算,并传递。而Symbol更类似于描述程序的逻辑,首先是申明计算然后再给参数赋值,机制类似正则和SQL(Examples in this category include regular expression and SQL,官网说的这个没有太懂)。

NDArray的优势:

  • 直接
  • 能简单的结合编程语言的特性(比如循环,判断)和函数库(比如numpy)
  • 能简单的一步步debug(吐槽一下,MXNet真的不容易debug,隔壁Tensorflow的Tensorboard貌似不错)

Symbol的优势:

  • 提供了NDArray几乎所有的方法,诸如 +, *, sin, shape
  • 提供了神经网络相关的操作,诸如卷积,激活函数,BatchNorm(不然就不用MXNet了)
  • 自动求导
  • 简单的构造和操纵复杂的计算,比如深度神经网络
  • 简单的存储,加载参数和可视化
  • 后端能简单的优化计算和内存占用

下一节将描述如何结合两者开发一个完整的训练程序,这里还是专注Symbol吧。

Symbol的操纵

上文讲到Symbol相比较NDArray的一个重要区别是,必须首先申明计算,然后赋值,才能计算。下面将介绍如何直接操纵一个Symbol,需要注意的是,下面部分与module联系紧密,如果不关心可以跳过。

Shape Inference

对于每个symbol可以直接查询其输入和输出。也可以通过给定的输入shape推断出输出的shape,这有利于更好的分配内存。

arg_name = c.list_arguments()  # get the names of the inputs
out_name = c.list_outputs()    # get the names of the 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)}}

Bind with Data and Evaluate

Symbol c声明了即将要运行的计算,为了得到结果,我们需要赋值。bind方法通过接收设备(cpu,gpu)信息以及一个字典(参数名称映射NDArrays)作为参数,然后返回一个执行器。这个执行器提供了forward用来计算以及得到所有的结果。

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())
number of outputs = 1
the first output = 
[[ 2.  2.  2.]
 [ 2.  2.  2.]]

定制Symbol

MXNet允许用户自己定制Symbol,只需要自行定义前向(forward )和后向(backward 计算方法,同时提供一些属性查询方法,比如 list_arguments and infer_shape.
forward and backward默认的参数类型都是NDArray,但为了显示MXNet的灵活性,这里展示使用numpy实现softmax 层。Numpy实现的操作只能在CPU上运行,但是提供的函数十分丰富,比较利于演示。

首先定义一个mx.operator.CustomOp的子类然后实现 forward and backward.

class Softmax(mx.operator.CustomOp):
    def forward(self, is_train, req, in_data, out_data, aux):
        x = in_data[0].asnumpy()     ###将NDArray转换成numpy.ndarray
        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))  ###将numpy.ndarray转换成NDArray
        ###CustomOp.assign 将转换后的y赋值给out_data[0],根据req的不同可能是覆盖或者写入

    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))

接下来在定义一个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()

最后我们就可以使用刚刚自己定制的操作了,需要注意的是操作的名称我们已经申明注册过了

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

定制这块的内容,官方tutorial给的十分简易,按照目前的学习进度也不需要掌握这块知识,所以略过啦~


这一次对tutorial中的内容翻译了不少,但有些方法名翻译成中文感觉还挺奇怪的,果然基础还是太查了/(ㄒoㄒ)/~~不管怎么样,欢迎大家拍砖讨论

你可能感兴趣的:(MXNet)