Pytorch教程


第一章 PyTorch介绍及安装

1.1 PyTorch简介

​ ​ ​ ​ ​ ​ ​ ​ Facebook公司开发的PyTorch框架是端到端深度学习平台最受欢迎的框架,是一个由Python、C++和CUDA语言编写的免费开源软件库,被广泛用于计算机视觉、语音识别和自然语言处理等深度学习领域。PyTorch有两大优势:一是具有强大GPU加速的张量计算;二是包含自动求导系统的深度神经网络。
​ ​ ​ ​ ​ ​ ​ ​ PyTorch的前身可追溯到2002年诞生于纽约大学的Torch,Facebook人工智能研究院于2017年在GitHub上开源了PyTorch,并迅速占据GitHub热度榜榜首。

1.2 PyTorch安装

1.2.1 Windows系统安装

1. CPU版PyTorch

pip install torch

2. GPU版Pytorch
步骤一
​ ​ ​ ​ ​ ​ ​ ​ 检查是否有合适的GPU,若有需要安装CUDA与CuDNN;
步骤二
​ ​ ​ ​ ​ ​ ​ ​ 进入PyTorch官网,检查目前PyTorch支持的CUDA版本;

https://pytorch.org/get-started/locally/

在这里插入图片描述

步骤三
​ ​ ​ ​ ​ ​ ​ ​ 进入CUDA官网,点击CUDA Toolkit 11.8.0,下载Base installer并安装;

https://developer.nvidia.com/cuda-downloads

在这里插入图片描述
在这里插入图片描述
步骤四
​ ​ ​ ​ ​ ​ ​ ​ 进入CuDNN官网,登录账号,选择最新版,点击Base installe;
在这里插入图片描述
在这里插入图片描述
步骤五
​ ​ ​ ​ ​ ​ ​ ​ 解压cudnn压缩包,并将其复制到cuda安装路径下;
步骤六
​ ​ ​ ​ ​ ​ ​ ​ 下载torch、torchvision和torchaudio

pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

1.2.2 Linux系统安装

1. CPU版PyTorch
待写
2. GPU版Pytorch
待写

第二章 PyTorch 基础编程

2.1 张量定义

  1. 张量的定义
    ​ ​ ​ ​ ​ ​ ​ ​ 张量(Tensor)是PyTorch的核心数据结构。张量的维度(dimension)也称为阶。零阶张量称为标量(scalar),1阶张量称为向量(vector),2阶张量称为矩阵(matrix),3阶以上的就直接称为张量。
     ​ ​ ​ ​ ​ ​ ​ Tensor类的shape属性和size()函数返回张量的具体维度分量。零阶张量,一般用于超参数、参数和损失函数。一阶张量用作神经网络偏置,以及神经网络各层的输入。二阶张量一般用作神经网络的批量输入和输出,以及线性神经元的权重参数。三阶张量通常用作图片表示,一张彩色图片的RGB这三个通道占一维,宽和高各占一维。四维张量通常在卷积神经网络中表示图像。一次输入到神经网络中进行训练的样本数目称为批大小。
  2. Tensor与Variable的差异
    ​ ​ ​ ​ ​ ​ ​ ​ 在PyTorch中,Tensor和Variable有一定的关系,但在最新的PyTorch版本中,Variable已经被弃用了。在早期版本的PyTorch中,Variable是一个用于自动求导的类,它封装了Tensor并提供了自动求导的功能。 当在计算图中使用Variable时,它会跟踪计算图的操作,并允许你对变量进行梯度计算。然而,从PyTorch 0.4.0版本开始,Variable被弃用了,因为自动求导的功能已经集成到了Tensor中。在当前版本的PyTorch中,Tensor是主要的数据类型。Tensor对象支持各种数学操作和函数,可以进行高效的计算和操作。而且,Tensor对象还具有自动求导的功能,可以通过设置requires_grad属性为True来跟踪操作并计算梯度。Variable与Tensor具体的数据结构如下图所示:
图 a Variable图 b Tensor

1)Variable数据类型
 ​ ​ ​ ​ ​ ​ ​ 在pytorch 0.4.0版本之前,Variable是torch.autograd中的数据类型,主要用于封装Tensor,然后进行自动求导。Variable对象具有以下几个主要的属性:

  • data:被包装的Tensor对象,存储实际的数据。
  • grad:一个Tensor对象,存储data的梯度。在进行反向传播时,梯度将累积到grad中。
  • grad_fn:创建该Variable的函数(或操作)的引用。它是构建计算图的关键,用于实现自动求导。
  • requires_grad:一个布尔值,指示是否需要计算梯度。如果设置为True,则Variable将跟踪操作并计算梯度;如果设置为False,则不会计算梯度。
  • is_leaf:一个布尔值,指示该Variable是否是计算图中的叶子节点。叶子节点是由用户直接创建的Variable,而非通过计算得到的。

2)Tensor数据类型
 ​ ​ ​ ​ ​ ​ ​ 在PyTorch 0.4.0及以后的版本中,Tensor对象支持自动求导,并且Variable类已经被弃用。Tensor对象具有以下几个主要的属性:

  • dtype:表示张量的数据类型;
  • shape:表示张量的形状,以元组形式表示;
  • device:表示张量所在的设备,可以是GPU或CPU。如果张量位于GPU上,则可以利用GPU的并行计算能力来加速运算。

2.2 张量操作

2.2.1 创建张量

  1. 创建张量
    1)判断是否为PyTorch张量
    ​ ​ ​ ​ ​torch.is_tensor()函数可判断输入参数是否为PyTorch张量。
    import torch
    arr = [1, 2, 3]
    print(torch.is_tensor(arr))
    
    运行结果如下:
    False
    
    2)从Python列表中创建张量
    ​ ​ ​ ​ ​torch.tensor()函数可直接从python列表中创建PyTorch张量。
    import torch
    arr = [1, 2, 3]
    t1 = torch.tensor(arr)
    print(f"t1是否为张量:{torch.is_tensor(t1)}")
    print(f"t1中的数据:{t1}")
    
    运行结果如下:
    t1是否为张量:True
    t1中的数据:tensor([1, 2, 3])
    
    3)初始化全0或全1张量
    ​ ​ ​ ​ ​torch.ones()函数和torch.zeros()函数分别创建全1或全0张量。
    import torch
    t = torch.ones(5)
    t1 = torch.zeros(5, 6)
    print(f"t中的数据:{t}")
    print(f"t1中的数据:{t1}")
    
    运行结果如下:
    t中的数据:tensor([1., 1., 1., 1., 1.])
    t1中的数据:tensor([[0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0.],
            [0., 0., 0., 0., 0., 0.]])
    
    4)创建随机数张量
    ​ ​ ​ ​ torch.randn()函数用于创建正态分布的随机张量,torch.rand()函数用于创建取值在[0, 1)范围内的均匀分布的随机张量。
    import torch
    t = torch.randn(2, 3)  # 正态分布随机张量
    t1 = torch.rand(2, 1)   # 均匀分布随机张量
    print(f"t中的数据:{t}")
    print(f"t1中的数据:{t1}")
    
    运行结果如下:
    t中的数据:tensor([[-1.1226,  1.0770,  1.0084],
            [ 0.2833,  0.0693, -0.0252]])
    t1中的数据:tensor([[0.7664],
            [0.6271]])
    
    注意:由于输出的是随机数,所以可能每次运行的实际输出不同于上一次运行结果。
    5)创建二维对角矩阵张量
    ​ ​ ​ ​ torch.eye()函数用于创建二维对角矩阵张量,并不要求矩阵张量必须是方阵。
    import torch
    t = torch.eye(3)        # 创建3*3的对角张量
    t1 = torch.eye(2, 3)    # 创建2*3的对角张量
    print(f"t中的数据:{t}")
    print(f"t1中的数据:{t1}")
    
    运行结果如下:
    t中的数据:tensor([[1., 0., 0.],
            [0., 1., 0.],
            [0., 0., 1.]])
    t1中的数据:tensor([[1., 0., 0.],
            [0., 1., 0.]])
    
    6)创建一维序列张量
    ​​ ​ ​ ​ torch.arange()和torch.linspace()函数创建以为序列张量。torch.arange()函数输入参数start(默认参数0),end指定结束值,step(默认参数1)。
    import torch
    t = torch.arange(0, 30)
    t1 = torch.linspace(0, 30, 10)
    print(f"t中的数据:{t}")
    print(f"t1中的数据:{t1}")
    
    运行结果如下:
    t中的数据:tensor([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16, 17,
            18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
    t1中的数据:tensor([ 0.0000,  3.3333,  6.6667, 10.0000, 13.3333, 16.6667, 20.0000, 23.3333,
            26.6667, 30.0000])
    
    注意:torch.arange()函数输出的序列不包含end值;torch.linspace()函数输出的序列包含end值。
    7)创建取值为指定范围的随机置乱的张量
    ​ ​ ​ ​ torch.arange()函数生成0~n-1的整数的随机排列,常用于数据采集样本的随机置乱。
    import torch
    t = torch.randperm(10)
    print(f"t中的数据:{t}")
    
    运行结果如下:
    t中的数据:tensor([3, 1, 8, 4, 7, 6, 0, 2, 9, 5])
    
    8)numpy数组与张量相互转换
    ​ ​ ​ ​ torch.from_numpy()函数将Numpy数组转换为Tensor,tensor.numpy()函数将Tensor转化为Numpy数组。
    import torch
    import numpy as np
    arr = np.arange(9)
    t = torch.from_numpy(arr)
    arr1 = t.numpy()
    print(f"t中的数据:{t}")
    print(f"arr1中的数据:{arr1}")
    
    运行结果如下:
    t中的数据:tensor([0, 1, 2, 3, 4, 5, 6, 7, 8], dtype=torch.int32)
    arr1中的数据:[0 1 2 3 4 5 6 7 8]
    
    注意:tensor.numpy()函数只能将CPU的张量转换为Numpy数组,无法将GPU的张量转换为Numpy数组。正确做法:先将GPU的张量存放到CPU中,在进行转换。

2.2.2 张量数据类型操作

  1. 数据类型设定

    数据类型dtypeCPU张量GPU张量
    32位浮点数torch.float32或torch.floattorch.FloatTensor()torch.cuda.FloatTensor()
    64位浮点数torch.float64或torch.doubletorch.DoubleTensor()torch.cuda.DoubleTensor()
    8位无符号整数torch.uint8torch.ByteTensor()torch.cuda.ByteTensor()
    8位符号整数torch.int8torch.CharTensor()torch.cuda.ShortTensor()
    16位有符号整数torch.int16或torch.shorttorch.ShortTensor()torch.cuda.ShortTensor()
    32位有符号整数torch.int32或torch.inttorch.IntTensor()torch.cuda.IntTensor()
    64位有符号整数torch.int64或torch.longtorch.LongTensor()torch.cuda.LongTensor()
    布尔型torch.booltorch.BoolTensor()torch.cuda.BoolTensor()

    1)构建张量时,设定数据类型
    ​ ​ ​ ​ 用特定函数构建张量时,可以用dtype参数来设定数据类型。

    import torch
    t1 = torch.tensor([1, 2, 4], dtype=torch.float)
    print(t1)
    t2 = torch.tensor([0, 1, 1], dtype=torch.bool)
    print(t2)
    

    运行结果如下:

    tensor([1., 2., 4.])
    tensor([False,  True,  True])
    

    2)转换数据类型
    ​ ​ ​ ​ 使用type_as()函数转换数据类型

    import torch
    t1 = torch.tensor([1, 2, 4], dtype=torch.int)
    print(t1)
    t2 = t1.type_as(torch.FloatTensor())
    print(t2)
    

    运行结果如下:

    tensor([1, 2, 4], dtype=torch.int32)
    tensor([1., 2., 4.])
    

2.2.3 张量索引、切片、拼接和形状变换

  1. 张量索引操作
    1)索引操作
    ​ ​ ​ ​ torch.index_select(input:tensor, dim:int, index:tensor)函数用于在参数dim指定维度上。
    import torch
    t = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2)
    t1 = torch.index_select(t, 0, torch.IntTensor([0, 2]))
    print(t1)
    
    运行结果如下:
    tensor([[1., 2.],
            [5., 6.]])
    
    2)Numpy索引方式
    import torch
    t = torch.tensor([1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2)
    print(t)
    t1 = t[0, :]
    t2 = t[[0, 2], :]
    print(t1)
    print(t2)
    
    运行结果如下:
    tensor([[1., 2.],
            [3., 4.],
            [5., 6.]])
    tensor([1., 2.])
    tensor([[1., 2.],
            [5., 6.]])
    
    3)masked_select()索引方式
    ​ ​ ​ ​ masked_select()是一种更为灵活的索引方式,根据掩码mask中的二元索引值,取出张量中指定项。
    import torch
    t = torch.tensor([-1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2)
    mask = torch.BoolTensor([[1, 0], [0, 0], [0, 0]])
    a = torch.masked_select(t, mask)
    print(a)
    
    运行结果如下:
    tensor([-1.])
    
    import torch
    t = torch.tensor([-1, 2, 3, 4, 5, 6], dtype=torch.float).reshape(3, 2)
    mask = t < 0
    a = torch.masked_select(t, mask)
    print(a)
    
    运行结果如下:
    tensor([-1.])
    
  2. 切片
    1)torch.chunk(input:tensor, chunks:int, dim:int)函数
    ​ ​ ​ ​ torch.chunk(input:tensor, chunks:int, dim:int)函数将张量在指定维度上分割为特定数量的块chunks,默认维度为0。
    import torch
    t = torch.arange(10).reshape(5, 2)
    a = torch.chunk(t, 2)
    print(a)
    
    运行结果如下:
    (tensor([[0, 1],
            [2, 3],
            [4, 5]]), tensor([[6, 7],
            [8, 9]]))
    
    2)torch.split(input:tensor, split_size_or_selections:int or List)函数
     ​ ​ ​ torch.split(input:tensor, split_size_or_selections:int or List)函数将张量分割为指定形状的块。
    import torch
    t = torch.arange(10).reshape(5, 2)
    a = torch.split(t, [2, 3])
    print(a)
    
    运行结果如下:
    (tensor([[0, 1],
            [2, 3]]), tensor([[4, 5],
            [6, 7],
            [8, 9]]))
    
  3. 拼接
    1)torch.cat(tensors: Tuple[Tensor, …] | List[Tensor], dim: int = 0)函数
    ​ ​ ​ ​ torch.cat(tensors: Tuple[Tensor, …] | List[Tensor], dim: int = 0)函数将张量按维度进行拼接,但不扩展张量的维度。
    import torch
    t = torch.arange(10).reshape(5, 2)
    t1 = torch.arange(4).reshape(2, 2)
    t2 = torch.cat((t, t1), 0)
    print(t2)
    
    运行结果如下:
    tensor([[0, 1],
            [2, 3],
            [4, 5],
            [6, 7],
            [8, 9],
            [0, 1],
            [2, 3]])
    
    2)torch.stack(Tuple[Tensor, …] | List[Tensor],dim: int = 0)函数
    ​ ​ ​ ​ torch.stack(Tuple[Tensor, …] | List[Tensor],dim: int = 0)函数在指定的新维度上进行拼接,会扩展张量的维度。
    import torch
    t = torch.arange(10).reshape(5, 2)
    t1 = torch.stack([t, t], dim=0)
    print(t1.size())
    print(t1)
    
    运行结果如下:
    torch.Size([2, 5, 2])
    tensor([[[0, 1],
             [2, 3],
             [4, 5],
             [6, 7],
             [8, 9]],
    
            [[0, 1],
             [2, 3],
             [4, 5],
             [6, 7],
             [8, 9]]])
    
  4. 拼接
    1)torch.reshape(input: Tensor, shape: Sequence[int | SymInt])函数
    ​ ​ ​ ​ torch.reshape(input: Tensor, shape: Sequence[int | SymInt])函数可以变化张量。
    案例 1
    import torch
    t = torch.arange(10)
    t1 = torch.reshape(t, (2, 5))
    print(t)
    print(t1)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    tensor([[0, 1, 2, 3, 4],
            [5, 6, 7, 8, 9]])
    
    案例 2
    import torch
    t = torch.arange(10)
    t1 = torch.reshape(t, [2, 5])
    print(t)
    print(t1)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    tensor([[0, 1, 2, 3, 4],
            [5, 6, 7, 8, 9]])
    
    2)tensor.reshape(shape: Sequence[int | SymInt])函数
     ​ ​ ​ tensor.reshape(shape: Sequence[int | SymInt])函数可以变化张量。
    import torch
    t = torch.arange(10)
    t1 = t.reshape((5, 2))
    print(t)
    print(t1)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    tensor([[0, 1],
            [2, 3],
            [4, 5],
            [6, 7],
            [8, 9]])
    
    3)torch.transpose(input: Tensor, dim0: int, dim1: int)函数
    ​ ​ ​ ​ torch.transpose(input: Tensor, dim0: int, dim1: int)函数可以交换张量的两个维度。
    import torch
    t = torch.rand((3, 4))
    print(t)
    t1 = t.transpose(0, 1)
    print(t1)
    t2 = torch.transpose(t, 0, 1)
    print(t2)
    
    运行结果如下:
    tensor([[0.1654, 0.4490, 0.1174, 0.3273],
            [0.5618, 0.2000, 0.7795, 0.2445],
            [0.8391, 0.0548, 0.1492, 0.2705]])
    tensor([[0.1654, 0.5618, 0.8391],
            [0.4490, 0.2000, 0.0548],
            [0.1174, 0.7795, 0.1492],
            [0.3273, 0.2445, 0.2705]])
    tensor([[0.1654, 0.5618, 0.8391],
            [0.4490, 0.2000, 0.0548],
            [0.1174, 0.7795, 0.1492],
            [0.3273, 0.2445, 0.2705]])
    
    4)torch.squeeze()函数
    ​ ​ ​ ​ torch.squeeze()函数或tensor.squeeze(input:tensor)可以压缩指定维度且长度为1的维度。如果dim取默认值None,则压缩全部长度为1的维度。
    import torch
    t = torch.rand((2, 3, 1, 2, 1))
    print(t.size())
    t1 = t.squeeze()
    print(t1.size())
    t2 = torch.squeeze(t)
    print(t2.size())
    
    运行结果如下:
    torch.Size([2, 3, 1, 2, 1])
    torch.Size([2, 3, 2])
    torch.Size([2, 3, 2])
    
    5)torch.unsqueeze(input: Tensor, dim: int)函数
    ​ ​ ​ ​ torch.unsqueeze(input: Tensor, dim: int)函数或tensor.unsqueeze(dim:int)函数扩展指定dim的维度且长度为1。
    import torch
    t = torch.arange(4)
    print(t.size())
    print(t.unsqueeze(dim=0).size())
    print(t.unsqueeze(dim=1).size())
    
    运行结果如下:
    torch.Size([4])
    torch.Size([1, 4])
    torch.Size([4, 1])
    

2.2.4 广播机制

  1. 广播机制
     ​ ​ ​  ​ ​ ​ PyTorch中的很多操作都支持广播(broadcasting),这种广播机制与NumPy中的广播机制类似,这使得张量的形状可以根据一组规则自动调整张量的形状,使其与其他张量具有相同的维度,不需要显式地复制数据。广播机制通常用于三种类型的元素运算:标量与张量运算、相同维度不同形状的张量运算和不同维度的张量运算。
    1)标量与张量运算
    import torch
    t = torch.arange(5)
    print(t)
    t = t + 1
    print(t)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4])
    tensor([1, 2, 3, 4, 5])
    
    2)相同维度但不同形状的张量运算
    import torch
    t = torch.arange(6).reshape(2, 3)
    print(t)
    t1 = torch.arange(3).reshape(1, 3)
    print(t1)
    t2 = t - t1
    print(t2)
    
    运行结果如下:
    tensor([[0, 1, 2],
        [3, 4, 5]])
    tensor([[0, 1, 2]])
    tensor([[0, 0, 0],
        [3, 3, 3]])   
    
    3)不同维度的张量运算
    import torch
    t = torch.arange(6).reshape(2, 3)
    print(t)
    t1 = torch.arange(3)
    print(t1)
    t2 = t - t1
    print(t2)
    
    运算结果如下:
    tensor([[0, 1, 2],
            [3, 4, 5]])
    tensor([0, 1, 2])
    tensor([[0, 0, 0],
            [3, 3, 3]])
    

2.2.5 GPU加速

  1. GPU上使用Tensor
    1)获取本机GPU配置
    import torch
    
    print(torch.cuda.is_available())  # GPU是否可用
    print(torch.cuda.device_count())  # GPU数量
    print(torch.cuda.current_device())  # 当前GPU设备
    print(torch.cuda.get_device_name())  # GPU设备名称
    
    运行结果如下:
    True
    1
    0
    NVIDIA GeForce GTX 1650
    
    2)指定张量的设备
    import torch
    
    t_gpu = torch.tensor([2.0, 2, 3], device='cuda')  # 新建GPU张量
    print(t_gpu)
    
    运行结果如下:
    tensor([2., 2., 3.], device='cuda:0')
    
    注意: 新建张量时,可以使用device属性来指定期望设备,device默认值为None。
    3)CPU与GPU张量转换
    方法一:
     ​ ​ ​  ​ ​ ​ tensor.to(device=None)可以将张量放到指定的设备中。
    import torch
    
    t = torch.arange(5)  # 新建一个cpu张量
    t2 = t.to(device='cuda:0')
    print(t2)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4], device='cuda:0')
    
    方法二:
     ​ ​ ​  ​ ​ ​ 在创建好张量后,可以调用tensor.cuda()函数创建GPU副本;也可以调用tensor.cpu()函数创建CPU张量副本。
    import torch
    
    t = torch.arange(5)  # 新建一个cpu张量
    t2 = t.cuda()  # 将cpu张量转换为gpu张量
    t3 = t.cuda(0)  # 指定gpu
    print(t2)
    print(t3)
    
    t4 = t3.cpu()  # 将gpu张量转换为cpu张量
    print(t4)	
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4], device='cuda:0')
    tensor([0, 1, 2, 3, 4], device='cuda:0')
    tensor([0, 1, 2, 3, 4])
    
    注意:不同设备之间的张量是不可以直接进行计算,必须先要将计算的张量放到同一设备中。另外,cuda表示gpu,cuda:0中的0表示GPU编号,实际指第一张GPU卡。
    4)根据配置选择GPU加速
     ​ ​ ​  ​ ​ ​ 为了避免假设与实际配置不符,最好根据配置来选择是否使用GPU加速。
    import torch
    
    t = torch.arange(5, device='cuda:0' if torch.cuda.is_available() else 'cpu')
    print(t)
    
    运行结果如下:
    tensor([0, 1, 2, 3, 4], device='cuda:0')
    

2.3 自动求导

        在PyTorch中,通过将某个张量的requires_grad属性设置为True,可以追踪对该张量的所有操作,并自动计算梯度。梯度的计算是通过调用backward函数实现的,计算得到的梯度会累加到张量的grad属性中。
        在训练模型时,一般会将模型的可训练参数的requires_grad属性设置为True,以便跟踪梯度并进行参数更新。但在评估模型性能时,通常不需要进行梯度跟踪。为了暂时停止梯度跟踪,可以使用detach函数将张量与计算历史隔离,阻止对该张量的计算记录。另外,还可以将计算代码放在torch.no_grad()块中,这样也可以停止梯度跟踪。这些方法在评估模型性能时经常会使用。 自动求导还有一个重要的概念是梯度函数(gradient function),它对完整的计算历史进行编码,并存储在张量的grad_fn属性中。grad_fn引用了创建张量的Function对象。如果张量是由用户手动创建的,则其grad_fn属性设置为None。
        如果想要计算张量的导数,可以调用张量的backward函数。如果张量本身是一个标量(scalar),则不需要为backward函数设置参数。但如果张量是一个向量、矩阵或更高维的张量,则需要指定一个gradient参数,其形状与张量相匹配,用于对梯度进行加权处理。

2.3.1 标量自动求导

案例一:

import torch

x = torch.tensor([2.0], requires_grad=True)
y = x ** 2
y.backward()
print(x.grad)

运行结果如下:

tensor([4.])

案例二:

import torch

x = torch.tensor([2.0])
w = torch.tensor([3.0], requires_grad=True)

b = torch.randn(1)
b.requires_grad_(True)

for _ in range(5):
    a = torch.mul(x, w)
    output = a + b

    output.backward()
    print(w.grad)

运行结果如下:

tensor([2.])
tensor([4.])
tensor([6.])
tensor([8.])
tensor([10.])

结果分析:

  • 当你多次运行同一个Python脚本文件时,会重新执行脚本中的代码,并创建新的张量对象。这意味着之前的张量对象及其梯度都会被丢弃。因此,每次运行脚本时,张量的梯度会被置空,梯度不会累加。
  • 在一个循环中多次运行相同的操作时,使用的是同一个张量对象,而不是每次都创建新的张量对象。因此,梯度会在每次反向传播时累积,而不会被清空。

2.3.2 梯度清零

        为了避免上述问题,需要在反向传播前进行梯度清零,否则会得到错误的结果。

import torch

x = torch.tensor([2.0])
w = torch.tensor([3.0], requires_grad=True)

b = torch.randn(1)
b.requires_grad_(True)

for _ in range(5):
    if w.grad is not None:  # w梯度清零
        w.grad.zero_()       
    if b.grad is not None:
        b.grad.zero_()      # b梯度清零
        
    a = torch.mul(x, w)  
    output = a + b
    output.backward()
    print(w.grad)

运行结果如下:

tensor([2.])
tensor([2.])
tensor([2.])
tensor([2.])
tensor([2.])

第三章 数据集

        在机器学习中,模型训练通常可以划分为四个子模块:数据、模型、损失函数和优化器。数据模块可以进一步细分为数据收集、数据划分、数据读取和数据预处理。为了方便读取和预处理数据,PyTorch提供了torch.utils.data模块,其中的Dataset类、IterableDataset类和DataLoader类是最为常用的工具。

3.1 自定义数据集

        在PyTorch中,通常使用Dataset类和IterableDataset类来重新定义数据集。Dataset类用于实现映射风格的数据集,而IterableDataset类则用于实现迭代风格的数据集。
1. Dataset类
        Dataset类定义的数据集是一种映射风格的数据集。当创建一个继承Dataset类的子类时,需要重写__getitem__()方法和__len__()方法。通过重写__getitem__()方法,可以将索引映射到数据集上的具体样本。而__len__()方法则用于返回数据集中样本的总数,以便在训练过程中进行迭代。

代码 3.1 定义鸢尾花数据集

import pandas as pd
import numpy as np
from torch.utils.data import Dataset

class IrisDataset(Dataset):
    def __init__(self):
        iris_path = r"../data/iris/iris.data"
        data = np.asarray(pd.read_csv(iris_path, header=None))
        self.X = data[:, 0:4]
        _, self.y = np.unique(data[:, 4], return_inverse=True)  # 特征编码
        self.len = len(data)

    def __getitem__(self, idx):
        return self.X[idx], self.y[idx]

    def __len__(self):
        return self.len

if __name__ == "__main__":
    dataset = IrisDataset()
    print(len(dataset))  # 数据数目

    first_data = next(iter(dataset))
    print(first_data)
    print("特征:", first_data[0], "标签:", first_data[1])

运行结果如下:

150
(array([5.1, 3.5, 1.4, 0.2], dtype=object), 0)
特征: [5.1 3.5 1.4 0.2] 标签: 0

注意: iter(dataset) 是将一个可迭代对象 dataset 转换为一个迭代器对象。迭代器对象可以用于遍历可迭代对象中的元素。next() 函数是一个内置函数,用于获取迭代器的下一个元素。
代码 3.2 定义Hymenoptera数据集

class Hymenoptera(Dataset):
    def __init__(self, root_dir, label):
        self.root_dir = root_dir
        self.label = label
        self.path = os.path.join(self.root_dir, self.label)
        self.img_paths = os.listdir(self.path)

    def __getitem__(self, idx):
        img_name = self.img_paths[idx]
        img_item_path = os.path.join(self.path, img_name)
        img = cv2.imread(img_item_path)
        return img, self.label

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

if __name__ == "__main__":
    root_path = r"../data/hymenoptera/train/"
    labels = "ants"
    hp = Hymenoptera(root_path, labels)
    first_img, target = hp[0]
    cv2.imshow(winname=target, mat=first_img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

运行结果如下:

2. IterableDataset类
        在 PyTorch 1.2 之前,唯一可用的数据集类是Dataset。这种数据集只需要继承Dataset 类,并重写len 和 getitem 方法,其中 getitem 方法接收一个索引,则该索引映射到数据集中的某个项。PyTorch 1.2引入了一个新的数据集类 IterableDataset,该类擅长将数据供给顺序模型,DataLoader会调用 next(iterable_dataset),直到构建完整的批次。
代码 3.2 定义Iris流数据集

from itertools import cycle
from torch.utils.data import IterableDataset, DataLoader

class IrisIterableDataset(IterableDataset):
    def __init__(self, file_path):
        super(IrisIterableDataset).__init__()
        self.file_path = file_path

    @staticmethod
    def parse_file(file_path):
        with open(file_path, 'r') as file_obj:
            for line in file_obj:
                tokens = line.strip('\n').split(',')
                yield from tokens

    def get_stream(self, file_path):
        return cycle(self.parse_file(file_path))

    def __iter__(self):
        return self.get_stream(self.file_path)

if __name__ == "__main__":
    path = r"../data/iris/iris.data"
    iris_data = IrisIterableDataset(path)
    loader = DataLoader(iris_data, batch_size=3)

    for i in loader:
        print(i)
        break

运行结果如下:

['5.1', '3.5', '1.4']

3.2 采集、组织数据

        自定义数据后,就可以返回数据样本了。但这种通过索引返回样本的方式比较原始,无法让数据集一次提供一个批次(batch)数据,也无法随机打乱和并行加速。
        DataLoader类就是一个数据加载器,它将数据集和样本抽样器集成在一起,并提供给定数据集上的可迭代对象,其构造函数如下:

DataLoader(dataset, batch_size=1, shuffle=False, sampler=None,
           batch_sampler=None, num_workers=0, collate_fn=None,
           pin_memory=False, drop_last=False, timeout=0,
           worker_init_fn=None, *, prefetch_factor=2,
           persistent_workers=False)
           
|- dataset: 数据集对象
|- batch_size: 批处理大小
|- shuffle: 设置为True时将在每个epoch重新置乱样本顺序
|- sampler: 样本抽样器,定义从数据集中抽取样本的策略
|- num_workers: 加载数据的子进程。数值为0表示数据由主进程加载
|- drop_last: 如果数据大小不能被批处理大小整除,则将该属性设置为True

3.3 计算机视觉库

        torchvision是一个用于计算机视觉任务的Python库,该库包含了一系列用于处理图像和视频数据的工具和模型。torchvision建立在PyTorch深度学习框架之上,旨在简化计算机视觉任务的开发过程。
torchvision的功能包括:

  1. 数据集加载:torchvision提供了用于加载常用计算机视觉数据集的函数,例如MNIST、CIFAR10、ImageNet等。这些函数使得加载和预处理数据集变得更加便捷。
  2. 数据转换:torchvision提供了一系列图像转换函数,用于对图像进行预处理和增强操作,例如裁剪、缩放、旋转、翻转、标准化等。这些转换函数可以应用于数据集加载之后,用于数据的预处理和数据增强。
  3. 模型定义:torchvision提供了一些经典的计算机视觉模型的实现,例如AlexNet、VGG、ResNet等。这些模型已经在大规模图像分类任务中被广泛使用,可以用于快速搭建和训练自己的计算机视觉模型。
  4. 图像工具:torchvision还提供了一些常用的图像处理工具,例如图像保存、显示、绘制边界框、计算图像直方图等。这些工具可以帮助用户对图像数据进行可视化和分析。
import os, torch
import numpy as np
from PIL import Image
from matplotlib import pyplot as plt
from torch.utils.data import Dataset, DataLoader


class CatsDogsDataset(Dataset):
    def __init__(self, root):
        self.root = root
        imgs = os.listdir(self.root)
        self.imgs = [os.path.join(self.root, img) for img in imgs]

    def __getitem__(self, idx):
        img_path = self.imgs[idx]
        label = 1 if "Dog" in img_path.split("/")[-1] else 0
        pil_img = Image.open(img_path)
        img_array = np.array(pil_img)
        img = torch.from_numpy(img_array)
        return img, label

    def __len(self):
        return len(self.imgs)

if __name__ == "__main__":
    path = r"../data/PetImages/Dog"
    dataset = CatsDogsDataset(path)
    img, label = dataset[0]
    print(img.shape)
    plt.imshow(img)
    plt.show()

运算结果如下:
在这里插入图片描述

  • 13
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值