简介:我相信在学习一门新的编程语言或框架时,很多程序员首先会理解语言或框架所涉及的数据结构。毕竟,当你清楚地理解了数据结构,你就可以更优雅地编写代码,MXNet也是如此。
在MXNet框架中,你至少需要知道三驾马车:NDArray、Symbol、Module。这三个将是您在将来使用MXNet框架时经常使用的接口。那么这三者在构建或训练一个深度学习算法的时候起到什么样的作用呢?
这里可以做一个简单的类比。如果把从建筑到训练算法的过程比作从建筑到装修房子的过程,那么NDArray相当于钢筋混凝土之类的零件,Symbol相当于房子每一层的设计,Module相当于房子整体框架的搭建。
在本文中,您将实际感受到命令式编程和符号式编程的区别,因为NDArray接口使用命令式编程,而符号式接口使用符号式编程。
如需转载,请联系大数据(ID: hzdashuju)
01 NDArray
NDArray是MXNet框架中数据流的基本结构,NDArray的公文地址为:
https://mxnet.apache.org/api/python/ndarray/ndarray.html
在本文档中可以找到与阵列相关的所有接口。在了解NDArray之前,希望你知道Python中的NumPy库:
http://www.numpy.org/
因为一方面,在大多数深度学习框架的Python接口中,NumPy库的使用非常频繁;另一方面,NumPy在大多数深度学习框架的基本数据结构的设计中被借鉴。
在NumPy库中,最基本的数据结构之一是数组,它代表多维数组。NDArray的用法与NumPy库中的数组数据结构非常相似,所以可以简单地认为它是可以在GPU上运行的NumPy数组。
接下来我会介绍一下NDArray中的一些常见操作,并提供NDArray和NumPy数组的一个比较,方便读者了解两者之间的关系。
首先导入MXNet和NumPy,然后通过NDArray初始化一个二维矩阵。代码如下:
importmxnet asmx
importnumpy asnp
a = mx.nd.array([[ 1,2],[ 3,4]])
打印(a)
输出结果如下:
[[ 1.2.]
[ 3.4.]]
& ltn数组2x2 @cpu( 0)>;
然后用NumPy数组初始化一个相同的二维矩阵,代码如下:
b = np.array([[ 1,2],[ 3,4]])
打印(b)
输出结果如下:
[[ 12]
[ 34]]
实际使用中,缩写mx代替mxnet,mx.nd代替mxnet.ndarray,np代替numpy,本书后续章节涉及的所有代码默认采用此类缩写。
我们来看看NumPy数组和NDArray常用的几种方法的比较,比如打印NDArray的维度信息:
打印(a.shape)
输出结果如下:
( 2, 2)
要打印数值数组的尺寸信息:
打印(b.shape)
输出结果如下:
( 2, 2)
要打印数组的数字类型:
打印(a.dtype)
输出结果如下:
& ltclass'numpy.float32 ' >;
要打印Numpy数组的数字类型:
打印(b.dtype)
输出结果如下:
int64
当大多数深度学习框架用于训练模型时,默认情况下采用float32数值类型,因此初始化NDArray对象时,默认的数值类型是float32。
如果要初始化指定数值类型的数组,可以通过dtype参数指定,代码如下:
c=mx.nd.array([[ 1,2],[ 3,4]],dt type = NP . int 8)
打印(c.dtype)
输出结果如下:
& ltclass'numpy.int8 ' >
如果要初始化指定数值类型的NumPy数组,可以输入如下代码:
d = np.array([[ 1,2],[ 3,4]],dtype=np.int8)
打印(d.dtype)
输出结果如下:
int8
NumPy的数组结构中一个非常常见的操作是slice,它也可以在NDArray中实现。具体代码如下:
c = mx.nd.array([[ 1,2,3,4],[ 5,6,7,8]])
打印(c[ 0,1: 3])
输出结果如下:
[ 2.3.]
& ltNDArray 2@cpu( 0)>;
在NumPy阵列中,可以按如下方式实现:
d = np.array([[ 1,2,3,4],[ 5,6,7,8]])
打印(d[ 0,1: 3])
输出结果如下:
[ 23]
在复制和修改现有的NumPy数组或NDArray时,为了避免影响原始数组,通过copy()复制数组而不是直接复制数组是非常重要的。让我们以NDArray为例,看看如何使用copy()方法复制数组。首先打印出C的内容:
打印(c)
输出结果如下:
[[ 1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
然后调用c的copy()方法将c的内容复制到f,打印f的内容:
f = c.copy()
打印(f)
输出结果如下:
[[ 1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
修改f中的一个值并打印f的内容:
f[ 0,0] = -1
打印(f)
输出结果如下,可以看到对应位置的值被修改了:
[[ -1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
那么C中对应位置的值修改了吗?此时可以打印c的内容:
打印(c)
输出结果如下,可以看出此时C中对应位置的值没有修改:
[[ 1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
接下来,让我们看看如果直接将C复制到E会发生什么:
e = c
打印(e)
输出结果如下:
[[ 1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
在E中修改一个值,打印E的内容:
e[ 0,0] = -1
打印(e)
输出如下:
[[ -1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
这时,打印c:
打印(c)
输出结果如下,可以看到对应位置的值也发生了变化:
[[ -1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;
其实NumPy数组和NDArray的转换也很方便,NDArray到NumPy数组的转换可以通过调用NDArray对象的asnumpy()方法来实现:
g=e.asnumpy()
打印(g)
输出结果如下:
[[ -1.2.3.4.]
[ 5.6.7.8.]]
通过mxnet . NDArray . array()接口可以实现将NumPy数组转移到ndaarray:
print(mx.nd.array(g))
输出结果如下:
[[ -1.2.3.4.]
[ 5.6.7.8.]]
& ltn数组2x4 @cpu( 0)>;?
前面说过,NDArray和NumPy数组最大的区别就是NDArray可以在GPU上运行。从打印出来的NDArray对象的内容可以看出,最后有一个@cpu,说明NDArray对象是在cpu上初始化的。那么如何在GPU上初始化NDArray对象呢?首先,调用NDArray对象的上下文属性来获取变量所在的环境:
打印(e.context)
输出结果如下:
cpu( 0)
然后,调用NDArray对象的as_in_context()方法指定变量的环境,例如,这里环境指定为第0个GPU:
e = e.as_in_context(mx.gpu( 0))
打印(e.context)
输出结果如下:
gpu( 0)
上下文是深度学习算法的重要内容。目前常用的环境是CPU或者GPU。在深度学习算法中,数据和模型必须在同一个环境中才能正常训练和测试。
在MXNet框架中,NDArray对象的默认初始化环境是CPU。在不同的环境中,变量初始化意味着变量的存储位置不同,存储在不同环境中的变量无法计算。例如,在中央处理器中初始化的阵列对象和在图形处理器中初始化的阵列对象在执行计算时会报告错误:
f = mx.nd.array([[ 2,3,4,5],[ 6,7,8,9]])
打印(e+f)
显示结果如下。从错误信息可以看出,两个对象的初始化环境不一致:
mxnet . base . mxnet error:[11:14:13]src/implemental/。/implemental _ utils . h:56:检查失败:输入[i]->;ctx()。dev_mask() == ctx.dev_mask() ( 1vs .2)操作员广播_添加要求所有输入在相同的上下文中实时进行。但是第一个参数在gpu( 0)上,而第二个参数在cpu( 0)上
接下来F的环境也修改为GPU,然后进行加法计算:
f = f.as_in_context(mx.gpu( 0))
打印(e+f)
输出结果如下:
[[ 1.5.7.9.]
[ 11.13.15.17.]]
& ltNDArray 2x4 @gpu( 0)>
NDArray是MXNet框架中最常用、最基本的数据结构,是一个可以在CPU或GPU上执行命令式操作的多维矩阵。这种命令式操作直观灵活,是MXNet框架的特点之一。因为在使用MXNet框架训练模型时,几乎所有的数据流都是通过NDArray数据结构实现的,所以熟悉这种数据结构非常重要。
02符号
Symbol是MXNet框架中用来搭建网络层的模块。文号的正式文件地址为:
https://mxnet.apache.org/api/python/symbol/symbol.html
在本文档中可以查询与符号相关的所有界面。与NDArray不同,Symbol使用符号编程,符号编程是MXNet框架实现快速训练和节省视频内存的关键模块。
符号编程的含义,简单来说就是符号编程需要定义一个带有符号接口的计算图,既包含定义好的输入输出格式,又将准备好的数据输入到计算图中完成计算。
但是NDArray采用命令式编程,计算过程可以一步一步实现。其实知道了NDArray之后,才可以通过NDArray定义和使用网络,那为什么还要提供Symbol呢?主要是提高效率。定义计算图后,可以优化整个计算图的内存占用,这样可以大大减少训练模型时的内存占用。
在MXNet中,Symbol接口主要用来构建网络结构层,然后用来定义输入数据。接下来我们再举一个例子。首先定义一个网络结构,如下。
用mxnet.symbol.Variable()接口定义输入数据,用该接口定义的输入数据类似于一个占位符。用mxnet.symbol.Convolution()接口定义一个卷积核尺寸为3*3,卷积核数量为128的卷积层,卷积层是深度学习算法提取特征的主要网络层,该层将是你在深度学习算法(尤其是图像领域)中使用最为频繁的网络层。用 mxnet.symbol.BatchNorm()接口定义一个批标准化(batch normalization,常用缩写BN表示)层,该层有助于训练算法收敛。用mxnet.symbol.Activation()接口定义一个ReLU激活层,激活层主要用来增加网络层之间的非线性,激活层包含多种类型,其中以ReLU激活层最为常用。用mxnet.symbol.Pooling()接口定义一个最大池化层(pooling),池化层的主要作用在于通过缩减维度去除特征图噪声和减少后续计算量,池化层包含多种形式,常用形式有均值池化和最大池化。用mxnet.symbol.FullyConnected()接口定义一个全连接层,全连接层是深度学习算法中经常用到的层,一般是位于网络的最后几层。需要注意的是,该接口的num_hidden参数表示分类的类别数。用mxnet.symbol.SoftmaxOutput()接口定义一个损失函数层,该接口定义的损失函数是图像分类算法中常用的交叉熵损失函数(cross entropy loss),该损失函数的输入是通过softmax函数得到的,softmax函数是一个变换函数,表示将一个向量变换成另一个维度相同,但是每个元素范围在[0,1]之间的向量,因此该层用mxnet.symbol.SoftmaxOutput()来命名。这样就得到了一个完整的网络结构了。网络结构定义代码如下:
importmxnet asmx
data = mx.sym.Variable( 'data ')
conv = mx.sym .卷积(data=data,num_filter= 128,kernel=( 3,3),pad=( 1,1),name= 'conv1 ')
bn = MX . sym . batchorm(数据=conv,名称= '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 . fully connected(data = pool,num_hidden= 2,name= 'fc1 ')
sym = MX . sym . SoftMaxOutPut(data = fc,name = ' SoftMaxOutPut ')
Mx.sym是mxnet.symbol常用的缩写,后面的章节默认采用。另外,最好在定义每个网络层的时候指定name参数,这样代码看起来更清晰。
定义好网络结构之后,肯定想看看网络结构包含哪些参数。毕竟训练模型的过程就是更新模型参数的过程。在MXNet中,list_arguments()方法可用于查看符号对象的参数。命令如下:
print(sym.list_arguments())
从下面的输出结果可以看出,第一个和最后一个分别是‘data’和‘soft max _ label’,分别代表输入数据和标签;‘conv 1 _ weight’和‘conv 1 _ bias’是卷积层的参数,具体来说,前者是卷积核的权重参数,后者是bias参数。bn1 _γ’和‘bn1 _β’是Bn层的参数;‘fc1 _ weight’和‘fc1 _ bias’是全连接层的参数。
['数据',' conv1_weight ',' conv1_bias ',' bn1_gamma ',' bn1_beta ',' fc1_weight ',' fc1_bias ',' softmax_label']
除了检查网络的参数层名称,有时还需要检查网络层参数的维度、网络输出维度等信息,这对于代码调试尤其有帮助。
在MXNet中,可以使用以下方法查看符号对象的图层参数尺寸、输出尺寸和辅助图层参数尺寸信息。调用此方法时,需要指定输入数据的维度,以便网络结构根据指定的输入维度计算维度信息,如图层参数和网络输出:
arg_shape,out_shape,aux _ shape = sym . expert _ shape(data =(1,3,10,10))
打印(arg_shape)
打印(out_shape)
打印(辅助形状)
从以下输出结果可以看出,第一行代表网络层参数的维度,对应于前面list_arguments()方法中列出的层参数名称,例如:
输入数据'data'的维度是(1, 3, 10, 10);卷积层的权重参数'conv1_weight'的维度是(128, 3, 3, 3);卷积层的偏置参数'conv1_bias'的维度是(128,),因为每个卷积核对应于一个偏置参数;全连接层的权重参数'fc1_weight'的维度是(2, 3200),这里的3000是通过计算5*5*128得到的,其中5*5表示全连接层的输入特征图的宽和高。第二行代表网络输出的维度,因为网络的最后一层是输出节点为2的全连接层,输入数据的批量维度为1,所以输出维度为[(1,2)]。
第三行是辅助参数的维度,是BN层的参数维度。
[( 1, 3, 10, 10), ( 128, 3, 3, 3), ( 128,), ( 128,), ( 128,), ( 2, 3200), ( 2,), ( 1,)]
[( 1, 2)]
[( 128,), ( 128,)]
如果截取Symbol模块定义的网络结构的一部分非常方便的话,可以通过MXNet中的get _ internals()方法获取Symbol对象的所有图层信息,然后选择要截取的图层,比如截取sym从输入到汇集图层:
sym _ mini = sym . get _ internal()[' pool 1 _ output ']
print(sym_mini.list_arguments())
输出结果如下,可以看出图层参数中没有sym的原始全连接图层和标签图层信息:
['数据',' conv1_weight ',' conv1_bias ',' bn1_gamma ',' bn1_beta']
截取后,可以继续在截取的Symbol对象后添加网络层,例如添加一个输出节点为5的全连接层和一个softmax层:
fc _ new = MX . sym . fully connected(data = sym _ mini,num_hidden= 5,name= 'fc_new ')
sym _ new = MX . sym . SoftMaxOutput(data = fc _ new,name = ' SoftMaxOutput ')
print(sym_new.list_arguments())
输出如下,您可以看到整个连接层已被替换:
['数据',' conv1_weight ',' conv1_bias ',' bn1_gamma ',' bn1_beta ',' fc_new_weight ',' fc_new_bias ',' softmax_label']
除了定义神经网络层,Symbol模块还可以实现NDArray的大部分操作。接下来,以数组的加法和乘法为例,介绍了利用符号模块实现上述运算的方法。两个输入data_a和data _ b通过mxnet.symbol.Variable()接口定义。然后定义data_a和data_b相加再乘以data_c得到结果s的运算,通过打印s的类型可以看到s的类型是Symbol,代码如下:
importmxnet asmx
data_a = mx.sym.Variable ( 'data_a ')
data_b = mx.sym.Variable ( 'data_b ')
data_c = mx.sym.Variable ( 'data_c ')
s = data_c*(data_a+data_b)
打印(类型)
输出结果如下:
& ltclass ' mxnet . symbol . symbol . symbol ' >;
接下来,调用S的bind()方法,将具体的输入和定义的操作绑定到执行器。同时,您需要为bind()方法指定是在CPU上还是GPU上执行计算。执行完绑定操作后,你得到执行器E,最后打印E的类型供查看。代码如下:
e = s.bind(mx.cpu(),{ 'data_a':mx.nd.array([ 1,2,3]),' data_b':mx.nd.array([ 4,5,6]),
data_c':mx.nd.array([ 2,3,4])})
打印(e型)
输出结果如下:
& ltclass'mxnet.executor.Executor ' >;
这个执行器是一个完整的计算图,所以可以调用执行器的forward()方法进行计算,得到结果:
output=e.forward()
打印(输出[ 0])
输出结果如下:
[ 10.21.36.]
& ltNDArray 3@cpu( 0)>;
相比之下,通过NDArray模块实现这些操作要简单直观得多。代码如下:
importmxnet asmx
data_a = mx.nd.array([ 1,2,3])
data_b = mx.nd.array([ 4,5,6])
data_c = mx.nd.array([ 2,3,4])
结果= data_c*(data_a+data_b)
打印(结果)
输出结果如下:
[ 10.21.36.]
& ltNDArray 3@cpu( 0)>;
虽然使用符号接口的实现看起来很复杂,但在定义计算图表后,许多视频内存可以重用或共享。比如在Symbol模块的实现版本中,底层计算的data_a+data_b的结果会存储在data_a或data_b所在的空中,因为在这个计算图中,data_a和data_b是在进行加法计算。
前面描述了由符号模块中的变量接口定义的操作和数组模块中的相应实现之间的相似性。另外,Symbol模块中网络层的操作基本上和NDArray模块中有对应的操作,对静态图的调试很有帮助。
如前所述,Symbol模块采用符号编程(或静态图),即需要先定义一个计算图,然后执行计算。这种方法虽然效率高,但实际上对代码调试并不友好,因为你很难获得中间变量的值。
现在,因为带有命令式编程的NDArray模块基本上包含了Symbol模块中同名的操作,所以可以在一定程度上帮助调试代码。然后以卷积层为例,看看如何用NDArray模块实现卷积层运算。首先用mxnet . ndaarray . arange()接口初始化输入数据。这里定义了一个四维数据,之所以定义为四维,是因为模型中的数据流基本上都是四维的。具体代码如下:
data = mx.nd.arange( 0,28)。重塑((1,1,4,7))
打印(数据)
输出结果如下:
[[[[ 0.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.]]]]
& ltNDArray 1x1x4x7 @cpu( 0)>
然后通过mxnet . ndaarray .卷积()接口定义卷积层操作。除了与mxnet.symbol .卷积()接口相同的数据、num_filter、内核和名称外,该接口的输入需要直接指定权重和偏差。
权重和偏差是卷积层的参数值。为了简单起见,这里将权重初始化为所有值都为1的4维变量,将偏差初始化为所有值都为0的1维变量,这样就可以得到最终的卷积结果。具体代码如下:
conv1 = mx.nd .卷积(data=data,weight = MX . nd . one((10,1,3,3)),
bias=mx.nd.zeros(( 10)),num_filter= 10,kernel=( 3,3),
名称= 'conv1 ')
打印(conv1)
输出结果如下:
[[[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]
[[ 72.81.90.99.108.]
[ 135.144.153.162.171.]]]]
& ltNDArray 1x10x2x5 @cpu( 0)>
总的来说,Symbol和NDArray有很多相似之处,两者在MXNet中都发挥着重要的作用。NDArray,采用命令式编程,特点是直观,常用来实现底层计算;符号,使用符号编程,特点是效率高,主要用于定义计算图。
03模块
在MXNet框架中,Module是一个高级封装模块,可以用来进行Symbol Module定义的网络模型的训练,与Module相关的接口介绍可以参考Module的公文地址:
https://mxnet.apache.org/api/python/module/module.html
Module接口为模型训练提供了很多非常方便的方法,只需要将准备好的数据和超级参数传递给相应的方法就可以开始训练。
早上,我们用符号接口定义了一个网络结构符号。接下来,我们将介绍基于这种网络结构的模块模块。首先,我们将看看如何通过模块模块来执行模型的预测操作。
通过mxnet.module.Module()接口初始化一个Module对象。初始化时需要传入定义的网络结构sym,指定运行环境,这里设置为GPU环境。
然后执行Module对象的绑定操作,类似于Symbol模块中的绑定操作。这种绑定操作的目的是将网络结构添加到执行器中,使定义的静态图能够真正运行。因为这个过程涉及到视频内存的分配,所以在执行绑定操作之前,需要提供输入数据和标签的维度信息。读者可以通过命令行“$ watch nvidia-smi”检查执行绑定前后视频内存的变化。
绑定操作中还有一个重要的参数,就是for_training。该参数默认为真,表示接下来进行训练过程。因为我们只需要进行网络的正向计算,所以我们将这个参数设置为False。
最后,调用Module对象的init_params()方法来初始化网络结构的参数。初始化方法是可选的,这里采用默认方法。此时,一个可用的网络结构执行器被初始化。初始化网络结构执行器的代码如下:
mod = MX . mod . module(sym = 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.500800670.4991993]
[ 0.501486120.49851385]
[ 0.501038370.4989616]
[ 0.501711310.49828872]
[ 0.502543870.4974561]
[ 0.501042540.49895743]
[ 0.502231480.49776852]
[ 0.497809590.50219035]]
& ltNDArray 8x2 @gpu( 0)>
接下来,我们介绍如何通过模块模块进行模型的训练操作。代码部分和预测操作有很多相似之处。具体代码如下面的代码清单3-1所示。接下来,我们详细介绍代码内容。
本文中的代码列表可以在本书的项目代码地址中找到:
https://github.com/miraclewkf/MXNet-Deep-Learning-in-Action
使用mxnet.io.NDArrayIter()接口初始化得到训练和验证数据迭代器,这里为了演示采用随机初始化的数据,实际应用中要读取有效的数据,不论读取的是什么样的数据,最后都需要封装成数据迭代器才能提供给模型训练。用mxnet.module.Module()接口初始化得到一个Module对象,这一步至少要输入一个Symbol对象,另外这一步还可以指定训练环境是CPU还是GPU,这里采用GPU。调用Module对象的bind()方法将准备好的数据和网络结构连接到执行器构成一个完整的计算图。调用Module对象的init_params()方法初始化网络的参数,因为前面定义的网络结构只是一个架子,里面没有参数,因此需要执行参数初始化。调用Module对象的init_optimizer()方法初始化优化器,默认采用随机梯度下降法(stochastic gradient descent,SGD)进行优化。调用mxnet.metric.create()接口创建评价函数,这里采用的是准确率(accuracy)。执行5次循环训练,每次循环都会将所有数据过一遍模型,因此在循环开始处需要执行评价函数的重置操作、数据的初始读取等操作。此处的while循环只有在读取完训练数据之后才会退出,该循环首先会调用Module对象的forward()方法执行模型的前向计算,这一步就是输入数据通过每一个网络层的参数进行计算并得到最后结果。调用Module对象的backward()方法执行模型的反向传播计算,这一步将涉及损失函数的计算和梯度的回传。调用Module对象的update()方法执行参数更新操作,参数更新的依据就是第9步计算得到的梯度,这样就完成了一个批次(batch)数据对网络参数的更新。调用Module对象的update_metric()方法更新评价函数的计算结果。读取下一个批次的数据,这里采用了Python中的try和except语句,表示如果try包含的语句执行出错,则执行except包含的语句,这里用来标识是否读取到了数据集的最后一个批次。调用评价对象的get_name_value()方法并打印此次计算的结果。调用Module对象的get_params()方法读取网络参数,并利用这些参数初始化Module对象了。调用数据对象的reset()方法进行重置,这样在下一次循环中就可以从数据的最初始位置开始读取了。代码清单3-1 通过Module模块训练模型importmxnet asmx
导入日志
data = mx.sym.Variable( 'data ')
conv = mx.sym .卷积(data=data,num_filter= 128,kernel=( 3,3),pad=( 1,1),
名称= 'conv1 ')
bn = MX . sym . batchorm(数据=conv,名称= '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 ',
名称= 'pool1 ')
fc = MX . sym . fully connected(data = pool,num_hidden= 2,name= 'fc1 ')
sym = MX . sym . SoftMaxOutPut(data = fc,name = ' SoftMaxOutPut ')
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},
label={ 'softmax_label':label},
批处理大小= 8,
洗牌=真)
打印(train_data.provide_data)
打印(train_data.provide_label)
mod = MX . mod . module(sym = 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 ')
前戏范围(5):
批次结束=假
eval_metric.reset()
data_iter = iter(train_data)
next_data_batch = next(data_iter)
whilenotend_of_batch:
data_batch = next_data_batch
mod.forward(data_batch)
mod.backward()
mod.update()
mod.update_metric(eval_metric,labels=data_batch.label)
尝试:
next_data_batch = next(data_iter)
mod.prepare(next_data_batch)
例外停止迭代:
批处理结束=真
eval _ name _ vals = eval _ metric . get _ name _ value()
print(" Epoch:{ } Train _ Acc:{:. 4f } "。格式(epoch,eval_name_vals[ 0][ 1])
arg_params,aux_params = mod.get_params()
mod.set_params(arg_params,aux_params)
train_data.reset()
事实上,清单3-1中的代码可以通过mod.bind()方法中的fit()方法从头到尾实现。fit()方法不仅封装了上述的绑定操作、参数初始化、优化器初始化、模型正向计算、反向传播、参数更新、评价指标计算等操作,还提供了保存训练结果等其他操作,因此未来使用MXNet训练模型时会频繁调用fit()方法。
下面的代码演示了fit()方法的调用。前两行设置命令行打印训练信息。这三行代码可以直接替换代码清单3-1中从mod.bind()行到末尾的所有代码。
在fit()方法的输入参数中,train_data参数是训练数据,num_epoch参数是整个训练集在训练过程中的迭代次数(也称为epoch数)。需要注意的是,只有当所有的train_data都通过模型时,才能完成一个历元,所以这里设置训练集数据在训练完成之前通过模型五次。
logger = logging.getLogger()
logger.setLevel(日志记录。INFO)
mod.fit(train_data=train_data,num_epoch= 5)
清单3-2显示了简化的代码。
代码清单3-2 通过Module模块训练模型(简化版)importmxnet asmx
导入日志
data = mx.sym.Variable( 'data ')
conv = mx.sym .卷积(data=data,num_filter= 128,kernel=( 3,3),pad=( 1,1),
名称= 'conv1 ')
bn = MX . sym . batchorm(数据=conv,名称= '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 ',
名称= 'pool1 ')
fc = MX . sym . fully connected(data = pool,num_hidden= 2,name= 'fc1 ')
sym = MX . sym . SoftMaxOutPut(data = fc,name = ' SoftMaxOutPut ')
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},
label={ 'softmax_label':label},
批处理大小= 8,
洗牌=真)
打印(train_data.provide_data)
打印(train_data.provide_label)
mod = MX . mod . module(sym = sym,context=mx.gpu( 0))
logger = logging.getLogger()
logger.setLevel(日志记录。INFO)
mod.fit(train_data=train_data,num_epoch= 5)
从下面打印的训练结果可以看出,输出结果与代码清单3-1的输出结果基本一致:
信息:根:历元[ 0]训练精度= 0.515000
信息:根:纪元[ 0]时间成本= 4.618
信息:根:纪元[ 1]训练精度= 0.700000
信息:根:纪元[ 1]时间成本= 4.425
信息:根:纪元[ 2]训练精度= 0.969000
信息:根:纪元[ 2]时间成本= 4.428
信息:根:纪元[ 3]训练精度= 0.988000
信息:根:纪元[ 3]时间成本= 4.410
信息:根:纪元[ 4]训练精度= 0.999000
信息:根:纪元[ 4]时间成本= 4.425
在上面的演示代码中,只设置了fit()方法的几个输入。其实fit()方法的输入有很多。在实际使用中,可以根据具体要求设置不同的输入参数,这将在本书后面的章节中详细介绍。
得益于MXNet的静态图形设计和计算过程的优化,你会发现MXNet的训练速度比大多数深度学习框架都要快,视频内存非常小!这样可以让你用更大的批量在一张卡片或者多张卡片上训练同一个模型,对于复杂模型的训练非常有利,有时甚至会影响训练结果。
04摘要
本文主要介绍了MXNet框架中最常用的三个模块:NDArray、Symbol和module,并比较了它们之间的关系,通过简单的代码就可以大致了解这三个模块的使用。
NDArray是MXNet框架中最基本的数据结构,借鉴了NumPy中数组的思想,可以在GPU上运行。同时,采用命令式编程的NDArray在代码调试方面非常灵活。NDArray提供了类似于NumPy数组的方法和属性,所以熟悉NumPy数组的用户应该能够快速操作NDArray,并且它们之间的转换非常方便。
符号是MXNet框架中定义网络结构层的接口。符号编程通过构造静态计算图,可以大大提高模型训练的效率。符号提供了多种查看符号对象信息的方法,包括参数图层、参数标注等。同时也方便用户在设计网络结构的过程中查漏补缺。
此外,Symbol中的大多数网络层接口在NDArray中都有相应的实现,因此可以通过NDArray中具有相应名称的网络层查看具体的计算过程。
该模块是一个高级接口,它封装了在MXNet框架中训练模型所需的大多数操作。用户可以执行绑定操作、参数初始化、优化器初始化、模型的正向计算、损失函数的反向传播、网络参数的更新、评估指标的计算等。同时,该模块还将常见的训练操作封装在fit()方法中,通过该方法用户可以更方便地训练模型,可以说是灵活简单。
关于作者:魏开丰,高级AI算法工程师、计算机视觉工程师,在MXNet、Pytorch、深度学习算法等方面有深入的研究和丰富的实践经验。,从事计算机视觉算法。他的主要研究兴趣包括目标检测、图像分类、图像对抗算法、模型加速和压缩。
本文摘自经出版社授权的《MXNet深度学习:计算机视觉算法的实现》。
“MXNet深度学习实践”拓展阅读
转载请联系微信:DoctorData
推荐语言:网易高级计算机视觉算法工程师编写,他从算法实现和框架原理两个维度详细讲解了计算机视觉算法的实现以及MXNet框架的使用和原理。
▼
问:你在用什么深度学习框架?
转载/提交请联系:baiyu@hzbook.com
1.《mxnet 深度学习高能干货:手把手教你搭建MXNet框架》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《mxnet 深度学习高能干货:手把手教你搭建MXNet框架》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/fangchan/1237713.html