The basic
创建矩阵
1 | import mxnet as mx |
或者也可以通过numpy.ndarray
来创建1
2
3
4
5
6import numpy as np
import math
c = np.arange(15).reshape(3,5)
# create a 2-dimensional array from a numpy.ndarray object
a = mx.nd.array(c)
{'a.shape':a.shape}
可以通过dtype
来指定元素类型1
2
3
4
5
6
7# float32 is used in deafult
a = mx.nd.array([1,2,3])
# create an int32 array
b = mx.nd.array([1,2,3], dtype=np.int32)
# create a 16-bit float array
c = mx.nd.array([1.2, 2.3], dtype=np.float16)
(a.dtype, b.dtype, c.dtype)
同时还可以初始化矩阵而不需要给每一个元素指定数值,其中后面两种需要特别注意1
2
3
4
5
6
7a=mx.nd.zeros((2,3))
b=mx.nd.ones((2,3))
# create a same shape array with all elements set to 7
c = mx.nd.full((2,3), 7)
# create a same shape whose initial content is random and
# depends on the state of the memory
d = mx.nd.empty((2,3))
基础操作
算数运算都是元素和元素之间的(elementwise)的,同时一个新的array
会被创建并用来存储这个结果。1
2
3
4
5
6
7
8
9
10
11
12a = mx.nd.ones((2,3))
b = mx.nd.ones((2,3))
# elementwise plus
c = a + b
# elementwise minus
d = - c
# elementwise pow and sin, and then transpose
e = mx.nd.sin(c**2).T
# elementwise max
f = mx.nd.maximum(a, c)
# convert to print format
f.asnumpy()
矩阵的乘法运算和numpy
一样,是使用dot
来实现的。1
2
3a = mx.nd.ones((2,2))
b = a * a
c = mx.nd.dot(a,a)
+=
和*=
操作会直接修改现有的array
而不是去生成一个新的。1
2
3
4a = mx.nd.ones((2,2))
b = mx.nd.ones(a.shape)
b += a
b.asnumpy()
索引和切片
其中将a
的第二行全部设置为了1
。1
2
3a = mx.nd.array(np.arange(6).reshape(3,2))
a[1:2] = 1
a[:].asnumpy()
也可以沿着特定的维度进行切片。沿着第二个维度进行切片,也就是将第二个维度中满足(begin,end]
的维度切出来。1
2d = mx.nd.slice_axis(a, axis=1, begin=1, end=2)
d.asnumpy()
形状操作
将array
修改成另一个大小相同的array
。1
2
3a = mx.nd.array(np.arange(24))
b = a.reshape((2,3,4))
b.asnumpy()
也可将多个array
进行拼接,可以使用concatenate
方法,在不给额外参数的情况下是沿着第0维拼接的。可以通过增加axis=1
来指定沿其他维度进行拼接,这时候要求这几个array
除了被axis
指定的维度的大小不一样以外,其他维度都要求是相同的。1
2
3
4a = mx.nd.ones((2,3))
b = mx.nd.ones((2,3))*2
c = mx.nd.concatenate([a,b])
c.asnumpy()
归约
沿着第一维求和,最后会让第一维消失1
2a=mx.nd.ones((2,3))
b=mx.nd.sum(a,axis=1)
Broadcast
这个操作会自动补齐维度,通过复制的方式1
2
3a = mx.nd.array(np.arange(6).reshape(6,1))
b = a.broadcast_to((6,2)) #
b.asnumpy()
1 | c = a.reshape((2,1,1,3)) |
*
和+
会自动使用boardcast
让两个矩阵的形状相同
数据拷贝
普通的赋值操作并不会产生新的数据,下面的b实际上还是a本身,类似指针。1
2
3a = mx.nd.ones((2,2))
b = a
b is a
1 | True |
下面的操作同样也没有产生新的数据1
2
3def f(x):
return x
a is f(a)
显示1
True
可以使用copy
操作来进行真正的拷贝1
2b = a.copy()
b is a
利用GPU进行运算
可以加上mx.gpu(0)
或者mx.gpu()
来切换到GPU
来进行计算1
2a = mx.nd.ones((100, 100), mx.gpu(0))
a
则显示1
<NDArray 100x100 @gpu(0)>
现在的MXNet
要求两个矩阵要在同一个设备上才能进行计算,可以将一个矩阵拷贝到GPU
上1
2
3
4
5
6
7a = mx.nd.ones((100,100), mx.cpu())
b = mx.nd.ones((100,100), mx.gpu())
c = mx.nd.ones((100,100), mx.gpu())
a.copyto(c) # copy from CPU to GPU
d = b + c
e = b.as_in_context(c.context) + c # same to above
{'d':d, 'e':e}
将数据写到磁盘以及从磁盘中读取数据
使用pickle
1 | import pickle as pkl |
使用mxnet自带的功能
可以dump一个list1
2
3
4
5a = mx.nd.ones((2,3))
b = mx.nd.ones((5,6))
mx.nd.save("temp.ndarray", [a,b])
c = mx.nd.load("temp.ndarray")
c
NDArray和Numpy的区别
- NDArray每次只能对一个维度做切片,像
x[:,1]
这样同时对两个维度做切片是做不到的 - 切片只能是连续的,不能使用
x[1:2:3]
这样的 - 布尔索引是不被支持的
- 缺乏像
max
和min
这样的reduce函数
Symbol
创建三个symbol,并将其命名为a
,b
。可以使用mx.sym
和mx.symbol
,两者是一样的东西1
2
3
4
5import mxnet as mx
a = mx.sym.Variable('a')
b = mx.sym.Variable('b')
c = a + b
(a, b, c)
大部分的NDArray
的操作可以直接作用于Symbol
。1
2
3
4
5
6
7
8
9# 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)
可以将网络可视化输出为pdf
只需要调用,但是似乎会报错。尽管会报错,但是输出的也是正常的。1
mx.viz.plot_network(symbol=g).view()
Symbol和NDArray的关系
Symbol
可以提供几乎所有的NDArray
的功能。- 提供大量的神经网络相关操作,比如卷积,激活和
BatchNorm
。 - 提供自动求导的功能
- 便于构建和操作复杂的计算,例如深度神经网络
- 便于存储、加载和可视化
- 便于后端优化计算和内存使用
Symbol Save和Load
1
2
3
4print(c.tojson())
c.save('symbol-c.json')
c2 = mx.symbol.load('symbol-c.json')
c.tojson() == c2.tojson()
建立自己的操作
1 | class Softmax(mx.operator.CustomOp): |
这里用asnumpy
来将NDArray
转变成numpy.ndarray
,最后再用CustomOp.assign
来将结果写回mxnet.NDArray
。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 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
和register name
来使用这个operator
1
net = mx.symbol.Custom(data=prev_input, op_type='softmax')
但是这个方法定义的operator
不能放在GPU
上运行。
Advanced Usages
MXNet
默认使用32-bit float
。有时候我们想要使用低精度的数据类型来获得更好的accuracy-performance trade-off
。例如Nvidia Tesla Pascal GPUs
已经改进了16-bit float
的性能,并且GTX Pascal GPUs
(e.g.GTX1080)在8-bit intergers
上是很快的。
我们可以使用mx.sym.Cast
操作来转换数据类型1
2
3
4
5
6
7
8a = 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})
Variable Sharing
1 | a = mx.sym.Variable('a') |
Training and Inference Module
在这个tutorial中,我们将使用一个简单的多层感知机和一个合成的数据集1
2
3
4
5
6
7
8
9
10
11
12import mxnet as mx
from data_iter import SyntheticData
# mlp
net = mx.sym.Variable('data')
net = mx.sym.FullyConnected(net, name='fc1', num_hidden=64)
net = mx.sym.Activation(net, name='relu1', act_type="relu")
net = mx.sym.FullyConnected(net, name='fc2', num_hidden=10)
net = mx.sym.SoftmaxOutput(net, name='softmax')
# synthetic 10 classes dataset with 128 dimension
data = SyntheticData(10, 128)
mx.viz.plot_network(net)
Create Module
最广泛使用的模块是Module
,这个包含了Symbol
和一个或多个Executor
。我们构建一个module
通过
symbol
网络的符号context
执行的设备data_names
数据变量名字的listlabel_names
1
2
3
4mod = mx.mod.Module(symbol=net,
context=mx.cpu(),
data_names=['data'],
label_names=['softmax_label'])
Train, Predict and Evaluate
前面我们获得了一个mod
,然后这个模块提供了一个fit
函数,可以看到其中需要一个数据迭代器来传送训练数据和标准数据。1
2
3
4
5
6
7
8
9
10
11
import logging
logging.basicConfig(level=logging.INFO)
batch_size=32
mod.fit(data.get_iter(batch_size),
eval_data=data.get_iter(batch_size),
optimizer='sgd',
optimizer_params={'learning_rate':0.1},
eval_metric='acc',
num_epoch=5)
当整个模型训练好了之后,可以使用predict
来预测新的数据,输入的是一个数据迭代器,虽然每次只是输入一个batch,但是它会收集并返回所有的预测结果。1
y = mod.predict(data.get_iter(batch_size))
考虑到如果内存是不够的,那么也可以每次只预测一个batch1
2
3
4for preds, i_batch, batch in mod.iter_predict(data.get_iter(batch_size)):
pred_label = preds[0].asnumpy().argmax(axis=1)
label = batch.label[0].asnumpy().astype('int32')
print('batch %d, accuracy %f' % (i_batch, float(sum(pred_label==label))/len(label)))
如果是用来在测试集上做评价的话,可以使用score
函数来得出结果1
mod.score(data.get_iter(batch_size), ['mse', 'acc'])
这里给了MSE
和ACC
两种度量方式,那么返回的也会是两种1
[('mse', 27.438781929016113), ('accuracy', 0.115625)]
Save and Load
Save
我们可以在每一个epoch
结束之后保存模型的参数,这个里的方式就是使用一个回调函数。1
2
3
4
5
6
7# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
# construct a callback function to save checkpoints
model_prefix = 'mx_mlp'
checkpoint = mx.callback.do_checkpoint(model_prefix)
mod = mx.mod.Module(symbol=net)
mod.fit(data.get_iter(batch_size), num_epoch=5, epoch_end_callback=checkpoint)
Load
从磁盘中读取上一次的网络状态,然后将它设置到模型中1
2
3
4
5sym, arg_params, aux_params = mx.model.load_checkpoint(model_prefix, 3)
print(sym.tojson() == net.tojson())
# assign the loaded parameters to the module
mod.set_params(arg_params, aux_params)
也可以直接直接开始训练1
2
3
4
5
6
7# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
mod = mx.mod.Module(symbol=sym)
mod.fit(data.get_iter(batch_size),
num_epoch=5,
arg_params=arg_params,
aux_params=aux_params,
begin_epoch=3)
Module as a computation “machine”
一个模块有多个状态
Initial state
内存还未分配,还没有准备好计算Binded
输入、输出和参数的形状都已经知道了,内存也已经分配给它了,已经准备好了计算Parameter initialized
一个模块如果没有对参数初始化就进行计算的话,会出现没定义好的输出Optimizer installed
指定了网络的优化方式,只有这样才能对权值进行更新。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
# initial state
mod = mx.mod.Module(symbol=net)
# bind, tell the module the data and label shapes, so
# that memory could be allocated on the devices for computation
train_iter = data.get_iter(batch_size)
mod.bind(data_shapes=train_iter.provide_data, label_shapes=train_iter.provide_label)
# init parameters
mod.init_params(initializer=mx.init.Xavier(magnitude=2.))
# init optimizer
mod.init_optimizer(optimizer='sgd', optimizer_params=(('learning_rate', 0.1), ))
# use accuracy as the metric
metric = mx.metric.create('acc')
# train one epoch, i.e. going over the data iter one pass
for batch in train_iter:
mod.forward(batch, is_train=True) # compute predictions
mod.update_metric(metric, batch.label) # accumulate prediction accuracy
mod.backward() # compute gradients
mod.update() # update parameters using SGD
# training accuracy
print(metric.get())
Loading Data
Basic Data Iterator
这是一个数据迭代器,类似python中的迭代器。1
2
3
4
5class SimpleBatch(object):
def __init__(self, data, label, pad=0):
self.data = data
self.label = label
self.pad = pad
一个batch
的形状应该是batch_size x num_chaneel x height x width
Symbol and Data Variables
新建一个数据迭代器1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38import numpy as np
class SimpleIter:
def __init__(self, data_names, data_shapes, data_gen,
label_names, label_shapes, label_gen, num_batches=10):
self._provide_data = zip(data_names, data_shapes)
self._provide_label = zip(label_names, label_shapes)
self.num_batches = num_batches
self.data_gen = data_gen
self.label_gen = label_gen
self.cur_batch = 0
def __iter__(self):
return self
def reset(self):
self.cur_batch = 0
def __next__(self):
return self.next()
@property
def provide_data(self):
return self._provide_data
@property
def provide_label(self):
return self._provide_label
def next(self):
if self.cur_batch < self.num_batches:
self.cur_batch += 1
data = [mx.nd.array(g(d[1])) for d,g in zip(self._provide_data, self.data_gen)]
assert len(data) > 0, "Empty batch data."
label = [mx.nd.array(g(d[1])) for d,g in zip(self._provide_label, self.label_gen)]
assert len(label) > 0, "Empty batch label."
return SimpleBatch(data, label)
else:
raise StopIteration
mxnet.symbol.SoftmaxOutput
在前向传播中,这个函数会返回softmax
的输出,而在后向传播的时候,这个函数会加上logit loss
。
用numpy为mx.nd.array赋值
1 | a=mx.nd.ones((2,3),ctx=mx.gpu()) |
不能使用1
a=np.random.rand(*a.shape)