《PyTorch深度学习实践》B站视频学习笔记
练习只做了简单的,剩下的都没做。
B站视频【《PyTorch深度学习实践》完结合集】-刘二大人
Overview
深度学习的系统
- 基于规则的深度学习
- 经典的机器学习——手动提取一些简单的特征
- 表示学习方法——提取特征
起因:“维数诅咒”——维度越高,训练一个性能较好的模型对数据量的要求就越高。
解决方法:希望通过降维的方法减少对数据量的要求,同时在降维的过程中还需要保持在高维空间里的一些度量信息,这样的过程就是“表示学习”。 - 深度学习——利用一些简单的特征,即较为原始的特征
- 深度学习与传统学习的区别:传统学习中特征学习和学习器是分开的;但是在深度学习中,所有的训练都是统一的。
线性模型
数据集的划分
样本损失和trainning过程整体损失(也叫平均平方损失MSE)
用穷举法确定线性模型的参数
visdom库可用于可视化
另外,np.meshgrid()可用于绘制三维图
梯度下降算法
分治法
即通过划分不同精度的搜索范围,逐步缩小搜索范围,以解决搜索范围过大的问题。
梯度下降算法
-
梯度下降算法是贪心算法的一种,贪心算法即只走眼前最好的一步,容易陷入局部最优。
-
深度学习的损失函数其实并没有太多的局部最优点,但是会有鞍点,当在鞍点时没有办法继续迭代。
其实在深度学习中最需要解决的是鞍点问题,高维空间中的鞍点是指在某个方向可能是最小点,但在另一个方向是最大点。 -
对上讲中的线性模型利用梯度下降算法编程求解
-
画图时可以对损失值进行一个平滑,更容易观察整体的下降趋势
其中 c i c_i ci是第i次的损失值,下面一行是平滑后的损失值,平滑的计算公式如下:
训练失败时的损失值变化情况
如果损失值的变化如下图所示,就说明这次训练失败了,训练发散。
随机梯度下降
在深度学习中梯度下降用的并不多,更多使用随机梯度下降。梯度下降用的是所有样本的损失平均值求梯度更新参数,而随机梯度下降是用一个样本的损失值求梯度来更新参数。
为什么要使用随机梯度下降
因为损失函数有可能是如下图所示的样子,存在鞍点,但是如果只用一个样本,因为我们拿到的样本基本都是有噪声的,这样就引入了一个随机噪声,有可能帮助我们跳出这个鞍点,继续训练。(??)
使用随机梯度下降后的变化
-
迭代公式的变化
-
代码实现的变化
使用随机梯度下降和梯度下降的优缺点
- 随机梯度下降
因为随机梯度下降在求下一个样本的值时是需要依赖上一个样本对参数的更新,所以随机梯度下降不能并行计算,时间效率较低,但是它的结果一般较好。 - 梯度下降
时间效率高,因为样本间值的求解,(即f(x))不存在依赖关系,但是结果不一定好。
深度学习中常用batch
综合考虑梯度下降和随机梯度下降的优缺点,深度学习中一般使用batch来训练,即小批量的随机梯度下降算法。
PS:实际上,在原始的表达中,batch代表整体的数据,小批量的数据用mini-batch表示,但因为目前mini-batch是主流,在说小批量时会将mini省略而说成batch。
反向传播算法
矩阵求导的计算公式可以在《matrix-cook-book》这本书中找到。
-
对于线性的模型,不论加多少层,最终都可以化简成一个只有一层的线性模型,即增加网络深度没有意义。
-
为了解决上述问题,加了一个非线性函数的变换
反向传播backward
正向传播——forward
-
反向传播过程图
-
举例
这里z对x和w求偏导都是根据f的计算得到的,这里没有解释损失对z求偏导的过程(??)。
3. 反向传播的计算过程
其中r为残差项,是y与y_hat的差值,即r=y_hat-y,在前向传播的过程中,根据每一步进行的运算可以很容易求出对应的偏导。接下来是梯度反向传播的过程:
Pytorch中的tensor
tensor中最重要的两个成员data和grad,它们分别存储值及对应的导数。
代码实现
step 1
step 2
-
注意这里的x转化为tensor类型,且在这一步已经构建了如右边所示的计算图,而且因为输入中有w需要计算梯度,所以两者相乘的结果也是需要计算梯度的。
-
看到这些代码要意识到它是在构建计算图。
step 3
-
这里计算的l是一个张量,故可以调用其backward()方法,从而将前向传播中所有需要梯度的地方都求出来。一旦调用完bachward()方法后,计算图就被释放了,下次计算loss时就会获得新的计算图。
-
这里的grad也是一个tensor数据,直接取它的data进行计算,否则又会生成一个计算图计算其梯度。
-
item()是将其变为一个标量。
-
注意如果要算整体数据的损失值,不能用sum+=l,而应该是sum+=l.item()。因为直接加l,当数据量很多的时候,在一次又一次加的过程中会生成一个很大的计算图,很吃内存。(tensor数据类型在做运算时会构建计算图。)
-
w.grad.data.zero_()对梯度清零,如果不对其清零,那么下一次求的梯度值就是在原梯度值的基础上再加上下一次的梯度值。
一个练习
import torch
x_data=[1,2,3]
y_data=[2,4,6]
w1=torch.Tensor([1.0])
w1.requires_grad=True
w2=torch.Tensor([1.0])
w2.requires_grad=True
b=torch.Tensor([1.0])
b.requires_grad=True
def forward(x):
return w1*x**2+w2*x+b
def loss(x,y):
y_pred=forward(x)
return (y_pred-y)**2
print('Predict (before training):',4,forward(4).item())
for epoch in range(100):
for x,y in zip(x_data,y_data):
l=loss(x,y)
l.backward()
w1.data=w1.data-0.01*w1.grad.data
w2.data = w2.data - 0.01 * w2.grad.data
b.data = b.data - 0.01 * b.grad.data
w1.grad.data.zero_()
w2.grad.data.zero_()
b.grad.data.zero_()
print('Progress:',epoch,l.item())
print('Predict (after training):',4,forward(4).item())
用PyTorch实现线性回归
整体步骤
- 注意在第2步求的不是损失而是y_hat。
小批量计算
-
在用pytorch进行深度学习时,重点在于构建计算图,因为一旦构建出计算图,可以很容易求出梯度,不再需要人工求计算梯度的公式。
-
在计算loss时,最后一定是一个标量而不是向量,因为只有标量才能进行backward。
代码
step 2构建模型
- 在这个类中必须要有两个函数__init__()和forward(),这里没有backward()是因为module这个类会自动根据forward得到的计算图去构建backward()。
- 注意如果自己定义的运算较为复杂,pytorch不能直接求梯度,那么可以有两种解决方法。方法1:将其拆分为pytorch中已经实现的各个小的计算单元。方法2:利用function类来定义计算,但要注意用function类的时候需要定义backward()方法。
- self.linear(x)在一个对象里面直接放x进行计算,说明这是一个可调用的,即有__call__(),torch.nn.Module里面有__call__()
step 3损失函数和优化器
torch.nn.MSELoss()也是继承自nn.Module
step 4
-
这里print(epoch,loss)中loss会自动调用__str__(),故不会产生计算图。
-
optimizer.zero_grad()将梯度归零。
-
optimizer.step()进行参数更新,类似于前面的w.data=w.data-0.01*w.grad.data()。
step 5
-
这里model.linear.weight虽然只有一个数值,但是是一个矩阵,为了直接显示数值故加上item()。
torch.nn.Linear类介绍
- 其中in_features和out_features都是指单个样本的维度。
self.linear(x)的解释
举例说明*args和 **kwargs的作用,在不确定用户传入多少参数时,对于1,2,4,3这样的参数会被传入args变成一个元组,对于x=3,y=5这样的参数会被传入kwargs变成一个字典。
torch.nn.MSELoss()类的介绍
是否求均值在每个batch数量都一样时对结果没有影响。
- torch.optim.SGD()中需要传入的params(参数)是指需要更新的参数,model.parameters()是将模型中所有的参数都取出来。
完整代码
不同优化器
torch官网有更多用法的介绍
自己敲一遍
import torch
x_data=torch.Tensor([[1],[2],[3]])
y_data=torch.Tensor([[2],[4],[6]])
class LinearModel(torch.nn.Module):
def __init__(self):
super(LinearModel,self).__init__()
self.linear=torch.nn.Linear(1,1)
def forward(self,x):
y_pred=self.linear(x)
return y_pred
model=LinearModel()
criterion=torch.nn.MSELoss(size_average=False)
optimeter=torch.optim.SGD(model.parameters(),lr=0.01)
for epoch in range(1000):
y_pred=model(x_data)
loss=criterion(y_pred,y_data)
print(epoch,loss.item())
optimeter.zero_grad()
loss.backward()
optimeter.step()
print('w=',model.linear.weight.item())
print('b=',model.linear.bias.item())
x_test=torch.Tensor([[4]])
print('x_test_pred=',model(x_test).data)
print('x_test_pred=',model(x_test).item())
逻辑斯蒂回归
Logistic Regression模型虽然叫回归模型,但是是实现分类的。
关于分类模型
对于分类模型并不能直接让输出结果等于类别标号。例如在手写数字识别的任务中,数字并不代表大小关系,在形态上7和9更加相像,即在输入空间7和9更相近,但如果使其对应的y只有一个数字,在输出空间上7和8更相近,显然是不合理的。故在分类任务中都是预测属于哪一类的概率是多大。
torchvision中的数据集
MNIST数据集和CIFAR10数据集。
logistic函数
- sigmoid函数如图所示,其导函数是0——>增大——>0,将导函数是这样的函数称为饱和函数。
其他sigmoid函数
- sigmoid函数是指满足1.有极限,如上在[-1,1]之间;2.是增函数;3.是饱和函数。
- tanh(x)在循环神经网络中用到很多。
- sigmoid函数中最出名的是logistic函数,故现在约定俗成的将logistic函数叫做sigmoid函数。
交叉熵损失
- 上式是一个交叉熵损失,用于衡量两个分布D和T之间的差异。
- 对于二分类损失函数BCE,为了让loss值小,当y=1时y_hat需要尽量大,当y=0时y_hat需要尽量小,满足预测分类的要求。
代码
完整代码
画图代码
其中np.linspace()表示在[0,10]之间取200个点,view()相当于reshape,将其变成(200,1)的tensor数据。
处理多维特征的输入
数据
损失函数的变化
多特征输入的批量计算
- 需要更改的只有self.linear(),输入特征是8维,输出是1维。
- 一般来说隐层数量越多,神经元越多,学习能力就会越强。但不是学习能力越强越好,因为学习能力过强可能会将噪声也学进去。
神经网络模型
代码
step 1
-
载入的数据是32位浮点数的类型,因为只有较好的显卡才支持double类型数据,对于神经网络来说float32已经够用了。
-
x_data读取中xy[:,:-1]表示最后一列数据不要。y_data只取最后一列数据,[-1]表示取的是一个矩阵,如果直接是-1则会变成一个向量。
-
torch.from_numpy()是根据numpy生成一个tensor。
step 2
-
sigmoid里面没有参数,因此只定义一个即可。
step 3
step 4
不同的激活函数
注意如果使用其他的激活函数,最后一个输出y_hat的激活函数要改成sigmoid,使得y_hat值在[0,1]。
加载数据集
Epoch,Batchsize,Iterations解释
- 若样本总数为1000,batchsize=100,则iteration=样本总数/batchsize=10。
Dataloader
- shufflr=True,说明数据先被打乱。
代码
step 1
- Dataset是一个抽象类,抽象类不能被实例化,只能被其他的子类继承。
- getitem()表示支持索引操作,是一个魔法方法。
- 有两种数据集的思路,当数据集不大时可以直接存储数据Data,用索引找样本。当数据集大且复杂时,存贮数据集样本的位置等,需要用时根据索引得到文件位置,进而读取样本。
- num_workers表示在读取数据时几个并行的进程,可以提高读取的效率。
step 2
step 3
- 可以将data改为(inputs,labels),就可以省略inputs,labels=data。
在windows系统下报错解决
报错是因为liunx和windows实现多进程的使用的方法不同,一个是fork,一个是spawn,解决在windows下的报错,可以做如下改变:
完整代码
其他数据集
- transforms.ToTensor()将数据变为tensor类型,且像素值变到[0,1]之间。
- 一般在训练数据集shuffle=True,而测试时shuffle=False
练习
没整出来,CSDN上有PyTorch深度学习实践概论笔记8练习-kaggle的Titanic数据集预测(一)数据分析数据分析那块用了好多图,超炫!
多分类问题
为什么用Softmax层
Softmax层的输出可以满足分布的要求,即总和为1,每个值都大于等于0.
- 其中z^l是Softmax层前最后一层的输出。
损失函数
- 右边输入的是标签号。
代码实现
Pytorch代码实现
- CrossEntropyLoss()交叉熵损失。在使用交叉熵损失时注意网络最后一层不用加softmax,因为softmax包含在交叉熵损失的计算里。
- LongTensor——长整型的张量。
举例
练习-交叉熵损失和NLL损失的区别
用MNIST数据集举例实现多分类问题
代码
step1
step 2
-
transforms时一些对图像的处理。在神经网路中,神经网络会希望输入尽量小(比如在[-1,1]之间)且最好符合一个正态分布。
-
ToTensor将数据变为chw,方便后续运算。
-
0.1307是均值,0.3081是标准差std
step 3
-
view改变大小。
-
最后一层不做激活。
step 4
step 5
step 6
- 一般用全连接层对图像进行训练结果不会太好。一是权重不够,二是用的特征比较原始。
- 在深度学习中一般会提取特征,一种是用FFT或者小波变换等。第二种是自动提取,比如利用CNN。
练习
卷积神经网络(基础篇)
卷积神经网络
栅格图像和矢量图像
栅格图像成像
卷积
max pooling默认stride=2,变化前后通道数不变。
手写数字识别任务的卷积神经网络
如何使用GPU
卷积神经网络(高级篇)
GoogLeNet
Inception Module的实现
- 在卷积里面有一些超参数,比如kernel,不知道多大的卷积核比较好,所以GoogleNet就定义了这样一个块inception Module,将几种卷积核都用上,如果33的卷积核效果会好,那么对应33卷积核的权重就会增加。
- concatenate是沿通道维度将其拼接起来,需要保证h和w是一致的。
1*1卷积——信息融合
- 使用1*1卷积改变通道数可以大大降低运算量。
Inception Module代码实现
- 因为tensor是(b,c,w,h),所以沿着dim=1即c进行concat。
完整代码
- 在inception之后通道的维度为24+16+24+24=88
- 1408是mnist数据集到x.view之后的维度。
梯度消失
当卷积层很深时,模型的性能不好可能是因为梯度消失,因为在反向传播更新梯度的过程中根据链式法则,梯度是累积相乘计算得出的,如果每一次的梯度都<1(很小)就可能造成梯度消失。
解决梯度消失的其中一个方法是逐层训练模型,例如如下图,第一次只要512的层,只训练它,第二次锁定训练好的512层,而只训练256的。
Residual
上述对于梯度消失问题的处理在深度学习中是较难实现的。
- Residual中先不做激活,将输出F(x)和输入相加之后再做激活。这样的好处是假设输出为z=F(x)+x(暂时不考虑激活),那么z对x求导=F对x求导+1。假如F对x求导的值很小,那么z对x求导的结果会在1附近而不是0附近,这样保证在输入附近的层会训练的比较好。
- 这样的结构要求F(x)和x的维度大小是一样的。
网络结构
- 这里虚线表示两者的张量维度不同,如果要做residual操作,可以加一个最大池化层将张量维度变成一样的。
以MNIST数据集为例实现Residual
Residual Block代码实现
代码实现
练习
学习路线的建议
循环神经网络(基础篇)
DNN
- Dense 稠密连接,就表示是全连接。
RNN
- RNN处理的是一个序列,序列的下一个值x2与x1有关。
- 在RNN中权重是共享的,即如果这里的RNN Cell是一个线性层,那么这个线性层被反复使用。
- 作为初始的h0,它可以是一个先验知识,例如用CNN+Fc学习的结果,也可以是一个与h1维度相同的值全为0的一个向量。
RNN的计算过程
- W i h W_ih Wih的维度为hidden_sizeinput_size; W h h W_hh Whh的维度为hidden_sizehidden_size。
- tanh是一个激活函数,在-1到1之间,是一个效果比较好的激活函数,在RNN中一般用tanh作为激活函数。
- 虽然看起来有两个线性变化,但这两个线性变换可以看作一个线性变换,如上图矩阵操作的公式所示,故在编程时可采用一个线性变化的形式。
如何实现RNN
方法一 构建自己的RNN Cell并据此构建RNN
- 注意这里的input的大小为batch*input_size。(一个batch的数据要一起输进去?)
方法二直接使用torch.nn.RNN
-
out对应[ h 1 h_1 h1,…, h N h_N hN];hidden对应 h N h_N hN。
-
使用torch.nn.RNN就不需要自己做循环。
-
SeqSize——序列长度。
-
numLayers——初始的隐层输入维度。
-
颜色相同的使用的是同一个线性层。
-
numLayers——RNN层的维度,即几层的RNN。
代码
batch_first=True
如果batch_size=True,则输入数据的大小需要改为(batch_size,seq_len,input_size)
实例编程
给字符创建字典
- vectors的长度和字典的长度相同。
模型结构
代码
用RNN Cell
step1
-
注意inputs维度改为(seqlen,batchsize,inputsize),labels维度改为(seqlen,1)。
step2
-
init_hidden默认生成初始的 h 0 h_0 h0。
-
batch_size只有在构造 h 0 h_0 h0时才需要。
step3
step4
用RNN
step 1
step 2
step 3
one-hot编码的缺点
缺点:
- 维度高——维数灾难
- 稀疏
- 硬编码。不是学习到的。
为了应对以上缺点,用embedding:
Embedding编码
Embedding编码就是将高维稀疏的映射到低维稠密的,即降维。
练习
循环神经网络(高级篇)
得到的隐层的输出与我们要求的输出维度不一定一致,故加一个线性层。
模型
对最后一个
h
N
h_N
hN加一个线性层,把结果变成18维(因为共有18个国家)。
代码
主循环
准备数据
首先将名字分离成一个列表,利用ASCII码作为字典(共128维),再进行padding使其变成长度一样的;对国家名进行一个编码。
模型设计
- input.t()表示transpose
- pack_padded_sequence()将数据进行打包,数据将根据序列长度排序且去除padding的0进行存储。
directions双向RNN
双向RNN中的forward和backward指的是序列的方向,它将两次的hidden进行拼接,最后输出的隐层是[ h N f h_Nf hNf, h N b h_Nb hNb]。