0%

动手学深度学习总结

本章节为<<动手学深度学习>>的知识点整理。

深度学习基础

线性回归

image-20220404205915668

:线性回归为一个单层的神经网络

线性回归的基本要素

模型

设房屋面积为x1,房龄为x2,售出价格为y,我们需要建立基于输入x1和x2来计算输出y的表达式,即模型,线性回归假设输出与各个输入之间是线性关系,则我们有公式

其中w1,w2是权重,b是偏差。模型输出 是线性回归对于真实价格y的预测与估计。

模型训练

我们需要寻找特定的模型的参数值,使得模型在数据上的误差尽可能的小。

训练数据

我们收集一系列的数据,在这个数据上面寻找模型参数来使得模型的预测价格与真实价格的误差最小。这个数据集被称之为训练集。

假设我们采集的样本数为n,索引为i的样本的特征为 ,标签为 ,对于索引为i的房屋,线性回归模型的房屋价格预测表达式为

损失函数

在模型训练中,我们需要衡量价格预测与真实值之间的误差,通常会选取一个非负作为误差,且数值越小表示误差越小,一个常用的式子为

其中常数1/2使对平方项求导后的常数系数为1,这样在形式上稍微简单,其中这个式子的误差越小表示预测价格与真实价格越相近。

通常我们用训练数据集中所有样本误差的平均来衡量模型预测的质量,即

在模型的训练中,我们希望找出一组模型参数,记为$ w^_1w^_2 b^*$,来使得训练样本平均损失最小:

优化算法

我们通过小批量随机梯度下降法,来进行求出最优的$ w^_1w^_2 b^*$,使得目标函数最小,小批量随机梯度下降法具有特征在每次迭代中使用b个样本。Mini-Batch梯度下降算法与批量梯度下降算法类似,只不过它会选取B个样本来进行迭代,介于批量梯度下降算法与随机梯度下降算法之间,对于Mini-Batch而言B在

批量梯度下降算法:在每次迭代中使用所有样本

随机梯度下降算法:在每次迭代中使用一个样本

Mini-Batch梯度下降算法:在每次迭代中使用b个样本

小批量随机梯度下降

图中是B为10的一个例子,Mini-batch在数据存取和求导的过程中使用向量化,进行并行计算,这样可以加快计算速度,而Mini-batch的一个缺点是需要确定参数B的大小。

对于上例中我们有算法

1
2
3
Repeat {

for i=1,i=b+1,i=2b+1,...n{
1
2
}
}

线性回归的表示方法

我们对训练数据集里的3个房屋样本(索引分别为1、2和3)逐一预测价格,将得到

将上面3个等式转化成矢量计算,则有

当数据样本数为n,特征数为d时,线性回归的矢量计算表达式为

其中模型输出$y^\in R^{n1}X \in R^{nd}W \in R^{d1}b \in Ry\in R^{n*1}\theta=[w_1,w_2,b]^T$,我 们重写损失函数为

小批量随机梯度下降可以改写为

线性回归从零开始实现

设我们训练数据集样本数为1000,输入个数(特征数)为2。给定随机生成的批量样本特征,我们使用线性回归模型真实权重和偏差以及一个随机噪声项来生成标签

其中噪声项服从均值为0、标准差为0.01的正态分布。噪声代表了数据集中无意义的干扰。

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
num_inputs=2 #特征项数
num_examples=1000 #样本数
true_w=[2,-3.4]
true_b=4.2
features=nd.random.normal(scale=1,shape(num_examples,num_inputs)) #生成1000*2个为1的标准差
labels=true_w[0]*features[:,0]+true_w[1]*features[;,1]+true_b
lables+=nd.random.normal(scale=0.01,shape=labels.shape) #

# 该函数返回batch_size(批量大小)个随机样本的特征和标签

def data_iter(batch_size,features,labels):
num_examples=len(features)
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) #take函数根据索引返回对应元素

#初始化模型参数
#我们将权重初始化成均值为0、标准差为0.01的正态随机数,偏差则初始化成0
w=nd.random.normal(scale=0.01,shape=(num_inputs,1))
b=nd.zeros(shape=(1,))

#申请梯度
w.attach_grad()
b.attach_grad()

#定义函数 使用dot函数做矩阵乘法
def linreg(X,w,b):
return nd.dot(X,w)+b
#定义损失函数
#在实现中,我们需要把真实值y变成预测值y_hat的形状
def squared_loss(y_hat,y):
return (y_hat-y.reshape(y.hat.shape))**2/2

#sgd函数实现了小批量随机梯度下降算法,通过不断迭代模型参数来优化损失函数。使用自动求梯度模块计算得来的#梯度是一个批量样本的梯度和,我们将它除以批量大小来得到平均值
# params参数,lr学习率,batch_size批量大小
def sgd(params,lr,batch_size):
for param in params:
param[:]=param-lr*param.grad/batch_size

lr=0.03 #学习率
num_epochs=3 #迭代周期
net=linreg
loss=squared_loss

for epoch in range(num_epochs):#训练模型一共需要num_epochs个迭代周期
#在每一个迭代周期中,会使用训练数据集中所有样本一次(假设样本数能够被批量大小整除)。
#x和y分别是小批量样本的特征和标签
for X,y in data_iter(batch_size,features,labels):
with autograd.record():
l=loss(net(X,w,b),y) #l是有关小批量x和y的损失
l.backward() #小批量的损失对模型参数求梯度
sgd([w,b],lr,batch_size) #使用小批量随机梯度下降迭代模型参数
train_l=loss(net(features,w,b),labels)
print('epoch %d,loss %f' % (epoch+1,train_l.mean().asnumpy()))

image-20220402214312255

通过上面的计算,我们得到结果(true_w为刚开始定义的值,w为我们通过计算得出的值)

1
true_w,w

image-20220402214736512

1
true_b,b

image-20220402215308326

通过以上结果我们可以知道,以上算法是通过优化目标模型来反向求得参数,使得求得的参数最接近原参数。

线性回归的简洁实现

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from mxnet import autograd,nd

num_inputs=2
num_examples=1000
true_w=[2,3.4]
true_b=4.2
features=nd.random.normal(scale=1,shape=(num_examples,num_inputs))
labels=true_w[0]*features[:,0]+true_w[1]*features[:,1]+true_b
labels+=nd.random.normal(scale=0.01,shape=labels.shape)

from mxnet.gluon import data as gdata

batch_size=10
#将训练数据的特征和标签组合
dataset=gdata.ArrayDataset(features,labels)
#随机读取小批量
data_iter=gdata.DataLoader(dataset,batch_size,shuffle=True)


#定义模型
#Sequential为一个串联各个层的容器,可以看作一个画板
#Dense为全连接层,定义该层的输出为1
#Gluon中我们无须指定每一层输入的形状,如线性回归的输入个数,当模型得到数据时,如后面的执行net(X)时
#模型将自动推断出每一层的输入个数,如若是全相连的,通过层n推断n+1层的输入也是比较合理,且能实现的
from mxnet.gluon import nn
net=nn.Sequential()
net.add(nn.Dense(1))

#初始化模型参数
#从mxnet导入init模块,该模块提供了模型参数初始化的各种方法,通过init.Normal(sigma=0.01)指权重
#参数每个元素在初始化时随机采样于均值为0,标准差为0.01的正态分布,偏差参数默认会为0
from mxnet import init
net.initialize(init.Normal(sigma=0.01))

#定义损失函数
from mxnet.gluon import loss as gloss
loss=gloss.L2Loss() #平方损失又称L2范数损失

#定义优化函数
#在导入Gluon之后,我们创建一个Trainer实例,并指定学习率为0.03的小批量随机梯度下降(sgd)为优化算法
#该优化算法用来迭代net实例所有通过add函数嵌套的层所包含的全部参数,这些参数可以通过collect_params
#函数获取
from mxnet import gluon
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.03})

#训练模型
#在使用Gluon训练模型时,我们通过调用Trainer实例的step函数来迭代模型参数,由于变量l是长度为
#batch_size的一维NDArray,执行l.backward()等价于执行l.sum().backward()。按照小批量随机
#梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均
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('epoch %d, loss:%f' % (epoch,l.mean().asnumpy()))

image-20220404212518086

下面我们分别比较学到的模型参数与真实的模型参数,从而从net获得需要的层,并访问其权重和偏差。

1
2
dense=net[0]
true_w,dense.weight.data()

image-20220404212656630

1
true_b,dense.bias.data()

image-20220404212748893

图像分类数据集(Fashion-MNIST)

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#导包
%matplotlib inline
import d2lzh as d2l
from mxnet.gluon import data as gdata
import sys
import time

#获取数据集,从网上获取数据集,通过train来指定获取训练数据集或测试数据集
mnist_train=gdata.vision.FashionMNIST(train=True)
mnist_test=gdata.vision.FashionMNIST(train=False)

# mnist_train[0]同时返回feature与label,变量feature对应⾼和宽均为28像素的图像。每个像素的数值为0
#到255之间8位⽆符号整数(uint8)
feature,label=mnist_train[0]

#输出 ((28,28,1),numpy.uint8)
feature.shape,feature.dtype

#输出 (2,numpy.int32,dtype('int32')) label为2,后面通过函数把标签的数字转成文字标签
label,type(label),label.dtype

#将标签中的数字转换成文字标签
def get_fashion_mnist_labels(labels):
text_labels=['t-shirt','trouser','pullover','dress','coat',
'sandal','shirt','sneaker','bag','ankle boot']
return [text_labels[int(i)] for i in labels]

#下⾯定义⼀个可以在⼀⾏⾥画出多张图像和对应标签的函数
def show_fashion_mnist(images,labels):
d2l.use_svg_display()
_,figs=d2l.plt.subplots(1,len(images),figsize=(12,12))
for f,img,lbl in zip(figs,images,labels):
f.imshow(img.reshape((28,28)).asnumpy())
f.set_title(lbl)
f.axes.get_xaxis().set_visible(True)
f.axes.get_yaxis().set_visible(True)

#我们看⼀下训练数据集中前9个样本的图像内容和⽂本标签
X,y=mnist_train[0:9]
show_fashion_mnist(X,get_fashion_mnist_labels(y))

image-20220409155515489

softmax回归从零开始实现

1
2
3
4
5
6
7
%matplotlib inline
import d2lzh as d2l
from mxnet import autograd,nd

#设置批处理大小,且导入数据到训练集与测试集中
batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)
1
2
3
4
5
6
7
8
9
#因为softmax回归模型是一个单层神经网络
#又由于输入向量的长度为28*28=784,该向量的每个元素对应图像中的每个像素,由于图像有十个类别,所以单层
#神经网络的输出层的输出个数为10
num_inputs=784
num_outputs=10
w=nd.random.normal(scale=0.01,shape=(num_inputs,num_outputs))
b=nd.zeros(num_outputs)
w.attach_grad()
b.attach_grad()

image-20220409181323633

1
2
3
4
5
6
7
8
9
#在下⾯的函数中,矩阵X的⾏数是样本数,
#列数是输出个数。为了表达样本预测各个输出的概率, softmax运算会先通过exp函数对每个元素
#做指数运算,再对exp矩阵同⾏元素求和,最后令矩阵每⾏各元素与该⾏元素之和相除。这样⼀
#来,最终得到的矩阵每⾏元素和为1且⾮负。因此,该矩阵每⾏都是合法的概率分布。 softmax运
#算的输出矩阵中的任意⼀⾏元素代表了⼀个样本在各个输出类别上的预测概率
def softmax(X):
X_exp=X.exp()
partition=X_exp.sum(axis=1,keepdims=True)
return X_exp/partition

image-20220409181537778

1
2
3
#定义了softmax的回归模型
def net(X):
return softmax(nd.dot(X.reshape((-1,num_inputs)),w)+b)

image-20220409181856448

1
2
3
4
5
#损失函数
#对于样本i,使其他在正确标签的的时候值为1,其余的时候为0
#又因为每个样本只有一个标签,那么损失函数可能以进行简写
def cross_entropy(y_hat,y)
return -nd.pick(y_hat,y).log()

上面的损失函数等价于,其余部分后面实现

image-20220409191415164

image-20220409191601846

1
2
3
4
5
#定义两个数之间的准确率
#相等条件判别式(y_hat.argmax(axis=1) == y)是⼀个值为0(相等为假)或1(相等
#为真)的NDArray。由于标签类型为整数,我们先将变量y变换为浮点数再进⾏相等条件判断。
def accuracy(y_hat,y):
return (y_hat.argmax(axis=1)==y.astype('float32')).mean().asscalar()
1
2
3
4
5
6
7
def evaluate_accuracy(data_iter,net):
acc_sum,n=0.0,0
for X,y in data_iter:
y=y.astype('float32')
acc_sum+=(net(X).argmax(axis=1)==y).sum().asscalar()
n+=y.size
return acc_sum/n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#训练模型
num_epochs,lr=5,0.1

def train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,params=None,lr=None,trainer=None):
for epoch in range(num_epochs):
train_l_sum,train_acc_sum,n=0.0,0.0,0
for X,y in train_iter: #train_iter 返回图像和标签
with autograd.record():
y_hat=net(X) #自己的y值
l=loss(y_hat,y).sum()
l.backward() #求导
if trainer is None:
d2l.sgd(params,lr,batch_size) #之前定义的小型梯度下降
else:
trainer.step(batch_size)
y=y.astype('float32')
train_l_sum+=l.asscalar()
train_acc_sum+=(y_hat.argmax(axis=1)==y).sum().asscalar()
n+=y.size
test_acc=evaluate_accuracy(test_iter,net)
print('epoch %d,loss %.4f,train acc %.3f,test acc %.3f'%(epoch+1,train_l_sum/n,train_acc_sum/n,test_acc))
1
train_ch3(net,train_iter,test_iter,cross_entropy,num_epochs,batch_size,[w,b],lr)

image-20220409192303044

1
2
3
4
5
6
7
8
#给定⼀系列图像(第三⾏图像输出),我们⽐较⼀下它们的真实标签(第⼀⾏⽂本输出)和模型预测结果(第⼆⾏⽂
#本输出)
for X,y in test_iter:
break
true_labels=d2l.get_fashion_mnist_labels(y.asnumpy())
pred_labels=d2l.get_fashion_mnist_labels(net(X).argmax(axis=1).asnumpy())
titels=[true+'\n'+pred for true,pred in zip(true_labels,pred_labels)]
d2l.show_fashion_mnist(X[0:9],titels[0:9])

image-20220409192405732

softmax回归简洁实现

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
%matplotlib inline
import d2lzh as d2l
from mxnet import autograd,nd
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)

#定义和初始化模型
#softmax回归的输出是一个全连接层,因此,我们添加一个输出个数为10的全连接层,我们使用均值0、标准差为#0.01的正态分布随机初始化模型的权重参数
net=nn.Sequential()
net.add(nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))

#softmax和交叉熵损失函数
#Gluon提供了⼀个包括softmax运算和交叉熵损失计算的函数。它的数值稳定性更好。
loss=gloss.SoftmaxCrossEntropyLoss()

#定义优化算法
#使用学习率为0.1的小批量随机梯度下降作为优化算法
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.1})

#训练模型
num_epochs=5
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,None,None,trainer)

多层感知机

image-20220409205054558

激活函数

image-20220409205422168

image-20220409205503297

多层感知机的从零开始实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
%matplotlib inline
import d2lzh as d2l
from mxnet import nd
from mxnet.gluon import loss as gloss

batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)

# Fashion-MNIST数据集中图像形状
#为28 × 28,类别数为10。本节中我们依然使⽤⻓度为28 × 28 = 784的向量表⽰每⼀张图像。因此,
#输⼊个数为784,输出个数为10。实验中,我们设超参数隐藏单元个数为256
num_inputs,num_outputs,num_hiddens=784,10,256
W1=nd.random.normal(scale=0.01,shape=(num_inputs,num_hiddens))
b1=nd.zeros(num_hiddens)
W2=nd.random.normal(scale=0.01,shape=(num_hiddens,num_outputs))
b2=nd.zeros(num_outputs)
params=[W1,b1,W2,b2]

多层感知机的简洁实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import d2lzh as d2l
from mxnet import gluon,init
from mxnet.gluon import loss as gloss,nn

#和softmax回归唯⼀的不同在于,我们多加了⼀个全连接层作为隐藏层。它的隐藏单元个数为256,
#并使⽤ReLU函数作为激活函数
net=nn.Sequential()
net.add(nn.Dense(256,activation='relu'),nn.Dense(10))
net.initialize(init.Normal(sigma=0.01))

batch_size=256
train_iter,test_iter=d2l.load_data_fashion_mnist(batch_size)

loss=gloss.SoftmaxCrossEntropyLoss()
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.5})
num_epochs=5
d2l.train_ch3(net,train_iter,test_iter,loss,num_epochs,batch_size,None,None,trainer)

欠拟合与过拟合

给定一个由标量数据特征x和对应的标题标签y组成的训练数据集,多项式函数拟合的目标是找一个K项多项式的函数

来近似,在上式中,的模型的权重参数,b是偏差参数。

因为⾼阶多项式函数模型参数更多,模型函数的选择空间更⼤,所以⾼阶多项式函数⽐低阶多项式函数的复杂度更⾼。因此,⾼阶多项式函数⽐低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。给定训练数据集,模型复杂度和误差之间的关系通常如图3.4所⽰。给定训练数据集,如果模型的复杂度过低,很容易出现⽋拟合;如果模型复杂度过⾼,很容易出现过拟合。应对⽋拟合和过拟合的⼀个办法是针对数据集选择合适复杂度的模型。

image-20220409220107017

影响⽋拟合和过拟合的另⼀个重要因素是训练数据集的⼤小。⼀般来说,如果训练数据集中样本数过少,特别是⽐模型参数数量(按元素计)更少时,过拟合更容易发⽣。此外,泛化误差不会随训练数据集⾥样本数量增加而增⼤。因此,在计算资源允许的范围之内,我们通常希望训练数据集⼤⼀些,特别是在模型复杂度较⾼时,如层数较多的深度学习模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
%matplotlib inline
import d2lzh as d2l
from mxnet import autograd,gluon,nd
from mxnet.gluon import data as gdata,loss as gloss,nn

n_train,n_test,true_w,true_b=100,100,[1.2,-3.4,5.6],5
features=nd.random.normal(shape=(n_train+n_test,1))
#拼接,ploy_featuers=x,x^2,x^3
ploy_features=nd.concat(features,nd.power(features,2),nd.power(features,3))
#y=1.2x-3.4x^2+5.6x^3+5
labels=(true_w[0]*ploy_features[:,0]+true_w[1]*ploy_features[:,1]+true_w[2]*ploy_features[:,2]+true_b)
#y+=噪声项
labels+=nd.random.normal(scale=0.1,shape=labels.shape)

image-20220409225842985

1
2
3
4
5
6
7
8
9
#作图函数
def semilogy(x_vals,y_vals,x_label,y_label,x2_vals=None,y2_vals=None,legend=None,figsize=(3.5,2.5)):
d2l.set_figsize(figsize)
d2l.plt.xlabel(x_label)
d2l.plt.ylabel(y_label)
d2l.plt.semilogy(x_vals,y_vals)
if x2_vals and y2_vals:
d2l.plt.semilogy(x2_vals,y2_vals,linestyle=':')
d2l.plt.legend(legend)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
num_epochs,loss=100,gloss.L2Loss()
def fit_and_plot(train_features,test_features,train_labels,test_labels):
net=nn.Sequential()
net.add(nn.Dense(1)) #单层神经网络,一个输出单元
net.initialize() #初始化参数
batch_size=min(10,train_labels.shape[0])
train_iter=gdata.DataLoader(gdata.ArrayDataset(train_features,train_labels),batch_size,shuffle=True)
trainer=gluon.Trainer(net.collect_params(),'sgd',{'learning_rate':0.01})
train_ls,test_ls=[], []
for _ in range(num_epochs):
for X,y in train_iter:
with autograd.record():
l=loss(net(X),y)
l.backward()
trainer.step(batch_size)
train_ls.append(loss(net(train_features),train_labels).mean().asscalar())
test_ls.append(loss(net(test_features),test_labels).mean().asscalar())
print('final epoch:train loss',train_ls[-1],'test loss',test_ls[-1])
semilogy(range(1,num_epochs+1),train_ls,'epochs','loss',range(1,num_epochs+1),test_ls,['train','test'])
print('weight:',net[0].weight.data().asnumpy(),'\nbias:',net[0].bias.data().asnumpy())
1
2
3
4
5
#三阶多项式函数拟合(正常)
#我们先使⽤与数据⽣成函数同阶的三阶多项式函数拟合。实验表明,这个模型的训练误差和在测
#试数据集的误差都较低。训练出的模型参数也接近真实值: w1 = 1:2; w2 = −3:4; w3 = 5:6; b = 5
#ploy_features[:n_train,:]取前100个元素,ploy_features[n_train:,:]取后100个元素
fit_and_plot(ploy_features[:n_train,:],ploy_features[n_train:,:],labels[:n_train],labels[n_train:])

image-20220409232656580

1
2
3
#线性函数拟合(欠拟合)
#这个地方,生成的函数是线性的,所以有欠拟合状态
fit_and_plot(features[:n_train,:],features[n_train:,:],labels[:n_train],labels[n_train:])

image-20220410092456601

1
2
#训练样本不足(过拟合)
fit_and_plot(ploy_features[0:2,:],ploy_features[n_train:,:],labels[0:2],labels[n_train:])

image-20220410092549529

权重衰减-原理暂不解释

image-20220410103609151

权重衰减1

在上图中我们的更新公式为

P(w_1,w_2,…,w_t)

P(w_1,w_2,…,w_t)=\prod_{t=1}^Tp(w_t|w_1,…,w_{t-1})

(Y^{})^{}=H_tW_{hq}+b

$$

长短期记忆

image-20220415182710558

image-20220415182738771

image-20220415183135068

image-20220415183156489