DW组队学习——深入浅出PyTorch笔记

本篇是针对DataWhale组队学习项目——深入浅出PyTorch而整理的学习笔记。由于水平实在有限,不免产生谬误,欢迎读者多多批评指正。

安装PyTorch


安装Anaconda

这里为了避免手动安装Python及数据科学相关的众多的package,我们直接安装Anaconda,在此基础上再安装Pytorch。

官网直接下载安装即可,地址https://www.anaconda.com/products/distribution

创建虚拟环境

为了不与未来安装的其他版本的Python及package发生冲突,我们单独为PyTorch创建一个Anaconda中的虚拟环境。

win用户可以在Anaconda Prompt中利用指令进行创建和后续操作,详见DW教程https://datawhalechina.github.io/thorough-pytorch/index.html
在这里我直接利用图形界面的选项进行操作了,也很简单,点击Environments-Create,按要求进行创建即可

由于外网速度限制,我们通常给conda换第三方镜像源,比如清华源等。需要修改C:\Users\User_name.condarc文件,换成以下内容:

channels:
  - defaults
show_channel_urls: true
default_channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud

具体操作可以参考https://mirror.tuna.tsinghua.edu.cn/help/anaconda/
如果弄不成,也可以在每次conda install时指定来源

安装PyTorch

首先在cmd命令行中利用nvidia-smi查看自己显卡的型号,然后对应下表找到合适的CUDA版本

然后在PyTorch官网(https://pytorch.org/)上选择自己需要的版本,会自动生成一个conda指令,可以粘贴到新创建的虚拟环境的terminal中执行来完成安装

同样,虚拟环境可以用conda activate env_name指令激活并进入,也可以直接在图形界面中相应虚拟环境中open terminal

安装其他需要的package

同样在对应的虚拟环境中可以安装cuda及cudnn等包,注意版本要搞对

conda install cudatoolkit==版本号
conda install cudnn==版本号

深度学习模型的基本组成


包的导入与基本参数定义

# 导入常用的包
import os
import numpy as np
import pandas as pd
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   # 最大训练回合数
# 调用GPU
# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1'

# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")

数据读入

PyTorch数据读入是通过Dataset+DataLoader的方式完成的,Dataset定义好数据的格式和数据变换形式,DataLoader用iterative的方式不断读入批次数据。
以下是自定义Dataset的一个实例。

class MyDataset(Dataset):         # 继承PyTorch中的Dataset类
    def __init__(self, data_dir, info_csv, image_list, transform=None):
        """
        # 内置函数用于传入可变的外部参数
        Args:
            data_dir: 图片所在的文件夹地址
            info_csv: 包含图片序号及对应标签的csv文件地址
            image_list: 包含有训练/验证集图片名称的txt文件地址
            transform: 应用于样本的变换(可选)
        """
        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: 样本的序号
        Returns:
            图片及对应标签
        """
        image_name = self.image_file[index].strip('\n')    
        raw_label = self.label_info.loc[self.label_info['Image_index'] == image_name]
        # self.label_info['Image_index'] == image_name相当于对'Image_index'一列中的每个元素做判断,返回一个序号对应True或False的DataFrame
        # 代码最终将返回'Image_index'列中与image_name相同元素的整行内容,格式仍为DataFrame
        label = raw_label.iloc[:,0]   # 返回序号为index图片的对应标签
        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)   

使用DataLoader按批次读入数据。

from torch.utils.data import DataLoader
from torch.utils.data import random_split

data = MyDataset(data_dir, info_csv, image_list)    # 假设已实例化了之前自定义的MyDataset类,将其作为原始数据集
train_num = int(data.len() * 0.8)
val_num = data.len() - train_num
train_data, val_data = random_split(data, [train_num, val_num])    # 利用random_split函数进行切分

train_loader = DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
'''
batch_size:样本是按“批”读入的,batch_size就是每次读入的样本数
num_workers:有多少个进程用于读取数据
shuffle:是否将读入的数据打乱
drop_last:对于样本最后一部分没有达到批次数的样本,使其不再参与训练
'''
# DataLoader的读取可用next和iter,如images, labels = next(iter(val_loader))

模型构建

Module 类是 nn 模块里提供的一个模型构造类,是所有神经⽹网络模块的基类,我们可以继承它来定义我们想要的模型。

import torch
from torch import nn

class MLP(nn.Module):
  # 声明带有模型参数的层,这里声明了两个全连接层
  def __init__(self, **kwargs):
    # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
    # 反向传播所需的backward函数由系统自动生成
    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)   

在构建神经网络模型的过程中,我们可以自定义不同类型的层。

import torch
from torch import nn

class MyLayer(nn.Module):                     # 定义一个不含参数的层(将输入减掉均值后输出)
    def __init__(self, **kwargs):
        super(MyLayer, self).__init__(**kwargs)
    def forward(self, x):
        return x - x.mean()  

class MyListDense1(nn.Module):                # 以下两个类展示了定义含模型参数(参数可训练)的层的两种方式
    def __init__(self):
        # Parameter是Tensor的子类,ParameterList为参数列表
        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])    # torh.mm矩阵相乘
        return x

class MyDictDense2(nn.Module):
    def __init__(self):
        # 以参数字典ParameterDict的形式定义
        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])

注意我们在实际构建模型时通常不需要这样定义层,而可以直接调用torch.nn中已封装好的网络层,以下是卷积网络AlexNet模型的构建。

import torch
import torch.nn as nn

class AlexNet(nn.Module):
    def __init__(self):
        super(AlexNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_channels=1, out_channels=96, kernel_size=11, stride=4, padding=1),
            # 二维卷积层Conv2d输入参数分别为输入通道、输出通道、卷积核大小、步长、填充
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=3, stride=2),  # 最大池化
            nn.Conv2d(96, 256, 5, 1, 2),
            # 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
            nn.ReLU(),
            nn.MaxPool2d(3, 2),
            '''
            以下连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数
            前两个卷积层后不使用池化层来减小输入的高和宽
            '''
            nn.Conv2d(256, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 384, 3, 1, 1),
            nn.ReLU(),
            nn.Conv2d(384, 256, 3, 1, 1),
            nn.ReLU(),
            nn.MaxPool2d(3, 2)
        )
         # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
        self.fc = nn.Sequential(
            nn.Linear(256*5*5, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 4096),
            nn.ReLU(),
            nn.Dropout(0.5),
            nn.Linear(4096, 10),   # 输出层。由于这里使用数据集为Fashion-MNIST,所以用类别数为10,而非论文中的1000
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output

参数初始化

权重的初始值影响模型收敛的速度和预测精准度,我们通常使用torch.nn.init进行初始化

import torch
import torch.nn as nn

conv = nn.Conv2d(1,3,3)
linear = nn.Linear(10,1)

isinstance(conv,nn.Conv2d)   # isinstance函数用于判断变量(conv)是否属于某一类型(nn.Conv2d),此处返回True
isinstance(linear,nn.Conv2d) # 此处返回False

# 查看随机初始化的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

也可以写一个通用的初始化函数,主要是遍历当前模型的每一层,然后判断各层属于什么类型,然后根据不同类型层,设定不同的权值初始化方法

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_()	

损失函数

根据不同的模型和任务需要,选择最合适的损失函数,torch中给出了许多现成的损失函数的接口。

# 交叉熵
torch.nn.CrossEntropyLoss(weight=None, size_average=None, ignore_index=-100, reduce=None, reduction='mean')
# L1
torch.nn.L1Loss(size_average=None, reduce=None, reduction='mean')
# MSE
torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
# KL散度
torch.nn.KLDivLoss(size_average=None, reduce=None, reduction='mean', log_target=False)

训练与评估

模型训练整体思路:for循环读取DataLoader中的全部数据——将数据放到GPU上——将优化器的梯度置零——将data送入模型中训练——根据预先定义的criterion计算损失函数
——将loss反向传播回网络——使用优化器更新模型参数

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(label, output)
        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))

验证/测试状态,则不应该修改模型参数。验证/测试的流程基本与训练过程一致,不同点在于:

  • 需要预先设置torch.no_grad,以及将model调至eval模式
  • 不需要将优化器的梯度置零
  • 不需要将loss反向回传到网络
  • 不需要更新optimizer
    验证函数如下所示。
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))

优化器

torch.optim中提供了10中优化方法。

详解模型的定义


三种简单定义方式

以下三种方式基于nn.Module,较为方便和实用。

  • nn.Sequential()
    用于构建各层简单串联的网络模型,可接收子模块(层)的有序字典或者一系列子模块作为参数。
# 构建方法一:直接排列
import torch.nn as nn
net = nn.Sequential(
        nn.Linear(784, 256),
        nn.ReLU(),
        nn.Linear(256, 10), 
        )

# 构建方法二:添加模块字典
import collections
net2 = nn.Sequential(collections.OrderedDict([
          ('fc1', nn.Linear(784, 256)),
          ('relu1', nn.ReLU()),
          ('fc2', nn.Linear(256, 10))
          ]))

# Sequential()计算顺序是确定的,不需要单独再写前向函数。
  • nn.ModuleList()
    接收一个子模块(层)的列表作为输入,然后也可以类似列表那样进行append和extend操作。
net = nn.ModuleList([nn.Linear(784, 256), nn.ReLU()])
net.append(nn.Linear(256, 10)) 
# ModuleList中元素的先后顺序并不代表其在网络中的真实位置顺序,需要经过forward函数指定各个层的先后顺序后才算完成了模型的定义,比如
class Mymodel(nn.Module):
  def __init__(self, ...):
    ...
  def forward(self, x):
    for layer in self.modulelist:
      x = layer(x)
    return x
  • nn.ModuleDict()
net = nn.ModuleDict({
    'linear': nn.Linear(784, 256),
    'act': nn.ReLU(),
})
net['output'] = nn.Linear(256, 10) 

模块化定义

当网络较为复杂时,我们可将其中一些重复出现的层定义为模块,然后利用模块这个更大的结构来快速搭建模型。
比如在U-Net模型中出现的,由两次卷积组成的一个模块可以定义如下

import torch
import torch.nn as nn

class DoubleConv(nn.Module):
    def __init__(self, in_channels, out_channels, mid_channels=None):
        super().__init__()
        if not mid_channels:
            mid_channels = out_channels
        self.double_conv = nn.Sequential(
            nn.Conv2d(in_channels, mid_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(mid_channels),
            nn.ReLU(inplace=True),
            nn.Conv2d(mid_channels, out_channels, kernel_size=3, padding=1, bias=False),
            nn.BatchNorm2d(out_channels),
            nn.ReLU(inplace=True)
        )

    def forward(self, x):
        return self.double_conv(x)

模型保存与读取

# 保存整个模型
torch.save(model, save_dir)
# 保存模型权重
torch.save(model.state_dict, save_dir)

利用一张显卡进行模型保存和读取时,可操作如下

import os
import torch
from torchvision import models

os.environ['CUDA_VISIBLE_DEVICES'] = '0'   # 这里替换成希望使用的GPU编号
model = models.resnet152(pretrained=True)  # 我们以一个现成的模型resnet152为例
model.cuda()

# 保存+读取整个模型
torch.save(model, save_dir)  # save_dir为绝对路径+保存文件名
loaded_model = torch.load(save_dir)
loaded_model.cuda()

# 保存+读取模型权重
torch.save(model.state_dict(), save_dir2)
loaded_dict = torch.load(save_dir2)
loaded_model = models.resnet152()        # 此时需要对模型结构有定义,然后才能赋权重
loaded_model.state_dict = loaded_dict
loaded_model.cuda()

进阶训练技巧


自定义损失函数

自定义损失函数时同样可以构建继承自nn.Module的类,在__init__中定义超参数,在forward函数中定义计算方法:

class MSELoss(nn.Module):
    def __init__(self):
        super().__init__()
        
    def forward(self,inputs,targets):
        return torch.mean(torch.pow((inputs - targets)*2))

# 使用方法    
criterion = MSELoss()
loss = criterion(input,targets)

动态调整学习率

学习率作为一个训练中的重要超参数,其值的大小关系着模型训练速度及能否收敛,为了平衡二者,我们有时会使用动态变化的学习率。
对于动态调整学习率,官方给出了很多常用的API。详细可见torch.optim.lr_scheduler函数。

# 假设已定义了模型MyModel
md = MyModel()
# 选择一种优化器
optimizer = torch.optim.Adam(md.parameters(), lr=initial_lr) 
# 选择动态调整学习率的方法(以LambdaLR为例)
scheduler1 = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=lambda epoch: 1/(epoch+1))
# 进行训练
for epoch in range(100):
    train(...)
    validate(...)
    optimizer.step()
    # 需要在优化器参数更新之后再动态调整学习率
    scheduler1.step() 

模型微调

PyTorch中提供了许多预训练的模型,我们可利用自己的数据集训练模型以适当调整参数,来完成相似任务间模型的迁移。这个过程就被称为模型微调。

  1. torchvision模块中包含许多已完成预训练的模型,我们可以选择这些模型作为需要微调的源模型。
  2. 确定好源模型后,我们将对模型进行训练。使pretrained=True可保留模型在外部原始数据集上训练后得到的权重。
  3. 我们保留大部分权重,但原输出层要修改成符合自身任务需求的输出层,并完成参数训练。

如果仅希望对新创建和初始化后的层进行训练,可如下操作

import torchvision.models as models
# 构建预训练模型resnet18
model = models.resnet18(pretrained=True)
# 冻结参数的梯度
feature_extract = 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)
# 之后在训练过程中,model仍会进行梯度回传,但是参数更新则只会发生在fc层

另一种可用的提供预训练模型的模块是timm,其使用方法与torchvision中的模型类似。

import timm
import torch

model = timm.create_model('resnet34',pretrained=True)   # 创建模型

list(dict(model.named_children())['conv1'].parameters())  # 查看第一层的参数

model = timm.create_model('resnet34',num_classes=10,pretrained=True,in_chans=1)   # 修改模型(num_classes=10将1000类改为10类输出,in_chans=1传入图片改为三通道)

torch.save(model.state_dict(),'./checkpoint/timm_model.pth')       # 保存模型参数
model.load_state_dict(torch.load('./checkpoint/timm_model.pth'))   # 加载模型参数

半精度训练

通过将torch中默认的数据存储格式torch.float32改为torch.float16,我们可以节省数据存储所需的空间,从而在训练中能够每次向GPU中读入更多的数据(相当于可设置更大的batch-size),这就被称为半精度训练。其主要步骤如下:

# 步骤一:导入autocast模块
from torch.cuda.amp import autocast

# 步骤二:在定义网络模型时,用autocast作为forward函数的装饰器
@autocast()   
def forward(self, x):
    ...
    return x

# 步骤三:在训练过程中,将数据输入模型及其之后的部分放入“with autocast():”中即可
for x in train_loader:
    x = x.cuda()
    with autocast():
    output = model(x)
    ...

数据增强

在许多领域尤其是计算机视觉中,我们有时会通过在已有数据的基础上加入噪声或进行变换以获得“新”数据,利用这样的方法扩充后的数据集进行训练,同样会获得比原数据集更准确、鲁棒性更高的模型,而这个扩充原始数据集的方法就被称为数据增强。这里主要学习和总结利用第三方库imgaug进行数据增强的方法。

import imageio        # 配合使用imageio进行图片读取比较方便,如果用opencv或PIL.Image读取则要进行一定的处理
import imgaug as ia
from imgaug import augmenters as iaa
%matplotlib inline

# 图片的读取
img = imageio.imread("./Lenna.jpg")

# 可视化图片(忽略输出)
ia.imshow(img)

# 设置随机数种子
ia.seed(4)

# 实例化方法
rotate = iaa.Affine(rotate=(-4,45))
img_aug = rotate(image=img)
ia.imshow(img_aug)

对多张图片进行处理时,我们需要用imgaug.augmenters.Sequential()来构造我们数据增强的pipline。

iaa.Sequential(children=None, # Augmenter集合
               random_order=False, # 是否对每个batch使用不同顺序的Augmenter list
               name=None,
               deterministic=False,
               random_state=None)

# 构建处理序列
aug_seq = iaa.Sequential([
    iaa.Affine(rotate=(-25,25)),
    iaa.AdditiveGaussianNoise(scale=(10,60)),
    iaa.Crop(percent=(0,0.2))
])
# 对图片进行处理,image不可以省略,也不能写成images
image_aug = aug_seq(image=img)
ia.imshow(image_aug)

这里给出一个完整的使用imgaug的模板。

import numpy as np
from imgaug import augmenters as iaa
from torch.utils.data import DataLoader, Dataset
from torchvision import transforms

# 构建pipline
tfs = transforms.Compose([
    iaa.Sequential([
        iaa.flip.Fliplr(p=0.5),
        iaa.flip.Flipud(p=0.5),
        iaa.GaussianBlur(sigma=(0.0, 0.1)),
        iaa.MultiplyBrightness(mul=(0.65, 1.35)),
    ]).augment_image,
    # 不要忘记了使用ToTensor()
    transforms.ToTensor()
])

# 自定义数据集
class CustomDataset(Dataset):
    def __init__(self, n_images, n_classes, transform=None):
		# 图片的读取,建议使用imageio
        self.images = np.random.randint(0, 255,
                                        (n_images, 224, 224, 3),
                                        dtype=np.uint8)
        self.targets = np.random.randn(n_images, n_classes)
        self.transform = transform

    def __getitem__(self, item):
        image = self.images[item]
        target = self.targets[item]

        if self.transform:
            image = self.transform(image)
        return image, target

    def __len__(self):
        return len(self.images)

def worker_init_fn(worker_id):
    imgaug.seed(np.random.get_state()[1][0] + worker_id)

custom_ds = CustomDataset(n_images=50, n_classes=10, transform=tfs)
custom_dl = DataLoader(custom_ds, batch_size=64,
                       num_workers=4, pin_memory=True, 
                       worker_init_fn=worker_init_fn)

模型参数的传入

在训练模型的过程中我们不可避免地要尝试多组超参数,以找到更优的参数组合。而想要把在命令行输入的超参数正确地导入到模型的各个位置中,就要使用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
'''

项目可视化


模型结构可视化

为了更清晰地查看我们所构建的模型结构,方便后续的分析和优化,我们可以利用torchinfo工具包对PyTorch模型进行结构化输出。

import torchvision.models as models
model = models.resnet18()    # 以resnet18模型为例

import torchvision.models as models
from torchinfo import summary
resnet18 = models.resnet18() # 实例化模型
summary(resnet18, (1, 3, 224, 224))  # 1:batch_size 3:图片的通道数 224: 图片的高宽

训练过程可视化

对模型训练过程尤其是损失函数变化的掌握有助于我们了解模型的训练效果,是模型训练中必不可少的反馈。在这里我们可以使用TensorBoard工具完成此项工作。

from torch.utils.tensorboard import SummaryWriter
import torch.nn as nn

class Net(nn.Module):          # 定义模型结构
    def __init__(self):
        ...

    def forward(self,x):
        ...

model = Net()

writer.add_graph(model, input_to_model = torch.rand(1, 3, 224, 224))   # 给定一个输入数据,前向传播后得到模型的结构,再通过TensorBoard进行模型可视化
writer.close()      

PyTorch生态


torchvision

torchvision包含了在计算机视觉中常常用到的数据集,模型和图像处理的方式。

其中torchvision.transforms提供了常见的数据预处理和数据增强的变换方法。

import torch
from torchvision import transforms
torch.manual_seed(17)  # torch和Python中的随机生成器使用相同种子不会产生相同的结果

data_transform = transforms.Compose([       # 把多个数据变换操作整合到一起
    transforms.ToPILImage(),   # 这一步取决于后续的数据读取方式,如果使用内置数据集则不需要
    transforms.Resize(image_size),    # 将图片转换到给定尺寸
    transforms.ToTensor()      # 将PIL格式转换为张量格式
])

'''
部分其他的变换方式:
ColorJitter([brightness, contrast, …])   # 随机更改图像的亮度、对比度、饱和度和色调
RandomCrop(size[, padding, pad_if_needed, …])    # 在随机位置裁剪给定图像
Grayscale([num_output_channels])  # 将图像转换为灰度图
...
'''

torchvision.models提供了一些预训练好的模型供我们使用。
其中图像分类模型包括AlexNet、VGG、ResNet、GoogLeNet、MNASNet、EfficientNet、RegNet等;
语义分割模型包括FCN ResNet50、FCN ResNet101、DeepLabV3 ResNet50等;
物体检测,实例分割和人体关键点检测的模型包括Faster R-CNN、Mask R-CNN、RetinaNet等;
视频分类模型包括ResNet 3D 18、ResNet MC 18等。

torchvision.io提供了视频、图片和文件的 IO 操作的功能,它们包括读取、写入、编解码处理操作。

'''
torchvision.io.read_image(path: str) → torch.Tensor      # 将JPEG或PNG图像读取为三维RGB张量,输出张量的值为0到255之间的uint8格式数字
torchvision.io.decode_image(input: torch.Tensor) → torch.Tensor    # 检测图像是JPEG还是PNG,并执行适当的操作将图像解码为三维RGB张量
torchvision.io.write_jpeg(input: torch.Tensor, filename: str, quality: int = 75)    # 在CHW布局中获取输入张量并将其保存在JPEG文件中
'''

torchvision.utils 为我们提供了一些可视化的方法,可以帮助我们将若干张图片拼接在一起、可视化检测和分割的效果。

torchtext

torchtext是PyTorch中用于自然语言处理项目的工具包。torchtext主要包含了以下的主要组成部分:

  • 数据处理工具 torchtext.data.functional、torchtext.data.utils

  • 数据集 torchtext.data.datasets

  • 词表工具 torchtext.vocab

  • 评测指标 torchtext.metrics

参考资料:

  1. https://datawhalechina.github.io/thorough-pytorch/index.html DataWhale——深入浅出PyTorch
  2. https://mirror.tuna.tsinghua.edu.cn/help/anaconda/ Anaconda镜像使用帮助
posted @ 2022-08-16 22:47  刘国栋_0017  阅读(127)  评论(0编辑  收藏  举报