Symbol
与caffe类似,MxNet中定义了符号运算。我对符号运算的理解是:区别于之前介绍的NDArray,通过符号,我们可以定义出一系列的表达式,或者网络。这样定义出来的表达式或网络有点类似于数学中的函数式,比如
f(x)=ax+b
,这时候,
a
,
换言之,Symbol构造出表达式之后,还需要与具体的数值绑定起来,才能发挥作用。
与NDArray相比,它们各自有这些优点:
NDArray:
- Straightforward.
- Easy to work with native language features (for loop, if-else condition, ..) and libraries (numpy, ..).
- Easy step-by-step code debugging.
- Symbol:
- Provides almost all functionalities of NDArray, such as +, *, sin, reshape etc.
- Easy to save, load and visualize.
- Easy for the backend to optimize the computation and memory usage.
通过符号组建基本表达式
无论是什么网络,都要有输入输出。MxNet中,通过Variable来创建最基本的变量(包括输入/输出),比如:
a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
然后符号之间就可以进行运算了,比如对a,b:
# elemental wise multiplication
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))
显然,这里对a,b分别先逐个相乘,然后再矩阵乘法,然后再将两种乘法结果加起来,变换维度……这里,NDArray所支持的那些运算,这里也支持。
网络的可视化
MxNet提供了很方便的API,对网络进行可视化。比如在上述的代码中,最后生成的网络是g,那么我们可以通过如下的方式对g进行可视化:
mx.viz.plot_network(g).view()
从而生成这样的图片:
通过符号构造神经网络
除了一些基本的运算符以外,MxNet还支持各种各样的层。比如下面的一个包含两个全连接层的网络:
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')
可以看到,每一个layer的建立都需要提供data(该层的输入)还有name,作为它唯一的标识符。上述只是简单地去构造一个网络,对于一些大的网络,这样写太费劲了,通常会将部分模块化,比如通过如下的函数,构造一个卷积层:
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_%s%s' %(name, suffix))
bn = mx.sym.BatchNorm(data=conv, name='bn_%s%s' %(name, suffix))
act = mx.sym.Activation(data=bn, act_type='relu', name='relu_%s%s'
%(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)}
除此之外,MxNet还支持将多个网络通过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()
对符号的操作
- 查看某一个网络里面有哪些符号,可以xxx.list_arguments,或者xxx.list_outputs只查看输出
- 当创建某一个layer时,它的输出名字都是由电脑分配的。但这个时候可以通过这样的方式来手动指定名字:
net = mx.symbol.Variable('data')
w = mx.symbol.Variable('myweight')
net = mx.symbol.FullyConnected(data=net, weight=w, name='fc1', num_hidden=128)
net.list_arguments()
- 定义好符号之后,可以通过bind来绑定数据然后进行运算:
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()
或者用eval:
ex = c.eval(ctx = mx.cpu(), a = mx.nd.ones([2,3]), b = mx.nd.ones([2,3]))
print('number of outputs = %d\nthe first output = \n%s' % (
len(ex), ex[0].asnumpy()))
还有一种常用的方法是使用simple_bind,这样的话不需要对每一层的节点容量进行设置。