概要
前面的文章中讲到了Symbol和NDArray,介绍了如何从零开始训练神经网络。这种混杂风是MXNet相较与其他深度学习框架的不同的地方,值得一提的是MXNet名字中的”MX”同样也蕴含着混合(mixed)的意思。官方提供了很多的方法并都实现了,而这一篇旨在介绍如何自己从零搭建网络并训练。本文与官方tutorial大体相同,详情请点这里
正文
训练一个多层感知机
下面将展示使用一个两层的感知机,首先定义网络
import mxnet as mx
num_classes = 10
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=num_classes)
net = mx.sym.SoftmaxOutput(data=net, name='out')
mx.viz.plot_network(net).view()
上面定义的自由变量包括两层的权值w和偏差b,训练数据,标签和输出,下面使用list_argument方法查看下参数,以求有一个直观的认识。需要注意的是后缀 _weight, _bias, _label 是自动加上去的
print(net.list_arguments())
['data', 'fc1_weight', 'fc1_bias', 'fc2_weight', 'fc2_bias', 'out_label']
回顾前面的文章,如果要前向和后向(forward and backward)计算,首先需要将所有的自由变量(free variables)绑定值。这里我们创建所有的NDArrays,然后进行绑定,就像我们在Symbol篇里那样。有一个简单的bind方法simple_bind可以完成上述的事情。这个方法实现原理是首先根据提供的数据shape推断出所有自由变量的shape,然后分配内存和绑定数据,最后返回一个执行器。其中绑定的自由变量的属性可以通过arg_arrays来访问。下面是例子,仍旧是数字识别,其中mx.cpu()可以改成mx.gpu():
num_features = 100
batch_size = 100
ex = net.simple_bind(ctx=mx.cpu(), data=(batch_size, num_features))
args = dict(zip(net.list_arguments(), ex.arg_arrays))
for name in args:
print(name, args[name].shape)
(‘fc2_weight’, (10L, 128L))
(‘fc1_weight’, (128L, 100L))
(‘out_label’, (100L,))
(‘fc2_bias’, (10L,))
(‘data’, (100L, 100L))
(‘fc1_bias’, (128L,))
这里需要特别注意的是bind方法在Symbol中和Module中都有提供,而simple_bind只有Symbol提供。两者的对比在文章最后简要介绍,详细了解请看官网API
绑定完之后,接下来就需要初始化了,我们随机初始化
for name in args:
data = args[name]
###可以看到我们真正需要初始化的参数只有weight和bias,两层对应4个参数
###mx.random.uniform是在区间内等概论随机取值
if 'weight' in name:
data[:] = mx.random.uniform(-0.1, 0.1, data.shape)
if 'bias' in name:
data[:] = 0
在训练之前,我们需要获取训练数据集,当然是程序自动生成啦
import numpy as np
import matplotlib.pyplot as plt
class ToyData:
"""
num_classes 分类个数
num_features 特征数
mu 正态分布中均值数组,0~1的随机数
sigma 正太分布中标准差数组,全为0.1
mu和sigma是为了规定每个数字对应的特征,定义标准的作用
"""
def __init__(self, num_classes, num_features):
self.num_classes = num_classes
self.num_features = num_features
self.mu = np.random.rand(num_classes, num_features)
self.sigma = np.ones((num_classes, num_features)) * 0.1
"""
get方法是取数据,x是训练数据,y是对应的分类
num_samples是训练数据的个数,这里默认是分类数的整数倍,对应数字识别的例子也就是10的整数倍
np.random.normal是正太分布随机采样
不用关心特征和数字是如何对应的,因为这是我们自己定义的,只要初始化规则就已经确定了
"""
def get(self, num_samples):
num_cls_samples = int(num_samples / self.num_classes)
x = np.zeros((num_samples, self.num_features))
y = np.zeros((num_samples, ))
for i in range(self.num_classes):
cls_samples = np.random.normal(self.mu[i,:], self.sigma[i,:], (num_cls_samples, self.num_features))
x[i*num_cls_samples:(i+1)*num_cls_samples] = cls_samples
y[i*num_cls_samples:(i+1)*num_cls_samples] = i
return x, y
def plot(self, x, y):
colors = ['r', 'b', 'g', 'c', 'y']
for i in range(self.num_classes):
cls_x = x[y == i]
plt.scatter(cls_x[:,0], cls_x[:,1], color=colors[i%5], s=1)
plt.show()
###此处初始化规则就确定了
toy_data = ToyData(num_classes, num_features)
x, y = toy_data.get(1000)
toy_data.plot(x,y)
终于,我们可以开始训练了。下面我们使用学习率固定的plain minibatch随机梯度下降方法,迭代10次
# @@@ AUTOTEST_OUTPUT_IGNORED_CELL
learning_rate = 0.1
final_acc = 0
for i in range(100):
x, y = toy_data.get(batch_size)
###赋值,args是我们之前跟网络绑定的参数
args['data'][:] = x
args['out_label'][:] = y
###is_train=True表示参数需要训练更新,不要此参数也可以,因为默认已经设置为True了
###由于ex中已经有args,而args中有训练的数据所以此处forward不需要提供训练数据
ex.forward(is_train=True)
ex.backward()
"""
这里不像学习2中的Module代码,没有update方法,参数都是手动更新
这里如果不懂可以参考知乎上的一篇笔记(https://zhuanlan.zhihu.com/p/25081671?utm_source=tuicool&utm_medium=referral)
mx.nd.argmax_channel求的是batch_size*num_classes的数组,每个行向量的最大值的下标,最后得出的结果是batch_size维的列向量
"""
for weight, grad in zip(ex.arg_arrays, ex.grad_arrays):
weight[:] -= learning_rate * (grad / batch_size)
if i % 10 == 0:
acc = (mx.nd.argmax_channel(ex.outputs[0]).asnumpy() == y).sum()
final_acc = acc
print('iteration %d, accuracy %f' % (i, float(acc)/y.shape[0]))
assert final_acc > 0.95, "Low training accuracy."
iteration 0, accuracy 0.210000
iteration 10, accuracy 0.990000
iteration 20, accuracy 1.000000
iteration 30, accuracy 1.000000
iteration 40, accuracy 1.000000
iteration 50, accuracy 1.000000
iteration 60, accuracy 1.000000
iteration 70, accuracy 1.000000
iteration 80, accuracy 1.000000
iteration 90, accuracy 1.000000
这一节我们展示了如何将NDArray和Symbol结合起来实现一个完整的训练算法。
NDArray常用在如下场景
- 数据容器
- 容器
- 对灵活性要求较高的理程序,比如实现更新参数,监控优化方法中的处理流程
- 实现Symbol运算
- debug,比如打印每一步的结果
Symbol常用来定义对象函数,它受益于对Symbol和自动求导的大量优化
未完待续
下一篇将接着本篇文章,讲多设备下的数据并行,并用程序实现