赞
踩
目录
1.1.1 Anaconda/miniconda installation
1.3.1 CUDA,Compute Unified Device Architecture
1.4.2 数据读入: Dataset读取、变换; DataLoader批加载数据
PyTorch的安装是我们学习PyTorch的第一步,也是经常出错的一步。在安装PyTorch时,我们通常使用的是Anaconda/miniconda+Pytorch+ IDE 的流程
在数据科学和最近很火的深度学习中,要用到大量成熟的package。我们一个个安装 package 很麻烦,而且很容易出现包之间的依赖不适配的问题。
而Anaconda/miniconda的出现很好的解决了我们的问题,它集成了常用于科学分析(机器学习, 深度学习)的大量package,并且借助于conda我们可以实现对虚拟Python环境的管理。
电脑用anaconda;服务器用miniconda
登陆Anaconda | Individual Edition,选择相应系统DownLoad,此处以Windows为例(Linux可以点击链接选择合适的版本进行下载或者通过官方提供的shell脚本进行下载)
在开始页找到Anaconda Prompt,一般在Anaconda3的文件夹下,双击运行。
bash Miniconda3-latest-Linux-x86_64.sh
Note 注意! Miniconda3最后一步是指定安装环境!可以安装文件转移动其他路径,类似于windows不装在默认的c盘里!使用没有影响!
conda info --root
Linux在终端(Ctrl
+Alt
+T
)进行, Windows在Anaconda Prompt
进行
查看现存虚拟环境: conda env list
Note 注意!!!anaconda env经常启动失败,所以要首先明确我们现在正在使用的python和pip是指向哪个env,是启动失败是的本地server home:~/.local/bin/pip,还是启动成功时的conda env:~/miniconda3/bin/pip.
- 查看当前运行环境和路径!
- 查看当前python版本和安装路径:python --version;which python
- 查看pip指令安装路径:which pip
创建虚拟环境的命令。
创建anaconda虚拟环境:conda create -n env_name python==version
# 注:将env_name 替换成你的环境的名称,version替换成对应的版本号,eg:3.8
- 激活环境命令:conda activate env_name
- 安装miniconda后,miniconda指令地址会自动覆盖server home本地路径,指向自己
- 不要随便动配置文件,容易造成混乱!导致pip、bash路径指向出错。
- 可能方法:echo 'export PATH="~/anaconda3/bin:$PATH"' >> ~/.bashrc source .bashrc。
- 查看python packages安装路径
- python; import numpy; print(numpy.__file__)
- pip show package
- 安装包packages:conda 和 pip
- pip 和 conda一般都是将packages安装在lib\site-packages目录下
- 一种方式:conda install package_name, e.g. conda install matplotlib==2.0.2
- 不推荐!conda安装torch引起的!通过anaconda-navigator安装pytorch 时它将自动安装cpu和gpu两个版本,因此产生多版本冲突错误
- 所以一定要用pip来安装,否则会遇到版本冲突error。
- 另一种方式:pip 定向给指定路径安装packages
- conda install pip;pip install numpy==1.22.0
- pip install -t D:\python3.5(32bit)\Lib\site-packages numpy==1.22.0
- Note:pip和conda都会将package安装到miniconda,但会独立表示,容易安装多个相同package,造成多版本torch或cuda冲突error!e.g. torch_sparse/_version_cpu.so: undefined symbol: _ZN5torch3jit17parseSchemaOrNameERKSs
- 卸载包:conda remove package_name
- 删除虚拟环境命令:conda remove -n env_name --all
- 退出当前环境:conda deactivate
- 用pip查看server本地python运行环境:pip list。
- 注意比较pip list 列出的server home本地运行环境与conda list列出的虚拟运行环境env是否一致!可信的是:激活虚拟环境,用conda list查看虚拟运行环境配置。
关于更多的命令,我们可以查看Anaconda/miniconda官方提供的命令,官网链接:点击这里
在安装package时,我们经常会使用pip install package_name
和conda install package_name
的命令,但是一些package下载速度会很慢,因此我们需要进行换源,换成国内源,加快我们的下载速度。
pip install package_name -i https://pypi.tuna.tsinghua.edu.cn/simple
Linux下的换源,我们首先需要在用户目录下新建文件夹.pip
,并且在文件夹内新建文件pip.conf
,具体命令如下
cd ~
mkdir .pip/
vi pip.conf
随后,我们需要在
pip.conf
添加下方的内容:[global]
index-url = http://pypi.douban.com/simple
[install]
use-mirrors =true
mirrors =http://pypi.douban.com/simple/
trusted-host =pypi.douban.com
Windows下换源:
1) 文件管理器文件路径地址栏敲:%APPDATA%
回车,快速进入 C:\Users\电脑用户\AppData\Roaming
文件夹中
2) 新建 pip 文件夹并在文件夹中新建 pip.ini
配置文件
3) 我们需要在pip.ini
配置文件内容,我们可以选择使用记事本打开,输入以下内容,并按下ctrl+s保存,在这里我们使用的是豆瓣源为例子。
[global]
index-url = http://pypi.douban.com/simple
[install]
use-mirrors =true
mirrors =http://pypi.douban.com/simple/
trusted-host =pypi.douban.com
cmd/terminal中
输入nvidia-smi
(Linux和Win命令一样),查看自己是否有NVIDIA的独立显卡及其型号
Note that我们需要看下版本号,看自己可以兼容的CUDA版本,等会安装PyTorch时是可以向下兼容的。具体适配表如下图所示。
使用conda下载或者pip下载(建议conda安装)
打开Terminal
,输入conda activate env_name
(env_name 为你对应的环境名称),切换到对应的环境下面,我们就可以进行PyTorch的安装了。
Note:
Stable代表的是稳定版本,Preview代表的是先行版本
可以结合电脑是否有显卡,选择CPU版本还是CUDA版本,CUDA版本需要拥有独显且是NVIDIA的GPU
官方建议我们使用Anaconda/miniconda来进行管理
关于安装的系统要求
Windows:
Windows 7及更高版本;建议使用Windows 10或者更高的版本
Windows Server 2008 r2 及更高版本
Linux:以常见的CentOS和Ubuntu为例
CentOS, 最低版本7.3-1611
Ubuntu, 最低版本 13.04,这里会导致cuda安装的最大版本不同
macOS:
macOS 10.10及其以上
有些电脑所支持的cuda版本<10.2,此时我们需要进行手动降级,即就是cudatoolkit = 你所适合的版本,但是这里需要注意下一定要保持PyTorch和cudatoolkit的版本适配。查看Previous PyTorch Versions | PyTorch
Awesome-pytorch-list:目前已获12K Star,包含了NLP,CV,常见库,论文实现以及Pytorch的其他项目。
PyTorch官方文档:官方发布的文档,十分丰富。
Pytorch-handbook:GitHub上已经收获14.8K,pytorch手中书。
PyTorch官方社区:PyTorch拥有一个活跃的社区,在这里你可以和开发pytorch的人们进行交流。
PyTorch官方tutorials:官方编写的tutorials,可以结合colab边动手边学习
动手学深度学习:动手学深度学习是由李沐老师主讲的一门深度学习入门课,拥有成熟的书籍资源和课程资源,在B站,Youtube均有回放。
Awesome-PyTorch-Chinese:常见的中文优质PyTorch资源
labml.ai Deep Learning Paper Implementations:手把手实现经典网络代码
YSDA course in Natural Language Processing:YSDA course in Natural Language Processing
huggingface:hugging face
ModelScope: 魔搭社区
在PyTorch中, torch.Tensor
是存储和变换数据的主要工具。如果你之前用过NumPy
,你会发现 Tensor
和NumPy的多维数组非常类似。然而,Tensor
提供GPU计算和自动求梯度等更多功能,这些使 Tensor
这一数据类型更加适合深度学习
几何代数中定义的张量是基于向量和矩阵的推广,比如我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。
张量维度 | 代表含义 |
---|---|
0维张量 | 代表的是标量(数字) |
1维张量 | 代表的是向量 |
2维张量 | 代表的是矩阵 |
3维张量 | 时间序列数据 股价 文本数据 单张彩色图片(RGB) |
4维 | 图像 |
5维 | 视频 |
pytorch中,所有神经网络的核心是autograd package
,也就是自动微分。autograd包为张量上所有操作提供了自动求导机制。它是一个在运行时定义(define-by-run)框架,这意味着反向传播是根据代码如何运行来决定的,并且每次迭代可以是不同的。
torch.Tensor是这个包的核心类。如果设置它的属性.requires_grad为True,那么它将会追踪对于该张量的所有操作。当完成计算后可以调用.backward()来自动计算所有的梯度。这个张量的所有梯度将会自动累加到.grad属性。-->注意:grad在反向传播过程中是累加的(accumulated),这意味着每一次运行反向传播,梯度都会累加之前的梯度,所以一般在反向传播之前需把梯度清零。
Note,在y.backward()时,如果y是标量,则不需要为backward()传入任何参数;否则,需要传入一个与y同形的Tensor。
.detach()方法,要防止一个张量被跟踪历史,可以调用.detach()方法将其与计算历史分离,并阻止它未来的计算记录被跟踪。
with torch.no_grad()方法,为了防止跟踪历史记录,可以将代码块包装在with torch.no_grad():中。在评估模型时特别有用,因为模型可能具有requires_grad=True的可训练参数,但是我们不需要在此过程中对它们进行梯度计算。
还有一个类对于autograd的实现非常重要:Function。Tensor和Function互相连接生成了一个无环图(acyclic graph),它编码了完整的计算历史。每个张量都有一个.grad_fn属性,该属性引用了创建Tensor自身的Function(除非这个tensor是用户手动创建的,即这个张量的grad_fn是None)
如果需要计算导数(梯度),可以在Tensor上调用.backward()方法。如果Tensor是一个标量,则不需要为backward()指定任何参数,但如果它有更多的元素,则需要指定一个gradient参数,该参数是形状匹配的张量。
因此out.backward()
和 out.backward(torch.tensor(1.))
等价。
- # 创建一个张量并设置requires_grad=True用来追踪其计算历史
- x = torch.ones(2, 2, requires_grad=True)
- print(x)
-
- tensor([[1., 1.],
- [1., 1.]], requires_grad=True)
-
- # 对这个张量做一次运算:
- y = x**2
- print(y) # y是计算的结果,所以它有grad_fn属性。
-
- tensor([[1., 1.],
- [1., 1.]], grad_fn=<PowBackward0>)
-
- # 对 y 进行更多操作
- z = y * y * 3
- out = z.mean()
-
- print(z, out)
- tensor([[3., 3.],
- [3., 3.]], grad_fn=<MulBackward0>) tensor(3., grad_fn=<MeanBackward0>)
-
- # 现在开始进行反向传播
- out.backward()
-
- # 输出导数 d(out)/dx
- print(x.grad)
-
- tensor([[3., 3.],
- [3., 3.]])
pytorch做DL过程中,可能会遇到数据量较大无法在单块GPU上完成,或者需要提升计算速度的场景,这时就需要用到并行计算--多个GPU来参与训练,减少训练时间。
为什么需要CUDA?
CUDA是GPU提供商NVIDA提供的GPU并行计算框架。对于GPU本身的编程,使用的是CUDA语言来实现的。
在pytorch中,使用CUDA表示要开始要求我们的模型或数据开始使用GPU了。
在编写程序中,当我们使用了.cuda()时,其功能是让我们的model或data从CPU迁移到GPU(0)当中,通过GPU开始计算。
Note:
- 我们使用GPU时使用的是.cuda()而不是.gpu()。这是因为当前GPU的编程接口采用CUDA,但是市面上的GPU并不是都支持CUDA,只有部分NVIDA的GPU才支持,AMD的GPU编程接口采用的是OpenCL,在现阶段pytorch并不支持。
- 数据在GPU和CPU之间传递时会比较耗时,我们应当尽量避免数据的切换。
- GPU运算很快,但是在使用简单的操作时,我们应该尽量使用CPU去完成。
- 当我们的服务器上有多个GPU,我们应该指明我们使用的GPU是哪一块,如果我们不设置的话,tensor.cuda()方法会默认将tensor保存到第一块GPU上,等价于tensor.cuda(0),这将会导致out of memory的错误。
常见的并行方法
(详见原文)
设置CUDA加速训练
我们可以通过一下两种方式设置显卡
本质:CUDA是用于深度学习模型,在cpu和GPU之间交换数据的并行计算架构。
简单粗暴的应用:将model running涉及到的model和tensors都传送到显卡上!!
建议使用model.to(device)的方式,将数据和模型送入GPU中。这样可以显示指定需要使用的计算资源,特别是有多个GPU的情况下。
- # CUDA GPU 设置方式
-
- # 方案一:使用os.environ,这种情况如果使用GPU不需要设置
- import os
- os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' # 指明调用的GPU为0,1号
- -------------------------------------------------------
- # 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
- device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") # 指明调用的GPU为1号
-
- # CUDA GPU 调用方式
- # 方案一:model.cuda()
- -------------------------------------------------------
- # 方案二:model.to(device)
-》判断当前运行环境是否成功调用显卡GPU和CUDA()
-》将vector变量copy一份到device指定的GPU上,之后的运算都在GPU上进行。
-》释放显存
原因: GPU CUDA 只支持tensor操作,不支持numpy操作
将tensor从显卡传回本地 -》tensor.cpu()
机器学习任务的几个重要步骤:
深度学习与机器学习在流程上类似,在代码上有较大差异:
深度学习与机器学习在模型表现上的差异:
损失函数和优化器要能够保证反向传播能够在用户自行定义的模型上实现。
程序默认是在cpu上运行的。因此在代码实现中,需要把模型和数据“放到”GPU上去做运算,同时还需要保证损失函数和优化器能够在GPU上工作。
深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。
导包和超参数设置
- import os
- import numpy as np
- import torch
- import torch.nn as nn
- from torch.utils.data import Dataset, DataLoader
- import torch.optim as optimizer
-
- batch_size = 16
- # 批次的大小
- lr = 1e-4
- # 优化器的学习率
- max_epochs = 100
pytorch数据读入是通过Dataset + DataLoader的方式完成的,
我们可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承pytorch自身的Dataset类。主要包含三个函数:
- class MyDataset(Dataset):
- def __init__(self, data_dir, info_csv, image_list, transform=None):
- """
- Args:
- data_dir: path to image directory.
- info_csv: path to the csv file containing image indexes
- with corresponding labels.
- image_list: path to the txt file contains image names to training/validation set
- transform: optional transform to be applied on a sample.
- """
- label_info = pd.read_csv(info_csv)
- image_file = open(image_list).readlines()
- self.data_dir = data_dir
- self.image_file = image_file
- self.label_info = label_info
- self.transform = transform
-
- def __getitem__(self, index):
- """
- Args:
- index: the index of item
- Returns:
- image and its labels
- """
- image_name = self.image_file[index].strip('\n')
- raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
- label = raw_label.iloc[:,0]
- image_name = os.path.join(self.data_dir, image_name)
- image = Image.open(image_name).convert('RGB')
- if self.transform is not None:
- image = self.transform(image)
- return image, label
-
- def __len__(self):
- return len(self.image_file)
构建好Dataset后,就可以使用DataLoader来按批次读入数据了,实现代码如下:
- from torch.utils.data import DataLoader
-
- train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size,
- num_workers=4, shuffle=True, drop_last=True)
- val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size,
- num_workers=4, shuffle=False)
查看加载的数据。pytorch中DataLoader读取的数据可以使用next和iter来完成。
- import matplotlib.pyplot as plt
- images, labels = next(iter(val_loader))
- print(images.shape)
- plt.imshow(images[0].transpose(1,2,0))
- plt.show()
Note that:
pytorch中的神经网络模型一般是基于nn.Module类的模型来完成的,它让模型构造更灵活。
Module类是torch.nn模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。下面是继承Module类构造的多层感知机。这里定义的MLP类重载了Module类的__init__函数和forward函数。他们分别用于创建模型参数和定义前向计算(正向传播)。
e.g. 一个具有两个隐藏层的多层感知机。
- import torch
- from torch import nn
-
- class MLP(nn.Module):
- # 声明带有模型参数的层,这里声明了两个全连接层
- def __init__(self, **kwargs):
- # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
- super(MLP, self).__init__(**kwargs)
- self.hidden = nn.Linear(784, 256)
- self.act = nn.ReLU()
- self.output = nn.Linear(256,10)
-
- # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
- def forward(self, x):
- o = self.act(self.hidden(x))
- return self.output(o)
上述MLP类中无需定义反向传播函数。系统将通过自动求梯度而自动生成反向传播所需的backward函数。
我们可以实例化MLP类得到模型变量net。下面的代码初始化net并传入数据x做一次前向计算。其中,net(x)会调用MLP继承自Module类的__call__函数,这个函数将调用MLP类定义的forward函数来完成前向计算。因此我们自己构造模型时需要明确定义模型的forward过程。
- X = torch.rand(2,784) # 设置一个随机的输入张量
- net = MLP() # 实例化模型
- print(net) # 打印模型
- net(X) # 前向计算
MLP( (hidden): Linear(in_features=784, out_features=256, bias=True) (act): ReLU() (output): Linear(in_features=256, out_features=10, bias=True) ) tensor([[ 0.0149, -0.2641, -0.0040, 0.0945, -0.1277, -0.0092, 0.0343, 0.0627, -0.1742, 0.1866], [ 0.0738, -0.1409, 0.0790, 0.0597, -0.1572, 0.0479, -0.0519, 0.0211, -0.1435, 0.1958]], grad_fn=<AddmmBackward>)
background:当我们的网络有一些其他的设计时,会需要一些额外的参数同样跟着整个网络的训练进行学习更新,最后得到最优的值
本质:torch.nn.Parameter(Tensor)的输入是tensor变量,用于生成参数矩阵W。将一个固定不可训练的tensor转化成一个可以训练改变的vector(即parameter),并将parameter绑定到这个module里面。
nn.Parameter类其实是Tensor的子类,所以它也会被自动记录计算历史和反向传播,如果一个Tensor是Parameter,那么它会自动被添加到模型的参数列表里。所以在自定义含模型参数的层时,我们应该将参数定义成Parameter,除了直接定义成Parameter类外,还可以使用ParameterList和ParameterDict分别定义参数的列表和字典。
- class MyListDense(nn.Module):
- def __init__(self):
- super(MyListDense, self).__init__()
- self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4))
- for i in range(3)])
- self.params.append(nn.Parameter(torch.randn(4, 1)))
-
- def forward(self, x):
- for i in range(len(self.params)):
- x = torch.mm(x, self.params[i])
- return x
- net = MyListDense()
- print(net)
- class MyDictDense(nn.Module):
- def __init__(self):
- super(MyDictDense, self).__init__()
- self.params = nn.ParameterDict({
- 'linear1': nn.Parameter(torch.randn(4, 4)),
- 'linear2': nn.Parameter(torch.randn(4, 1))
- })
- self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))}) # 新增
-
- def forward(self, x, choice='linear1'):
- return torch.mm(x, self.params[choice])
-
- net = MyDictDense()
- print(net)
二维卷积层将输入和卷积核做互相关运算,并加上一个标量偏差来得到输出。卷积层的模型参数草扩列卷积核和标量偏差。在训练模型的时候,通常我们先对卷积核随机初始化,然后不断迭代卷积核和偏差。
- import torch
- from torch import nn
-
- # 卷积运算(二维互相关)
- def corr2d(X, K):
- h, w = K.shape
- X, K = X.float(), K.float()
- Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
- for i in range(Y.shape[0]):
- for j in range(Y.shape[1]):
- Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
- return Y
-
- # 二维卷积层
- class Conv2D(nn.Module):
- def __init__(self, kernel_size):
- super(Conv2D, self).__init__()
- self.weight = nn.Parameter(torch.randn(kernel_size))
- self.bias = nn.Parameter(torch.randn(1))
-
- def forward(self, x):
- return corr2d(x, self.weight) + self.bias
在深度学习模型中,权重的初始化极为重要。一个好的初始值,会使模型收敛速度提高,使模型准确率更加精确。一般情况下,我们不适用全为0初始值训练网络。为了利于训练和减少收敛时间,我们需要对模型进行合理的初始化。
torch.nn.init为我们提供了常用的初始化方法。
通过访问torch.nn.init的官方文档链接 ,我们发现torch.nn.init
提供了以下初始化方法: 1 . torch.nn.init.uniform_
(tensor, a=0.0, b=1.0) 2 . torch.nn.init.normal_
(tensor, mean=0.0, std=1.0) 3 . torch.nn.init.constant_
(tensor, val) 4 . torch.nn.init.ones_
(tensor) 5 . torch.nn.init.zeros_
(tensor) 6 . torch.nn.init.eye_
(tensor) 7 . torch.nn.init.dirac_
(tensor, groups=1) 8 . torch.nn.init.xavier_uniform_
(tensor, gain=1.0) 9 . torch.nn.init.xavier_normal_
(tensor, gain=1.0) 10 . torch.nn.init.kaiming_uniform_
(tensor, a=0, mode='fan__in', nonlinearity='leaky_relu') 11 . torch.nn.init.kaiming_normal_
(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu') 12 . torch.nn.init.orthogonal_
(tensor, gain=1) 13 . torch.nn.init.sparse_
(tensor, sparsity, std=0.01) 14 . torch.nn.init.calculate_gain
(nonlinearity, param=None) 关于计算增益如下表:
torch.nn.init需要根据实际模型来进行初始化,通常使用isinstance()来进行判断模块类型。
- import torch
- import torch.nn as nn
-
- conv = nn.Conv2d(1,3,3)
- linear = nn.Linear(10,1)
-
- isinstance(conv,nn.Conv2d) # 判断conv是否是nn.Conv2d类型
- isinstance(linear,nn.Conv2d) # 判断linear是否是nn.Conv2d类型
-
- # 查看随机初始化的conv参数
- conv.weight.data
- # 查看linear的参数
- linear.weight.data
-
- # 对conv进行kaiming初始化
- torch.nn.init.kaiming_normal_(conv.weight.data)
- conv.weight.data
- # 对linear进行常数初始化
- torch.nn.init.constant_(linear.weight.data,0.3)
- linear.weight.data
人们常常将各种初始化方法定义为一个initialize_weights()的函数并在模型初始后进行使用。
- def initialize_weights(self):
- for m in self.modules():
- # 判断是否属于Conv2d
- if isinstance(m, nn.Conv2d):
- torch.nn.init.xavier_normal_(m.weight.data)
- # 判断是否有偏置
- if m.bias is not None:
- torch.nn.init.constant_(m.bias.data,0.3)
- elif isinstance(m, nn.Linear):
- torch.nn.init.normal_(m.weight.data, 0.1)
- if m.bias is not None:
- torch.nn.init.zeros_(m.bias.data)
- elif isinstance(m, nn.BatchNorm2d):
- m.weight.data.fill_(1)
- m.bias.data.zeros_()
-
- # 模型的定义
- class MLP(nn.Module):
- # 声明带有模型参数的层,这里声明了两个全连接层
- def __init__(self, **kwargs):
- # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
- super(MLP, self).__init__(**kwargs)
- self.hidden = nn.Conv2d(1,1,3)
- self.act = nn.ReLU()
- self.output = nn.Linear(10,1)
-
- # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
- def forward(self, x):
- o = self.act(self.hidden(x))
- return self.output(o)
-
- mlp = MLP()
- print(list(mlp.parameters()))
- print("-------初始化-------")
-
- initialize_weights(mlp)
- print(list(mlp.parameters()))
模型好的training离不开优质的负反馈,这里的负反馈就是损失函数loss。
pytorch常用torch.nn来定义损失函数。
--》先解决实际问题的过程中可以进一步探索、借鉴现有工作,设计自己的损失函数。
模型优化器 Optimizer
优化器optimizer是根据NN反向传播的梯度信息来更新网络参数,以起到降低loss函数计算值,使得模型输出更加接近真实标签。
Optimizer有三个属性:defaults存储优化器的超参数;state参数的缓存;para_groups管理的参数组,是一个list,其中每个元素是一个字典,顺序是params, lr, momentum, dampening, weight_decay, nesterov。
Optimzer方法:
Optimizer 使用流程:
1)定义优化器
2)梯度置零
3)梯度更新
- # 每个优化器都是一个类,我们需要进行实例化才能使用
- class Net(nn.Moddule):
- ···
- net = Net()
-
- optimizer = torch.optim.SGD(net.parameters(), lr=1e-5)
- for epoch in range(EPOCH):
- ...
- optimizer.zero_grad() #梯度置零
- loss = ... #计算loss
- loss.backward() #BP反向传播
- optimizer.step() #梯度更新
Optimizer给网络不同的层赋予不同的优化器参数。
- from torch import optim
- from torchvision.models import resnet18
-
- net = resnet18()
-
- optimizer = optim.SGD([
- {'params':net.fc.parameters()},#fc的lr使用默认的1e-5
- {'params':net.layer4[0].conv1.parameters(),'lr':1e-2}],lr=1e-5)
-
- # 可以使用param_groups查看属性
Optimizer实际操作
- import os
- import torch
-
- # 设置权重,服从正态分布 --> 2 x 2
- weight = torch.randn((2, 2), requires_grad=True)
- # 设置梯度为全1矩阵 --> 2 x 2
- weight.grad = torch.ones((2, 2))
- # 输出现有的weight和data
- print("The data of weight before step:\n{}".format(weight.data))
- print("The grad of weight before step:\n{}".format(weight.grad))
- # 实例化优化器
- optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9)
- # 进行一步操作
- optimizer.step()
- # 查看进行一步后的值,梯度
- print("The data of weight after step:\n{}".format(weight.data))
- print("The grad of weight after step:\n{}".format(weight.grad))
- # 权重清零
- optimizer.zero_grad()
- # 检验权重是否为0
- print("The grad of weight after optimizer.zero_grad():\n{}".format(weight.grad))
- # 输出参数
- print("optimizer.params_group is \n{}".format(optimizer.param_groups))
- # 查看参数位置,optimizer和weight的位置一样,我觉得这里可以参考Python是基于值管理
- print("weight in optimizer:{}\nweight in weight:{}\n".format(id(optimizer.param_groups[0]['params'][0]), id(weight)))
- # 添加参数:weight2
- weight2 = torch.randn((3, 3), requires_grad=True)
- optimizer.add_param_group({"params": weight2, 'lr': 0.0001, 'nesterov': True})
- # 查看现有的参数
- print("optimizer.param_groups is\n{}".format(optimizer.param_groups))
- # 查看当前状态信息
- opt_state_dict = optimizer.state_dict()
- print("state_dict before step:\n", opt_state_dict)
- # 进行5次step操作
- for _ in range(50):
- optimizer.step()
- # 输出现有状态信息
- print("state_dict after step:\n", optimizer.state_dict())
- # 保存参数信息
- torch.save(optimizer.state_dict(),os.path.join(r"D:\pythonProject\Attention_Unet", "optimizer_state_dict.pkl"))
- print("----------done-----------")
- # 加载参数信息
- state_dict = torch.load(r"D:\pythonProject\Attention_Unet\optimizer_state_dict.pkl") # 需要修改为你自己的路径
- optimizer.load_state_dict(state_dict)
- print("load state_dict successfully\n{}".format(state_dict))
- # 输出最后属性信息
- print("\n{}".format(optimizer.defaults))
- print("\n{}".format(optimizer.state))
- print("\n{}".format(optimizer.param_groups))
模型评价指标
我们在完成了模型的训练后,需要在测试集/验证集上完成模型的验证,以确保我们的模型具有泛化能力、不会出现过拟合等问题。在PyTorch中,训练和评估的流程是一致的,只是在训练过程中需要将模型的参数进行更新,而在评估过程中则不需要更新参数。
Learning Target:
完成了上述设定后就可以加载数据开始训练模型了。
在pytorch中,模型的状态设置非常简便,如下的两个操作二选一即可:
- model.train() # 训练状态
- model.eval() # 验证/测试状态
for data, label in train_loader:
data, label = data.cuda(), label.cuda()
optimizer.zero_grad()
output = model(data)
loss = criterion(output, label)
loss.backward()
optimizer.step()
这样一个训练过程就完成了,对于测试或验证过程,可以计算分类准确率,这部分会在下一节图像分类中介绍。
验证/测试的流程基本与训练过程一致,不同点在于:
- 需要预先设置torch.no_grad,以及将model调至eval模式
- 不需要将优化器的梯度置零
- 不需要将loss反向传播回到网络
- 不需要更新optimizer
一个完整的图像分类的训练过程如下所示:
- def train(epoch):
- model.train()
- train_loss = 0
- for data, label in train_loader:
- data, label = data.cuda(), label.cuda()
- optimizer.zero_grad()
- output = model(data)
- loss = criterion(output, label)
- loss.backward()
- optimizer.step()
- train_loss += loss.item()*data.size(0)
- train_loss = train_loss/len(train_loader.dataset)
- print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
对应的,一个完成图像分类的验证过程如下所示:
with torch.no_grad():
- def val(epoch):
- model.eval()
- val_loss = 0
- with torch.no_grad():
- for data, label in val_loader:
- data, label = data.cuda(), label.cuda()
- output = model(data)
- preds = torch.argmax(output, 1)
- loss = criterion(output, label)
- val_loss += loss.item()*data.size(0)
- running_accu += torch.sum(preds == label.data)
- val_loss = val_loss/len(val_loader.dataset)
- print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, val_loss))
在pytorch深度学习中,可视化是一个可选项。指的是某些任务在训练完成后,需要对一些必要的内容进行可视化,比如分类的ROC曲线,卷积网络中的卷积核,一个训练/验证过程的损害函数曲线等。
基础实战——FashionMNIST时装分类 — 深入浅出PyTorch
- import os
- import numpy as np
- import pandas as pd
- import torch
- import torch.nn as nn
- import torch.optim as optim
- from torch.utils.data import Dataset, DataLoader
- # 配置GPU,这里有两种方式
- ## 方案一:使用os.environ
- os.environ['CUDA_VISIBLE_DEVICES'] = '0'
- # 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
- device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
-
- ## 配置其他超参数,如batch_size, num_workers, learning rate, 以及总的epochs
- batch_size = 256
- num_workers = 4 # 对于Windows用户,这里应设置为0,否则会出现多线程错误
- lr = 1e-4
- epochs = 20
这里同时展示两种方式:
下载并使用PyTorch提供的内置数据集
从网站下载以csv格式存储的数据,读入并转成预期的格式
第一种数据读入方式只适用于常见的数据集,如MNIST,CIFAR10等,PyTorch官方提供了数据下载。这种方式往往适用于快速测试方法(比如测试下某个idea在MNIST数据集上是否有效)
第二种数据读入方式需要自己构建Dataset,这对于PyTorch应用于自己的工作中十分重要
同时,还需要对数据进行必要的变换,比如说需要将图片统一为一致的大小,以便后续能够输入网络训练;需要将数据格式转为Tensor类,等等。
这些变换可以很方便地借助torchvision包来完成,这是PyTorch官方用于图像处理的工具库,上面提到的使用内置数据集的方式也要用到。PyTorch的一大方便之处就在于它是一整套“生态”,有着官方和第三方各个领域的支持。这些内容我们会在后续课程中详细介绍。
- # 首先设置数据变换
- from torchvision import transforms
-
- image_size = 28
- data_transform = transforms.Compose([
- transforms.ToPILImage(),
- # 这一步取决于后续的数据读取方式,如果使用内置数据集读取方式则不需要
- transforms.Resize(image_size),
- transforms.ToTensor()
- ])
- ## 读取方式一:使用torchvision自带数据集,下载可能需要一段时间
- from torchvision import datasets
-
- train_data = datasets.FashionMNIST(root='./', train=True, download=True, transform=data_transform)
- test_data = datasets.FashionMNIST(root='./', train=False, download=True, transform=data_transform)
- ## 读取方式二:读入csv格式的数据,自行构建Dataset类
- # csv数据下载链接:https://www.kaggle.com/zalando-research/fashionmnist
- class FMDataset(Dataset):
- def __init__(self, df, transform=None):
- self.df = df
- self.transform = transform
- self.images = df.iloc[:,1:].values.astype(np.uint8)
- self.labels = df.iloc[:, 0].values
-
- def __len__(self):
- return len(self.images)
-
- def __getitem__(self, idx):
- image = self.images[idx].reshape(28,28,1)
- label = int(self.labels[idx])
- if self.transform is not None:
- image = self.transform(image)
- else:
- image = torch.tensor(image/255., dtype=torch.float)
- label = torch.tensor(label, dtype=torch.long)
- return image, label
-
- train_df = pd.read_csv("./FashionMNIST/fashion-mnist_train.csv")
- test_df = pd.read_csv("./FashionMNIST/fashion-mnist_test.csv")
- train_data = FMDataset(train_df, data_transform)
- test_data = FMDataset(test_df, data_transform)
在构建训练和测试数据集完成后,需要定义DataLoader类,以便在训练和测试时加载数据
- train_loader = DataLoader(train_data, batch_size=batch_size, shuffle=True, num_workers=num_workers, drop_last=True)
- test_loader = DataLoader(test_data, batch_size=batch_size, shuffle=False, num_workers=num_workers)
读入后,我们可以做一些数据可视化操作,主要是验证我们读入的数据是否正确
- import matplotlib.pyplot as plt
- image, label = next(iter(train_loader))
- print(image.shape, label.shape)
- plt.imshow(image[0][0], cmap="gray")
- ------------------------------------------
- torch.Size([256, 1, 28, 28])
- torch.Size([256])
- <matplotlib.image.AxesImage at 0x7f19a043cc10>
手搭一个CNN,模型构建完成后,将模型放到GPU上用于训练。
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
- self.conv = nn.Sequential(
- nn.Conv2d(1, 32, 5),
- nn.ReLU(),
- nn.MaxPool2d(2, stride=2),
- nn.Dropout(0.3),
- nn.Conv2d(32, 64, 5),
- nn.ReLU(),
- nn.MaxPool2d(2, stride=2),
- nn.Dropout(0.3)
- )
- self.fc = nn.Sequential(
- nn.Linear(64*4*4, 512),
- nn.ReLU(),
- nn.Linear(512, 10)
- )
-
- def forward(self, x):
- x = self.conv(x)
- x = x.view(-1, 64*4*4)
- x = self.fc(x)
- # x = nn.functional.normalize(x)
- return x
-
- model = Net()
- model = model.cuda()
- # model = nn.DataParallel(model).cuda() # 多卡训练时的写法,之后的课程中会进一步讲解
使用torch.nn模块自带的CrossEntropy损失
PyTorch会自动把整数型的label转为one-hot型,用于计算CE loss
这里需要确保label是从0开始的,同时模型不加softmax层(使用logits计算),这也说明了PyTorch训练中各个部分不是独立的,需要通盘考虑
- criterion = nn.CrossEntropyLoss()
- # criterion = nn.CrossEntropyLoss(weight=[1,1,1,1,3,1,1,1,1,1])
optimizer = optim.Adam(model.parameters(), lr=0.001)
各自封装成函数,方便后续调用
关注两者的主要区别:
模型状态设置
是否需要初始化优化器
是否需要将loss传回到网络
是否需要每步更新optimizer
此外,对于测试或验证过程,可以计算分类准确率
- def train(epoch):
- model.train()
- train_loss = 0
- for data, label in train_loader:
- data, label = data.cuda(), label.cuda()
- optimizer.zero_grad()
- output = model(data)
- loss = criterion(output, label)
- loss.backward()
- optimizer.step()
- train_loss += loss.item()*data.size(0)
- train_loss = train_loss/len(train_loader.dataset)
- print('Epoch: {} \tTraining Loss: {:.6f}'.format(epoch, train_loss))
- def val(epoch):
- model.eval()
- val_loss = 0
- gt_labels = []
- pred_labels = []
- with torch.no_grad():
- for data, label in test_loader:
- data, label = data.cuda(), label.cuda()
- output = model(data)
- preds = torch.argmax(output, 1)
- gt_labels.append(label.cpu().data.numpy())
- pred_labels.append(preds.cpu().data.numpy())
- loss = criterion(output, label)
- val_loss += loss.item()*data.size(0)
- val_loss = val_loss/len(test_loader.dataset)
- gt_labels, pred_labels = np.concatenate(gt_labels), np.concatenate(pred_labels)
- acc = np.sum(gt_labels==pred_labels)/len(pred_labels)
- print('Epoch: {} \tValidation Loss: {:.6f}, Accuracy: {:6f}'.format(epoch, val_loss, acc))
- for epoch in range(1, epochs+1):
- train(epoch)
- val(epoch)
训练完成后,可以使用torch.save保存模型参数或者整个模型,也可以在训练过程中保存模型
这部分会在后面的课程中详细介绍
- save_path = "./FahionModel.pkl"
- torch.save(model, save_path)
基于nn.Module,我们可以通过Sequential, ModuleList和ModuleDict三种方式定义pytorch模型。
对应的模块为nn.Sequential()。
当模型的前向计算为简单串联各个层的计算时,Sequential类可以通过更加简单的方式定义模型。它可以接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。我们结合Sequential和定义方式加以理解:
- from collections import OrderedDict
- class MySequential(nn.Module):
- def __init__(self, *args):
- super(MySequential, self).__init__()
- if len(args) == 1 and isinstance(args[0], OrderedDict): # 如果传入的是一个OrderedDict
- for key, module in args[0].items():
- self.add_module(key, module)
- # add_module方法会将module添加进self._modules(一个OrderedDict)
- else: # 传入的是一些Module
- for idx, module in enumerate(args):
- self.add_module(str(idx), module)
- def forward(self, input):
- # self._modules返回一个 OrderedDict,保证会按照成员添加时的顺序遍历成
- for module in self._modules.values():
- input = module(input)
- return input
- import torch.nn as nn
- net = nn.Sequential(
- nn.Linear(784, 256),
- nn.ReLU(),
- nn.Linear(256, 10),
- )
- print(net)
- --------------------------
- Sequential(
- (0): Linear(in_features=784, out_features=256, bias=True)
- (1): ReLU()
- (2): Linear(in_features=256, out_features=10, bias=True)
- )
- import collections
- import torch.nn as nn
- net2 = nn.Sequential(collections.OrderedDict([
- ('fc1', nn.Linear(784, 256)),
- ('relu1', nn.ReLU()),
- ('fc2', nn.Linear(256, 10))
- ]))
- print(net2)
- ----------------------------------------
- Sequential(
- (fc1): Linear(in_features=784, out_features=256, bias=True)
- (relu1): ReLU()
- (fc2): Linear(in_features=256, out_features=10, bias=True)
- )
我们可以看到,使用Sequential定义模型的好处在于简单、易读,同时使用Sequential定义的模型不需要在写forward,因为顺序已经定义好了。但使用Sequential也会使得模型定义丧失灵活性,比如需要在模型中间加入一个外部输入时就不适合用Sequential的方式实现。使用时需要根据实际需求加以选择。
对应模块为nn.ModuleList()。
ModuleList接收一个子模块(或层,需属于nn.Module类)的列表作为输入,然后也可以类似List那样进行append和extend操作。同时,子模块或层的权重也会自动添加到网络中来。
- net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
- net.append(nn.Linear(256, 10)) # # 类似List的append操作
- print(net[-1]) # 类似List的索引访问
- print(net)
- ------------------------------------------------------
- Linear(in_features=256, out_features=10, bias=True)
- ModuleList(
- (0): Linear(in_features=784, out_features=256, bias=True)
- (1): ReLU()
- (2): Linear(in_features=256, out_features=10, bias=True)
- )
Note that,nn.ModuleList并没有定义一个网络,它只是将不同的模块储存在一起。Modulelist中元素的先后顺序并不代表其在网络中的真实位置顺序,需要经过forward函数指定各个层的先后顺序后才算完成了模型的定义。具体实现时用for循环可完成:
- class model(nn.Module):
- def __init__(self, ...):
- super().__init__()
- self.modulelist = ...
- ...
-
- def forward(self, x):
- for layer in self.modulelist:
- x = layer(x)
- return x
对应模块为nn.ModuleDict()。
ModuleDict和ModuleList的作用类似,只是ModuleDict能够更方便得作为神经网络的层添加名称。
- net = nn.ModuleDict({
- 'linear': nn.Linear(784, 256),
- 'act': nn.ReLU(),
- })
- net['output'] = nn.Linear(256, 10) # 添加
- print(net['linear']) # 访问
- print(net.output)
- print(net)
- ------------------------------------
- Linear(in_features=784, out_features=256, bias=True)
- Linear(in_features=256, out_features=10, bias=True)
- ModuleDict(
- (act): ReLU()
- (linear): Linear(in_features=784, out_features=256, bias=True)
- (output): Linear(in_features=256, out_features=10, bias=True)
- )
Sequential适用于快速验证结果,因为已经明确了要用哪些层,直接写一下就好了,不需要同时写__init__和forward。
ModuleList和Dict在某个完全相同的层需要重复出现多次时,非常方便实现,可以"一行顶多行"。
当我们需要之前层的信息时,比如ResNets中的残差计算,当前层的结果需要和之前层中的结果进行融合,一般使用ModuleList/moduleDict比较方便。
problem:当模型深度非常大时,有几百行代码中很多重复出现的结构,使用起来很不方便。
solution:将这些重复出现的层定义为一个“模块”,若干层串联成的“模块”也有其输入和输出。
将模型(net)最后名称为"fc"的层替换成了名称为"classifier"的结构。
- import torchvision.models as models
- net = models.resnet50()
- print(net)
-
- # 重新定义一个"模块"
- from collections import OrderedDict
- classifier = nn.Sequential(OrderedDict([('fc1', nn.Linear(2048, 128)),
- ('relu1', nn.ReLU()),
- ('dropout1',nn.Dropout(0.5)),
- ('fc2', nn.Linear(128, 10)),
- ('output', nn.Softmax(dim=1))
- ]))
-
- net.fc = classifier
将原模型作为一个整体参数输入到新模型中,新模型调整好原模型输入与后续添加输入、后续层之间的关系,从而完成模型修改。
- import torchvision.models as models
- net = models.resnet50()
- print(net)
-
- class Model(nn.Module):
- def __init__(self, net):
- super(Model, self).__init__()
- self.net = net
- self.relu = nn.ReLU()
- self.dropout = nn.Dropout(0.5)
- self.fc_add = nn.Linear(1001, 10, bias=True)
- self.output = nn.Softmax(dim=1)
-
- def forward(self, x, add_variable):
- x = self.net(x)
- x = torch.cat((self.dropout(self.relu(x)), add_variable.unsqueeze(1)),1)
- x = self.fc_add(x)
- x = self.output(x)
- return x
target:输出模型某一中间层的结果,以施加额外的监督,获得更好的中间层结果。
idea:修改模型定义中forward函数的return变量。
- import torchvision.models as models
- net = models.resnet50()
- print(net)
-
- class Model(nn.Module):
- def __init__(self, net):
- super(Model, self).__init__()
- self.net = net
- self.relu = nn.ReLU()
- self.dropout = nn.Dropout(0.5)
- self.fc1 = nn.Linear(1000, 10, bias=True)
- self.output = nn.Softmax(dim=1)
-
- def forward(self, x, add_variable):
- x1000 = self.net(x)
- x10 = self.dropout(self.relu(x1000))
- x10 = self.fc1(x10)
- x10 = self.output(x10)
- return x10, x1000
pytorch存储模型主要采用pkl,pt,pth三种格式。就使用层面来说并没有区别。
一个pytorch模型主要包含两个部分:模型结构和权重。其中,模型时继承nn.Module的类,权重的数据结构是一个字典(keyi是层名,value是权重向量)。
因此,存储也由此分为两种形式:存储整个模型(包括结构和权重),和只存储模型权重。
- import os
- import torch
- from torchvision import models
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '0' #这里替换成希望使用的GPU编号
- model = models.resnet152(pretrained=True)
- model.cuda()
-
- save_dir = 'resnet152.pt' #保存路径
-
- # 保存+读取整个模型
- torch.save(model, save_dir)
- loaded_model = torch.load(save_dir)
- loaded_model.cuda()
-
- # 保存+读取模型权重
- torch.save(model.state_dict(), save_dir)
- loaded_model = models.resnet152() #注意这里需要对模型结构有定义
- loaded_model.load_state_dict(torch.load(save_dir))
- loaded_model.cuda()
这种情况的处理比较简单,读取单卡保存的模型后,使用nn.DataParallel
函数进行分布式训练设置即可(相当于3.1代码中.cuda()替换一下):
- import os
- import torch
- from torchvision import models
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '0' #这里替换成希望使用的GPU编号
- model = models.resnet152(pretrained=True)
- model.cuda()
-
- # 保存+读取整个模型
- torch.save(model, save_dir)
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '1,2' #这里替换成希望使用的GPU编号
- loaded_model = torch.load(save_dir)
- loaded_model = nn.DataParallel(loaded_model).cuda()
-
- # 保存+读取模型权重
- torch.save(model.state_dict(), save_dir)
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '1,2' #这里替换成希望使用的GPU编号
- loaded_model = models.resnet152() #注意这里需要对模型结构有定义
- loaded_model.load_state_dict(torch.load(save_dir))
- loaded_model = nn.DataParallel(loaded_model).cuda()
- import os
- import torch
- from torchvision import models
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '1,2' #这里替换成希望使用的GPU编号
-
- model = models.resnet152(pretrained=True)
- model = nn.DataParallel(model).cuda()
-
- # 保存+读取整个模型
- torch.save(model, save_dir)
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '0' #这里替换成希望使用的GPU编号
- loaded_model = torch.load(save_dir).module
- import os
- import torch
- from torchvision import models
-
- os.environ['CUDA_VISIBLE_DEVICES'] = '0,1,2' #这里替换成希望使用的GPU编号
-
- model = models.resnet152(pretrained=True)
- model = nn.DataParallel(model).cuda()
-
- # 保存+读取模型权重,强烈建议!!
- torch.save(model.state_dict(), save_dir)
- loaded_model = models.resnet152() #注意这里需要对模型结构有定义
- loaded_model.load_state_dict(torch.load(save_dir)))
- loaded_model = nn.DataParallel(loaded_model).cuda()
- def my_loss(output, target):
- loss = torch.mean((output - target)**2)
- return loss
因为损失函数的_loss和_WeightedLoss继承自nn.Module,所以我们可以将其当做神经网络的一层来对待。
- class DiceLoss(nn.Module):
- def __init__(self,weight=None,size_average=True):
- super(DiceLoss,self).__init__()
-
- def forward(self,inputs,targets,smooth=1):
- inputs = F.sigmoid(inputs)
- inputs = inputs.view(-1)
- targets = targets.view(-1)
- intersection = (inputs * targets).sum()
- dice = (2.*intersection + smooth)/(inputs.sum() + targets.sum() + smooth)
- return 1 - dice
-
- # 使用方法
- criterion = DiceLoss()
- loss = criterion(input,targets)
problem: 学习率的选择是DL中困扰人们许久的问题。
solution:可以通过一个适当的学习率衰减策略来改善这种现象,提高我们的精度。这种方式在pytorch中称为scheduler。
- # 选择一种优化器
- optimizer = torch.optim.Adam(...)
- # 选择上面提到的一种或多种动态调整学习率的方法
- scheduler1 = torch.optim.lr_scheduler....
- scheduler2 = torch.optim.lr_scheduler....
- ...
- schedulern = torch.optim.lr_scheduler....
- # 进行训练
- for epoch in range(100):
- train(...)
- validate(...)
- optimizer.step()
- # 需要在优化器参数更新之后再动态调整学习率
- # scheduler的优化是在每一轮后面进行的
- scheduler1.step()
- ...
- schedulern.step()
自定义函数adjust_learning_rate来改变param_group中lr的值。
- def adjust_learning_rate(optimizer, epoch):
- lr = args.lr * (0.1 ** (epoch // 30))
- for param_group in optimizer.param_groups:
- param_group['lr'] = lr
-
- optimizer = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
- for epoch in range(10):
- train(...)
- validate(...)
- adjust_learning_rate(optimizer,epoch)
problem: 在实际应用中,我们数据集可能只有几千张,这时从头开始训练具有几千万参数的大型神经网络是不现实的。
solution:迁移学习的一大应用场景--微调 finetuning。比如pytorch中预训练好的网络模型(VGG,ResNet系列,mobilenet系列)
在源数据集(如ImageNet数据集)上预训练一个神经网络模型,即源模型。
创建一个新的神经网络模型,即目标模型。它复制了源模型上除了输出层外的所有模型设计及其参数。我们假设这些模型参数包含了源数据集上学习到的知识,且这些知识同样适用于目标数据集。我们还假设源模型的输出层跟源数据集的标签紧密相关,因此在目标模型中不予采用。
为目标模型添加一个输出⼤小为⽬标数据集类别个数的输出层,并随机初始化该层的模型参数。
在目标数据集上训练目标模型。我们将从头训练输出层,而其余层的参数都是基于源模型的参数微调得到的。
在默认情况下,参数的属性.requires_grad=True,如果我们从头开始训练或微调不需要注意这里。但如果我们正在提取特征并且只想为新初始化的层计算梯度,其他参数不进行改变。我们就需要通过设置requires_grad=False来冻结部分层。
- def set_parameter_requires_grad(model, feature_extracting):
- if feature_extracting:
- for param in model.parameters():
- param.requires_grad = False
-
- import torchvision.models as models
- # 冻结参数的梯度
- feature_extract = True
- model = models.resnet18(pretrained=True)
- set_parameter_requires_grad(model, feature_extract)
- # 修改模型
- num_ftrs = model.fc.in_features
- model.fc = nn.Linear(in_features=num_ftrs, out_features=4, bias=True)
为了节省显卡显存。
pytorch默认的浮点数存储方式是torch.float32,小数点后位数更多固然能保证数据的准确性,但绝大多数场景下不需要这么精确,只保留一半的信息也不会影响结果,即使用torch.float16格式。由于精度减了一半,所以被称为“半精度”。
from torch.cuda.amp import autocast
在模型定义中,使用python的装饰器方法,用autocast装饰模型中的forward函数。关于装饰器的使用,可以参考这里
- @autocast()
- def forward(self, x):
- ...
- return x
在训练过程中,只需在将数据输入模型及其之后的部分放入“with autocast():“即可:
- for x in train_loader:
- x = x.cuda()
- with autocast():
- output = model(x)
- ...
problem: 为了解决模型过拟合问题。
我们可以通过加入正则项或减少模型学习参数来解决。
但最直接的避免过拟合方法是增加数据。
但有些应用场景下我们无法获得大量数据,针对这种有限limited数据问题的解决方案
数据增强,提高训练数据集的大小和质量,以便我们可以用它们来构建更好的深度学习模型。
在服务器上跑模型时,如何更方便的修改超参数。
Argparse
总的来说,我们可以将argparse的使用归纳为以下三个步骤。
创建ArgumentParser()
对象
调用add_argument()
方法添加参数
使用parse_args()
解析参数 在接下来的内容中,我们将以实际操作来学习argparse的使用方法。
- # demo.py
- import argparse
-
- # 创建ArgumentParser()对象
- parser = argparse.ArgumentParser()
-
- # 添加参数
- parser.add_argument('-o', '--output', action='store_true',
- help="shows output")
- # action = `store_true` 会将output参数记录为True
- # type 规定了参数的格式
- # default 规定了默认值
- parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3')
-
- parser.add_argument('--batch_size', type=int, required=True, help='input batch size')
- # 使用parse_args()解析函数
- args = parser.parse_args()
-
- if args.output:
- print("This is some output")
- print(f"learning rate:{args.lr} ")
-
- ---------------------------------------------------------------------------
- 我们在命令行使用python demo.py --lr 3e-4 --batch_size 32,就可以看到以下的输出
- ----------------------------------------------------------------------------
- This is some output
- learning rate: 3e-4
argparse的参数主要可以分为可选参数和必选参数。可选参数就跟我们的lr
参数相类似,未输入的情况下会设置为默认值。必选参数就跟我们的batch_size
参数相类似,当我们给参数设置required =True
后,我们就必须传入该参数,否则就会报错。
- import argparse
-
- def get_options(parser=argparse.ArgumentParser()):
-
- parser.add_argument('--workers', type=int, default=0,
- help='number of data loading workers, you had better put it '
- '4 times of your gpu')
-
- parser.add_argument('--batch_size', type=int, default=4, help='input batch size, default=64')
-
- parser.add_argument('--niter', type=int, default=10, help='number of epochs to train for, default=10')
-
- parser.add_argument('--lr', type=float, default=3e-5, help='select the learning rate, default=1e-3')
-
- parser.add_argument('--seed', type=int, default=118, help="random seed")
-
- parser.add_argument('--cuda', action='store_true', default=True, help='enables cuda')
- parser.add_argument('--checkpoint_path',type=str,default='',
- help='Path to load a previous trained model if not empty (default empty)')
- parser.add_argument('--output',action='store_true',default=True,help="shows output")
-
- opt = parser.parse_args()
-
- if opt.output:
- print(f'num_workers: {opt.workers}')
- print(f'batch_size: {opt.batch_size}')
- print(f'epochs (niters) : {opt.niter}')
- print(f'learning rate : {opt.lr}')
- print(f'manual_seed: {opt.seed}')
- print(f'cuda enable: {opt.cuda}')
- print(f'checkpoint_path: {opt.checkpoint_path}')
-
- return opt
-
- if __name__ == '__main__':
- opt = get_options()
我们可以在training文件中用argparse调用、修改参数
- # 导入必要库
- ...
- import config
-
- opt = config.get_options()
-
- manual_seed = opt.seed
- num_workers = opt.workers
- batch_size = opt.batch_size
- lr = opt.lr
- niters = opt.niters
- checkpoint_path = opt.checkpoint_path
-
- # 随机数的设置,保证复现结果
- def set_seed(seed):
- torch.manual_seed(seed)
- torch.cuda.manual_seed_all(seed)
- random.seed(seed)
- np.random.seed(seed)
- torch.backends.cudnn.benchmark = False
- torch.backends.cudnn.deterministic = True
-
- ...
-
-
- if __name__ == '__main__':
- set_seed(manual_seed)
- for epoch in range(niters):
- train(model,lr,batch_size,num_workers,checkpoint_path)
- val(model,lr,batch_size,num_workers,checkpoint_path)
随着深度神经网络做的的发展,网络的结构越来越复杂,我们也很难确定每一层的输入结构,输出结构以及参数等信息,这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的。类似的功能在另一个深度学习库Keras中可以调用一个叫做model.summary()
的API来很方便地实现,调用后就会显示我们的模型参数,输入大小,输出大小,模型的整体参数等,但是在PyTorch中没有这样一种便利的工具帮助我们可视化我们的模型结构。
torchinfo
- # 安装方法一
- pip install torchinfo
- # 安装方法二
- conda install -c conda-forge torchinfo
trochinfo的使用也是十分简单,我们只需要使用torchinfo.summary()
就行了,必需的参数分别是model,input_size[batch_size,channel,h,w]。
- import torchvision.models as models
- from torchinfo import summary
- resnet18 = models.resnet18() # 实例化模型
- summary(resnet18, (1, 3, 224, 224)) # 1:batch_size 3:图片的通道数 224: 图片的高宽
CNN可视化
tensorflow可视化。TensorBoard工具
深度学习的最终目的是要实现模型的部署以方便我们的生活和解决传统方法不能解决的问题。通常人们会将模型部署在手机端、开发板,嵌入式设备上,但是这些设备上由于框架的规模,环境依赖,算力的限制,我们无法直接使用训练好的权重进行推理
我们会将PyTorch训练好的模型转换为ONNX( Open Neural Network Exchange) 格式,然后使用ONNX Runtime运行它进行推理。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。