0%

MXNET机器学习初见

1、基础知识

NDArray、NumPy的广播机制:数组维度不同,后缘维度的轴长(从末尾算起的维度)相同;(4,3)+(3,);(3,4,2)+(4,2) 2、数组维度相同,其中有个轴为1;(4,3)+(4,1):在1轴上广播扩展。

NDArray,NumPy的相互变换:

1
2
3
P = np.ones((2,3))
D = nd.array(P)
D.asnumpy()

自动求梯度(gradient)MXNET中使用autograd模块自动求梯度:

1
2
3
4
5
x = nd.arange(4).reshape((4,1))
x.attach_grad() #申请内存
with autograd.record():
y = 2 * nd.dot(x.T,x) #若y为标量,贼会默认对y元素求和后,求关于X的梯度
y.backward()

uniform:均匀分布采样;normal:正态分布采样;poisson:泊松分布采样。

2、线性回归

损失函数:平方函数,平方损失;在模型训练中,希望找到一组模型参数为w1,w2,b使得训练样本平均损失最小。

解析解:误差最小化问题的解刚好可用数学公式表达出来;大多数为数值解,只能利用优化算法有限次迭代模型参数,从而尽可能降低损失函数的值。

全连接层:又名稠密层,输出层中的神经元与输入层中的各个输入完全连接;

矢量计算比标量逐个相加更加省时间,故往往利用矢量矩阵运算来实现深度学习;

优化算法:小批量随机梯度下降:批量大小batch size,学习率 lr 均为超参数,为人为设定并非模型学习出来的,

调参:通过反复试错来寻找合适的超参数,

1
2
3
def sgd(params,lr,batch_size): #sgd函数实现小批量随机梯度下降算法
for param in params:
param[:] = param - lr * param.grad / batch_size

在一个迭代周期epoch中,将完整遍历一遍data_iter函数,并对训练数据集中所有样本都使用一次。

Gluon简洁实现:

1、提供data包来读取数据:

2、提供大量预定义层,nn模块:neural networks:

1
2
3
from mxnet.gluon import nn
net = nn.Sequential() #Sequential实例是串联各层的容器,依次添加层,每一层一次计算并作为下一层输入
net.add(nn.Dense(1)) #Dense全连接层,GLUON无须指定各层形状,模型会自动推断

3、利用init模块来实现模型参数初始化的各种方法:

1
2
from mxnet.gluon import init
net.initialize(init.Normal(sigma=0.01)) #均值为0,标准差0.01的正态分布

4、定义损失函数:利用loss模块:

1
2
from mxnet.gluon import loss as gloss
loss = gloss.L2Loss() #平方损失又是L2范数损失

5、定义优化算法:创建Trainer实例,以sgd作为优化算法,用来迭代net实例所有通过add函数嵌套的层包含的全部参数,可通过collect_params函数获取。

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

6、训练模型:调用Trainer实例的step函数来迭代模型参数,按sgd的定义,在step中指明批量大小,从而对样本梯度求平均。

1
2
3
4
5
6
7
8
9
num_epochs = 3
for epoch in range(1,num_epochs + 1):
for X,y in data_iter:
with autograd.record():
l = loss(net(X),y)
l.backward()
trainer.step(batch_size)
l = loss(net(features), labels)
print('epoach %d,loss: %f'%(epoch,l.mean().asnumpy()))

3、softmax回归

模型输出为图像类别这种离散值时,使用softmax回归,其输出单元从一个变成了多个,且引入了softmax运算使输出更加适合离散值的预测和训练。

sofemax回归模型:将输出特征与权重做线性叠加,输出值个数等于标签里的类别数。例:有4种特征(4个像素的图片)和3种输出动物类别,则权重包含12个标量(带下标w)、偏差包含3个标量(带下标b)。每个On计算都依赖所有输入,故为全连接层。

softmax计算:直接用最高On作为预测输出,有2个问题。1、输出值范围不定,难以直观判断;2、误差难以衡量。softmax运算符可以解决,即归一化,但softmax运算不改变预测类别输出。
$$
y1 = exp(O1)/exp(O1)+exp(O2)+exp(O3)
$$
交叉熵函数:使用更适合衡量分布差异的测量函数,只关心对正确类别的预测概率,
$$
H(yi,yi) = -Σ(yilogyi)
$$
交叉熵损失函数,最小化其等价于最大化训练数据集所有标签类别的联合预测概率。
$$
l(o) =1/n * Σ(yilogy*i)
$$
图像分类数据集:Fashion-MNIST

Gluon的DataLoader中可用多进程来加速数据读取;通过ToTensor实例将图像数据从unit8格式变换成32位浮点数格式,并除以255使得所有像素均在0至1之间。

Gluon简洁实现

1、导入模块并获取函数

1
2
3
4
5
6
%matplotlib inline
import d2zlzh as d2l
from mxnet import gluon, init
from mxnet.gluon import loss as gloss, nn
batch_size = 256 #批量大小设置
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size)

2、定义和初始化模型

添加输出为10的全连接层,并用均值为0、标准差为0.01的正态分布随机初始化模型的权重参数。

1
2
3
net = nn.Sequential()
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))

3、同时定义softmax和交叉熵损失函数,使数值稳定性更好,使用Gluon提供的函数。

定义优化算法:使用学习率为0.1的小批量随机梯度下降算法。

1
2
loss = gloss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(),'sgd',{'learining_rate':0.1})

4、使用上一节定义的训练函数来训练模型:

1
2
num_epochs = 5
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,None,None,trainer)

4、多层感知机

深度学习主要关注多层模型,以多层感知机NLP(multilayer perceptron)为例。在单层网络的基础上引入了隐藏层hidden layer,但多个仿射线性变换叠加仍然是线性仿射,需引入非线性函数,该函数被称为激活函数

RELU函数:RELU(x) = max(x,0)

sigmoid函数:sigmoid(x) = 1/[1+exp(-x)]

sigmoid函数的导数:sigmoid(x)(1-sigmoid(x))

tanh(双曲正切)函数:[1- exp(-2x)]/[1+exp(-2x)]

tanh函数的导数:1 - [tanh(x)]^2

Gluon的简洁实现

1、导入包与模块,并定义模型,,多加一个全连接作为隐藏层,单元数为256,用RELU作为激活函数。

1
2
3
4
5
6
import d2zlzh as d2l
from mxnet import gluon, init
from mxnet.gluon import loss as gloss, nn
net = nn.Sequential()
net.add(nn.Dense(256,activation = 'relu'),nn.Dense(10))
net.initialize(init.Normal(sigma = 0.01))

2、使用与softmax回归几乎相同的步骤来读取数据并训练模型,学习率为0.5

1
2
3
4
5
6
batch_size = 256 #批量大小设置
train_iter,test_iter = d2l.load_data_fashion_mnist(batch_size)
oss = gloss.SoftmaxCrossEntropyLoss()
trainer = gluon.Trainer(net.collect_params(),'sgd',{'learining_rate':0.5})
num_epochs = 5 #迭代周期num_epochs指的是要循环学习几次
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,None,None,trainer)

5、模型选择与拟合问题

训练误差:模型在训练集上表现出来的误差;

泛化误差:任意一个测试数据样本上表现出的误差的期望;

使用验证数据集来进行模型选择:预留一部分在训练、测试数据集之外的数据来进行模型选择。K折交叉验证:将原始数据分成K个不重合的子数据集,做K次模型训练和验证,每一次用一个子数据集来验证模型,其他用于训练模型。最后对这K次结果分别求平均。

欠拟合:模型无法得到较低的训练误差。

过拟合:模型训练误差远小于其在测试集上误差;模型越复杂、训练集越小越容易过拟合。

6、权重衰减、丢弃法来处理过拟合

权重衰减:等价于L2范数正则化,通过为模型损失函数添加惩罚项,使学到的模型参数值较小。

L2惩罚项指的是:模型权重参数的每一个元素的平方和与一个正的常数的乘积。

1
2
def l2_penalty(w):
return (w**2).sum() / 2

权重衰减Gluon简洁实现:

构造Trainer实例时通过wd参数来指定权重衰减超参数,默认下会对权重、偏差同时衰减。

1
2
3
4
#对权重参数衰减,权重名称一般以weight结尾
trainer_w = gluon.Trainer(net.collect_params('.*weight'),'sgd',{'learning_rate':lr,'wd':wd})
#不对偏差参数衰减,偏差名称一般以bias结尾
trainer_b = gluon.Trainer(net.collect_params('.*bias'),'sgd',{'learning_rate':lr})

丢弃法:隐藏单元有一定的概率P被丢弃掉,丢弃概率是丢弃法的超参数。具体而言,随机变量$为0和1的概率分别为P和1-P。

定义dropout函数,以drop_prob的概率丢弃NDArray输入X中的元素。

丢弃法Gluon简洁实现:

1
2
3
4
net = nn.Sequential()
net.add(nn.Dense(256,activation = 'relu'),nn.Dropout(drop_prob1), #在第一个全连接层后添加丢弃层
nn.Dense(256,activation = 'relu'),nn.Dropout(drop_prob2),nn.Dense(10))
net.initialize(init.Normal(sigma = 0.01))

7、反向传播

反向传播:指计算神经网络梯度的方法,依据链式法则,其梯度计算可能依据各变量的当前值,而这些变量的当前值是通过正向传播计算得到的。

正向传播的计算可能依赖于模型参数的当前值,而参数是在反向传播的梯度计算后通过优化算法迭代的。

模型参数初始化完成后,交替地进行正向传播和反向传播,并根据反向传播计算的梯度迭代模型参数。

梯度衰减、爆炸:由于层数过大时,输出呈幂次爆炸增长,故梯度爆炸或梯度消失。

8、深度学习计算原理细节

1、基于Block类的模型构建

Block类是nn模块里提供的一个模型构造类,继承Block类来构造多层感知机,重载init函数与forward函数,分别用于创建模型参数与定义前向计算。

1
2
3
4
5
6
7
8
9
class MLP(nn.Block): #声明带有模型参数的层,声明2个全连接层
def __init__(self, **kwargs):
#调用父类构造函数来进行必要初始化。
super(MLP,self).__init__(**kwargs)
self.hidden = nn.Dense(256, activation='relu') #隐藏层
self.output = nn.Dense(10) #输出层

def forward(self,x): #定义模型的前向计算,即如何根据输入x计算返回所需的模型输出
return self.output(self.hidden(x))

无需定义反向传播,系统将自动求梯度而生成反向传播所需的backward函数

实例化MLP类得到net,并传入输入数据X并做一次前向计算。

1
2
3
4
X = nd.random.uniform(shape=(2,20))
net = MLP()
net.initialize()
net(X)

2、构建一个继承于Block类的继承类

提供add函数来逐一添加串联的Block子类实例,而模型的前向计算就是将这些实例按顺序逐一计算。

1
2
3
4
5
6
7
8
9
10
11
class MySequential(nn.Block):
def__init__(self,**kwargs):
super(MySequential,self).__init__(**kwargs)
def add(self,block):
#block为Block实例,当MySequential实例调用initialize函数时,系统会自动对其所有成员初始化
self._children[block.name] = block
def forward(self,x):
#OrderedDict保证按添加顺序遍历成员
for block in self._children.values():
x = block(x)
return x

3、自定义初始化模型参数

对于Sequential类构造的神经网络,可通过方括号[]来访问网络的任一层。同时Sequential实例中含模型参数的层,可通过Block类的params属性来访问该层包含的参数。

共享模型参数:在利用Block类中的forward函数里多次调用一个层来计算。或者,在构造层时指定特定的参数,若不同层使用同一份参数,则它们会在前向、反向时均共享相同的参数。

延后初始化:只有将形状是(,)的输入X传进网络做前向计算net(X)时,系统才能推断出该层权重参数形状为(,),此时才能真正地开始初始化参数。

避免延后初始化:1、要对已初始化的模型重新初始化时,由于参数形状不会变化,能够立即重新初始化;2、创建层的时候指定了它的输入个数,故系统不需要额外信息来推测参数形状。

4、自定义层

5、读取与存储

将内存中训练好的模型参数存储在硬盘上,供后续读取使用。

-------------本文结束感谢您的阅读-------------
坚持原创技术分享,您的支持将鼓励我继续创作!