PyTorch Learning
学习资料:https://github.com/zergtant/pytorch-handbook
中文手册:https://handbook.pytorch.wiki/
入门学习第一弹
Chapter1
1.1 Pytorch 简介
PyTorch是一个基于Torch的Python开源机器学习库,是一个Python包,提供两个高级功能:
- 具有强大的GPU加速的张量计算(如NumPy)
- 包含自动求导系统的深度神经网络
1.2 PyTorch 环境搭建
完成入门任务1.1时一边搭fairseq框架一边补上pytorch环境,感觉可能踩的坑都让我踩了一遍…
1.3 相关知识简单介绍
1.3.1 张量tensor
张量(Tensor)是一个定义在一些向量空间和一些对偶空间的笛卡儿积上的多重线性映射,其坐标是|n|维空间内,有|n|个分量的一种量, 其中每个分量都是坐标的函数。
r称为该张量的秩或阶(与矩阵的秩和阶均无关系)
在同构的意义下,第零阶张量 (r = 0) 为标量 (Scalar),第一阶张量 (r = 1) 为向量 (Vector), 第二阶张量 (r = 2) 则成为矩阵 (Matrix),第三阶以上的统称为多维张量。
Tensor的基本数据类型有5种:
- torch.FloatTensor
- torch.LongTensor
- torch.IntTensor
- torch.ShortTensor
- torch.DoubleTensor
Tensors与Numpy种的ndarrays类似,但是在PyTorch中Tensors可以用GPU进行计算。
1 | from __future__ import print_function |
操作
1.创建矩阵:
1 | #创建一个5*3未初始化的矩阵 |
2.根据现有张量创建张量:
1 | #new_*方法创建对象 |
3.获取size:
torch.size返回值是tuple类型,支持tuple类型的所有操作
1 | print(x.size()) |
1 | torch.size([5,3]) |
4.加法
1 | #法一 |
5.替换
任何以“_”结尾的操作都会用结果替换原变量,如x.copy_(y),x.t_()都会改变x
1 | #add x to y |
6.改变维度与大小
torch.view函数的运用(与Numpy的reshape类似)
1 | x=torch.ones(4,4) |
若只有一个元素的张量(即为标量),使用.item()来得到Python数据类型的数值
1 | x=torch.randn(1) |
1 | torch([-0.2368]) |
更多操作:https://pytorch.org/docs/stable/torch.html
NumPy转换
Torch Tensor与NumPy数组共享底层内存地址,修改一个会导致另一个的变化。
- tensor->numpy:
1 | a=torch.ones(5) |
- numpy->tensor
1 | import numpy as np |
设备间转换
使用.cuda方法将tensor移动到gpu
使用.cpu方法将tensor移动到cpu
有多个gpu时,用.to来确定使用哪个设备:
1 | if torch.cuda.is_available(): |
1 | tensor([0.7632], device='cuda:0') |
1.3.2 Autograd:自动求导机制
autograd
包为张量上的所有操作提供了自动求导。
它是一个在运行时定义的框架,这意味着反向传播是根据你的代码来确定如何运行,并且每次迭代可以是不同的。
核心类:torch.Tensor
和Function
如果设置.requires_grad
为 True
,那么将会追踪所有对于该张量的操作。 当完成计算后通过调用 .backward()
,自动计算所有的梯度,这个张量的所有梯度将会自动积累到 .grad
属性。
每个张量都有一个.grad_fn
属性,这个属性引用了一个创建了Tensor
的Function
(除非这个张量是用户手动创建的,即这个张量的grad_fn是None
)
为了防止跟踪历史记录:
- 调用
.detach()
方法将其与计算历史记录分离 - 将代码块包装在
with torch.no_grad():
中
1 | import porch |
1 | tensor([[3., 3.], |
.requires_grad_( ... )
可以改变现有张量的 requires_grad
属性。如果没有指定的话,默认输入的flag是 False
。
1 | a = torch.randn(2, 2) |
1 | False |
梯度
在数学上,如果我们有向量值函数 $\vec{y} = f(\vec{x})$ ,且 $\vec{y}$ 关于 $\vec{x}$ 的梯度是一个雅可比矩阵(Jacobian matrix):
$J = \begin{pmatrix} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{1}}{\partial x_{n}} \ \vdots & \ddots & \vdots \ \frac{\partial y_{m}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{pmatrix}$
一般来说,**torch.autograd
就是用来计算vector-Jacobian product的工具。**也就是说,给定任一向量 $v=(v_{1};v_{2};\cdots;v_{m})^{T}$ ,计算 $v^{T}\cdot J$ ,如果 $v$ 恰好是标量函数 $l=g(\vec{y})$ 的梯度,也就是说 $v=(\frac{\partial l}{\partial y_{1}};\cdots;\frac{\partial l}{\partial y_{m}})^{T}$,那么根据链式法则,vector-Jacobian product 是 $l$ 关于 $\vec{x}$ 的梯度:
$J^{T}\cdot v = \begin{pmatrix} \frac{\partial y_{1}}{\partial x_{1}} & \cdots & \frac{\partial y_{m}}{\partial x_{1}} \ \vdots & \ddots & \vdots \ \frac{\partial y_{1}}{\partial x_{n}} & \cdots & \frac{\partial y_{m}}{\partial x_{n}} \end{pmatrix} \begin{pmatrix} \frac{\partial l}{\partial y_{1}}\ \vdots \ \frac{\partial l}{\partial y_{m}} \end{pmatrix} = \begin{pmatrix} \frac{\partial l}{\partial x_{1}}\ \vdots \ \frac{\partial l}{\partial x_{n}} \end{pmatrix}$
(注意,$v^{T}\cdot J$ 给出了一个行向量,可以通过 $J^{T}\cdot v$ 将其视为列向量)
- 简单的自动求导:
如z=x+y
z.backward()
此时z.backward()相当于z.backward(torch.tensor(1.))的简写
- 复杂的自动求导
如z=x**2+y**3
z.backward(torch.ones_like(x))
//因为我们的返回值不是一个标量,所以需要输入一个大小相同的张量作为参数,这里我们用ones_like函数根据x生成一个张量//
举个栗子:
1 | x=tprch.randn(3,requires_grad=True) |
1 | tensor([ 293.4463, 50.6356, 1031.2501], grad_fn=<MulBackward0>) |
关于autograd的官方文档:https://pytorch.org/docs/stable/autograd.html
如果需要自定义autograd扩展新的功能,就需要扩展Function类。
需定义以下函数:
1 | __init__(optional): #不需要的话可以忽略 |
1.3.3 Neural Networks
训练过程:
1.定义参数/权重
2.在数据集上迭代
3.通过神经网络处理输入
4.计算损失
5.将梯度反向传播回网络的参数
6.更新网络的参数
PyTorch中已经为我们准备好了现成的网络模型,只要继承nn.Module,并实现它的forward方法,PyTorch会根据autograd,自动实现backward函数。
具体分为两大步:
- 定义一个网络–>处理输入,调用backward:
1 | #定义一个网络 |
- 计算损失–>更新网络权重:
损失函数:传入一对(output,target),计算一个值来估计网络的输出和目标值相差多少。
如nn.MSELoss(计算输出和目标间的均方误差)
1 | output=net(input) |
反向传播:调用loss.backward()获得反向传播的误差
1 | net.zero_grad() #清除梯度 |
更新权重
使用torch.optim包,可实现不同的更新规则
1 | import torch.optim as optim |
1.3.4 Cifar10 Tutorial:训练一个分类器
对数据的处理:使用标准的Python包来加载数据到一个numpy数组中,然后把这个数组转换成torch.*Tensor
- 图像可以使用 Pillow, OpenCV, torchvision
- 音频可以使用 scipy, librosa
- 文本可以使用原始Python和Cython来加载,或者使用 NLTK或 SpaCy 处理
训练一个图像分类器
步骤:
1.使用torchvision加载和归一化CIFAR10训练集和测试集
2.定义一个卷积神经网络
3.定义损失函数
4.在训练集上训练网络
5.在测试集上测试网络
1.使用torchvision加载和归一化CIFAR10训练集和测试集
主要利用函数torchvision.datasets
和 torch.utils.data.DataLoader
1 | import torch |
2.定义一个卷积神经网络
见1.3.2节
3.定义损失函数和优化器
1 | import torch.optim as optim |
4.训练网络
在数据迭代器上循环,将数据输入给网络,并优化
1 | for epoch in range(2): #多批次循环 |
5.在测试集上测试网络
通过预测神经网络输出的类别标签与实际情况标签进行对比来进行检测。如果预测正确,我们把该样本添加到正确预测列表。
1 | class_correct=list(0. for i in range(10)) |
下一步,在GPU上训练
递归遍历所有模块并将模块的参数和缓冲区转换成CUDA张量:
1 | net.to(device) |
1.3.5 Data parallel tutorial数据并行
使用DataParallel可以让模型并行运行在多个GPU上
1 | model=nn.DataParallel(model) |
1.导入PyTorch模块和定义参数
1 | import torch |
2.虚拟数据集
实现__getitem__函数
1 | class RandomDataset(Dataset): |
3.简单模型
1 | class Model(nn.Module): |
4.创建一个模型和数据并行
我们需要创建一个模型实例和检测我们是否有多个GPU。如果有多个GPU,使用nn.DataParallel
来包装我们的模型。然后通过model.to(device)
把模型放到GPU上。
1 | model=Model(input_size,output_size) |
5.运行模型
可以看到输入和输出张量的大小
如果有多个GPU,DataParallel会自动地划分数据,并将作业发送到多个GPU上的多个模型。并在每个模型完成作业后,收集合并结果并返回。
1 | for data in rand_loader: |
Chapter 2
第一节 PyTorch basics
见1.3
第二节 深度学习基础及数学原理
1.监督学习和无监督学习
常见的机器学习方法:
- 监督学习:通过已有的训练样本(即已知数据以及其对应的输出)去训练得到一个最优模型(这个模型属于某个函数的集合,最优则表示在某个评价准则下是最佳的),再利用这个模型将所有的输入映射为相应的输出。
- 无监督学习:没有任何训练样本,直接对数据进行建模
- 半监督学习:在训练阶段结合了大量未标记的数据和少量标签数据
- 强化学习:设置回报函数(reward function)
2.线性回归
表达形式为y = w’x+e,e为误差服从均值为0的正态分布
简言之:线性回归对于输入x和输出y有一个映射f,y=f(x),f的形式为aX+b,我们训练的就是a,b这两个参数。
#利用pytorch做线性回归过程:
定义一个线性函数–>生成一些随机的点作为训练数据–>开始训练(输入输出、计算损失、反向传播、优化…)–>提取模型参数进行对比
3.损失函数(Loss Function)
损失函数是一个非负实值函数,损失函数越小,模型越好,代表算法达到意义上的最优。
我们训练模型的过程,就是通过不断的迭代计算,使用梯度下降的优化算法,使得损失函数越来越小。
注:因为PyTorch是使用mini-batch来进行计算的,所以损失函数的计算出来的结果已经对mini-batch取了平均
————————————
mini-batch
使用整个训练集的优化算法是batch梯度算法,每次只使用单个样本的优化算法是stochastic算法。介于两者之间的是mini-batch算法,可视作在学习过程中加入了噪声扰动,这种扰动会带来一些正则化效果。
————————————
常见pytorch内置的损失函数如下:
nn.L1Loss
输入x和目标y之间差的绝对值。
$loss(x,y)=\frac{1}{n}\Sigma|x_i-y_i|$
nn.NLLLoss
用于多分类的负对数似然损失函数
$loss(x,class)=-x[class]$
NLLLoss中如果传递了weights参数,会对损失进行加权,公式变为:
$loss(x,class)=-weights[class]*x[class]$
nn.MSELoss
均方损失函数,输入x和目标yy之间的均方差
$loss(x,y)=\frac{1}{n}\Sigma(x_i-y_i)^2$
nn.CrossEntropyLoss
多分类用的交叉熵损失函数,会调用nn.NLLLoss函数
$ \begin{aligned} loss(x, class) &=
-\text{log}\frac{exp(x[class])}{\sum_j exp(x[j]))}\ &= -x[class] +
log(\sum_j exp(x[j])) \end{aligned} $
同理,可传入weights参数:
$ loss(x, class) = weights[class] * (-x[class] + log(\sum_j exp(x[j]))) $
一般多分类的情况会使用这个损失函数
nn.BCELoss
计算x与y之间的二进制交叉熵
$ loss(o,t)=-\frac{1}{n}\sum_i(t[i] log(o[i])+(1-t[i]) log(1-o[i])) $
也可加权重参数:
$ loss(o,t)=-\frac{1}{n}\sum_iweights[i] (t[i] log(o[i])+(1-t[i])* log(1-o[i])) $
4.梯度下降
梯度下降是一个使损失函数越来越小的优化算法。
梯度是函数变化增加最快的地方,沿着梯度方向容易找到函数的最大值,沿着梯度向量相反的方向梯度减少最快,更容易找到函数的最小值。
Mini-batch的梯度下降法:
我们将大数据集分成小数据集,一部分一部分的训练,这个训练子集即称为Mini-batch,上一章有关dataloader的介绍里面的batch_size就是一个Mini-batch大小
Mini-batch size的计算规则如下,在内存允许的最大情况下使用2的N次方个size
可以直接调用torch.optim里的优化算法,如torch.optim.SGD,torch.optim.RMSprop,torch.optim.Adam等
5.方差/偏差
偏差度量了学习算法的期望预测与真实结果的偏离程序,即刻画了学习算法本身的拟合能力
方差度量了同样大小的训练集的变动所导致的学习性能的变化,即模型的泛化能力
1.欠拟合:high bias(高偏差),即我们的模型没有很好地去适配现有的数据,拟合度不够
解决方法:
- 增加网络结构,如增加隐藏层数目
- 训练更长时间
- 寻找合适的网络架构,使用更大的NN结构
2.过拟合:high variance(高方差),即模型对于训练数据拟合度太高了,失去了泛化的能力(通俗来说就是无法推广到一般情况)
解决方法:
- 使用更多的数据
- 正则化(regulation)
- 寻找合适的网络结构
6.正则化
正则化是在 Cost function 中加入一项正则化项,惩罚模型的复杂度
L1正则化
损失函数基础上加上权重参数的绝对值
$ L=E_{in}+\lambda{\sum_j} \left|w_j\right|$
L2正则化
损失函数基础上加上权重参数的平方和
$ L=E_{in}+\lambda{\sum_j} w^2_j$
L1相比于L2会更容易获得稀疏解
第三节 神经网络简介introduction
每一个神经元(上面说到的简单单元)接受输入x,通过带权重w的连接进行传递,将总输入信号与神经元的阈值进行比较,最后通过激活函数处理确定是否激活,并将激活后的计算结果y输出,而我们所说的训练,所训练的就是这里面的权重w。
我们可以将神经元拼接起来,两层神经元,即输入层+输出层(M-P神经元),构成感知机。
而多层功能神经元相连构成神经网络,输入层与输出层之间的所有层神经元,称为隐藏层:
激活函数
用来判断我们所计算的信息是否达到了往后面传输的条件
在神经网络的计算过程中,每层都相当于矩阵相乘,无论神经网络有多少层输出都是输入的线性组合,所以需要激活函数来引入非线性因素,故激活函数都是非线性的。
常见的激活函数:
sigmoid函数
$a=\frac{1}{1+e^{-z}}$ 导数 :$a^\prime =a(1 - a)$
此函数的输出是在(0,1)这个开区间,它能够把输入的连续实值变换为0和1之间的输出。
当输入稍微远离了坐标原点,函数的梯度就变得很小了(几乎为零)。在神经网络反向传播的过程中不利于权重的优化,这个问题叫做梯度饱和,也可以叫梯度弥散。
tanh函数
双曲正切函数
$a=\frac{e^z-e^{-z}}{e^z+e^{-z}}$ 导数:$a^\prime =1 - a^2$
优点在于以0为中心点,能起到归一化(均值为0)的结果。
一般二分类问题中,隐藏层用tanh函数,输出层用sigmod函数,但是随着Relu的出现所有的隐藏层基本上都使用relu来作为激活函数了
ReLU函数
$a=max(0,z)$ 导数大于0时1,小于0时0
当输入是负数的时候,ReLU是完全不被激活的,这就表明一旦输入到了负数,ReLU就会死掉。但是到了反向传播过程中,输入负数,梯度就会完全到0,这个和sigmod函数、tanh函数有一样的问题。 但是实际的运用中,该缺陷的影响不是很大。
Leaky Relu函数
该函数保证在z<0的时候,梯度仍然不为0
ReLU的前半段设为αz而非0,通常α=0.01 $ a=max(\alpha z,z)$
在隐藏层中推荐优先尝试ReLU函数
第四节 卷积神经网络cnn
暂略过
第五节 循环神经网络rnn
简介
本质是:拥有记忆的能力,并且会根据这些记忆的内容来进行推断。因此,他的输出就依赖于当前的输入和记忆。
循环神经网络的提出便是基于记忆模型的想法,期望网络能够记住前面出现的特征.并依据特征推断后面的结果,而且整体的网络结构不断循环,因为得名循环神经网络。
最常用的RNN类型是LSTM,RNN在NLP中常用来语言建模与生成文本,如机器翻译,语音识别,生成图像描述等。
RNN的网络结构及原理
RNN
pytorch 中使用 nn.RNN 类来搭建基于序列的循环神经网络,它的构造函数有以下几个参数:
- input_size:输入数据X的特征值的数目
- hidden_size:隐藏层的神经元数量,也就是隐藏层的特征数量
- num_layers:循环神经网络的层数,默认值是 1
- bias:默认为 True,如果为 false 则表示神经元不使用 bias 偏移参数
- batch_first:如果设置为 True,则输入数据的维度中第一个维度就是 batch 值,默认为 False。默认情况下第一个维度是序列的长度,第二个维度才是batch,第三个维度是特征数目
- dropout:如果不为空,则表示最后跟一个 dropout 层抛弃部分数据,抛弃数据的比例由该参数指定
其中最主要的参数是input_size和hidden_size,其余参数采用默认值就可以了
1 | rnn = torch.nn.RNN(20, 50, 2) |
RNN的工作机制
相较于普通的神经网络,RNN多了一个hidden_state来保存历史信息。
对于RNN来说我们只需要记住一个公式:
$h_t = \tanh(W_{ih} x_t + b_{ih} + W_{hh} h_{(t-1)} + b_{hh}) $
$x_t$ :当前状态的输入值,$h_{(t-1)}$ :要传入的上一个状态的hidden_state,也就是记忆部分
整个网络要训练的部分就是 $W_{ih}$ (当前状态输入值的权重),$W_{hh}$ (hidden_state,也就是上一个状态的权重)还有两个输入偏置值。
这四个值加起来使用tanh进行激活,pytorch默认是使用tanh作为激活,也可以通过设置使用relu作为激活函数。
上面讲的步骤就是用红框圈出的一次计算的过程
一个RNN模型如下:
1 | class RNN(object): |
LSTM
翻译为长的短时记忆网络(Long Short Term Memory Networks)
标准的循环神经网络内部只有一个简单的层结构,而 LSTM 内部有 4 个层结构:
第一层是个忘记层:决定状态中丢弃什么信息
第二层tanh层用来产生更新值的候选项,说明状态在某些维度上需要加强,在某些维度上需要减弱
第三层sigmoid层(输入门层),它的输出值要乘到tanh层的输出上,起到一个缩放的作用,极端情况下sigmoid输出0说明相应维度上的状态不需要更新
最后一层决定输出什么,输出值跟状态有关。候选项中的哪些部分最终会被输出由一个sigmoid层来决定。
pytorch 中使用 nn.LSTM 类来搭建基于序列的循环神经网络,参数基本与RNN类似
1 | lstm = torch.nn.LSTM(10, 20,2) |
GRU
GRU是gated recurrent units的缩写
GRU 和 LSTM 最大的不同在于 GRU 将遗忘门和输入门合成了一个”更新门”,同时网络不再额外给出记忆状态,而是将输出结果作为记忆状态不断向后循环传递,网络的输人和输出都变得特别简单。
1 | rnn = torch.nn.GRU(10, 20, 2) |
循环网络的向后传播(BPTT)
反向传播(BPTT)指“回到过去”改变权重
词嵌入
我们需要对词汇进行表征,才能让计算机更好地理解我们的语言
词嵌入:用不同的特征来对各个词汇进行表征,相对与不同的特征,不同的单词均有不同的值
实际上是在多维空间中,寻找词向量之间各个维度的距离相似度,这样我们就可以实现类比推理,找到不同词之间的关联关系
在 PyTorch 中我们用 nn.Embedding 层来做嵌入词袋模型,Embedding层第一个输入表示我们有多少个词,第二个输入表示每一个词使用多少维度的向量表示。
其他重要概念
Beam search集束搜索
Beam search可以看做是做了约束优化的广度优先搜索,首先使用广度优先策略建立搜索树,树的每层,按照启发代价对节点进行排序,然后仅留下预先确定的个数(Beam width-集束宽度)的节点,仅这些节点在下一层次继续扩展,其他节点被剪切掉。
- 将初始节点插入到list中
- 将给节点出堆,如果该节点是目标节点,则算法结束;
- 否则扩展该节点,取集束宽度的节点入堆。然后到第二步继续循环。
- 算法结束的条件是找到最优解或者堆为空。
可参考
Attention Model注意力模型
即模拟大脑关注一幅画面的部分信息,从而略去大量无用的视觉信息。
神经网络通过改变权重实现。
Feelings
暂时学到这里,等以后有时间了再进一步学习
主要是后面还有好多东西需要我学呢呜呜
这个领域对我来说是一个全新的事物,我要新学好多东西才能开始做入门任务(sigh