《动手学深度学习》(pytorch版)摘要

深度学习简介

与其设计一个解决问题的程序,不如从最终的需求入手来寻找一个解决方案。事实上,这也是目前的机器学习和深度学习应用共同的核心思想:我们可以称其为“用数据编程”。与其枯坐在房间里思考怎么设计一个识别猫的程序,不如利用人类肉眼在图像中识别猫的能力。我们可以收集一些已知包含猫与不包含猫的真实图像,然后我们的目标就转化成如何从这些图像入手得到一个可以推断出图像中是否有猫的函数。这个函数的形式通常通过我们的知识来针对特定问题选定。例如,我们使用一个二次函数来判断图像中是否有猫,但是像二次函数系数值这样的函数参数的具体值则是通过数据来确定。

时至今日,绝大多数神经网络都包含以下的核心原则。

  • 交替使用线性处理单元与非线性处理单元,它们经常被称为“层”。
  • 使用链式法则(即反向传播)来更新网络的参数。

机器学习研究如何使计算机系统利用经验改善性能。它是人工智能领域的分支,也是实现人工智能的一种手段。在机器学习的众多研究方向中,表征学习关注如何自动找出表示数据的合适方式,以便更好地将输入变换为正确的输出,而本书要重点探讨的深度学习是具有多级表示的表征学习方法。在每一级(从原始数据开始),深度学习通过简单的函数将该级的表示变换为更高级的表示。因此,深度学习模型也可以看作是由许多简单函数复合而成的函数。当这些复合的函数足够多时,深度学习模型就可以表达非常复杂的变换。

深度学习的一个外在特点是端到端的训练。也就是说,并不是将单独调试的部分拼凑起来组成一个系统,而是将整个系统组建好之后一起训练

预备知识

环境配置

配置miniconda3
在服务器上配置jupyterNotebook
需python 3.6.5以上,不然ipython报错,

pip install jupyter notebook
pip install pytorch

数据操作 on torch

创建Tensor

常用函数:
函数

操作

我们还可以使用类似NumPy的索引操作来访问Tensor的一部分,需要注意的是:索引出来的结果与原数据共享内存,也即修改一个,另一个会跟着修改
索引
使用view()改变Tensor形状:

y=x.view(15)

注意view()返回的新Tensor与源Tensor虽然可能有不同的size,但是是共享data的,也即更改其中的一个,另外一个也会跟着改变。(顾名思义,view仅仅是改变了对这个张量的观察角度,内部数据并未改变)

所以如果我们想返回一个真正新的副本(即不共享data内存)该怎么办呢?Pytorch还提供了一个reshape()可以改变形状,但是此函数并不能保证返回的是其拷贝,所以不推荐使用。推荐先用clone创造一个副本然后再使用view。即:

x_cp=x.clone().view(15)

另外一个常用的函数就是item(), 它可以将一个标量Tensor转换成一个Python number:

广播机制

当对两个形状不同的Tensor按元素运算时,可能会触发广播(broadcasting)机制:先适当复制元素使这两个Tensor形状相同后再按元素运算。

运算的内存开销

前面说了,索引操作是不会开辟新内存的,而像y = x + y这样的运算是会新开内存的,然后将y指向新内存。
注:虽然view返回的Tensor与源Tensor是共享data的,但是依然是一个新的Tensor(因为Tensor除了包含data外还有一些其他属性),二者id(内存地址)并不一致。

tensor 和numpy相互转换

tensor转numpy:

a = torch.ones(5)
b = a.numpy()

numpy转tensor:

a = np.ones(5)
b = torch.from_numpy(a)

所有在CPU上的Tensor(除了CharTensor)都支持与NumPy数组相互转换。上述方法都是共享内存,改变其中一个时另一个也会发生改变。

此外上面提到还有一个常用的方法就是直接用torch.tensor()将NumPy数组转换成Tensor,需要注意的是该方法总是会进行数据拷贝,返回的Tensor和原来的数据不再共享内存。

自动求梯度

在深度学习中,我们经常需要对函数求梯度(gradient)。PyTorch提供的autograd包能够根据输入和前向传播过程自动构建计算图,并执行反向传播。本节将介绍如何使用autograd包来进行自动求梯度的有关操作。

上一节介绍的Tensor是这个包的核心类,如果将其属性.requires_grad设置为True,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用.backward()来完成所有梯度计算。此Tensor的梯度将累积到.grad属性中。

如果不想要被继续追踪,可以调用.detach()将其从追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外,还可以用with torch.no_grad()将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数(requires_grad=True)的梯度。

Function是另外一个很重要的类。Tensor和Function互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor都有一个.grad_fn属性,该属性即创建该Tensor的Function, 就是说该Tensor是不是通过某些运算得到的,若是,则grad_fn返回一个与这些运算相关的对象,否则是None。
2x+4

x.grad_fn #x是如何计算得到的,如果是创建的,为None
out.backward() # 等价于 out.backward(torch.tensor(1.))

注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。

此外,如果我们想要修改tensor的数值,但是又不希望被autograd记录(即不会影响反向传播),那么我么可以对tensor.data进行操作。

深度学习基础

线性回归

线性回归输出是一个连续值,因此适用于回归问题。softmax回归则适用于分类问题。

误差最小化问题的解可以直接用公式表达出来。这类解叫作解析解(analytical solution)。然而,大多数深度学习模型并没有解析解,只能通过优化算法有限次迭代模型参数来尽可能降低损失函数的值。这类解叫作数值解(numerical solution)。

在求数值解的优化算法中,**小批量随机梯度下降(mini-batch stochastic gradient descent)**在深度学习中被广泛使用。它的算法很简单:先选取一组模型参数的初始值,如随机选取;接下来对参数进行多次迭代,使每次迭代都可能降低损失函数的值。在每次迭代中,先随机均匀采样一个由固定数目训练数据样本所组成的小批量(mini-batch)\mathcal{B}B,然后求小批量中数据样本的平均损失有关模型参数的导数(梯度),最后用此结果与预先设定的一个正数的乘积作为模型参数在本次迭代的减小量。

输出层中的神经元和输入层中各个输入完全连接。因此,这里的输出层又叫全连接层(fully-connected layer)或稠密层(dense layer)。

Reo 线性回归

仅使用Tensor和autograd模块就可以很容易地实现一个模型
代码1
代码2
代码3

线性回归简洁实现

torch.utils.data模块提供了有关数据处理的工具,torch.nn模块定义了大量神经网络的层,torch.nn.init模块定义了各种初始化方法,torch.optim模块提供了很多常用的优化算法。
代码1
PyTorch提供了大量预定义的层,这使我们只需关注使用哪些层来构造模型。下面将介绍如何使用PyTorch更简洁地定义线性回归。

首先,导入torch.nn模块。实际上,“nn”是neural networks(神经网络)的缩写。顾名思义,该模块定义了大量神经网络的层。之前我们已经用过了autograd,而nn就是利用autograd来定义模型。nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。

代码2
可以通过net.parameters()来查看模型所有的可学习参数,此函数将返回一个生成器。:
代码3
有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。一种是修改optimizer.param_groups中对应的学习率,另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是新建优化器对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。

在使用训练模型时,我们通过调用optim实例的step函数来迭代模型参数。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。

代码4

softmax回归

softmax回归跟线性回归一样将输入特征与权重做线性叠加。与线性回归的一个主要不同在于,softmax回归的输出值个数等于标签里的类别数。

既然分类问题需要得到离散的预测输出,一个简单的办法是将输出值当作预测类别是i的置信度,并将值最大的输出所对应的类作为预测输出,然而,直接使用输出层的输出有两个问题。一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。例如,刚才举的例子中的输出值10表示“很置信”图像类别为猫,因为该输出值是其他两类的输出值的100倍。那么输出值10却又表示图像类别为猫的概率很低。另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
softmax运算符(softmax operator)解决了以上两个问题。它通过下式将输出值变换成值为正且和为1的概率分布:
softmax
矢量计算

交叉熵损失函数

交叉熵
也就是说,交叉熵只关心对正确类别的预测概率,因为只要其值足够大,就可以确保分类结果正确。当然,遇到一个样本有多个标签时,例如图像里含有不止一个物体时,我们并不能做这一步简化。但即便对于这种情况,交叉熵同样只关心对图像中出现的物体类别的预测概率。从另一个角度来看,即最小化交叉熵损失函数等价于最大化训练数据集所有标签类别的联合预测概率

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

本节我们将使用torchvision包,它是服务于PyTorch深度学习框架的,主要用来构建计算机视觉模型。torchvision主要由以下几部分构成:

  • torchvision.datasets: 一些加载数据的函数及常用的数据集接口;
  • torchvision.models: 包含常用的模型结构(含预训练模型),例如AlexNet、VGG ResNet等;
  • torchvision.transforms: 常用的图片变换,例如裁剪、旋转等;
  • torchvision.utils: 其他的一些有用的方法。

transforms.ToTensor()将尺寸为 (H x W x C) 且数据位于[0, 255]的PIL图片或者数据类型为np.uint8的NumPy数组转换为尺寸为(C x H x W)且数据类型为torch.float32**且位于[0.0, 1.0]**的Tensor。

代码1
代码2
代码3

Fashion-MNIST中一共包括了10个类别,分别为t-shirt(T恤)、trouser(裤子)、pullover(套衫)、dress(连衣裙)、coat(外套)、sandal(凉鞋)、shirt(衬衫)、sneaker(运动鞋)、bag(包)和ankle boot(短靴)。

Re0 softmax

模型的输入向量的长度是 28 x 28 = 784:该向量的每个元素对应图像中每个像素。由于图像有10个类别,单层神经网络输出层的输出个数为10,因此softmax回归的权重和偏差参数分别为784 x 10和1×10的矩阵。

code1
code2
code3
code4

softmax回归简洁实现

因为前面我们数据返回的每个batch样本x的形状为**(batch_size, 1, 28, 28), 所以我们要先用view()将x的形状转换成(batch_size, 784)**才送入全连接层。

code1
code2

多层感知机

我们将以多层感知机(multilayer perceptron,MLP)为例,介绍多层神经网络的概念。
全连接层只是对数据做仿射变换(affine transformation),而多个仿射变换的叠加仍然是一个仿射变换。解决问题的一个方法是引入非线性变换,例如对隐藏变量使用按元素运算的非线性函数进行变换,然后再作为下一个全连接层的输入。这个非线性函数被称为激活函数(activation function)。

ReLU

ReLU(rectified linear unit)函数提供了一个很简单的非线性变换。给定元素xx,该函数定义为
ReLU
ReLU图

sigmoid

sigmoid函数可以将元素的值变换到0和1之间:
sigmoid
sigmoid图导数
sigmoid导数图

tanh函数

tanh(双曲正切)函数可以将元素的值变换到-1和1之间:
tanh
tanh图tanh导数
tanh导数图
多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例并沿用本节之前定义的符号,多层感知机按以下方式计算输出:
多层感知机

实现 MLP

多了隐藏层

模型选择、欠拟合和过拟合

我们需要区分训练误差(training error)泛化误差(generalization error)。通俗来讲,前者指模型在训练数据集上表现出的误差,后者指模型在任意一个测试数据样本上表现出的误差的期望,并常常通过测试数据集上的误差来近似。
由于验证数据集不参与模型训练,当训练数据不够用时,预留大量的验证数据显得太奢侈。一种改善的方法是K折交叉验证(KK-fold cross-validation)。在K折交叉验证中,我们把原始训练数据集分割成K个不重合的子数据集,然后我们做K次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他K-1个子数据集来训练模型。在这K次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这K次训练误差和验证误差分别求平均。

一类是模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting);另一类是模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。

权重衰减

权重衰减等价于 L_2范数正则化(regularization)。正则化通过为模型损失函数添加惩罚项使学出的模型参数值较小,是应对过拟合的常用手段。
L_2范数惩罚项指的是模型权重参数每个元素的平方和与一个正的常数的乘积。
新损失函数
在这里插入图片描述
可见,L_2范数正则化令权重w_1和w_2先自乘小于1的数,再减去不含惩罚项的梯度。因此,L_2范数正则化又叫权重衰减。权重衰减通过惩罚绝对值较大的模型参数为需要学习的模型增加了限制,这可能对过拟合有效。实际场景中,我们有时也在惩罚项中添加偏差元素的平方和。

这里我们直接在构造优化器实例时通过weight_decay参数来指定权重衰减超参数。默认下,PyTorch会对权重和偏差同时衰减。我们可以分别对权重和偏差构造优化器实例,从而只对权重衰减。

optimizer_w = torch.optim.SGD(params=[net.weight], lr=lr, weight_decay=wd) # 对权重参数衰减

丢弃法 dropout

深度学习模型常常使用丢弃法(dropout) 来应对过拟合问题
dropout自己实现dropout
在这里插入图片描述
dropout只在训练模型时使用

参考文献:

动手学深度学习-pytorch

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值