《MXNet深度学习实战》
530
2022-05-30
3.3 Module
在MXNet框架中,Module是一个高级的封装模块,可用来执行通过Symbol模块定义的网络模型的训练,与Module相关的接口介绍都可以参考Module的官方文档地址:https://mxnet.apache.org/api/python/module/module.html。Module接口提供了许多非常方便的方法用于模型训练,只需要将准备好的数据、超参数等传给对应的方法就能启动训练。
在3.2节,我们用Symbol接口定义了一个网络结构sym,接下来我们将基于这个网络结构介绍Module模块,首先来看看如何通过Module模块执行模型的预测操作。通过mxnet.module.Module()接口初始化一个Module对象,在初始化时需要传入定义好的网络结构sym并指定运行环境,这里设置为GPU环境。然后执行Module对象的bind操作,这个bind操作与Symbol模块中的bind操作类似,目的也是将网络结构添加到执行器,使得定义的静态图能够真正运行起来,因为这个过程涉及显存分配,因此需要提供输入数据和标签的维度信息才能执行bind操作,读者可以在命令行通过“$ watch nvidia-smi”命令查看执行bind前后,显存的变化情况。bind操作中还存在一个重要的参数是for_training,这个参数默认是True,表示接下来要进行的是训练过程,因为我们这里只需要进行网络的前向计算操作,因此将该参数设置为False。最后调用Module对象的init_params()方法初始化网络结构的参数,初始化的方式是可以选择的,这里采用默认方式,至此,一个可用的网络结构执行器就初始化完成了。初始化网络结构执行器的代码具体如下:
mod = mx.mod.Module(symbol=sym, context=mx.gpu(0))
mod.bind(data_shapes=[('data',(8,3,28,28))],
label_shapes=[('softmax_label',(8,))],
for_training=False)
mod.init_params()
接下来随机初始化一个4维的输入数据,该数据的维度需要与初始化Module对象时设定的数据维度相同,然后通过mxnet.io.DataBatch()接口封装成一个批次数据,之后就可以作为Module对象的forward()方法的输入了,执行完前向计算后,调用Module对象的get_outputs()方法就能得到模型的输出结果,具体代码如下:
data = mx.nd.random.uniform(0,1,shape=(8,3,28,28))
mod.forward(mx.io.DataBatch([data]))
print(mod.get_outputs()[0])
输出结果如下,因为输入数据的批次大小是8,网络的全连接层输出节点数是2,因此输出的维度是8*2:
[[ 0.50080067 0.4991993 ]
[ 0.50148612 0.49851385]
[ 0.50103837 0.4989616 ]
[ 0.50171131 0.49828872]
[ 0.50254387 0.4974561 ]
[ 0.50104254 0.49895743]
[ 0.50223148 0.49776852]
[ 0.49780959 0.50219035]]
接下来介绍如何通过Module模块执行模型的训练操作,代码部分与预测操作有较多地方是相似的,具体代码见代码清单3-1(本书中的代码清单都可以在本书的项目代码地址中找到:https://github.com/miraclewkf/MXNet-Deep-Learning-in-Action),接下来详细介绍代码内容。
1)使用mxnet.io.NDArrayIter()接口初始化得到训练和验证数据迭代器,这里为了演示采用随机初始化的数据,实际应用中要读取有效的数据,不论读取的是什么样的数据,最后都需要封装成数据迭代器才能提供给模型训练。
2)用mxnet.module.Module()接口初始化得到一个Module对象,这一步至少要输入一个Symbol对象,另外这一步还可以指定训练环境是CPU还是GPU,这里采用GPU。
3)调用Module对象的bind()方法将准备好的数据和网络结构连接到执行器构成一个完整的计算图。
4)调用Module对象的init_params()方法初始化网络的参数,因为前面定义的网络结构只是一个架子,里面没有参数,因此需要执行参数初始化。
5)调用Module对象的init_optimizer()方法初始化优化器,默认采用随机梯度下降法(stochastic gradient descent,SGD)进行优化。
6)调用mxnet.metric.create()接口创建评价函数,这里采用的是准确率(accuracy)。
7)执行5次循环训练,每次循环都会将所有数据过一遍模型,因此在循环开始处需要执行评价函数的重置操作、数据的初始读取等操作。
8)此处的while循环只有在读取完训练数据之后才会退出,该循环首先会调用Module对象的forward()方法执行模型的前向计算,这一步就是输入数据通过每一个网络层的参数进行计算并得到最后结果。
9)调用Module对象的backward()方法执行模型的反向传播计算,这一步将涉及损失函数的计算和梯度的回传。
10)调用Module对象的update()方法执行参数更新操作,参数更新的依据就是第9步计算得到的梯度,这样就完成了一个批次(batch)数据对网络参数的更新。
11)调用Module对象的update_metric()方法更新评价函数的计算结果。
12)读取下一个批次的数据,这里采用了Python中的try和except语句,表示如果try包含的语句执行出错,则执行except包含的语句,这里用来标识是否读取到了数据集的最后一个批次。
13)调用评价对象的get_name_value()方法并打印此次计算的结果。
14)调用Module对象的get_params()方法读取网络参数,并利用这些参数初始化Module对象了。
15)调用数据对象的reset()方法进行重置,这样在下一次循环中就可以从数据的最初始位置开始读取了。
代码清单3-1 通过Module模块训练模型
import mxnet as mx
import logging
data = mx.sym.Variable('data')
conv = mx.sym.Convolution(data=data, num_filter=128, kernel=(3,3), pad=(1,1),
name='conv1')
bn = mx.sym.BatchNorm(data=conv, name='bn1')
relu = mx.sym.Activation(data=bn, act_type='relu', name='relu1')
pool = mx.sym.Pooling(data=relu, kernel=(2,2), stride=(2,2), pool_type='max',
name='pool1')
fc = mx.sym.FullyConnected(data=pool, num_hidden=2, name='fc1')
sym = mx.sym.SoftmaxOutput(data=fc, name='softmax')
data = mx.nd.random.uniform(0,1,shape=(1000,3,224,224))
label = mx.nd.round(mx.nd.random.uniform(0,1,shape=(1000)))
train_data = mx.io.NDArrayIter(data={'data':data},
label={'softmax_label':label},
batch_size=8,
shuffle=True)
print(train_data.provide_data)
print(train_data.provide_label)
mod = mx.mod.Module(symbol=sym,context=mx.gpu(0))
mod.bind(data_shapes=train_data.provide_data,
label_shapes=train_data.provide_label)
mod.init_params()
mod.init_optimizer()
eval_metric = mx.metric.create('acc')
for epoch in range(5):
end_of_batch = False
eval_metric.reset()
data_iter = iter(train_data)
next_data_batch = next(data_iter)
while not end_of_batch:
data_batch = next_data_batch
mod.forward(data_batch)
mod.backward()
mod.update()
mod.update_metric(eval_metric, labels=data_batch.label)
try:
next_data_batch = next(data_iter)
mod.prepare(next_data_batch)
except StopIteration:
end_of_batch = True
eval_name_vals = eval_metric.get_name_value()
print("Epoch:{} Train_Acc:{:.4f}".format(epoch, eval_name_vals[0][1]))
arg_params, aux_params = mod.get_params()
mod.set_params(arg_params, aux_params)
train_data.reset()
假设你拉取了本书的项目代码,项目代码的根目录用“~/”表示,因为该脚本保存在“~/chapter3-baseKnowledge-of-MXNet/Module_code3-1.py”中,因此可以通过如下命令运行该脚本:
$ cd ~/chapter3-baseKnowledge-of-MXNet
$ python Module_code3-1.py
成功运行时可以得到如下结果:
Epoch:0 Train_Acc:0.5090
Epoch:1 Train_Acc:0.7010
Epoch:2 Train_Acc:0.9620
Epoch:3 Train_Acc:0.9860
Epoch:4 Train_Acc:0.9950
mx.mod是mxnet.module常用的缩写,后续篇章默认采用缩写形式。
代码清单3-1中的代码其实从mod.bind()方法这一行到最后都可以用Module模块中的fit()方法来实现。fit()方法不仅封装了上述的bind操作、参数初始化、优化器初始化、模型的前向计算、反向传播、参数更新和计算评价指标等操作,还提供了保存训练结果等其他操作,因此fit()方法将是今后使用MXNet训练模型时经常调用的方法。下面这段代码就演示了fit()方法的调用,前面两行设置命令行打印训练信息,这三行代码可以直接替换代码清单3-1中从mod.bind()那一行到最后的所有代码。在fit()方法的输入参数中,train_data参数是训练数据,num_epoch参数是训练时整个训练集的迭代次数(也称epoch数量)。需要注意的是,将所有train_data过一遍模型才算完成一个epoch,因此这里设定为将这个训练集数据过5次模型才完成训练。
logger = logging.getLogger()
logger.setLevel(logging.INFO)
mod.fit(train_data=train_data, num_epoch=5)
简化版的代码如代码清单3-2所示。
代码清单3-2 通过Module模块训练模型(简化版)
import mxnet as mx
import logging
data = mx.sym.Variable('data')
conv = mx.sym.Convolution(data=data, num_filter=128, kernel=(3,3), pad=(1,1),
name='conv1')
bn = mx.sym.BatchNorm(data=conv, name='bn1')
relu = mx.sym.Activation(data=bn, act_type='relu', name='relu1')
pool = mx.sym.Pooling(data=relu, kernel=(2,2), stride=(2,2), pool_type='max',
name='pool1')
fc = mx.sym.FullyConnected(data=pool, num_hidden=2, name='fc1')
sym = mx.sym.SoftmaxOutput(data=fc, name='softmax')
data = mx.nd.random.uniform(0,1,shape=(1000,3,224,224))
label = mx.nd.round(mx.nd.random.uniform(0,1,shape=(1000)))
train_data = mx.io.NDArrayIter(data={'data':data},
label={'softmax_label':label},
batch_size=8,
shuffle=True)
print(train_data.provide_data)
print(train_data.provide_label)
mod = mx.mod.Module(symbol=sym,context=mx.gpu(0))
logger = logging.getLogger()
logger.setLevel(logging.INFO)
mod.fit(train_data=train_data, num_epoch=5)
该脚本代码保存在“~/chapter3-baseKnowledge-of-MXNet/Module_code3-2.py”中,下面使用如下命令运行该脚本:
$ cd ~/chapter3-baseKnowledge-of-MXNet
$ python Module_code3-2.py
从下面打印出来的训练结果可以看到,输出结果与代码清单3-1的输出结果基本吻合:
INFO:root:Epoch[0] Train-accuracy=0.515000
INFO:root:Epoch[0] Time cost=4.618
INFO:root:Epoch[1] Train-accuracy=0.700000
INFO:root:Epoch[1] Time cost=4.425
INFO:root:Epoch[2] Train-accuracy=0.969000
INFO:root:Epoch[2] Time cost=4.428
INFO:root:Epoch[3] Train-accuracy=0.988000
INFO:root:Epoch[3] Time cost=4.410
INFO:root:Epoch[4] Train-accuracy=0.999000
INFO:root:Epoch[4] Time cost=4.425
上面的演示代码中只设定了fit()方法的几个输入,其实fit()方法的输入还有很多,实际使用中可根据具体要求设定不同的输入参数,本书后面的章节还会进行详细介绍。
得益于MXNet的静态图设计和对计算过程的优化,你会发现MXNet的训练速度相较于大部分深度学习框架要快,而且显存占用非常少!这使得你能够在单卡或单机多卡上使用更大的batch size训练相同的模型,这对于复杂模型的训练非常有利,有时候甚至还会影响训练结果。
机器学习 深度学习
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。