1.pytroch模型类
PyTorch 是一个易学且清晰明了的深度学习库。本节讲解如何查看一个模型的结构。 首先,最简单创建模型的方式如下:
#导入必要的库
import torch.nn as nn
myNet=nn.Sequential(
nn.Linear(2,10),#第一层(全连接层):接受两个输入特征,输出10个特征
nn.ReLU(),#对第一层的输出进行非线性变换,增加网络的表达能力,对第一层的输出进行非线性变换,增加网络的表达能力
nn.Linear(10,1)#将ReLU层的10个输出特征映射到1个输出特征
nn.Sigmoid()#将第二层的输出转换为0到1之间的值,常用于二分类问题中将输出转换为概率。
)
一般来说,PyTorch 的模型都会定义成一个类,然后在主函数中直接实例化这个类。比如类是设计图,实例化就是按照这个设 计图做出来的实物。
#导入必要的库
import torch
import torch.nn as nn
import torch.nn.functional as F #torch.nn.functional 包含了一些函数式的接口,可以用来构建自定义的前向传播。
#开始定义模型类
class Network(nn.Module):#定义了一个名为 Network 的新类,它继承自 nn.Module。nn.Module 是PyTorch中所有神经网络模块的基类
def __init __(self):#这行代码调用了父类 nn.Module 的构造函数。
super(Network,self). __init __()#定义了一个名为 dis 的 nn.Sequential 模型
self.dis=nn.Sequential(
nn.Linear(2,32),#nn.Sequential 是一个容器,它按照它们在构造器中传递的顺序执行模块
nn.LeakyReLU(0.2),#这是一种改进的ReLU函数,它允许负输入有一个小的非零输出,这有助于解决梯度消失问题。参数 0.2 指定了负斜率
nn.Linear(32,32),
nn.LeakyReLU(0.2),
nn.Linear(32,1),
nn.Sigmoid()#这是一个将输出值压缩到0和1之间的激活函数,通常用于二分类问题的输出层
)
#模型包含三个全连接层(nn.Linear)和两个 LeakyReLU 激活函数,最后是一个 Sigmoid 激活函数。每个 nn.Linear 层都指定了输入和输出的特征数量。
def forward(self,x):
#forward 方法定义了数据通过模型的前向传播路径。在这个方法中,输入数据 x 通过 dis 序列模型进行处理,然后返回处理后的结果
x=self.dis(x)
return x
#梯度消失(Vanishing Gradients)是深度学习中常见的一个问题,特别是在训练深层神经网络时。这个问题指的是在反向传播过程中,梯度(误差的导数)随着层数的增加而迅速减小
# 最终变得非常接近于零。当梯度非常小的时候,权重的更新也会非常小,导致网络学习速度极慢,甚至完全停止学习。
下面是一个标准的pytroch模型的定义:
#导入必要的库
import torch
import torch.nn as nn
import torch.nn.functional as F
#开始定义模型类
class Network(nn.Module):## 开始定义模型类
class Network(nn.Module):
def __init __(self):
super(Network,self). init__()#__init__ 方法在创建类的新实例时被调用
self.dis=nn.Sequential(#这里创建了一个 nn.Sequential 容器,它按照它们在构造器中被添加的顺序执行每个模块
nn.Linear(2,32),#nn.Linear(2, 32) 创建了一个全连接层,它将输入的2维向量转换为32维的向量
nn.LeakyReLU(0.2),#创建了一个LeakyReLU激活函数,它允许小的梯度值通过,参数0.2是负斜率。
nn.Linear(32,32),
nn.LeakReLU(0.2),
nn.Linear(32,1),#nn.Linear(32, 1) 创建了一个全连接层,它将32维的向量转换为1维的向量。
nn.Sigmoid()#nn.Sigmoid() 添加了一个Sigmoid激活函数,它将输出值压缩到0和1之间,通常用于二分类问题中。
)
def forward(self,x):
x=self.dis(x)
return x
#在神经网络中,激活函数是用来增加网络的非线性特性的。没有激活函数,即使网络有很多层
# 它也只是一个简单的线性变换,无法解决复杂的非线性问题。
(1)必须要继承 nn.Module 。 所以假设在阅读一个新的PyTorch 编写的代码时,只需要找到nn.Module, 就可以知道代码中定义模型的地方了。
#实例化网络
Net=vetwor1()
该模型本质上就是一个函数, 一个映射关系,输入数据时可以根据这个函数关系计算出输出数据。现在创建一些输入数据
#创建一些数据
input=torch.FloatTensor(5,2)
print(input)
输入数据
output=net(input)
在实例化网络时:net=Network(),这个过程调用了Network 类中的__init_函数。
(2)在网络接收到输入数据input 时,实际上是调用了Network 类中的 forward 函数, 也可以说调用了net 这个实例的forward 函数;forward 函数中的x 其实就是输入数据 input,forward 函数return 回来的值就是 output 值。
class Network(nn.Module)
def __init __(self):
super(Network,self). __init__()
self.dis1=nn.Sequential(
nn.Linear(2,32),
nn.LeakyReLU(0.2),
nn.Linear(32,32),
nn.LeakyReLU(0.2)
)
self.dis2=nn.Sequential(
nn.Linear(16,1),
nn.Sigmoid()
)
def forward(self,x):
x=self.dis1(x)
output1=x.view(-1,16)
output2=self.dis2(output1)
return output1,output2
2.PyTorch的 data类
对于深度学习,整个流程比较重要的两个部分一个是之前讲的模型的定义,另外一个就 是数据的处理。模型其实对于数据的要求是比较高的, 一个是需要数据的质量高, 一个就是 要求数据的格式统一。
例如, 一个图像分类的卷积模型的输入图片,假设模型输入要求大小为416像素的正方形图片。而原始图片是长方形或者尺寸大小不是416的图片,这样就需要对图片进行一些预处理,比方说在原始图片中剪裁出416的正方形部分或者把原始图片压缩到416像素大小。 这次主要讲解就是PyTorch 中另外两个类,DataLoader类和Dataset类。
#训练模型
for epoch in range(num_epoch)
#它用于将一个可迭代对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,通常用于for循环中
for i,data in enumerate(loader):
第一层循环就是要循环的 epoch 次数,第二层就是要讲的 DataLoader 了,从 DataLoader中用循环的方式不断取出来一个 batch的数据。可以把DataLoader看成[第一 个batch 的数据,第二个 batch 的数据,第三个 batch 的数据....
#从dataloader中取数据
#dataloader的实例化
loader=DataLoader(dataset=set,batch size=2)#dataset为数据集
Dataset 就是定义每一个 batch 的数据的。例如:
from torch.utils.data import Dataset,DataLoader#导入必要的库
#Dataset 是一个抽象类,用于定义数据集的结构和如何访问数据。DataLoader 是一个迭代器
# 它封装了 Dataset 对象,并提供了批量获取数据、打乱数据、多线程加载等功能。
import os
class MyDataset1(Dataset):
def __init__(self):
self.data=['我','爱','祖','国']
def __getitem__(self,index):
return self.data[index]
def __len__(self):
return len(self.data)#它返回数据集的总大小。这里它返回 self.data 列表的长度
#实例化数据集类
set=MyDataset1()
loader=DataLoader(dataset=set,batch_size=2)#这里首先创建了 MyDataset1 类的一个实例 set,然后使用 DataLoader 来包装这个实例。
#DataLoader 的 batch_size=2 参数指定了每个批次的数据项数量
print("len(loader):",len(loader))
#从dataloader中抽取batch数据
for data in loader:
print(data)
3.激活函数
激活函数其实是神经网络可以拟合任意函数的灵魂。假设一个两输入、 一输出的全连接网络没有激活函数,那么每一层之间的关系都是wx+b 的形式。无论有多少层,每一层 有多少的神经元,最后总可以用y=C₁x₁+c₂x₂+c₃ 这样的公式表示。线性的堆积多少层都是线性的。然而在利用神经网络分类的时候,很多分类界限是非线性的。简单来说, 激活函数可以把线性的关系映射成非线性的,这样神经网络才有能力去逼近任意的函数。
激活函数有几十种之多,这里主要介绍常见的几种激活函数。首先介绍PyTorch 深度学习库中已经封装好的激活函数:
import torch.nn as nn
import matplotlib.pyplot as plt#Matplotlib 的 pyplot 模块,它用于绘制图形
#随便定义一个画图函数
def plot(x,y,name=""):
plt.plot(x,y,'r')#x 是横坐标数据,y 是纵坐标数据,name 是图形的标题。
#函数使用 Matplotlib 的 plot 函数绘制 x 和 y,设置坐标轴的范围,显示网格,并设置标题
plt.xlim((-2,2))
plt.ylim((-2,2))
plt.grid()
plt.title(name)
x=torch.arange(-2,2,0.01)
Activation=nn.ReLU()#这里创建了一个 ReLU 激活函数的实例
y=Activation(x)#应用 ReLU 激活函数到 x 上,得到 y
plot(x,y,'ReLU')#这里调用之前定义的 plot 函数,传入 x、y 和标题 'ReLU'。
plt.show()#这行代码调用 Matplotlib 的 show 函数,将绘制好的图形显示出来。
四个经典的激活函数:
(1)ReLU: 线性整流单元。由图可以看出,大于零的值经过这个函数没有影响,小于0的值经过这个激活函数会变成0。数学表达:ReLU(x)= max(0,x)。
优点1:ReLU 的收敛速度快于Sigmoid/Tanh (因为Sigmoid 激活函数的最大值只有 1,而ReLU 没有最大值的限制)。
优点2:ReLU 要求的计算量小,因为没有指数运算(见 Sigmoid 的数学表达)。
缺点:ReLU 对于小于0的数据的梯度是0(因为ReLU 的导数在x<0 的时候是0,而 不是因为ReLU 在 x<0 的时候为0)。梯度为0意味着不会再更新,不会再更新意味着梯 度一直是0,这样神经元就“坏死”了,没用了。避免这样情况发生的操作就是设置一个比较 小的学习率。
(2)Sigmoid: 把输入压缩成0~1的值,负无穷映射0,正无穷映射1。数学表达:
优点:非线性的经典激活函数,而且压缩成0~1刚好体现了概率值。在图像分类任务 中,希望模型输出的正是输入图像属于每一个类别的概率。
缺点1:依然是梯度消失问题。与 ReLU 的坏死类似,当输入非常大或者非常小的时候,可以发现 Sigmoid 的导数近乎为0,这样就产生了梯度消失问题。
缺点2:不难发现,因为输出的数据是0~1,所以 Sigmoid 的输出不是0均值的(zero- centered,以0为中心),导致收敛速度缓慢。
缺点3:包含指数运算,计算量大。但是其实这个计算量相对于整个模型的计算,九牛 一毛,这个缺点其实不算什么问题。
3.Tanh: 与 Sigmoid类似。数据的输出范围是[-1,1]。数学表达:
优点:从图中看到,解决了Sigmoid 的 zero-centered的问题。
缺点:依然存在gradient vanishing 的问题。
4.LeakyReLU: 是 ReLU 的改进版本,为了解决ReLU 的坏死问题,LeakyReLU 在 x<0 的时候用一个较小的斜率(图中的斜率是0.1,不过一般默认是0.01)。
优点:解决了ReLU 的坏死问题。
缺点:x<0 斜率的取值是 一 个问题。这里简单提及 一 下 PReLU 激活函数 ,这是一个可以根据训练过程,自动找到一个好的斜率的 LeakyReLU 。 换句话说,PReLU 的 x<0 部分的斜率也参与到了梯度下降中。
以上就是常见的4种激活函数。最后再提一下 Softmax 激活函数 。这个不与上面的放在一起是因为这个函数的输入要求多个值, 一般用在多分类任务。数学表达式上。
损失函数
均 方 误 差
均方误差(Mean Squared Error,MSE)非常经典,是真实值和估计值的差的平方的期望。数学公式是:
先设置两个变量:
a=torch.FloatTensor([1,1,1])
b=torch.FloatTensor([1,2,4])
在上面的两个变量a、b中 ,batch是3。来计算一下a 和b 的均方误差:
#定义loss函数
loss_function=nn.MSELoss()
#loss_function=nn.MSELoss(reduction='mean')
print(loss_function(a,b))
就是说如果输入“reduction='sum'”, 那么会返回10,MSE 公式中求均值就会变成求 和,上式中就不会先乘以1/3。reduction默认是求均值, 一般使用的也是求均值的版本。
交叉熵
交叉熵是最常见的Loss 函数,经常用来处理多分类问题,当然也 可以处理二分类问题。想象一个多分类的神经网络,这个网络输出层的节点个数应该是与分类任务的类别数量相等,假设这是一个三分类问题(猫、狗、鸟),有一个样本狗的图片,所以这个样本的分类真实值应该是1(如果是猫的话就是0,如果是鸟的话就是2)。
Softmax 的意义就是把这些没有限制的值变成概率值,把模型打分的高低转换成图片 属于某一个类别的概率。图片被模型判断正确的概率越大,交叉熵越小。交叉熵是表明实际输出的概率与期望输出的概率的距离,交叉熵越小,两个概率越接近。
交叉熵CrossEntropy 就是完全等价于一个 Softmax+ 自然对数In+NLLLoss。 一般如果 Softmax 在模型中嵌入了,那就直接可以NLLLoss 作为损失函数。
#计算softmax
output=torch.FloatTensor([-1,0.5,2],[-1,0.5,2],[-1,0.5,3])
Softmax=nn.Softmax(dim=1)#dim=1 指定了 Softmax 函数沿着哪个维度(列)进行计算。在这个例子中,我们希望对每一行的元素进行 Softmax 计算,因此 dim=1
output=Softmax(output)#将 Softmax 函数应用于 output 张量,计算每一行的 Softma
print('做一个Softmax:\n{}'.format(output))
#求自然对数
output=torch.log(output)
print('做一个自然对数:\n{}'.format(output))
#计算NLLLoss
NLLLoss=nn.NLLLoss()
output=NLLLoss(output,torch.tensor([1,2]))
print('做一个NLLLoss:{}'.format())
#使用 PyTorch 框架中的 CrossEntropyLoss 函数来计算交叉熵损失
output=torch.FloatTensor([-1,0.5,2],[-1,0.5,3])
loss_function=nn.CrossEntropyLoss()#CrossEntropyLoss 需要两个参数:模型的输出(logits)和目标标签(ground truth labels)。
CE=loss_function(output,torch.tensor([1,2]))#这里调用 loss_function 计算交叉熵损失
#第一个参数是模型的输出 output,第二个参数是目标标签,这里使用 torch.tensor([1, 2]) 表示第一个样本的正确类别是索引 1(对应第二个元素),第二个样本的正确类别是索引 2(对应第三个元素)
print('用CrossEntropyLoss:{}'.format(CE))#这行代码打印出计算得到的交叉熵损失值
在代码中可能会看到这样的代码,在训练的时候调用model.train() 告诉模型现在要开 始训练了,在测试或者验证的时候调用model.eval()告诉模型现在不训练了。
(1)model.train()是启用模型的 BatchNormalization和 Dropout层; (2)model.eval() 是限制模型的BatchNormalization 和 Dropout 层。
在学习了 BN 层之后,可以知道BN 计 算均值和方差是需要一整个 batch 的数据进行计算的。batch是指一次训练或推断中的一组数据样本。
BN的数学公式如下:
gamma 和 beta是训练中学习的参数,在测试过程中,已经训练完成并且固定下来了, 那均值和方差就是用训练集的全部样本来计算均值和方差。Dropout层则更简单,如果是训练过程,就让一定量的神经元失活,即让这个神经元不 再输出内容,在测试过程中,Dropout 层失效,没有神经元失活。假设 Dropout rate=0.2,就是会有20%的神经元失活,那么存活的80%的神经元输出 的值就要除以0.8来抵消有20%神经元失活的事实。
(1)model.train() 启 用 BN 层的训练,并且求取一个 batch 数据的均值和方差; Dropout层正常启用,并且存活的神经元输出值会经过处理来抵消Dropout 失活的影响。
(2)model.eval()限制 BN 层,参数不再学习,使用所有训练数据求取均值和方差; Dropout 禁止使用,不对神经元输出值做任何处理。
注意:一般BN 层和Dropout 层不同时使用。
Python的命令行库argparse
import argparse
# 创建 ArgumentParser 对象,并设置描述
parser = argparse.ArgumentParser(description='这里一般是讲述代码是干什么的')
# 使用 add_argument 方法添加命令行参数:--name 和 --age
# 每个参数都有一个默认值(default),一个帮助信息(help),以及一个标识符(--name 或 --age)
parser.add_argument('--name', default='啥也没写', help='这里输入你的名字')
parser.add_argument('--age', default='不告诉你我几岁', help='这里输入你的年龄')
# 解析命令行参数
args = parser.parse_args()
# 打印解析后的参数值
print('你的名字:{}'.format(args.name))
print('你的年龄:{}'.format(args.age))
就算有了命令行库,也是可以照常运行代码的。不管遇到什么代码,只要输入“-help”就可以查询如何正确地使用这个命令行,并且查 询到每一个属性的help内容。