一、符号分类

符号对我们想要进行的计算进行了描述, 下图展示了符号如何对计算进行描述.

我们定义了符号变量A, 符号变量B, 生成了符号变量C, 其中, A, B为参数节点, C为内部节点! mxnet.symbol.Variable可以生成参数节点, 用于表示计算时的输入.

二、常用符号方法

一个Symbol具有的属性和方法如下图所示:

关联节点查看

list_arguments()用来检查计算图的输入参数;

list_outputs()返回此Symbol的所有输出,输出的自动命名遵循一定的规则

input = mx.sym.Variable('data')  # 生成一个符号变量,名字是可以随便取的
fc1 = mx.sym.FullyConnected(data=input, num_hidden=128,name='fc1') # 全连接层
act1 = mx.sym.Activation(fc1, act_type='relu') # 激活 type(fc1) # mxnet.symbol.Symbol, act1的类型也是这个!!!
fc1.list_outputs() # ['fc1_output'],自动在输入name属性名的后面加上"_output"作为本节点名称
fc1.list_arguments() # ['data','fc1_weight','fc1_bias'],自动生成fc1_weight,fc1_bias两个参数节点 act1.list_outputs() # ['actvation0_output'] 这个名字就不是随便起的了!!!
act1.list_arguments() # ['data','fc1_weight','fc1_bias']

返回逻辑如下图,

数据维度推断

mxnet.symbol.Symbol.infer_shape(self, *args, **kwargs): 推测输入参数和输出参数的shape, 返回一个list of tuple;

a = mx.sym.Variable('A')
b = mx.sym.Variable('B')
c = (a + b) / 10
d = c + 1
input_shapes = {'A':(10,2), 'B':(10,2)} # 定义输入的shape
d.infer_shape(**input_shapes) # ([(10L, 2L), (10L, 2L)], [(10L, 2L)], [])
arg_shapes, out_shapes, aux_shapes = d.infer_shape(**input_shapes)

In [1]: arg_shapes

Out[1]: [(10L, 2L), (10L, 2L)]

In [2]: out_shapes

Out[2]: [(10L, 2L)]

In [3]: aux_shapes

Out[3]: []

附、可视化

mx.viz.plot_network(d).view()

三、绑定执行

A = mx.sym.Variable('A')
B = mx.sym.Variable('B')
C = A * B
D = mx.sym.Variable('D')
E = C + D
a = mx.nd.empty(1) # 生成一个维度为1的随机值
b = mx.nd.ones(1) # b等于1
d = mx.nd.ones(1)
executor = E.bind(ctx=mx.cpu(), args={'A':a, 'B':b, 'D':d})
type(executor) # mxnet.executor.Executor
executor.arg_dict # {'A': <NDArray 1 @cpu(0)>, 'B': <NDArray 1 @cpu(0)>, 'D': <NDArray 1 @cpu(0)>}
executor.forward() # [<NDArray 1 @cpu(0)>]
executor.outputs[0] # <NDArray 1 @cpu(0)>, 值呢? 还是看不到值啊???
executor.outputs[0].asnumpy() # array([ 1.], dtype=float32)

首先我们需要调用绑定函数(bind function:*.bind)来绑定NDArrays(下图中的a/b/d)到参数节点(argument nodes: A/B/D,不是内部节点C/E),从而获得一个执行器(Executor),其作用是获取数组大小,以分配内存或显存:

然后,调用Executor.Forward 便可以得到输出结果.

执行器属性方法如下:

绑定多个输出

我们可以使用mx.symbol.Group([])来将symbols进行分组,然后将它们进行绑定,从而得到更多的中间变量输出。

下图中,A/B/D为参数节点,C/E为内部节点,将E/C绑定为G,这样,E和C的计算结果都可以得到,但是出于优化计算图的考虑,不建议过多绑定输出节点。

梯度计算

在绑定函数中,可以指定NDArrays来保存梯度,在Executor.forward()的后面调用Executor.backward()可以得到相应的梯度值.

辅助变量

四、新建symbol节点

文档介绍

官方文档例子,复现一个softmax节点,并进行一次反向传播(没有更新参数):

import mxnet as mx
from mxnet.test_utils import get_mnist_iterator
import numpy as np
import logging
import mxnet.ndarray as nd 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)) @mx.operator.register("softmax")
class SoftmaxProp(mx.operator.CustomOpProp):
def __init__(self):
    """使用need_top_grad = False调用基础构造函数,
      因为softmax是一个损失层,不需要前面层的梯度输入"""
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):
    """提供infer_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 infer_type(self, in_type):
return in_type, [in_type[0]], [] def create_operator(self, ctx, shapes, dtypes):
    """定义一个create_operator函数,该函数将由后端调用以创建softmax的实例"""
return Softmax()
# define mlp net = mx.sym.Variable('data')
net = mx.sym.FullyConnected(net, name='fc', num_hidden=6)
net = mx.sym.Activation(net, name='relu', act_type="relu")
mlp = mx.symbol.Custom(data=net, name='softmax', op_type='softmax') # train
# logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(level=logging.INFO) # MXNET_CPU_WORKER_NTHREADS must be greater than 1 for custom op to work on CPU
context=mx.cpu()
# Uncomment this line to train on GPU
# context=mx.gpu(0)
print(mlp.list_arguments(), mlp.list_outputs())
input_shapes = {'data':(5, 28*28)}
print(mlp.infer_shape(**input_shapes)) args = {'data': mx.nd.ones((1, 4)), 'fc_weight': mx.nd.ones((6, 4)),
'fc_bias': mx.nd.array((1, 4, 4, 4, 5, 6)), 'softmax_label': mx.nd.ones((1))}
args_grad = {'fc_weight': mx.nd.zeros((6, 4)), 'fc_bias': mx.nd.zeros((6))} executor = mlp.bind(ctx=mx.cpu(0), args=args, args_grad=args_grad, grad_req='write') # 所有参数节点数组
print("executor.arg_dict 初始值\n", executor.arg_dict) # 所有参数节点对应梯度数组
print("executor.grad_dict 初始值\n", executor.grad_dict) executor.backward() # # data
# train, val = get_mnist_iterator(batch_size=100, input_shape = (784,))
# mod = mx.mod.Module(mlp, context=context)
# mod.fit(train_data=train, eval_data=val, optimizer='sgd',
# optimizer_params={'learning_rate':0.1, 'momentum': 0.9, 'wd': 0.00001},
# num_epoch=10, batch_end_callback=mx.callback.Speedometer(100, 100))
['data', 'fc_weight', 'fc_bias', 'softmax_label'] ['softmax_output']
([(5, 784), (6, 784), (6,), (5,)], [(5, 6)], [])
executor.arg_dict 初始值
{'data':
[[1. 1. 1. 1.]]
<NDArray 1x4 @cpu(0)>, 'fc_weight':
[[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]
[1. 1. 1. 1.]]
<NDArray 6x4 @cpu(0)>, 'fc_bias':
[1. 4. 4. 4. 5. 6.]
<NDArray 6 @cpu(0)>, 'softmax_label':
[1.]
<NDArray 1 @cpu(0)>}
executor.grad_dict 初始值
{'data': None, 'fc_weight':
[[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]
[0. 0. 0. 0.]]
<NDArray 6x4 @cpu(0)>, 'fc_bias':
[0. 0. 0. 0. 0. 0.]
<NDArray 6 @cpu(0)>, 'softmax_label': None}

可以看到,bind方法其实蛮麻烦的,需要将参数、梯度参数全部初始化,才能进行下一步的操作,这就引出了两个其他方法:

  • 仅仅指定输入shape以申请内存的Symbol.simple_bind(),其参数仅仅是shape,这也意味这此方法仅仅能够测试,由于没有引进实际数据执行forward、backward两个方法的返回值并无意义。
  • mxnet.mod.Module类,集成了参数初始化、前传反传、参数更新等一系列方法,简化了训练的繁琐,个人感觉是介于gluon和基础symbol之间产物。

simple_bind

反向传播时,我们需要定义很多新的grad节点并绑定给Executor,过程较为繁琐,Symbol.simple_bind()函数可以帮助我们简化这个过程,指定输入数据的大小(shape),这个函数可以定位梯度参数并将其绑定为Executor.

v1 = mx.ndarray.array([[1, 1]])
v2 = mx.ndarray.array([[2, 2]])
v3 = mx.ndarray.array([[3, 3]])
a = mx.symbol.Variable('a')
b = mx.symbol.Variable('b')
c = mx.symbol.Variable('c')
d = b + c
b_stop_grad = mx.symbol.BlockGrad(3 * d)
loss = mx.sym.MakeLoss(b_stop_grad + a)
executor = loss.simple_bind(ctx=mx.cpu(), a=(1,2), b=(1,2), c=(1,2))
executor.forward(is_train=True, a=v1, b=v2, c=v3)
executor.outputs
Out[5]:
[
 [[16. 16.]]
 <NDArray 1x2 @cpu(0)>]
executor.backward()
executor.grad_dict
Out[6]:
{'b':
 [[0. 0.]]
 <NDArray 1x2 @cpu(0)>, 'c':
 [[0. 0.]]
 <NDArray 1x2 @cpu(0)>, 'a':
 [[1. 1.]]
 <NDArray 1x2 @cpu(0)>}

五、Modue对象

更为常用的方法是使用symbol生成计算图后将之转换为Module对象,再进行训练,

import mxnet as mx

# construct a simple MLP
data = mx.symbol.Variable('data')
fc1 = mx.symbol.FullyConnected(data, name='fc1', num_hidden=128)
act1 = mx.symbol.Activation(fc1, name='relu1', act_type="relu")
fc2 = mx.symbol.FullyConnected(act1, name = 'fc2', num_hidden = 64)
act2 = mx.symbol.Activation(fc2, name='relu2', act_type="relu")
fc3 = mx.symbol.FullyConnected(act2, name='fc3', num_hidden=10)
out = mx.symbol.SoftmaxOutput(fc3, name = 'softmax') # construct the module
mod = mx.mod.Module(out) mod.bind(data_shapes=train_dataiter.provide_data,
label_shapes=train_dataiter.provide_label) mod.init_params()
mod.fit(train_dataiter, eval_data=eval_dataiter,
optimizer_params={'learning_rate':0.01, 'momentum': 0.9},
num_epoch=n_epoch

首先是定义了一个简单的MLP,symbol的名字就叫做out,然后可以直接用mx.mod.Module来创建一个mod。之后mod.bind的操作是在显卡上分配所需的显存,所以我们需要把data_shapehe label_shape传递给他,然后初始化网络的参数,再然后就是mod.fit开始训练了。

fit方法核心代码如下:

for epoch in range(begin_epoch, num_epoch):
tic = time.time()
eval_metric.reset()
for nbatch, data_batch in enumerate(train_data):
if monitor is not None:
monitor.tic()
self.forward_backward(data_batch) #网络进行一次前向传播和后向传播
self.update() #更新参数
self.update_metric(eval_metric, data_batch.label) #更新metric if monitor is not None:
monitor.toc_print() if batch_end_callback is not None:
batch_end_params = BatchEndParam(epoch=epoch, nbatch=nbatch,
eval_metric=eval_metric,
locals=locals())
for callback in _as_list(batch_end_callback):
callback(batch_end_params)

对于训练过程我们可以做出很多改进,举个最简单的例子:如果我们的训练网络是大小可变怎么办? 我们可以实现一个mutumodule,基本上就是,每次data的shape变了的时候,我们就重新bind一下symbol,这样训练就可以照常进行了。

『MXNet』第十一弹_符号式编程初探的更多相关文章

  1. 『MXNet』第十弹_物体检测SSD

    全流程地址 一.辅助API介绍 mxnet.image.ImageDetIter 图像检测迭代器, from mxnet import image from mxnet import nd data_ ...

  2. 『TensorFlow』第十一弹_队列&多线程&TFRecod文件_我辈当高歌

    TF数据读取队列机制详解 一.TFR文件多线程队列读写操作 TFRecod文件写入操作 import tensorflow as tf def _int64_feature(value): # val ...

  3. 『MXNet』第八弹_数据处理API_下_Image IO专题

    想学习MXNet的同学建议看一看这位博主的博客,受益良多. 在本节中,我们将学习如何在MXNet中预处理和加载图像数据. 在MXNet中加载图像数据有4种方式. 使用 mx.image.imdecod ...

  4. 『MXNet』第八弹_数据处理API_上

    一.Gluon数据加载 下面的两个dataset处理类一般会成对出现,两个都可做预处理,但是由于后面还可能用到原始图片,.ImageFolderDataset不加预处理的话可以满足,所以建议在.Dat ...

  5. 『MXNet』第七弹_多GPU并行程序设计

    资料原文 一.概述思路 假设一台机器上有个GPU.给定需要训练的模型,每个GPU将分别独立维护一份完整的模型参数. 在模型训练的任意一次迭代中,给定一个小批量,我们将该批量中的样本划分成份并分给每个G ...

  6. 『MXNet』第四弹_Gluon自定义层

    一.不含参数层 通过继承Block自定义了一个将输入减掉均值的层:CenteredLayer类,并将层的计算放在forward函数里, from mxnet import nd, gluon from ...

  7. 『TensorFlow』第七弹_保存&载入会话_霸王回马

    首更: 由于TensorFlow的奇怪形式,所以载入保存的是sess,把会话中当前激活的变量保存下来,所以必须保证(其他网络也要求这个)保存网络和载入网络的结构一致,且变量名称必须一致,这是caffe ...

  8. 『PyTorch』第四弹_通过LeNet初识pytorch神经网络_下

    『PyTorch』第四弹_通过LeNet初识pytorch神经网络_上 # Author : Hellcat # Time : 2018/2/11 import torch as t import t ...

  9. 『MXNet』第六弹_Gluon性能提升 静态图 动态图 符号式编程 命令式编程

    https://www.cnblogs.com/hellcat/p/9084894.html 目录 一.符号式编程 1.命令式编程和符号式编程 2.MXNet的符号式编程 二.惰性计算 用同步函数实际 ...

随机推荐

  1. UVA11417 GCD

    题目地址 题目链接 题解 先讨论任何没有限制的情况 \[ \large { \begin{aligned} &\sum_{i=1}^{n}\sum_{j=1}^{n}gcd(i,j)\\ &a ...

  2. (转载)C#:Enum、Int和String的互相转换,枚举转换

    Enum为枚举提供基类,其基础类型可以是除 Char 外的任何整型.如果没有显式声明基础类型,则使用 Int32.编程语言通常提供语法来声明由一组已命名的常数和它们的值组成的枚举.注意:枚举类型的基类 ...

  3. 最简单的服务器和客户机(python3的编码与解码问题)

    在学习python的过程中,我越来越感觉到python2和python3之间有很多不同点,最近发现的一点就是编码问题. 在代码清单14-1和14-2中,因为作者是用python2来写得,然后我是用py ...

  4. sql注入解析

    sql注入解析 sql注入解析(一)基本语法 sql注入解析(二)执行注入 sql注入解析(三)数据库类型 sql注入解析(四)避开过滤

  5. React Native 异步存储

    异步存储 http://blog.csdn.net/yulianlin/article/details/52473456

  6. hdu 3861 The King’s Problem trajan缩点+二分图匹配

    The King’s Problem Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Other ...

  7. "ProgrammerHome"项目笔记

    系统目的: 1.技术练习:把平时不用的,重要技术栈,在此项目中打磨(java.python.算法.系统构架) 2.新技术(工具)应用:有些平时想做,想实现的技术,可以在这里实现.而且以微服务的方式,轻 ...

  8. vs 2015 编译cocos2d-x-3.9

    下载地址:链接: https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ 提取码: p1pb 或者去官网下载. 解压后,进入build文件夹,点击如下工程链接 ...

  9. python中网络编程基础

    一:什么是c\s架构 1.c\s即client\server 客户端\服务端架构. 客户端因特定的请求而联系服务器并发送必要的数据等待服务器的回应最后完成请求 服务端:存在的意义就是等待客户端的请求, ...

  10. C++.【转】C++数值类型与string的相互转换

    1.C++数值类型与string的相互转换 - JohnGu - 博客园.html(https://www.cnblogs.com/johngu/p/7878029.html) 2. 1.数值类型转换 ...