mxnet gluon 教程速览

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bea_tree/article/details/80152223

mxnet出了gluon好久了,沐神也出了一系列精品课程,这么好的东西就系统的看了一遍教程。做了如下笔记,希望以后回顾的时候能有些用处。
另外,gluon有自己的官方中文社区,而且有大牛坐镇,推荐!

0 什么是Gluon ?

解浚源知乎中所言

Gluon是MXNet的动态图接口;Gluon学习了Keras,Chainer,和Pytorch的优点,并加以改进。接口更简单,且支持动态图(Imperative)编程。相比TF,Caffe2等静态图(Symbolic)框架更加灵活易用。同时Gluon还继承了MXNet速度快,省显存,并行效率高的优点,并支持静、动态图混用,比Pytorch更快。

1 预备知识

1.1 NDArray

NDArray 是 MXNet用于储存和变化数据的主要工具。使用方法与Numpy相似。
这里只写一下数据替换操作

from mxnet import ndarray as nd
x=nd.ones((3,4))
y=nd.ones((3,4))
y=y+x #此时等号左右两边的y不是同一个id
#下面两种方法可避免新内存开销
y[:]=y+x#此时等号左右两边相同
y+=x#此时等号左右两边相同
###
z=nd.zeros_like(x)
z[:]=x+y #此时z的id相同,但是仍有额外临时x+y的开销,下面方法可避免
nd.elemwise_add(x,y,out=z)

1.2 autograd自动求导

mxnet.autograd可以方便对算式记性求导,使用方法:

import mxnet.ndarray as nd
import mxnet.autograd as ag
# f=2*x*x
x=nd.array([[1,2],[3,4]])
x.attach_grad()# 对x求导,先开辟导数所需要的空间
with ag.record():
    y=2*x*x
y.bachward()
print(x.grad)

另外还可以将链式求导的head gradient 显示的作为backward的参数

2 深度学习基础

2.1 线性回归

这节的意义在于手动实现一遍使用SGD求解线性回归的过程。对于新手还是挺重要的。
所谓线性回归就是对于数据x及目标y,求映射参数w和b使得,yhat=xw+b与y的值尽量接近,即sum((yhat-y)^2) 最小,求解伪代码如下:

for batch(x,y) in data_iter():
    with autograd.record():
        yhat=dot(x,w)+b# x,w需要事先attach_grad
        loss=sum((yhat-y)^2)
    loss.backward()
    for p in [w,b]:
        w[:]=w-lr*w.grad#注意替换的用法哦; 这里的grad其实是batch数据的sum,sgd中应该使用平均

下面是gluon的代码

net=gluon.nn.Sequential()
net.add(gluon.nn.Dense(1))#指明输出数量为1
net.initialize()#初始化时默认随机初始化
square_loss=gluon.loss.L2Loss()
trainer=gluon.Trainer(
    net.collect_params,
    'sgd',
    {'learning_rate':0.1})
###############
epochs = 5
batch_size = 10
for e in range(epochs):
    total_loss = 0
    for data, label in data_iter:
        with autograd.record():
            output = net(data)
            loss = square_loss(output, label)
        loss.backward()
        trainer.step(batch_size)
        total_loss += nd.sum(loss).asscalar()
        print("Epoch %d, average loss: %f" % (e, total_loss/num_examples))

对于dense的权重访问:

net[0].weight.data();net[0].bias.data()

2.2 多分类逻辑回归

例子中使用多分类逻辑回归对Fashion MNIST数据集进行分类。
对于分类如果使用线性回归,得到对于输入数据x的线性映射,这时候不能很好的表示类别,为了将映射结果转为类别表示,就用了softmax即yhat=exp(x)/exp(x).sum(axis=1,keepdims)(为了避免overflow,通常会在exp之前减去最大值)
为了表示分类的效果,使用交叉熵损失表示即loss=-log(yhat)yhat为对应的y为1时的值,此时如果yhat为1,则得到的损失为0.
有了yhat和loss,替换2.1中的伪代码即可。
gluon实现:

net=gluon.nn.Sequential()
with net.name_scope():#name scope便于命名
    net.add(gluon.nn.Flatten())
    net.add(gluon.nn.Dense(10))
net.initialize()
softmax_cross_entropy=gluon.loss.SoftmaxCrossEntropyLoss()
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate:0.1})
#后续autograd.record();backward;trainer.step(batch_size);不再赘述

2.3 多层感知机

类似于2.2,此处yhat的得到时多了一各求中间隐含层的过程。gluon中net的多了个一各Dense层

net=gluon.nn.Squential()
with net.name_scope():
    net.add(gluon.nn.Dense(256,activtion='relu'))
    net.add(gluon.nn.Dense(10))
net.initialize()

2.4 欠拟合与过拟合

欠拟合与过拟合是机器学习模型训练过程中必须要知道的概念,欠拟合是指模型还没有充分的从训练数据集中学到东西,过拟合是指对于数据集中细枝末节过于学习,造成了不能get到更有代表性的特征,解决过拟合的方法有很多比如添加正则项、增加训练数据、ensemble、dropout等等。
教程中关于欠拟合和过拟合的例子非常好。

2.5 正则化

教程中使用输入数据维度较大,训练样本比较少的时候,查看了正则化的好处,所谓正则化就是在loss中增加参数(通常只是w)的范数,直观上来说不能使权重过大,这样可以使分类界限更加平滑。

贝叶斯的角度,l2正则是先验为高斯分布下的后验最大估计,l1正则是先验为拉普拉斯分布下的后验最大估计

gluon实现的时候,在trainer中加入weight_decay项

trainer = gluon.Trainer(
net.collect_params(), 'sgd', 
{'learning_rate': learning_rate, 'wd': weight_decay})

2.6 dropout

dropout可以看成每次随机 扔掉某一层的某一些输入数据,使用部分输入用以学习特征,可以看成ensemble的一种变种,注意的是只在训练过程中使用,且输入数据在去掉后要扩大相应的倍数,以使前传与训练时输入分布相似。
gluon中的使用方法如下:

net=nn.Sequential()
with net.name_scope():
    net.add(nn.Flatten())
    net.add(nn.Dense(256,activation='relu'))
    net.add(nn.Dropout(0.2))
    net.add(nn.Dense(256,activation='relu'))
    net.add(nn.Dropout(0.5))
    net.add(nn.Dense(10))
net.initialize()

2.7 正向传播与反向传播

简单来说正向传播计算loss,反向传播利用loss计算各参数的梯度,用梯度来更新参数

3 Gluon使用基础

3.1 模型构造

Gluon中主要使用Block来构造模型,Block是Gluon里的一个类,位于incubator-mxnet/python/mxnet/gluon/block.py,前面使用的Sequential是他的一个子类,位于incubator-mxnet/python/mxnet/gluon/nn/basic_layers.py

3.1.1 使用block将模型定义一个类

使用Block无需定义求导或反传函数,MXNet会使用autograd对forward自动生成相应的backward函数。

class MLP(nn.block):
    def __init__(self,**kwargs):
        super(MLP,self).__init__(**kwargs)
        with self.name_scope():
            self.hidden =nn.Dense(256,active='relu')
            self.output =nn.Dense(10)
    def forward(self,x)
        return self.output(self.hidden(x))

使用的时候只需要实例化之后initialize一下赋予输入值即可进行一次forward:

net=MLP()
net.initialize()

3.1.2 Sequential与Block嵌套使用

class NestMLPnn.Block):
    def __init__(self,**kwargs):
        super(NestMLP,self).__init__(**kwargs)
        self.net = nn.Squential()
        with self.name_scope():
            self.net.add(nn.Dense(64,activation='relu'))
            self.net.add(nn.Dense(32,activation='relu'))
            self.dense=nn.Dense(16,activation='relu')
    def forward(self,x):
        return self.dense(self.net(x))
###
net=nn.Sequential()
net.add(NestMLP())
net.add(nn.Dense(10))
net.initialize()

值得注意的是使用block时,定义的网络需要为block类型,如self.denses = [nn.Dense(256), nn.Dense(128), nn.Dense(64)]赋值为list不是block会出现无法注册的问题

3.2 模型初始化

在Gluon 中,模型参数的类型是Parameter。
collect_params 来访问Block ⾥的所有参数。
使用Siamese或者其他网络时可能需要共享参数,这时候只需要通过Block 的params
来指定模型参数即可如:

net.add(nn.Dense(4, activation='relu', params=net[1].params))
#或者
self.hidden3 = nn.Dense(4, activation='relu',params=self.hidden2.params)

3.3 自定义层

搞深度自定义层还是经常用到的
这里只列出简单的自定义层的方法,与使用Block构建模型相似:

class mydense(nn.Block):
    def __init__(self,uints,in_units,**kwargs):#uints,in_uints分别是输出单元个数及输入单元个数
        super (mydense,self).__init__(**kwargs)
        with self.name_scope():
            self.weight=self.params.get('weight',shape=(in_uints,uints))
            self.bias=self.params.get('bias',shape=(uints,))
    def forward(self,x):
        linear=nd.dot(x,self.weight.data())+self.bias.data()
        return nd.relu(linear)

自己定义延迟初始化层稍微麻烦点,建议看下gluon源代码。需要hybridize,以及intializer延后调用

3.4 模型参数读写

block支持直接使用save_paramsload_params记性模型的写与读:

net1.save_params('./net1.params')
net2.load_params('./net1.params')

3.5 gpu计算

mx.cpu(), mx.gpu()可以用来指定ctx
net.initialize(ctx=mx.gpu())
可以使用copyto 和as_in_context函数在CPU/GPU 之间传输数据,如果源变量和⽬标变量的context ⼀致,as_in_context 使⽬标变量和源变量共享源变量的内存,而copyto 总是为⽬标变量新创建内存。

y = x.copyto(mx.gpu())
z = x.as_in_context(mx.gpu())

打印NDArray 或将NDArray 转换成NumPy 格式时,MXNet 会⾃动将数据复制到主
内存

4. 计算性能

4.1 混合编程

命令式编程比较灵活,符号式编程效率较高且容易部署,Gluon使用混合编程,集两者之长

在混合式编程中,我们可以通过使⽤HybridBlock 或者HybridSequential 类构建模型。默认情况下,它们和Block 或者Sequential类⼀样依据命令式编程的⽅式执⾏。当我们调⽤hybridize 函数后,Gluon 会转换成依据符号式编程的⽅式执⾏。事实上,绝⼤多数模型都可以享受符号式编程的优势。

4.1.1 HybridSequential

使用方法与Sequential类似

net=nn.HybridSequential()
with net.name_scope():
    net.add(
    nn.Dense(256,activation='relu'),
    nn.Dense(256,activation='relu'),
    nn.Dense(2,activation='relu')
    )
net.initialize()
net.hybridize()#通过调⽤hybridize 函数来编译和优化HybridSequential 实例中串联的层的计算。
net(x)
net.export('sy')#export 函数保存符号式程序和模型参数

4.1.2 HybridBlock

class HybridNet(nn.HybridBlock):
    def __init__(self,**kwargs):
        super(HybridNet,self).__init__(**kwargs)
        with self.name_scope():
            self.hidden=nn.Dense(10)
            self.output=nn.Dense(2)
    def hybrid_forward(self,F,x):#多了一个F,命令编程时F为NDArray,符号式编程时为Symbol(mxnet中的符号式程序类)
        x=F.relu(self.hidden(x))
        return self.output(x)
net=HybridNet()
net.initialize()
net.hybridize()#####
net(x)

调用hybridize 函数后运⾏net(x) 的时候,得到符号式程序,之后再运⾏net(x)的时候MXNet 将不再访问Python 代码,而是直接在C++ 后端执⾏符号式程序。

4.2 惰性计算

所谓惰性计算简单点说就是用到啥再算啥

MXNet 包括⽤⼾直接⽤来交互的前端和系统⽤来执⾏计算的后端,⽤⼾可以使⽤不同的前端语⾔编写MXNet 程序,像Python、R、Scala 和C++。⽆论使⽤何种前端编程语⾔,MXNet 程序的执⾏主要都发⽣在C++ 实现的后端。换句话说,⽤⼾写好的前端MXNet 程序会传给后端执⾏计算。后端有⾃⼰的线程来不断收集任务,构造、优化并执⾏计算图。

MXNet 后端将默认使⽤惰性计算来获取最⾼的计算性能。

如果想让后端结果与前端同步可以使用wait_to_readnd.waitall来等待后端计算,同时像print/ asnumpy /asscalar等也可以实现同步。

惰性计算与内存

惰性计算会优化计算效率,但是可能会增加使用内存,所以:

由于深度学习模型通常⽐较⼤,而内存资源通常有限,我们建议读者在训练模型时对每个小批量都使⽤同步函数。类似地,在使⽤模型预测时,为了减小内存开销,我们也建议读者对每个小批量预测时都使⽤同步函数,例如直接打印出当前批量的预测结果。

4.3 并行运算

4.3.1 cpu/gpu

同时又cpu和gpu程序运行时会mxnet会自动优化(包括从CPU到GPU的复制数据),而非两个程序顺序执行。

4.3.2 多GPU并行

Gluon 提供了split_and_load函数。它可以划分⼀个小批量的数据样本并
复制到各个CPU/GPU 上,当我们使⽤多个GPU 来训练模型时,gluon.Trainer 会⾃动做数据并⾏,可以划分一个batch数据样本并复制到各个GPU 上,对各个GPU 上的梯度求和再同步到所有GPU 上。

sce_loss = gluon.loss.SoftmaxCrossEntropyLoss()
ctx=[mx.gpu(i) for i in range(yourdevice)]
net.collect_params().initialize(init=init.Xavier(),ctx=ctx,force_reinit=True)# 多卡同时初始化
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':lr})
for epoch in range(1,epoch_num):
    total_loss=0
    for feature,label in train_data:
    gpu_data=gluon.utils.split_and_load(feature,ctx)
    gpu_label=gluon.utils.split_and_load(label,ctx)
    with autograd.record():
        losses=[sec_loss(net(x),y)for x,y in zip(gpu_data,gpu_labels)]
        total_loss+=sum([loss.sum().asscalar() for loss in losses])
        trainer.step(batch_size)
    nd.waitall()

5. 其他

5.1 图像扩增

mxnet提供了image工具

from mxnet import image
img = image.imdecode(open('../img/cat1.jpg', 'rb').read())
augs = [image.HorizontalFlipAug(.5),\
    image.RandomCropAug([200,200]),\
    image.RandomSizedCropAug((200,200), .1, (.5,2))\#随机裁剪,要求保留⾄少0.1 的区域,随机长宽⽐在.52 之间。
    image.BrightnessJitterAug(.5)\#随机将亮度增加或者减⼩在0-50% 间的⼀个量
    aug = image.HueJitterAug(.5)#随机⾊调变化
    ]
for f in augs:
    img = f(img)

5.2 设置学习率

conv0 = gluon.nn.Conv2D(我的参数)
conv0.bias.lr_mult = 2.
net = gluon.nn.Sequential()
net.add(conv0)
#或者
net[k].bias.lr_mult = 2.

5.3 输入数据

gluon.data.DataLoader自带多进程读取数据

5.4 其他

到这里就基本会使用gluon了。教程之后从AlexNet 、VGG、 GoogLeNet 讲到ResNet、DenseNet。还有计算机视觉中的检测、分割入门,自然语义处理、时间序列、GAN等等方面的入门知识,非常适合刚刚深度学习的同学用来学习,这里就不展开了。
从零开始系列也是非常的利于理解基础知识

猜你喜欢

转载自blog.csdn.net/bea_tree/article/details/80152223
今日推荐