Pytorch基本教程
此博客以机器学习中的一些简单模型为基础,学习Pytorch的基本架构,不做过多深入每个函数的研究与探讨,这部分只需要面向具体的项目代码即可!文章最后面会附上一些相关链接用来参考学习。
Pytorch基础:Tensor(张量)
对于张量(Tensor),就可以理解为多为矩阵,也只是一种特别的存储方式而已,当然也可以表示一个元素的张量(或,标量)
1 | tensor = torch.tensor([3.1433223]) |
1 | tensor([3.1433]) |
Tensor 的基本类型
Tensor的基本数据类型有五种:
- 32位浮点型:torch.FloatTensor。 (默认)
- 64位整型:torch.LongTensor。
- 32位整型:torch.IntTensor。
- 16位整型:torch.ShortTensor。
- 64位浮点型:torch.DoubleTensor。
除以上数字类型外,还有 byte和chart型
1 | tensor([3.1426], dtype=torch.float16) |
设备转换
相对于 Numpy
中多维矩阵的表示 ndarray
只能在 CPU 上运行,Tensor
可以在 GPU 上运行。所以有时候需要统一好每个 Tensor 所在设备。
1 | #使用torch.cuda.is_available()来确定是否有cuda设备 |
1 | cuda |
Autograd计算梯度数值
在创建张量时,可以通过设置 requires_grad=True
来告诉 Pytorch 对该张量进行自助求导,Pytorch 会记录该张量的每一步操作历史并自动计算梯度
1 | x = torch.rand(5, 5, requires_grad=True) |
在张量进行操作后,grad_fn
已经被赋予了一个新的函数,这个函数引用了一个创建了这个 Tensor 类的 Function 对象。 Tensor 和 Function 互相连接生成了一个非循环图,它记录并且编码了完整的计算历史。每个张量都有一个 `.grad_fn
属性,如果这个张量是用户手动创建的那么这个张量的 grad_fn
是 None
。
当计算完成后调用 .backward()
方法自动计算梯度并且将计算结果保存到 grad
属性中。
1 | z.backward() |
我们可以使用 with torch.no_grad()
上下文管理器临时禁止对已设置 requires_grad=True
的张量进行自动求导。这个方法在测试集计算准确率的时候会经常用到,例如:
1 | with torch.no_grad(): |
使用 .no_grad()
进行嵌套后,代码不会跟踪历史记录,也就是说保存的这部分记录会减少内存的使用量并且会加快少许的运算速度。
数据的加载和预处理
PyTorch通过 torch.utils.data
对一般常用的数据加载进行了封装,可以很容易地实现多线程数据预读和批量加载。 并且 torchvision
已经预先实现了常用图像数据集,包括前面使用过的CIFAR-10,ImageNet、COCO、MNIST、LSUN等数据集,可通过 torchvision.datasets
方便的调用。这里不具体介绍。
Dataset
Dataset 是一个抽象类,为了能够方便的读取,需要将要使用的数据包装为 Dataset 类。 自定义的 Dataset 需要继承它并且实现两个成员方法:
- `__getitem__()` 该方法定义用索引(`0` 到 `len(self)`)获取一条数据或一个样本
- `__len__()` 该方法返回数据集的总长度
1 | #引用 |
自定义的数据集已经创建好了,下面我们使用官方提供的数据载入器,读取数据。
Dataloader
DataLoader 为我们提供了对 Dataset 的读取操作,常用参数有:
- batch_size(每个batch的大小)、
- shuffle(是否进行shuffle操作)、
- num_workers(加载数据的时候使用几个子进程)。
下面做一个简单的操作:
1 | dl = torch.utils.data.DataLoader(ds_demo, batch_size=10, shuffle=True, num_workers=0) |
我们已经可以通过 Dataset 定义数据集,并使用 Datalorder 载入和遍历数据集,除了这些以外, PyTorch 还提供能 torcvision 的计算机视觉扩展包。
torchvision 包
torchvision 是PyTorch中专门用来处理图像的库。
torchvision.datasets
torchvision.datasets 可以理解为PyTorch团队自定义的dataset,这些dataset帮我们提前处理好了很多的图片数据集,我们拿来就可以直接使用: - MNIST - COCO - Captions - Detection - LSUN - ImageFolder - Imagenet-12 - CIFAR - STL10 - SVHN - PhotoTour 我们可以直接使用,示例如下:
1 | import torchvision.datasets as datasets |
torchvision.models
torchvision 不仅提供了常用图片数据集,还提供了训练好的模型,可以加载之后,直接使用,或者在进行迁移学习 torchvision.models
模块的 子模块中包含以下模型结构。 - AlexNet - VGG - ResNet - SqueezeNet - DenseNet
1 | #我们直接可以使用训练好的模型,当然这个与datasets相同,都是需要从服务器下载的 |
torchvision.transforms
transforms 模块提供了一般的图像转换操作类,用作数据处理和数据增强
1 | from torchvision import transforms as transforms |
肯定有人会问:(0.485, 0.456, 0.406), (0.2023, 0.1994, 0.2010) 这几个数字是什么意思?官方的这个帖子有详细的说明: https://discuss.pytorch.org/t/normalization-in-the-mnist-example/457/21 这些都是根据ImageNet训练的归一化参数,可以直接使用,我们认为这个是固定值就可以。
神经网络包nn和优化器optimizer
torch.nn
是专门为神经网络设计的模块化接口。nn
构建于 Autograd
之上,可用来定义和运行神经网络。 这里我们主要介绍几个一些常用的类。除了nn别名以外,我们还引用了nn.functional,这个包中包含了神经网络中使用的一些常用函数,这些函数的特点是,不具有可学习的参数(如ReLU,pool,DropOut等),这些函数可以放在构造函数中,也可以不放,但是这里建议不放。一般情况下我们会将nn.functional 设置为大写的F,这样缩写方便调用。
1 | # 首先要引入相关的包 |
定义一个网络
PyTorch 中已经为我们准备好了现成的网络模型,只要继承 nn.Module
,并实现它的 forward
方法,PyTorch 会根据 autograd
,自动实现 backward
函数,在 forward
函数中可使用任何 tensor 支持的函数,还可以使用 if、for 循环、print、log 等 Python 语法,写法和标准的 Python 写法一致。
1 | class Net(nn.Module): |
网络的可学习参数通过 net.parameters()
返回
1 | for parameters in net.parameters(): |
net.named_parameters
可同时返回可学习的参数及名称
1 | for name,parameters in net.named_parameters(): |
forward函数的输入和输出都是Tensor
1 | input = torch.randn(1, 1, 32, 32) # 这里的对应前面fforward的输入是32 |
在反向传播前,先要将所有参数的梯度清零
1 | net.zero_grad() |
注意:torch.nn
只支持 mini-batches
,不支持一次只输入一个样本,即一次必须是一个batch。也就是说,就算我们输入一个样本,也会对样本进行分批,所以,所有的输入都会增加一个维度,我们对比下刚才的 input
,nn
中定义为3维,但是我们人工创建时多增加了一个维度,变为了4维,最前面的1即为batch-size。
损失函数
在nn中PyTorch还预制了常用的损失函数,下面我们用MSELoss用来计算均方误差:
1 | y = torch.arange(0,10).view(1,10).float() |
优化器
在反向传播计算完所有参数的梯度后,还需要使用优化方法来更新网络的权重和参数,例如随机梯度下降法(SGD)的更新策略如下:
weight = weight - learning_rate * gradient
在 torch.optim
中实现大多数的优化方法,例如 RMSProp、Adam、SGD等,下面我们使用SGD做个简单的样例:
1 | import torch.optim |
这样,神经网络的数据的一个完整的传播就已经通过PyTorch实现了。
深度学习基础
监督学习和无监督学习
监督学习、无监督学习、半监督学习、强化学习是我们日常接触到的常见的四个机器学习方法:
- 监督学习:通过已有的训练样本(即已知数据以及其对应的输出)去训练得到一个最优模型(这个模型属于某个函数的集合,最优则表示在某个评价准则下是最佳的),再利用这个模型将所有的输入映射为相应的输出。
- 无监督学习:它与监督学习的不同之处,在于我们事先没有任何训练样本,而需要直接对数据进行建模。
- 半监督学习 :在训练阶段结合了大量未标记的数据和少量标签数据。与使用所有标签数据的模型相比,使用训练集的训练模型在训练时可以更为准确。
- 强化学习:我们设定一个回报函数(reward function),通过这个函数来确认否越来越接近目标,类似我们训练宠物,如果做对了就给他奖励,做错了就给予惩罚,最后来达到我们的训练目的。
这里我们只着重介绍监督学习,因为我们后面的绝大部们课程都是使用的监督学习的方法,在训练和验证时输入的数据既包含输入x,又包含x对应的输出y,即学习数据已经事先给出了正确答案。
线性回归 (Linear Regreesion)
这里不解释具体原理,直接看写法,体会训练过程:
1 | # 注意,这里我们使用了一个新库叫 seaborn 如果报错找不到包的话请使用pip install seaborn 来进行安装 |
训练完成了,看一下训练的成果是多少。用 model.parameters()
提取模型参数。 $w$, $b$ 是我们所需要训练的模型参数,我们期望的数据 $w=5$,$b=7$ 可以做一下对比
1 | [w, b] = model.parameters() |
损失函数(Loss Function)
损失函数(loss function)是用来估量模型的预测值(我们例子中的output)与真实值(例子中的y_train)的不一致程度,它是一个非负实值函数,损失函数越小,模型的鲁棒性就越好。 我们训练模型的过程,就是通过不断的迭代计算,使用梯度下降的优化算法,使得损失函数越来越小。损失函数越小就表示算法达到意义上的最优。
这里有一个重点:因为PyTorch是使用mini-batch来进行计算的,所以损失函数的计算出来的结果已经对mini-batch取了平均。
常见(PyTorch内置)的损失函数有以下几个:
nn.L1Loss:
输入x和目标y之间差的绝对值,要求 x 和 y 的维度要一样(可以是向量或者矩阵),得到的 loss 维度也是对应一样的
nn.NLLLoss:
用于多分类的负对数似然损失函数$loss(x, class) = -x[class]$;
NLLLoss中如果传递了weights参数,会对损失进行加权,公式就变成了
n.MSELoss:
均方损失函数 ,输入x和目标y之间均方差
nn.CrossEntropyLoss:
多分类用的交叉熵损失函数,LogSoftMax 和 NLLLoss 集成到一个类中,会调用 nn.NLLLoss 函数,我们可以理解为 CrossEntropyLoss()=log_softmax() + NLLLoss()
因为使用了NLLLoss,所以也可以传入weight参数,这时loss的计算公式变为:
所以一般多分类的情况会使用这个损失函数;
nn.BCELoss:
计算 x 与 y 之间的二进制交叉熵。$loss(o,t)=-\frac{1}{n}\sum_i(t[i] log(o[i])+(1-t[i]) log(1-o[i]))$ 与NLLLoss类似,也可以添加权重参数:
用的时候需要在该层前面加上 Sigmoid 函数。
梯度下降
在介绍损失函数的时候我们已经说了,梯度下降是一个使损失函数越来越小的优化算法,在无求解机器学习算法的模型参数,即约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一。所以梯度下降是我们目前所说的机器学习的核心,了解了它的含义,也就了解了机器学习算法的含义。
Mini-batch的梯度下降法
对整个训练集进行梯度下降法的时候,我们必须处理整个训练数据集,然后才能进行一步梯度下降,即每一步梯度下降法需要对整个训练集进行一次处理,如果训练数据集很大的时候处理速度会很慢,而且也不可能一次的载入到内存或者显存中,所以我们会把大数据集分成小数据集,一部分一部分的训练,这个训练子集即称为Mini-batch。 在PyTorch中就是使用这种方法进行的训练,可以看看上一章中关于dataloader的介绍里面的batch_size就是我们一个Mini-batch的大小。
为了介绍的更简洁,使用 吴恩达老师的 deeplearning.ai 课程板书。
对于普通的梯度下降法,一个epoch只能进行一次梯度下降;而对于Mini-batch梯度下降法,一个epoch可以进行Mini-batch的个数次梯度下降。 普通的batch梯度下降法和Mini-batch梯度下降法代价函数的变化趋势,如下图所示:
如果训练样本的大小比较小时,能够一次性的读取到内存中,那我们就不需要使用Mini-batch,
如果训练样本的大小比较大时,一次读入不到内存或者现存中,那我们必须要使用 Mini-batch来分批的计算
- Mini-batch size的计算规则如下,在内存允许的最大情况下使用2的N次方个size
torch.optim
是一个实现了各种优化算法的库。大部分常用优化算法都有实现,我们直接调用即可。
torch.optim.SGD
随机梯度下降算法,带有动量(momentum)的算法作为一个可选参数可以进行设置,样例如下:
1 | #lr参数为学习率,对于SGD来说一般选择0.1 0.01.0.001,如何设置会在后面实战的章节中详细说明 |
torch.optim.RMSprop
除了以上的带有动量Momentum梯度下降法外,RMSprop(root mean square prop)也是一种可以加快梯度下降的算法,利用RMSprop算法,可以减小某些维度梯度更新波动较大的情况,使其梯度下降的速度变得更快
1 | #我们的课程基本不会使用到RMSprop所以这里只给一个实例 |
torch.optim.Adam
Adam 优化算法的基本思想就是将 Momentum 和 RMSprop 结合起来形成的一种适用于不同深度学习结构的优化算法
1 | # 这里的lr,betas,还有eps都是用默认值即可,所以Adam是一个使用起来最简单的优化方法 |
方差/偏差
- 偏差度量了学习算法的期望预测与真实结果的偏离程序,即刻画了学习算法本身的拟合能力
- 方差度量了同样大小的训练集的变动所导致的学习性能的变化,即模型的泛化能力
从图中我们可以看出 - 高偏差(high bias)的情况,一般称为欠拟合(underfitting),即我们的模型并没有很好的去适配现有的数据,拟合度不够。 - 高方差(high variance)的情况一般称作过拟合(overfitting),即模型对于训练数据拟合度太高了,失去了泛化的能力。
如何解决这两种情况呢?
欠拟合:
- 增加网络结构,如增加隐藏层数目;
- 训练更长时间;
- 寻找合适的网络架构,使用更大的NN结构;
过拟合 :
- 使用更多的数据;
- 正则化( regularization);
- 寻找合适的网络结构;
正则化
利用正则化来解决High variance 的问题,正则化是在 Cost function 中加入一项正则化项,惩罚模型的复杂度,这里我们简单的介绍一下正则化的概念
L1正则化
损失函数基础上加上权重参数的绝对值 $L=E_{in}+\lambda{\sum_j} \left|w_j\right|$;
L2正则化
损失函数基础上加上权重参数的平方和 $L=E_{in}+\lambda{\sum_j} w^2_j$;
需要说明的是:$L_1$ 相比于 $L_2$ 会更容易获得稀疏解。
卷积神经网络经典模型LeNet-5
卷积神经网路的开山之作,麻雀虽小,但五脏俱全,卷积层、pooling层、全连接层,这些都是现代CNN网络的基本组件 - 用卷积提取空间特征;
- 由空间平均得到子样本;
- 用 tanh 或 sigmoid 得到非线性;
- 用 multi-layer neural network(MLP)作为最终分类器;
- 层层之间用稀疏的连接矩阵,以避免大的计算成本。
输入:图像Size为32*32。
输出:10个类别,分别为0-9数字的概率
- C1层是一个卷积层,有6个卷积核(提取6种局部特征),核大小为5 * 5
- S2层是pooling层,下采样(区域:2 * 2 )降低网络训练参数及模型的过拟合程度。
- C3层是第二个卷积层,使用16个卷积核,核大小:5 * 5 提取特征
- S4层也是一个pooling层,区域:2*2
- C5层是最后一个卷积层,卷积核大小:5 * 5 卷积核种类:120
- 最后使用全连接层,将C5的120个特征进行分类,最后输出0-9的概率
1 | import torch.nn as nn |