本篇博客将记录Pytorch的基本知识,包括Pytorch的环境配置、Pytorch的结构,Pytorch的基本操作等,本文中使用的Pytorch版本为1.12.0
Pytorch环境配置
我是采用在Anaconda下安装Pytorch的方案,关于这个可以参考网上的大佬写的博客,跟着一步步走就行,比如下面这一篇:
在Anaconda下安装Pytorch的超详细步骤
同时,有的时候,我们需要安装的Pytorch版本不一定是最新的,这时需要在Pytorch官网上找到过去的Pytorch版本:
INSTALLING PREVIOUS VERSIONS OF PYTORCH
除此之外,如果采用在Anaconda下安装Pytorch的方案的话,还需要了解一些conda常用的指令,可以参考下面的博客:
Anaconda conda常用命令:从入门到精通
本篇博客为了便于使用,将部分常用的conda命令列举如下:
- conda --version:查看conda版本
- conda config --show:查看conda的环境配置
- conda update conda:更新conda
- conda create --help:查询某个命令的帮助
管理环境:
- conda create -n env_name python=3.8:创建Python版本为3.8,名字为env_name的虚拟环境,创建后的env_name文件可以在Anaconda安装目录envs文件下找到
- conda env list/conda info -e/conda info --envs:查看当前有哪些虚拟环境,在显示的列表中,前面带*表示当前的活动环境
- conda activate env_name:激活创建的虚拟环境
- conda activate/conda deactivate:退出当前的工作环境,这两条命令中任一条都会让环境回到base environment,它们从不同的角度出发到达了同一个目的地,因为activate的缺省值是base,deactivate的缺省值是当前环境,因此它们最终的结果都是回到base environment
conda remove --name env_name --all:将指定的虚拟环境以及其中所安装的包都删除
- conda remove --name env_name package_name:只删除虚拟环境中的某个包
- conda list:查询环境中安装了哪些包
- conda search package_name:查询当前Anaconda repository中是否有你想要安装的包
- conda list pkgname/conda list pkgname*:查找某一个指定的包是否已安装,支持*通配模糊查找
conda install package_name:在当前虚拟环境中安装一个包
- conda install numpy=0.20.3:安装某一个特定版本的包
- conda update numpy:将某个包更新到最新版本
- conda uninstall package_name:卸载包
除了conda的命令以外,还需要对Pytorch中有关环境信息的程序语句有所了解,方便在使用Pytorch前检查环境,在此列举如下:
- torch.__version__:查看Pytorch的版本
- torch.cuda.is_available():判断显卡是否可用
- torch.cuda.device_count():查看可用的CUDA数量
- torch.version.cuda:查看CUDA的版本号
- torch.cuda.get_device_name(0):获取GPU的类型
Pytorch结构
Pytorch中的各个常用模块及其功能
- torch:torch模块本身包含了pytorch中常用的一些激活函数,比如sigmoid(torch.sigmoid)、ReLU(torch.relu)和tanh(torch.tanh)以及pytorch张量的一些操作,比如矩阵的乘法(torch.mm)、张量元素的选择(torch.select)等。除此之外,还可以产生一定形状的张量,比如产生元素全为0的张量(torch.zeros)、产生元素服从标准正态分布的张量(torch.randn)等
- torch.cuda:torch.cuda模块定义与CUDA运算相关的一系列函数,包括检查系统的CUDA是否可用、当前进程对应的GPU序号(在多GPU情况下)、清楚GPU上的缓存、设置GPU的计算流、同步GPU上执行的所有核函数等
torch.nn:torch.nn模块是pytorch神经网络模块化的核心,这个模块定义了一系列网络层模块,包括卷积层nn.ConvNd(N=1,2,3)和全连接层(nn.Linear)等,当构建深度学习模型时,可以通过继承nn.Module类并重写forward方法来实现一个新的神经网络。此外,torch.nn中也定义了一系列损失函数,比如平方损失函数(torch.nn.MSELoss)、交叉熵损失函数(torch.nn.CrossEntropyLoss)等;一些激活函数类,比如sigmoid(torch.nn.Sigmoid)、ReLU(torch.nn.Relu)和tanh(torch.nn.Tanh)等;以及一些搭建好的网络,比如RNN(torch.nn.RNN)、LSTM(torch.nn.LSTM)等
- torch.nn.functional:torch.nn.functional是pytorch的函数模块,定义了一些和神经网络相关的函数,包括卷积函数和池化函数等,torch.nn中定义的模块一般会调用torch.nn.functional中的函数,比如nn.ConvNd(N=1,2,3)模块会调用torch.nn.functional.convNd(N=1,2,3)函数。另外torch.nn.functional中还定义了一些不常用的激活函数,比如torch.nn.functional.relu6和torch.nn.functional.elu等;以及一些标准化函数torch.nn.functional.xxNorm(1d2d3d)
- torch.nn.init:torch.nn.init模块定义神经网络权重的初始化,比如均匀初始化(torch.nn.init.uniform_)、正态分布初始化(torch.nn.init.normal_)等
- torch.nn.Module、torch.nn.Sequential和torch.nn.ModuleList:torch.nn.Module模块是构建神经网络的基础,自己定义的模型是torch.nn.Module的子类;torch.nn.Sequential模块已经实现了forward方法,并且里面的模块需要按照顺序排列;torch.nn.ModuleList模块可以将nn.Module的任意子类(比如nn.Conv2d,nn.Linear等)添加到list中,方法和Python自带的list一样,加入到torch.nn.ModuleList里面的module会自动注册到整个网络上
有关torch.nn.ModuleList和torch.nn.Sequential的区别,可以参考一下这一篇文章,讲的很好:
PyTorch 中的 ModuleList 和 Sequential: 区别和使用场景
详解PyTorch中的ModuleList和Sequential
- torch.nn.functional:torch.nn.functional是pytorch的函数模块,定义了一些和神经网络相关的函数,包括卷积函数和池化函数等,torch.nn中定义的模块一般会调用torch.nn.functional中的函数,比如nn.ConvNd(N=1,2,3)模块会调用torch.nn.functional.convNd(N=1,2,3)函数。另外torch.nn.functional中还定义了一些不常用的激活函数,比如torch.nn.functional.relu6和torch.nn.functional.elu等;以及一些标准化函数torch.nn.functional.xxNorm(1d2d3d)
- torch.optim:torch.optim定义了一系列的优化器,比如随机梯度下降算法(torch.optim.SGD)、Adagrad算法(torch.optim.Adagrad)、RMSProp(torch.optim.RMSProp)和Adam算法(torch.optim.Adam)等。这个模块还包含了学习率衰减的算法的子模块,即torch.optim.lr_scheduler,这个子模块中包含了学习率阶梯下降算法(torch.optim.lr_scheduler.StepLR)和余弦退火算法(torch.optim.lr_scheduler.CosineAnnealingLR)等学习率衰减算法
- torch.autograd:torch.autograd模块是pytorch的自动微分算法模块,定义了一系列自动微分函数,包括torch.autograd.backward函数,主要用于在求得损失函数之后进行反向梯度传播;torch.autograd.grad函数用于一个标量张量对另一个张量求导,以及在代码中设置不参与求导的部分。另外这个模块还内置了数值梯度功能和检查自动微分引擎是否输出正确结果的功能
- torch.hub:torch.hub模块提供了一系列的模型供用户使用,比如可以用torch.hub.list函数来获取某个模型镜像站点的模型信息;用torch.hub.load来载入预训练的模型等
- torch.random:torch.random提供了一系列的方法来保存和设置随机数生成器的状态,包括使用get_rng_state函数获取当前随机数生成器状态,set_rng_state函数设置当前随机数生成器状态,并可以用manual_seed函数来设置随机种子,也可以使用initial_seed函数来得到程序初始的随机种子
- torch.onnx:torch.onnx定义了pytorch导出和载入onnx格式的深度学习模型描述文件,onnx格式的存在时为了方便不同深度学习框架之间交换模型,引入这个模块可以方便pytorch导出模型给其他深度学习框架使用,或者让pytorch可以载入其他深度学习框架构建的深度学习模型
- torch.utils.bottleneck:torch.utils.bottleneck模块用于检查深度学习模型中模块的运行时间,从而可以找到导致性能瓶颈的那些模块,通过优化这些模块的运行时间,从而优化整个深度学习模型的性能
- torch.utils.checkpoint:torch.utils.checkpoint模块用于节约深度学习使用的内存,要进行梯度反向传播,在构建计算图时需要保存中间的数据,增加了深度学习的内存消耗。为了减少内存消耗,使得mini-batch的大小得到提高,可以通过torch.utils.checkpoint模块记录中间数据的计算过程,然后丢弃这些中间数据,等需要时再重新计算这些数据,即以计算时间换内存空间
- torch.utils.data:torch.utils.data模块引入了数据集(Dataset)和数据载入器(DataLoader),前者代表包含了所有数据的数据集,通过索引可以得到某一条特定的数据;后者通过对数据集的包装,可以对数据集进行随机排列(shuffle)和采样(sample),得到一系列打乱数据顺序的mini-batch
torchvision:主要处理图像数据,包含一些常用数据集、模型、转换函数等,torchvision独立于pytorch,需要专门安装
- torchvision.models:提供深度学习中经典的网络结构、预训练好的模型,比如AlexNet、VGG、ResNet、Inception等
- torchvision.datasets:提供常用的数据集,设计上继承torch.utils.data.Dataset,包括MNIST、CIFAR-10、ImageNet、COCO等
- torchvision.transforms:提供常用的数据预处理操作,主要包括对Tensor以及PIL Image对象的操作
- torchvision.utils:工具类
Pytorch基本操作
张量
张量(Tensor)是Pytorch中的基础的数据类型,类似于Numpy中的数组(Array),张量也可以和Numpy的数组相互转换,不同的是,张量可以定义在GPU上(显存中),并使用GPU做运算
创建张量
可以通过Python中的列表或numpy中的数组来定义一个张量:
t = torch.tensor([[1, 2, 3], [4, 5, 6]])
创建一些特殊的张量的方法列举如下:
- torch.empty(5, 3):创建一个5行3列的没有初始化的tensor
torch.rand(5, 3):创建一个5行3列的从[0,1)的均匀分布中抽取的随机数tensor
- torch.randn:标准正态分布
- torch.randint:生成在指定范围内均匀分布的整数张量
- torch.normal:生成符合指定参数的高斯分布的随机张量
- torch.zeros(5, 3, dtype=torch.long):创建一个全零矩阵
- torch.ones((2, 2)):创建一个全一张量
- torch.full((3, 3), 2):创建3*3的元素全是2的张量
- 某一个张量.new_ones(5, 3):返回一个与size大小相同的用1填充的张量,默认返回的Tensor具有与此张量相同的torch.dtype和torch.device
- torch.randn_like(某一个张量, dtype=torch.float):返回一个和输入大小相同的张量,张量服从标准正态分布
torch.FloatTensor
张量的属性与方法
获取张量的维度:t.shape或t.size()
- 获取某一个维度:t.shape[0]或t.size(0)
这里需要注意,张量是类Tensor的实例,其中shape是其属性,size()是其继承的方法,因此两者均可以获得张量的维度,同时由于shape是属性,使用中括号,size()是函数,使用小括号。
- 获取某一个维度:t.shape[0]或t.size(0)
dim
length
获取张量数据的类型:t.dtype
张量中的数据支持类型
- 16位浮点数:torch.float16或torch.half
- 32位浮点数:torch.float32或torch.float
- 64位浮点数:torch.float64或torch.double
- 8位无符号整数:torch.uint8
- 8位有符号整数:torch.int8
- 16位有符号整数:torch.int16或torch.short
- 32位有符号整数:torch.int32或torch.int
- 64位有符号整数:torch.int64或torch.long
- 布尔类型:torch.bool
- 张量转换为numpy中的数组:t.numpy
- 张量转换为对应的列表:t.tolist
- 对于只有一个元素的张量,获取这个元素的数值:t.item
t.unsqueeze(dim)
t.contiguous()
Pytorch常用函数
torch.zeros()函数
torch.zeros()函数返回一个形状为为size,类型为torch.dtype,里面的每一个值都是0的tensor
函数定义如下,需要注意的是,pytorch采用@overload定义了多种形式的函数,@overload是Python中的一个函数装饰器,它允许我们为同一个函数定义多个不同的签名。当然实际上Python的函数不支持通过参数类型重载,因此@overload只是为一个函数提供多个类型标注,用于提高代码的可读性,因此这里只放了一种,其他的可以自己看:
def zeros(size: _size, *, out: Optional[Tensor]=None, dtype: _dtype=None, layout: Optional[_layout]=strided, device: Union[_device, str, None]=None, pin_memory: _bool=False, requires_grad: _bool=False) -> Tensor: ...
对于一些参数的解释:
- size:定义tensor的shape,可以是一个list,也可以是一个tuple
- out(可选):表示输出张量,如果指定了这个参数,那么函数会将结果存储在这个张量里,而不是新建一个张量
- dtype(可选):指定返回tensor的数据类型
一些测试代码如下:
a = torch.zeros([2, 5], dtype=torch.float)
b = torch.zeros((2, 5), dtype=torch.float)
c = a.equal(b)
d = x = torch.zeros(3, 4, 2)
torch.normal()函数
torch.normal()函数返回一个张量,张量中每个元素是从相互独立的正态分布中随机生成的。每个正态分布的均值和标准差对应着mean中的一个值和std中的一个值
函数定义如下:
def normal(mean: Tensor, std: Tensor, *, generator: Optional[Generator]=None, out: Optional[Tensor]=None) -> Tensor: ...
def normal(mean: _float, std: _float, size: _size, *, generator: Optional[Generator]=None, out: Optional[Tensor]=None, dtype: _dtype=None, layout: Optional[_layout]=strided, device: Union[_device, str, None]=None, pin_memory: _bool=False, requires_grad: _bool=False) -> Tensor: ...
_size = Union[torch.Size, List[_int], Tuple[_int, ...]]
对于一些参数的解释:
- mean:一个张量,存储着输出张量中每个元素正态分布的均值;或一个浮点值,输出张量中元素正态分布的均值,不过是输出张量中的所有元素所共享的
- std:一个张量,存储着输出张量中每个元素正态分布的标准差;或一个浮点值;或一个浮点值
- size:输出张量的形状(输入的mean和std为浮点值时可以使用)
一些测试代码如下:
import math
a = torch.normal(mean=torch.arange(1, 11.0), std=torch.arange(1, 0.0, -0.1))
b = torch.normal(mean=0.5, std=torch.arange(1, 6.0))
c = torch.normal(mean=torch.arange(1, 6.0))
d = torch.normal(0, math.sqrt(1 / 2), size=[1, 2])
torch.arange()函数
torch.exp()函数
torch.sin()函数
torch.cos()函数
损失函数
torch.nn.CrossEntropyLoss类
Python中导入:
from torch.nn import CrossEntropyLoss
CrossEntropyLoss类用于计算输入的logits和目标之间交叉熵,类的构造方法的参数如下所示:
- weight=None:如果使用的话需为1-D tensor,表示n个类别的权重,当训练数据不均衡的情况下使用
- size_average=None:
- ignore_index=-100
- reduce=None
- reduction='mean'
- label_smoothing=0.0
调用时参数如下所示:
input:包含每个类的没有经过标准化的logits(即不一定是正数,同时也不一定和为1),对于没有batch的输入,尺寸为
(C)
;对于batch的输入,尺寸为(mini-batch, C)
或(mini-batch, C, d_1, d_2, ..., d_k)
,后者主要用于输入为高维数据的图像上,比如在二维图像上逐像素计算交叉熵- 尺寸:
(C)
、(N, C)
或(N, C, d_1, d_2, ..., d_k)
,其中k≥1
- 尺寸:
target:target有以下两种形式可以选择:
- 尺寸:
()
、(N)
或(N, d_1, d_2, ..., d_k)
,其中k≥1,每一个值的范围为[0,C)
;如果包括类别的概率,则尺寸和输入相同,每一个值的范围为[0,1]
- 范围为
[0,C)
的类别的索引,C为类别的数量,如果指定了ignore_index
的参数,损失仍然会接收这个类别的索引(此时索引不一定要在范围内),不减少的损失(文档原文为unreduced
,即reduction设置为'none')如下式所示:
$$ l(x,y)=L= \left\{ l_1,...,l_N \right\}^T\\ l_n=-w_{y_n}log\frac{exp(x_{n,y_n})}{\sum_{c=1}^C exp(x_{n,c})}*1\left\{ y_n\neq ignore\_index \right\} $$
其中x为输入,y为target,w为权重,N表示batch以及d_1,...,d_k的范围
当reduction设置为'mean'或'sum'时,损失的计算如下式所示:
$$ l(x,y)=\begin{cases} \sum_{n=1}^N\frac{1}{\sum_{n=1}^Nw_{y_n}*1\left\{ y_n\neq ignore\_index \right\}}l_n & if\ reduction='mean'\\ \sum_{n=1}^Nl_n & if\ reduction='sum' \end{cases} $$- 每个类的概率:当每一个batch的标签类别超过一个时使用,不减少的损失如下式所示:
$$ l(x,y)=L= \left\{ l_1,...,l_N \right\}^T\\ l_n=-\sum_{c=1}^Cw_clog\frac{exp(x_{n,c})}{\sum_{i=1}^C exp(x_{n,i})}*y_{n,c} $$
当reduction设置为'mean'或'sum'时,损失的计算如下式所示:
$$ l(x,y)=\begin{cases} \frac{\sum_{n=1}^Nl_n}{N} & if\ reduction='mean'\\ \sum_{n=1}^Nl_n & if\ reduction='sum' \end{cases} $$- 尺寸:
需要注意的是以上计算损失的式子中,当reduction='mean'
时会发现两个式子的计算是不同的,关于这一点可以看一下下面这篇博客:
在Pytorch之中的nn.CrossEntropyLoss同时使用reduction与weights产生错误结果的相关问题
经过损失计算后,同时当reduction为'none'时,输出的尺寸为()
、(N)
或(N, d_1, d_2, ..., d_k)
,其中k≥1;若reduction为其它值,则输出的尺寸为标量
一些测试的代码和结果如下:
数据集
在Pytorch中构建数据集主要需要使用到如下两个类:
- torch.utils.data.Dataset
- torch.utils.data.DataLoader
在Python中导入:
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
DataLoader类
- 参数
参数的意义:
- collate_fn:在实例化Dataloader的时候,以函数形式传递,可以自定义取出一个batch数据的格式
Dataset类
将模型加载到GPU上
在训练中往往需要在GPU上加速训练,因此学会使用Pytorch时将模型和张量加载到GPU上是必要的,首先可以检查一下GPU是否可用:
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
而将模型或张量加载到GPU上可以采用两种方法
# 方法1
if torch.cuda.is_available():
model = model.cuda()
x = x.cuda()
y = y.cuda()
# 方法2
device=torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model=model.to(device)
x=x.to(device)
y=y.to(device)
激活函数
构建自己的网络结构
nn.Module
nn.ModuleList
nn.Sequential
常用的基本网络层
torch.nn.Linear类
nn.Linear类用于设置网络中的全连接层
torch.nn.Embedding类
torch.nn.Dropout类
torch.nn.LayerNorm类
获取模型的参数
pytorch可以采用3种类似的方法来获取模型的参数,3种方法如下所示:
- model.parameters()
- model.named_parameters()
- model.state_dict()
首先定义一个简单的模型,代码如下所示,可见模型就是由几层全连接层构成:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(28 * 28, 256)
self.fc2 = nn.Linear(256, 64)
self.fc3 = nn.Linear(64, 10)
def forward(self, x):
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = F.relu(self.fc3(x))
return x
接下来就使用上面的几种方法,同时再加上直接打印模型,来看看结果如何,代码如下所示:
net = Net()
print(net)
print(net.parameters())
print(net.named_parameters())
print(net.state_dict())
(1)直接打印模型的结果如下所示:
Net(
(fc1): Linear(in_features=784, out_features=256, bias=True)
(fc2): Linear(in_features=256, out_features=64, bias=True)
(fc3): Linear(in_features=64, out_features=10, bias=True)
)
可见直接打印模型的结果就是每一层的名字、类别、以及构造时使用的参数
(2)使用model.parameters()
的结果如下所示:
<generator object Module.parameters at 0x000002065ACF35F0>
可见model.parameters()的结果是一个生成器对象,可以采用for循环看看生成的值是什么:
Parameter containing:
torch(...., requires_grad=True)
Parameter containing:
tensor(...., requires_grad=True)
....
Parameter containing:
tensor(...., requires_grad=True)
可见model.parameters()生成的结果就是网络中参数对应的张量
(3)使用model.named_parameters()
的结果如下所示:
<generator object Module.named_parameters at 0x000001BAD32E35F0>
可见model.named_parameters()的结果也是一个生成器对象,同样采用for循环看看生成的值是什么:
('fc1.weight', Parameter containing:
tensor(...., requires_grad=True))
('fc1.bias', Parameter containing:
tensor(...., requires_grad=True))
('fc2.weight', Parameter containing:
tensor(...., requires_grad=True))
('fc2.bias', Parameter containing:
tensor(...., requires_grad=True))
('fc3.weight', Parameter containing:
tensor(...., requires_grad=True))
('fc3.bias', Parameter containing:
tensor(...., requires_grad=True))
可见model.named_parameters()生成的结果长度为2的元组,元组内的第一个元素是name,即fc1.weight、fc1.bias等;元组内的第二个元素是name对应的值
(4)使用model.state_dict()
的结果如下所示:
OrderedDict([('fc1.weight', tensor(....)), ('fc1.bias', tensor(....)), ('fc2.weight', tensor(....)), ('fc2.bias', tensor(....)), ('fc3.weight', tensor(....)), ('fc3.bias', tensor(....))])
可见model.state_dict()的结果是一个有序字典,该有序字典中保存了模型所有参数的参数名和具体的参数值,所有参数包括可学习参数和不可学习参数。因此该方法可用于保存模型,当保存模型时,会将不可学习参数也存下,当加载模型时,也会将不可学习参数进行赋值
(5)三种方法的区别
- model.state_dict()存储的是该model中包含的所有layer中的所有参数;而model.named_parameters()则只保存可学习、可被更新的参数
- model.state_dict()所存储的模型参数tensor的require_grad属性都是False,而model.named_parameters()的require_grad属性都是True
- 与model.named_parameters()相比,model.parameters()不会保存参数的名字
这里的第一点区别可以在上面的网络模型中的构造方法中加入self.b = nn.BatchNorm2d(10)
,可以发现model.state_dict()的关于batch normalization层的结果如下:
('b.weight', tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])), ('b.bias', tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])), ('b.running_mean', tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])), ('b.running_var', tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.])), ('b.num_batches_tracked', tensor(0))
而model.named_parameters()的关于batch normalization层的结果如下:
('b.weight', Parameter containing:
tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1.], requires_grad=True))
('b.bias', Parameter containing:
tensor([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.], requires_grad=True))
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。