gluon的用法

2018/06/12 深度学习

深度学习基础

自动求梯度

任何参数x要想要得到其梯度,必须首先x.attach_grad(),然后在每次计算之前都要autograd.record(),最后y.backend()来更新x。(y是以x为变量的表达式)

一定要先为待求导的变量分配存储导数的空间x.attach_grad(),再定义求导函数with autograd.record(),不然会报错。

>>> with autograd.record():
...     for i in range(0, 10, 1):
...             x[:] = x * 2 此句错误
...             y = x * 2
...             z = y * x
...             print(x)
...             z.backward(retain_graph= True)
...             print(x.grad)
>>> with autograd.record():
...     for i in range(0, 10, 1):
...             x[:] = x * 2 此句错误
...             y = x * 2
...             z = y * x
...             print(id(x))
...             print(x)
...             z.backward()
...             print(x.grad)
mxnet.base.MXNetError: [14:55:41] src/imperative/imperative.cc:192: Check failed: AGInfo::IsNone(*(outputs[i])) Assigning to NDArrays that are already in a computational graph will cause undefined behavior when evaluating gradients. Please call backward first to clear the graph or do this out side of a record section. Also note that you cannot use inplace operations like +=, *=, relu(x, out=x), etc inside a record section.

>>> with autograd.record():
...     for i in range(0, 10, 1):
...             y = x * 2
...             z = y * x
...             print(x)
...             z.backward()
...             print(x.grad)

>>> x = nd.random_normal(shape=(3))
>>> x.attach_grad()
>>> with autograd.record():
...     for i in range(0, 10, 1):
...             y = x * 2
...             z = y * x
...             print(x)
...             z.backward(retain_graph=True)
...             print(x.grad)

使用autograd来自动求导 讨论区

头梯度是什么?为什么要传头梯度?头梯度怎么定?

乘以头梯度,相当于乘以一个系数。相同位置相乘。很多算法中都要用到头梯度,比如GAN中G的梯度要经过D计算损失,然后通过D网络backward给G,单纯的G是不能产生梯度的。

https://discuss.gluon.ai/t/topic/743/10

param[:] = param - lr * param.grad 目的是直接在param上进行修改,不会新开辟内存。

而把param[:] = param - lr * param.grad换成param = param - lr * param.grad,训练结果就不对了?
因为paramparam = ...这个表达式会重新创建新param,这个是没有attach_grad的。

x.backward() 等价于 x.sum().backward()。
原文代码中:
    with autograd.record():
        loss = square_loss(w,b,label,data)
    loss.backward()
这里的loss是代价矩阵,其中每一行(列,具体看个人设置,这里个人的w设置与原文有点出入)的标量代表某个样本的loss。那么求和之后,就是所有样本的代价。(《统计学习方法》里的代价函数和经验损失函数)

关于mxnet得autograd的疑问。
最近通过教程在学习mxnet,发现mxnet自动微分的函数很强大,也很好用。但是对下面这段段代码有点疑问:
from mxnet import autograd
x = nd.array([[1, 2], [3, 4]])
x.attach_grad()
with autograd.record():
    y = x * 2
    z = y * x
z.backward()
x.grad == 4*x
这里的z.backward就是z对x的求导结果,但是如果我还想知道y对x的求导结果呢?我应该怎么操作。尝试用下面的代码会报错:
from mxnet import autograd
x = nd.array([[1, 2], [3, 4]])
x.attach_grad()
with autograd.record():
    y = x * 2
    z = y * x
z.backward()
y.backward()
print(x.grad)
请问这个为什么会报错,应该如何改正?
backward函数中retain_graph参数默认为False,默认清除了computaion历史。
Cannot differentiate node because it is not in a computational graph. You need to set is_recording to true or use autograd.record() to save computational graphs for backward. If you want to differentiate the same graph twice, you need to pass retain_graph=True to backward.

参数延后初始化

当你定义了网络net,当你初始化网络参数net.initialize(),参数并没有真正初始化。Because initialization was deferred. Actual initialization happens during the first forward pass. Please pass one batch of data through the network before accessing Parameters. You can also avoid deferred initialization by specifying in_units, num_features, etc., for network layers.

正则化

使用Gluon的wd超参数可以使用正则化来应对过拟合问题。我们可以定义多个Trainer实例对不同的模型参数使用不同的迭代方法。

丢弃法

当神经网络中的某一层使用丢弃法时,该层的神经元将有一定概率被丢弃掉。设丢弃概率为p。具体来说,该层任一神经元在应用激活函数后,有p的概率自乘0,有1−p的概率自除以1−p做拉伸。

只需在训练模型时使用丢弃法。

深度学习计算

数据集生成,batch数据读取

from mxnet.gluon import data as gdata

batch_size = 10
dataset = gdata.ArrayDataset(features, labels)
data_iter = gdata.DataLoader(dataset, batch_size, shuffle=True)

for X, y in data_iter:
    print(X, y)
    break

模型构造

from mxnet.gluon import nn

继承gluon.nn里面的Block类来定义我们想要的模型。

  • 重载__init__函数:声明带有模型参数的层。
  • 重载forward函数:定义模型的前向计算,即如何根据输出计算输出。
  • 我们无需在这里定义反向传播函数,系统将通过自动求导,来自动生成backward函数。
class MLP(nn.Block):
	pass

x = nd.random.uniform(shape=(2,20))
net = MLP()
net.initialize()
net(x) 会调用了MLP继承至Block的__call__函数,这个函数将调用MLP定义的forward函数来完成前向计算。

模型参数的访问、初始化和共享

访问模型参数

Gluon里参数类型为Parameter类,其包含参数权重和它对应的梯度,它们可以分别通过data和grad函数来访问。

net是一个网络结构,通过net[0].params来查看第一层网络的参数,是一个ParameterDict类型的数据。通过net[0].weight来查看第一层网络的参数weight,是一个Parameter类型的数据。net[0].weight.data()为参数权重,是NDArray类型的数据。net[0].weight.grad()为参数权重对应的梯度,是NDArray类型的数据。

获取网络net的所有参数,net.collect_params()。

初始化模型参数

from mxnet import init 包含了多种模型初始化方法:

  • init=init.Normal(sigma=0.01):初始化成均值为0,标准差为0.01的正态分布随机数。
  • init=init.Xavier():使用Xavier方法来进行初始化。

针对整个网络结构或者某一层的特定参数初始化方法:

  • net.initialize(init=init.Normal(sigma=0.01), force_reinit=True):针对整个网络结构net初始化。
  • net[0].weight.initialize(init=init.Xavier(), force_reinit=True):针对第一层网络的weight参数初始化。

自定义初始化方法:

  • 实现一个Initializer类的子类使得我们可以跟前面使用init.Normal那样使用它。在这个方法里,我们只需要实现_init_weight这个函数,将其传入的NDArray修改成需要的内容。 class MyInit(init.Initializer)
  • 通过Parameter类的set_data函数来直接改写模型参数。 net[0].weight.set_data(net[0].weight.data()+1)

共享模型参数

shared = nn.Dense(8, activation=’relu’)

nn.Dense(8, activation=’relu’, params=shared.params)

通过指定param参数来共享参数。

模型参数的延后初始化

模型默认会延后初始化。

如果要避免延后初始化,就需要系统在调用initialize函数时能够知道所有参数形状,那么延后初始化就不会发生。

还有一种情况是我们在创建层到时候指定了每个层的输入大小,使得系统不需要额外的信息来推测参数形状。我们可以通过in_units来指定每个全连接层的输入大小,使得初始化能够立即进行。

自定义层

利用nn.Block

不含模型参数的自定义层

继承nn.Block。 class CenteredLayer(nn.Block)

将层的计算放在forward函数里。

含模型参数的自定义层

继承nn.Block。 class MyDense(nn.Block)

__init__函数里面初始化权重,self.weight = self.params.get(‘weight’, shape=(in_units, units))。

读取和存储

读写NDArrays

from mxnet import nd

nd.save()、nd.load()

读写Gluon模型的参数

Block类提供了save_params和load_params函数来读写模型参数。它实际做的事情就是将所有参数保存成一个名称到NDArray的字典到文件。读取的时候会根据参数名称找到对应的NDArray并赋值

GPU计算

MXNet使用context来指定用来存储和计算的设备,例如可以是CPU或者GPU。默认情况下,MXNet会将数据创建在主内存,然后利用CPU来计算。在MXNet中,CPU和GPU可分别由cpu()和gpu()来表示。需要注意的是,mx.cpu()表示所有的物理CPU和内存。这意味着计算上会尽量使用所有的CPU核。但mx.gpu()只代表一块显卡和相应的显卡内存。如果有多块GPU,我们用mx.gpu(i)来表示第 ii 块GPU( ii 从0开始)。

NDArray的GPU计算

存储:

通过NDArray的context属性来查看其所在的设备。

a = nd.array([1, 2, 3], ctx=mx.gpu()) ctx指定存储设备

y = x.copyto(mx.gpu()),z = x.as_in_context(mx.gpu()) 通过copyto和as_in_context函数在设备之间传输数据。

如果源变量和目标变量的context一致,as_in_context使目标变量和源变量共享源变量的内存,而copyto总是为目标变量新创建内存

计算:

MXNet的计算会在数据的context上执行。为了使用GPU计算,我们只需要事先将数据放在GPU上面。而计算结果会自动保存在相同的GPU上。

Gluon的GPU计算

同NDArray类似,Gluon的模型可以在初始化时通过ctx指定设备。 net.initialize(ctx=mx.gpu())

计算时,模型参数和输入必须在同一个设备上?或者输入必须在同一个设备上,不能特征在一个设备上,标号却在另一个设备上?

gluonbook包

import gluonbook as gb

batch数据读取

batch_size = 10
def data_iter(batch_size, num_examples, features, labels):
    indices = list(range(num_examples))
    random.shuffle(indices)
    for i in range(0, num_examples, batch_size):
        j = nd.array(indices[i: min(i + batch_size, num_examples)])
        yield features.take(j), labels.take(j)

plt作图函数

循环神经网络

语言模型

N元语法是基于n−1阶马尔可夫链的概率语言模型。但它有一定的局限性。

参考

model zoo里提供了很多image classification的模型的预训练参数

MXNet GLUON动手学深度学习

GLuon NLP

github gluon-nlp

Search

    Table of Contents