Pytorch基础

文章目录


  Torchvision、torchaudio和torch是PyTorch框架的三个重要组成部分:

  • torch:是PyTorch的一个核心库提供了张量(tensor)操作和计算图构建的功能,也提供了自动求导(Autograd)功能,使得用户可以轻松地构建和训练神经网络模型。
  • Torchvision:是PyTorch的一个独立子库,主要用于计算机视觉任务,包括图像处理、数据加载、数据增强、预训练模型等。在Torchvision中提供了各种经典的计算机视觉数据集的加载器,如CIFAR-10、ImageNet,以及用于数据预处理和数据增强的工具,可以帮助用户更轻松地进行图像分类、目标检测、图像分割等任务。
  • Torchaudio:是PyTorch的一个独立子库,用于处理音频信号和音频数据,提供了加载、处理和转换音频数据的工具,以及用于构建声音处理模型的函数。

零、tensorboard

  Pytorch框架下提供了可视化工具torch.utils.tensorboard,可用于记录训练数据、评估数据、网络结构、图像等,对于观察神经网络的过程非常有帮助。

0.1基本使用案例

  tensorboard基本使用流程为:

  • 1.导入from torch.utils.tensorboard import SummaryWriter,实例化SummaryWriter对象并指明记录日志的保存路径。
  • 2.调用SummaryWriter对象的API添加内容,
  • 3.关闭SummaryWriter对象,在命令行中输入tensorboard --logdir=日志路径
  • 4.在浏览器中打开。

一、数据结构:Tensor

1.1数据类型

  Tensor,即张量,是PyTorch中的基本操作对象,可以看做是包含单一数据类型元素的多维矩阵。Tensor与numpy中的ndarrays对象,不同之处在于,Tensor对象可在CPU、GPU上进行运算,而ndarrays对象只能在CPU上进行运算。Tensor共有八种数据类型,其中有七种只能在CPU上运算,在创建指定类型张量时需指定对应的函数:
在这里插入图片描述

x = torch.DoubleTensor(2,3)
print(type(x))
print(x)

在这里插入图片描述

1.2Tensor的创建方式

  Tensor对象与Ndarrays对象的创建方式基本一致:

方法名说明
Tensor()指定大小创建Tensor对象,支持将list、numpy数组转化为Tensor类型
eye()创建指定大小的单位阵
linspace(start,end,count)创建[start,end]内含有count个元素的一维tensor
logspace(start,end,count)创建[ 1 0 s t a r t 10^{start} 10start 1 0 e n d 10^{end} 10end]内含有count个元素的一维tensor
ones(size)返回大小为size全1的Tensor对象
zeros(size)返回大小为size全0的Tensor对象
ones_like(t)传入Tensor对象,返回相同大小且全1的Tensor对象
zeros_like(t)传入Tensor对象,返回相同大小且全0的Tensor对象
arange(start,end,step)在区间[s,e)上以间隔sep生成一个序列张量
rand()创建均匀分布的张量,范围为[0,1)
randn()创建正态分布的张量,范围为[0,1)
randperm()创建含有[0,n)数据的张量,并随机排列
empty()创建未初始化的张量
import torch
import numpy as np

#1.Tensor()
t1 = torch.Tensor(2, 3, 3)
t2 = torch.Tensor(np.arange(2, 3))  #从numpy对象创建Tensor
t3 = torch.Tensor([[1, 2], [3, 3]])  #从list对象创建Tensor

#2.eye()
t4 = torch.eye(2, 3)

#3.linspace()
t5 = torch.linspace(1, 50, 3)

#4.logspace()
t6 = torch.logspace(2, 3, 10)

#5.ones_like()
t7 = torch.ones_like(t6)

#6.zeros_like()
t8 = torch.zeros_like(t7)

#7.arange()
t9 = torch.arange(1, 10, 2)

#8.创建均匀分布的张量
t10 = torch.rand((2,3))

#9.创建正态分布的张量
t11 = torch.randn((2, 3))

#10.创建含有n个整数,随机排列的Tensor
t12 = torch.randperm(10)

#10.创建未初始化的张量
t13 = torch.empty((2, 3))

1.3张量的基本运算

函数作用
torch.abs(A)将张量元素取绝对值并返回
torch.add(A,B)将两个张量对应元素相加并返回,支持常量运算、广播运算
torch.clamp(A,min,max)裁剪张量,小于min的元素置换为min,大于max的元素同理
torch.div(A,B)将两个张量对应元素相除并返回,支持常量运算、广播运算
torch.pow(A,B)以A元素为底数,B元素为指数运算,支持常量运算、广播运算
torch.mm(A,B)将两个张量作矩阵乘法,需满足运算法则,且二者必须是矩阵,不能是向量
torch.mv(A,B)将矩阵A与向量B进行矩阵向量乘法运算
Tensor.T将Tensor进行转置操作
Tensor.numpy()将Tensor对象转为Numpy类型
Tensor.item()将Tensor转为常量,仅当只含一个元素时可使用
Tensor.shape查看形状
Tensor.dtype查看数据类型
Tensor.view()相当于shape(),返回修改后的新张量
A[1:]、A[-1,-1]=100、A[:,:-1]
torch.stack((A,B),dim)指定维度拼接张量,同numpy的stack()操作

1.4张量的属性

属性名作用
device张量所在设备,GPU 或CPU,张量在GPU上才能用其加速。
data用于存放tensor,是数据本体。
grad存放data的梯度值。
grad_fn记录生成该张量时所用的方法,用于反向传播自动求梯度时选择求导方式。
requires_grad指示该是否需要梯度,对于需要求导的tensor,其requires_grad属性必须为True,这样自动求导时才会对该张量求梯度。神经网络层中的权值wtensorrequires_grad属性默认为True,求导对象与被求导对象之间的中间变量也默认True
is_ leaf指示是否是叶子结点(张量),求各节点的梯度值时,都要带入叶子结点的值进表达式,所以在一次反向传播完成前叶子结点的值不能在原地址上被修改。
dtype张量的数据类型。

二、数据集加载器DataLoaders

  Pytorch中提供了数据加载器,存于torch.utils.data包下,用于对数据进行批量加载和处理。事实上,模型的训练过程通常需要大量的数据,而将所有数据一次性加载到内存中是不可行的。这时候就需要使用数据加载器DataLoader将数据分成小批次进行加载。并且,DataLoader可以自动完成数据的批量加载、随机洗牌(shuffle)、并发预取等操作,从而提高模型训练的效率。同时,可使用torchvision提供的工具进行常用的图像处理。

2.0前置知识

2.0.1torch.scatter()、torch.scatter_()

  torch.scatter()与torch.scatter_()功能相同,但scatter()不会修改原来的Tensor,后者则会直接在原Tensor上进行修改。常用参数:

ouput = torch.scatter(input, dim, index, src)
# 或者是
input.scatter_(dim, index, src)
  • src:源Tensor对象。
  • index:选中需要填充的input元素,当src是Tensor对象时,index形状需与src相同。
  • dim:决定index操作的维度。
  • input:被修改的Tensor对象,与src类型(dtype)应相同。

dim=0:遵循映射规则input[index[i][j]][j] = src[i][j]

链接

链接

  index=[0,0,1]表示对第0、1个数据执行操作,相应的,index=[1,0,0]表示对第1、2个数据进行操作。而src中对数据索引的定义方式需使用dim进行指定。当dim=0时,表示对src维度0的数据操作:
在这里插入图片描述
dim=1时表示对src维度1的数据进行操作:
在这里插入图片描述
执行代码:

2.1官方案例

2.1.1从TorchVision加载数据集

  在TorchVision模块中提供了一些常用的图片数据集,如MNIST、COCO等。现使用DataLoader加载Fashion-MINIST数据集示例,数据集中包含7000个28x28灰度图像,其中6000个用作训练,一共有10个类别标签。使用了如下参数:

  • root:指定本地存储训练/测试数据的路径。
  • train:指定加载测试集或训练集数据。
  • download:若无法从root路径中加载数据集,则从网络上进行下载。
  • transform:使用转换器预处理数据。
  • target_transform:使用转换器预处理标签。

torchvision.transforms下的ToTensor可用于对特征数据进行预处理;

  • ToTensor:将PIL图像或NumPy ndarray转换为浮点张量,并将特征张量归一化处理,处理后范围为[0,1]。若大小为[HxWxC]则处理后大小为[CxHxW],即[通道,高度,宽度],卷积层默认使用该方式输入。

  执行代码下载数据集:

import torch
from torchvision import datasets
from torchvision.transforms import ToTensor, Lambda

train_ds = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor(),
)
test_ds = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor(),
)
#查看数据集基本信息
print(train_ds)

在这里插入图片描述

2.1.2迭代和可视化数据集

labels_map = {
    0: "T-Shirt",
    1: "Trouser",
    2: "Pullover",
    3: "Dress",
    4: "Coat",
    5: "Sandal",
    6: "Shirt",
    7: "Sneaker",
    8: "Bag",
    9: "Ankle Boot",
}
figure = plt.figure(figsize=(8, 8))
cols, rows = 3, 3	
for i in range(1, cols * rows + 1):	#定义循环添加9张图片
	#随机生产一个整数,范围为[0,60000],其中60000是训练集图片的个数
    sample_idx = torch.randint(len(training_data), size=(1,)).item()
    #取出随机一张图片对应的矩阵与标签
    img, label = training_data[sample_idx]	#img为(1,28,28图片)
    #向画布上添加子图
    figure.add_subplot(rows, cols, i)
    #加上图片名
    plt.title(labels_map[label])
    #关闭axis
    plt.axis("off")
    #img.squeeze()用于删除多余的轴,28x28的灰度图被Tensor处理后变为1x28x28,此时需去掉表示通道的维度
    plt.imshow(img.squeeze(), cmap="gray")
plt.show()

在这里插入图片描述

2.2创建自定义数据集Dataset

2.2.1官方文档

  在Pytorch中可以创建自定义的数据集来加载数据,创建时需继承torch.utils.data下的Dataset类,并实现__init____len____getitem__方法。官方文档代码:

import os
import pandas as pd
from torchvision.io import read_image
from torch.utils.data import DataLoader,Dataset

class CustomImageDataset(Dataset):
	# 传入标签目录、数据目录、transform、target_transform
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform
	# 返回样本个数
    def __len__(self):
       return len(self.img_labels)
	# 返回指定索引处样本数据及标签
    def __getitem__(self, idx):
    	#获取样本数据的路径
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        #读取数据文件
        image = read_image(img_path)
        #读取对应标签
        label = self.img_labels.iloc[idx, 1]
        #查看是否进行
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label

  在Pytorch中可以创建自定义的数据集来加载数据,创建时需继承Dataset类,并实现__init____len____getitem__方法。

  • __init__:在实例化Dataset对象时自动执行,一般用于初始化数据以及对应标签的目录、加载调用DataLoader时传入的transformtarget_transform
  • __len__:返回数据集中样本的个数。
  • __getitem__:加载给定索引处的样本(包括图片、标签),根据索引识别样本在磁盘上的位置,若含有transformtarget_transform转换器,则进行转化并返回张量图片和相应的标签。

  事实上,直接从数据集中读出数据时,读出的是样本数据与标签封装成的元组:

import torchvision.datasets
# 导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 获取测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10", train=False, transform=torchvision.transforms.ToTensor(), download=True)

#从测试集中读取数据
img=test_dataset[0]
print(img)
print(type(img))

在这里插入图片描述

2.2.2自定义数据集

from torch.utils.data import Dataset, DataLoader  
  
class MyDataset(Dataset):  
    def __init__(self, x_tensor, y_tensor):  
        self.x = x_tensor  
        self.y = y_tensor  
  
    def __getitem__(self, index):  
        return (self.x[index], self.y[index])  
  
    def __len__(self):  
        return len(self.x)  
  
x = torch.arange(10)  
y = torch.arange(10) + 1  
  
my_dataset = MyDataset(x, y)  
loader = DataLoader(my_dataset, batch_size=4, shuffle=True, num_workers=0)  
  
for x, y in loader:  
    print("x:", x, "y:", y)  

在这里插入图片描述

2.3数据集加载器DataLoader

2.3.1函数介绍

  数据集加载器DataLoader是Pytorch提供的数据加载器,用于对数据进行批量加载和处理。它存在于torch.utils.data包下,需要的时候应该在该包下导入DataLoader。引入以下概念:

  • Epoch(轮):所有训练样本都已输入到模型中,称为一个epoch。
  • Iteration(批次): 一批样本(batch_size)输入到模型中,称为一个Iteration。
  • Batchsize: 一批样本的大小, 决定一个epoch有多少个Iteration。

DataLoader()的常用参数:

  • dataset:加载的数据集。
  • batch_size:指定一批(Iteration)数据集的大小。模型并不会一个一个读入样本进行训练,而是以Batchsize为单位输入数据,当最后一批数据不足Batchsize个数时,通过drop_last=True可将其丢弃。
  • shuffle:布尔类型,每轮训练时是否将数据洗牌,默认为False。若不洗牌,则每轮训练时同一批次传入的Batchsize大小的样本数据是相同的。将输入数据洗牌可使样本更有独立性(不同批次数据更独立)。但对于有序的数据不应设为True。
  • num_workers:加载数据时的并发线程/进程数。根据计算机硬件的配置和数据加载的速度来设置,并发加载数据以加快训练速度。但是,过多的并行加载也可能会导致性能下降,因此需要进行适当的调整。
  • drop_last:当数据样本数量不能被批次大小整除时,是否丢弃最后一个不完整的批次。

  此处使用torchvision.datasets提供的CIFAR10的测试数据集查看各个参数的功能:

import torchvision.datasets
# 导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 获取测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="CIRFA10", train=False, transform=torchvision.transforms.ToTensor(),
                                            download=True)
# 创建一个dataloader,批大小为4,每一个epoch重新洗牌,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset, batch_size=4, shuffle=True, drop_last=False)

2.3.2读取数据

  在获取数据集时已使用transform=torchvision.transforms.ToTensor()参数进行归一化处理,故获取到的样本数据是Tensor类型。并且,数据加载器在加载数据时会将样本数据与其对应的标签打包为元组,当直接从加载器属性dataset中读取数据时,得到的是一个元组对象:

#直接从加载器的dataset中读取样本和标签
print('test_dataloader.dataset[0]的数据类型为',type(test_dataloader.dataset[0]))
img,label=test_dataloader.dataset[0]
print('图片对应标签为:',label)
print('图片类型为:',type(img),',图片大小为:',img.shape)

#查看图片
writer=SummaryWriter('logs')
writer.add_image('img',img)
writer.close()

在这里插入图片描述
输入命令查看图片:

%load_ext tensorboard
%tensorboard --logdir logs

在这里插入图片描述

事实上,在经过DataLoader加载后,同一批次的所有数据及其标签会被打包并封装在了dataset属性,即:

  • 同一批次的所有图片对象进行打包,形成了一个对象。
  • 同一批次的所有的标签进行打包,形成一个了对象。

在这里插入图片描述
  注意,通过test_dataloader.dataset[0]取出的数据与通过for迭代出的数据并不是同一个。
  通过for循环迭代取出加载器封装的样本数据集、标签集对象,其个数等于数据集中对象个数/batch_size,本例中为10000/4=2500个对象。遍历代码:

writer = SummaryWriter("logs")
#loader中对象
for data in test_dataloader:#读取批次封装对象
    imgs,targets = data
    print(imgs.shape)	#打印数据打包对象的形状
    print(targets.shape)	#打印标签打包对象的形状
    writer.add_images("loader",imgs)
writer.close()

在这里插入图片描述
  可见从for迭代出的imgs并非原始的图像数据,而是在使用数据集加载器加载时按batch_size=4对原始图像进行了打包,打包数据维度为[4, 3, 32, 32],同样打包的还有标签,维度为[4]。可通过torch.utils.tensorboard查看该打包数据:

writer = SummaryWriter("logs")
#loader中对象
step = 0	#记录批次数
for data in test_dataloader:
    imgs,targets = data
    writer.add_images("loader",imgs,step)
    step+=1
writer.close()
#此时用的是jupyter notebook
%load_ext tensorboard
%tensorboard --logdir logs

在这里插入图片描述
可见打包对象本质是batch_size张图片的叠加(add_images()函数适用于该数据的读取)。

2.3.2可视化drop_last作用

  修改batch_size=64,设置drop_last=True,此时最后一个批次所获取的数据只有16个( 10000 % 64 = 16 10000\%64=16 10000%64=16),模型加载器会自动舍去不满足大小的批次。使用torch.utils.tensorboard查看这一过程:

import torchvision.datasets
# 导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 获取测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="CIRFA10", train=False, transform=torchvision.transforms.ToTensor(),
                                            download=True)
# 创建一个dataloader,批大小为64,每一个epoch重新洗牌,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=True, drop_last=True)

writer = SummaryWriter("logs")
#loader中对象
step = 0	#记录批次数
for data in test_dataloader:
    imgs,targets = data
    writer.add_images("loader",imgs,step)
    step+=1
writer.close()

在这里插入图片描述
此时不再含有最后的16张图片。

2.3.3可视化shuffle作用

  每一轮epoch之后就是分配完了一次数据,而shuffle决定了是否在新一轮epoch开始时打乱所有图片的属性进行分配。一般的,加载训练数据集时设置为True,加载测试数据集时设置为False。

import torchvision.datasets
# 导入dataloader的包
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter

# 获取测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10", train=False, transform=torchvision.transforms.ToTensor(), download=True)
# 创建一个dataloader,批大小为4,每一个epoch重新洗牌,不舍弃不能被整除的批次
test_dataloader = DataLoader(dataset=test_dataset, batch_size=64, shuffle=True, drop_last=True)

writer = SummaryWriter("log")

for epoch in range(2):
    step = 0
    for data in test_dataloader:
        imgs, targets = data
        writer.add_images("Epoch:{}".format(epoch), imgs, step)
        step += 1

writer.close()

在这里插入图片描述
可见数据已被打乱。

三、Transforms

  Transform是torchvision下的一个模块,其中定义很很多用于图像预处理的类,如归一化(Normalize类),尺寸变化(Resize类),转换为tensor格式(ToTensor类),通过实例化该工具类,可以方便地对图像进行各种变换操作。事实上,所有 TorchVision 数据集都有两个参数:用于处理图像数据的转换transform和用于处理标签数据的target_transform。在Transforms模块中提供了几种常用的转换方式可供直接调用。

3.1transforms.ToTensor()

在这里插入图片描述
  将PIL图像或Ndarray图像转化为Tensor类型(torch.FloatTensor),并对图像像素进行归一化处理,即将像素由 [ 0 , 255 ] [0,255] [0255]放缩到 [ 0 , 1 ] [0,1] [01],本质是除以 255 255 255。并且,原始的图像形状为(HxWxC),通过transforms.ToTensor()处理后图像形状变为(CxHxW),即将通道数提前。

import cv2
import numpy as np
import torch
from torchvision import transforms
# 定义数组Ndarray型图像,要求是unit8类型(此时像素范围为[0,255])
data = np.array([
                [[1,1,1],[1,1,1],[1,1,1],[1,1,1],[1,1,1]],
                [[2,2,2],[2,2,2],[2,2,2],[2,2,2],[2,2,2]],
                [[3,3,3],[3,3,3],[3,3,3],[3,3,3],[3,3,3]],
                [[4,4,4],[4,4,4],[4,4,4],[4,4,4],[4,4,4]],
                [[5,5,5],[5,5,5],[5,5,5],[5,5,5],[5,5,5]]
        ],dtype='uint8')# 定义数组
print('data.shape=',data.shape)
#实例化ToTensor对象
toTensor=transforms.ToTensor()
data = toTensor(data)
print(data)
print(data.shape)

在这里插入图片描述
  事实上,深度学习模型的权重是随机初始化的,而图像像素值的范围通常是 [ 0 , 255 ] [0, 255] [0,255],输入的图像数据的值域过大,就会超出了模型的初始权重所能处理的范围。为了避免这种情况,我们需要对图像进行归一化操作,将图像像素值缩放到一个较小的范围内,例如 [ 0 , 1 ] [0, 1] [0,1]或者 [ − 1 , 1 ] [-1, 1] [1,1]。这样可以避免在训练过程中出现梯度爆炸或者梯度消失的问题。

3.2transforms.Normalize()

在这里插入图片描述
  用于将Tensor格式的图像像素进行标准化处理,即将数据转化为标准高斯分布,均值变为0,标准差变为1。并且不支持PIL格式、Ndarray格式图像作为输入(通常与transforms.ToTensor()配合使用)。构造方式为:

transforms.Normalize(mean, std, inplace=False)
  • mean:均值,Tensor数据类型,元素个数应等于通道数。
  • std:标准差,Tensor数据类型,元素个数应等于通道数。

函数输出为: o u t p u t [ c h a n n e l ] = ( i n p u t [ c h a n n e l ] − m e a n [ c h a n n e l ] ) / s t d [ c h a n n e l ] output[channel] = (input[channel] - mean[channel]) / std[channel] output[channel]=(input[channel]mean[channel])/std[channel],其中 c h a n n e l channel channel指对特征图的每个通道都进行这样的操作。通过减去均值并除以标准差,我们可以将图像数据的分布转换为标准正态分布(均值为0,标准差为1)。事实上,数据如果分布在(0,1)之间,可能实际的bias,就是神经网络的输入b会比较大,而模型初始化时b=0的,这样会导致神经网络收敛比较慢,经过Normalize后,可以加快模型的收敛速度。例:

import torch
import numpy as np
from torchvision import transforms

# 模拟图像数据
data = np.random.rand(28,28,3)

# 像素值转化到[0,1]
toTensor=transforms.ToTensor()
data=toTensor(data)

# 计算均值、标准差
def calculate(data):
    # 需要对数据进行扩维,增加batch维度
    data = torch.unsqueeze(data,0)    #在pytorch中一般都是(batch,C,H,W)
    nb_samples = 0.
    #创建3维的空列表
    channel_mean = torch.zeros(3)
    channel_std = torch.zeros(3)
    N, C, H, W = data.shape[:4]
    data = data.view(N, C, -1)  #将数据的H,W合并
    #展平后,w,h属于第2维度,对他们求平均,sum(0)为将同一纬度的数据累加
    channel_mean += data.mean(2).sum(0)  
    #展平后,w,h属于第2维度,对他们求标准差,sum(0)为将同一纬度的数据累加
    channel_std += data.std(2).sum(0)
    #获取所有batch的数据,这里为1
    nb_samples += N
    #获取同一batch的均值和标准差
    channel_mean /= nb_samples
    channel_std /= nb_samples
    return channel_mean, channel_std   

data_mean,data_std=calculate(data)
normalize=transforms.Normalize(data_mean, data_std, inplace=False)
data=normalize(data)
print(data)

在这里插入图片描述

3.3transfoms.Resize()

  transforms.Resize用于修改图像的大小,若传入图像是Tensor类型,则默认其形状格式为[...,H,W],其中...指图像通道数。

import torchvision.datasets
# 导入dataloader的包
from torch.utils.data import DataLoader
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter

# 获取测试数据集
test_dataset = torchvision.datasets.CIFAR10(root="./CIRFA10", train=False, transform=torchvision.transforms.ToTensor(), download=True)

img,label=test_dataset[0]
print('img形状为:',img.shape)
# 初始化transforms对象
transforms_resize=transforms.Resize([250,250])
img_resize=transforms_resize(img)
print('img_resize形状为:',img_resize.shape)

在这里插入图片描述
注意,Resize()只能修改长宽大小,而无法修改通道数,在传入参数时也无需传入通道数。

writer=SummaryWriter('log')
writer.add_image('img',img)
writer.add_image('resize',img_resize)
writer.close

3.4transforms.Compose()

  transforms.Compose()可将多个图像变换步骤组合到一起使用,例如:

# 图像首先被调整到256x256的大小,接着转换为张量类型并归一化,最后进行标准化
transform = transforms.Compose([
    transforms.Resize((256, 256)),
    transforms.ToTensor(),
    transforms.Normalize([0.5, 0.5, 0.5], [0.5, 0.5, 0.5])
])

四、构建神经网络

  在Pytorch中提供了集成度较高的模块化接口torch.nn,该接口提供了网络模组、优化器和初始化策略等一系列功能,可用于构建自定义的神经网络。

4.1NCHW与NHWC数据格式

  在神经网络模型中经常可以看到NCHW与NHWC数据格式,含义为:

  • N:Batch,即一批次所含图片的数目。
  • C:Channel,即图片通道数。
  • H:Height,即图片高度。
  • W:Width,即图片宽度。

在传入一个NCHW或NHWC格式的数据时可理解为传入了N个大小为HxW的C通道图片。如(N=2):
在这里插入图片描述
但无论逻辑表达上是几维的数据,在计算机中存储时都是按照一维进行存储。即,对于不同格式的数据在存储时都要转化为一维数据:
在这里插入图片描述

  • NCHW:先取W方向数据;然后H方向;再C方向;最后N方向。
  • NHWC:先取C方向数据;然后W方向;再H方向;最后N方向。
  • CHWN:先取N方向数据;然后W方向;再H方向;最后C方向。

  以RGB图像为例,一个像素的RGB值需使用三个数值表示,即通道为3,假设N=1,则:
在这里插入图片描述

4.2常用模块函数

  Pytorch中为神经网络的不同层提供了相应的模块以构建模型,常见模块包括但不限于:

  • 卷积层nn.Conv1d, nn.Conv2d, nn.Conv3d分别用于一维、二维和三维数据的卷积操作,常应用于图像识别、语音处理等领域。
  • 池化层nn.MaxPool1d, nn.MaxPool2d, nn.AvgPool2d用于下采样特征图。
  • 正则化层:如批量归一化nn.BatchNorm1d, nn.BatchNorm2d等。
  • 激活函数:如nn.ReLU, nn.Sigmoid, nn.Tanh等非线性激活层。

注意,这些模块都是nn.Module的子类,在使用前应当先初始化。

4.2.1卷积层常用模块:torch.nn.CovXxx()

  卷积层用于将输入数据与卷积核进行运算从而得到输出作为特征映射,不同的卷积核可作为不同特征的提取器。在Pytorch中,针对卷积操作的对象和使用场景不同,针对一维卷积、二维卷积、三维卷积、转置卷积(卷积的逆操作)等都提供了相关的模块来实现,常用卷积操作模块如下:

函数名功能
torch.nn.Conv1d()对输入数据使用一维卷积
torch.nn.Conv2d()对输入数据使用二维卷积
torch.nn.Conv3d()对输入数据使用三维卷积
torch.nn.ConvTranspose1d()对输入数据使用一维转置卷积
torch.nn.ConvTranspose2d()对输入数据使用二维转置卷积
torch.nn.ConvTranspose3d()对输入数据使用三维转置卷积

  跳转至别的博客进行查看:

4.2.2池化层常用模块:torch.nn.MaxXxx()、torch.nn.MaxUnXxx()、torch.nn.AvgXxx()、torch.nn.AdaptiveXxx()

  池化层常用于对卷积得到的结果进行进一步处理,常见池化方法包括平均池化、最大池化:
在这里插入图片描述
  在Pytorch中同样提供了大量池化层常用模块用于模型的构建:

函数名功能
torch.nn.MaxPool1d()对输入信号使用1D最大值池化
torch.nn.MaxPool2d()对输入信号使用2D最大值池化
torch.nn.MaxPool3d()对输入信号使用3D最大值池化
torch.nn.MaxUnPool1d()对输入信号使用1D最大值池化的部分逆运算
torch.nn.MaxUnPool2d()对输入信号使用2D最大值池化的部分逆运算
torch.nn.MaxUnPool3d()对输入信号使用3D最大值池化的部分逆运算
torch.nn.AvgPool1d()对输入信号使用1D平均值池化
torch.nn.AvgPool2d()对输入信号使用2D平均值池化
torch.nn.AvgPool3d()对输入信号使用3D平均值池化
torch.nn.AdaptiveMaxPool1d()对输入信号使用1D自适应最大值池化
torch.nn.AdaptiveMaxPool2d()对输入信号使用2D自适应最大值池化
torch.nn.AdaptiveMaxPool3d()对输入信号使用3D自适应最大值池化
torch.nn.AdaptivAvgPool1d()对输入信号使用1D自适应平均值池化
torch.nn.AdaptivAvgPool2d()对输入信号使用2D自适应平均值池化
torch.nn.AdaptivAvgPool3d()对输入信号使用3D自适应平均值池化

  此处举出MaxPool2d()的使用案例,模块初始化方式如下:

torch.nn.MaxPool2d(kersize,
                  stride=None,
                  padding=0,
                  dilation=1,
                  return_indices=False,
                  ceil_mode=False)

源码中提到输入数据格式应为(N, C, H, W),输出数据格式与之相同。参数如下:
在这里插入图片描述
1.读取图片,转化为ndarry类型

import torch
from torchvision import transforms
from torch.utils.tensorboard import SummaryWriter
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
from torch.utils.tensorboard import SummaryWriter

#读取图像
img=Image.open("heart.png")

#将图像转化为ndarry类型
img = np.array(img, dtype=np.uint8)
print('img.shape=',img.shape)
plt.figure(figsize=(6,6))
plt.imshow(img)
plt.axis("off")
plt.show()

在这里插入图片描述
2.将图片转为(N,C,H,W)格式

#将图片转化为模型训练的输入形式
toTensor=transforms.ToTensor()
img_tensor=toTensor(img)#img_tensor.shape=torch.Size([3, 860, 860])
img_tensor=img_tensor.view((1,3,860,860))

3.调用池化函数

#调用最大池化函数
maxpool2 = torch.nn.MaxPool2d(2,stride=2)#池化窗口大小为2,移动步长为2
img_out=maxpool2(img_tensor)#img_out.shape=[1, 3, 430, 430]
img_out=img_out.squeeze()#img_out.shape=[3, 430, 430]
#展示处理后的图片
#展示处理后的图片
write=SummaryWriter('log')
write.add_image('img_out',img_out)
write.close()
%reload_ext tensorboard
%tensorboard --logdir=log

在这里插入图片描述

4.2.3常用激活函数模块

  torch.nn下提供了常用的激活函数模块:

模块名功能
torch.nn.SigmoidSigmoid激活函数
torch.nn.TanhTanh激活函数
torch.nn.ReLUReLU激活函数
torch.nn.SoftplusSoftplus激活函数

  以torch.nn.Sigmoid为例:

import torch.nn as nn
import torch
import matplotlib.pyplot as plt

#定义一组一维张量
x = torch.linspace(-6,6,20)
#初始化Sigmoid函数对象
sigmoid = nn.Sigmoid()
#将张量数据输入函数
ysigmoid = sigmoid(x)

#使用matplotlib绘图
plt.scatter(x.data.numpy().tolist(), ysigmoid.data.numpy().tolist())
plt.xlabel('x')
plt.ylabel('sigmoid(x)')
plt.title("Sigmoid")

在这里插入图片描述

4.2.4常用循环层模块

  Pytorch中,提供了三种循环层的实现,如下所示:

模块名功能
torch.nn.RNN()多层RNN单元
torch.nn.LSTM()多层长短期记忆LSTM单元
torch.nn.GRU()多层门限循环GRU单元
torch.nn.RNNCell()一个RNN循环层单元
torch.nn.LSTMCell()一个长短期记忆LSTM单元
torch.nn.GRUCell()torch.nn.GRUCell()

4.2.5全连接层模块nn.Linear()

  全连接层是一个由多个神经元组成的层,其所有的输出和该层的所有输入都有连接,实现的是最为简单的线性计算 y = w T x + b y=w^{T}x+b y=wTx+b。事实上,在深度学习中输入变量大多是多维张量,故此处使用的实际是矩阵乘法,加法则是矩阵加法。即:
Y n x o = W n x i X i x o + b n x 1 Y_{nxo}=W_{nxi}X_{ixo}+b_{nx1} Ynxo=WnxiXixo+bnx1
  初始化模块声明如下:

torch.nn.Linear(in_features,out_features,bias=True)
  • in_features:输入神经元的个数。
  • out_features:输出神经元的个数
  • bias:是否加入偏置(加入后模型会自动初始化偏置并更新)。

案例一

from torch import nn
import torch

model = nn.Linear(2, 1) # 输入特征数为2,输出特征数为1
input = torch.Tensor([1, 2]) # 输入一个含有两个特征的样本
output = model(input)
print('output=',output)

#查看模型参数
for param in model.parameters():
    print(param)

在这里插入图片描述

可见,模型参数为 w = [ − 0.4950 , 0.4673 ] 、 b = 0.3152 w=[-0.4950, 0.4673]、b=0.3152 w=[0.4950,0.4673]b=0.3152,输出为 0.7548 0.7548 0.7548。验证:
[ − 0.4950 , 0.4673 ] ∗ [ 1 , 2 ] T + 0.3152 = 0.7548 [-0.4950, 0.4673]*[1, 2]^{T}+0.3152=0.7548 [0.4950,0.4673][1,2]T+0.3152=0.7548

案例二
  假设一批次有三个样本数据( b a t c h s i z e = 3 batch_size=3 batchsize=3),每个样本含有5个特征:

X = torch.Tensor([
    [0.1,0.2,0.3,0.3,0.3],
    [0.4,0.5,0.6,0.6,0.6],
    [0.7,0.8,0.9,0.9,0.9],
])#torch.Size([3, 5])

定义全连接层,输入特征为5,下一层神经元数为10,则此时模型参数是大小为5x10的矩阵:

model = nn.Linear(in_features=5, out_features=10, bias=True)

Y 3 x 10 = X 3 x 5 W 5 x 10 + b Y_{3x10}=X_{3x5}W{5x10}+b Y3x10=X3x5W5x10+b
(此时特征数据使用行向量形式存放,故w使用左乘形式)
对应神经网络图像为:
在这里插入图片描述
其中 X 0 、 X 1 、 X 2 X_0、X_1、X_2 X0X1X2分别表示输入的三个行向量, W 1 、 W 2 、 W 3 、 . . . . . . 、 W 9 W_1、W_2、W_3、...... 、W_9 W1W2W3......W9分别表示系数矩阵的10个列向量。在经过全连接的线性运算后,每个样本最终从五维数据(五个特征)扩展成十维数据(十个特征)。

output=model(X)
print('output=',output)#torch.Size([3, 10])
#遍历查看参数
for param in model.parameters():
    print(param)
    print(param.shape)

在这里插入图片描述

4.3容器类

  Pytorch提供了各种容器类用于构建自定义的神经网络。

4.3.1torch.nn.Module

  nn.Module是构建神经网络的基类,提供了一个模块化和灵活的方式来组织复杂的神经网络结构。通过继承nn.Module类,并实现__init__()forward()方法,即可创建自定义的神经网络层、模型或整个神经网络。在构建模型时一般有以下注意点:

  • 一般将网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中。
  • 一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,而是在forward方法里面可以使用nn.functional来实现。
  • forward方法(实现前向传播)是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。当模型开始训练时,会自动执行__init__()forward()方法开始训练。
  • 所有nn.Module的子类,都会在实例化的同时随机生成w和b的初始值。所以实例化之后,我们就可以调用属性weightbias来查看生成的w和b。其中w是必然会生成的,b是我们可以控制是否要生成的。
  • 由于w和b是随机生成的,所以同样的代码多次运行后的结果是不一致的。如果我们希望控制随机性,则可以使用torch中的random类。如:torch.random.manual_seed(420) 来人为设置随机数种子。

注意,在Pytorch当中神经网络由其他模块(层)组合而成,所有的模块(层)都是nn.Module的子类,且神经网络本身也是一个模块(层),这种嵌套结构允许轻松构建和管理复杂的架构。
  定义模型并进行训练的基本流程为:

  • 1.继承nn.Module类创建自定义模型,并在构造函数 __init__()中定义需要的层结构。
  • 2.实现forward(self, input)方法,描述前向传播的实现过程。
  • 3.创建模型实例并传入必要参数进行初始化。
  • 4.使用优化器(torch.optim)对模型的可学习参数进行优化,结合数据加载器torch.utils.data.DataLoader加载数据集,并在循环中迭代执行前向传播、计算损失、反向传播和参数更新。

  以官方文档中构建的神经网络为例,在准备训练之前需要检测是否可使用当前电脑的GPU进行训练:

import os
import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

device = (
    "cuda"
    if torch.cuda.is_available()
    else "cpu"
)
print(f"Using {device} device")	# cuda/cpu

继承nn.Module定义神经网络,在定义时无需实现反向传播,Pytorch会根据定义的正向传播自动实现反向传播:

class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),	#初始化全连接层对象,将28*28个数据特征映射为512个
            nn.ReLU(),				#初始化ReLU激活函数对象
            nn.Linear(512, 512),	#初始化全连接层对象,将512个数据特征映射为512个
            nn.ReLU(), 				#初始化ReLU激活函数对象	
            nn.Linear(512, 10),		#初始化全连接层对象,将512个数据特征映射为10个
        )
    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

  在官方文档中定义的模型输入数据格式为(1,28,28),类比于常见的(C,H,W)格式图片,在输入到前向传播函数forward后会将其降维为(28,28)再输入模型。最终模型输出含有十个数据的向量,类比于官方文档中FashionMNIST数据集的类别数。

#打印模型,查看模型基本信息
model=NeuralNetwork().to(device)	#创建模型对象,并将模型放在GPU上准备计算
print(model)

在这里插入图片描述
其中,(flatten): Flatten(start_dim=1, end_dim=-1)意为去掉dim=0对应的维度。
  调用模型(此时模式未被训练过,参数是随机的):

#随机生成一张Tensor类型的图片,并放在GPU上
X = torch.rand(1, 28, 28, device=device)
logits = model(X)
print(logits)
print('logits.shape=',logits.shape)

在这里插入图片描述
  输出结果为(1,10)向量,分别对应在10个类别上的训练结果。将结果输入softmax函数(输出层,映射到(0,1)范围内)得到每个类别的预测概率(一般输出层激活函数都放在模型内,官方文档写法仅作参考):

#1表示维度
pred_probab = nn.Softmax(dim=1)(logits)
#输出数据在不同类别上的概率
print(pred_probab)
#1表示维度,最大值的下标即为预测的类别号
y_pred = pred_probab.argmax(1)
print(f"Predicted class: {y_pred}")

在这里插入图片描述

4.3.2torch.nn.Sequential()

  nn.Sequential是一个序列容器,用于搭建神经网络的模块被按照被传入构造器的顺序添加到容器中。除此之外,一个包含神经网络模块的OrderedDict也可以被传入nn.Sequential()容器中。利用nn.Sequential()搭建好模型架构,模型前向传播时调用forward()方法,会按照顺序遍历nn.Sequential()中存储的网络模块,并以此计算输出结果,并返回最终的计算结果:
在这里插入图片描述

from torch import nn
class net(nn.Module):
    def __init__(self, in_channel, out_channel):
        super(net, self).__init__()
        self.layer = nn.Sequential(
            nn.Conv2d(1,20,5),
            nn.ReLU(),
            nn.Conv2d(20,64,5),
            nn.ReLU()
        ) 
    def forward(self, x):
        x = self.layer(x)
        return x
model=net(2,10) # 传入in_channel、out_channel
print(model)

在这里插入图片描述

  当传入OrderedDict对象时,Sequential容器会依次取出OrderedDict内每个元素的key(自定义的网络模块名)和value(网络模块),然后将其通过add_module方法添加到nn.Sequrntial()中。源码如下:

def __init__(self, *args):
        super(Sequential, self).__init__()
        if len(args) == 1 and isinstance(args[0], OrderedDict):
            for key, module in args[0].items():
                self.add_module(key, module)
        else:
            for idx, module in enumerate(args):
                self.add_module(str(idx), module)

4.3.3torch.nn.ModuleList()

  nn.ModuleList()可用于将多个模块组合成一个模块列表,之后ModuleList对象将被视为模型的一部分,并且在模型中注册为子模块,以便在模型的其他部分中使用。

import torch
import torch.nn as nn

class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layers = nn.ModuleList([
            nn.Linear(10, 20),
            nn.ReLU(),
            nn.Linear(20, 10)
        ])
    def forward(self, x):
        for layer in self.layers:
            x = layer(x)
        return x
model = MyModel()
print(model)

在这里插入图片描述

4.3.4torch.nn.ModuleDict()

  torch.nn.ModuleDict()用于将多个模块组合成一个模块字典,并按照键值对的形式进行访问。

import torch
import torch.nn as nn
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.layers = nn.ModuleDict({
            'linear1': nn.Linear(10, 20),
            'relu': nn.ReLU(),
            'linear2': nn.Linear(20, 10)
        })
    def forward(self, x):
        x = self.layers['linear1'](x)
        x = self.layers['relu'](x)
        x = self.layers['linear2'](x)
        return x
model = MyModel()
print(model)

在这里插入图片描述

4.4模型参数

4.4.1torch.nn.Parameter()

  torch.nn.Parameter()是Tensor的子类,在创建模型时若为其分配了Parameter()对象作为属性,则该对象会被自动添加到模型的参数列表当中,并在模型训练过程中不断进行更新。可通过模型参数迭代器module.parameters()进行访问,但设置的张量类型属性并不会被自动添加到模型的参数列表当中。例如:

import torch
import torch.nn
class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.W = nn.Parameter(torch.randn(10, 3))
        self.b = nn.Parameter(torch.randn(3))
        self.c = torch.Tensor((2,3))	# 不会作为参数对象在参数列表中被遍历
    
    def forward(self, x):
        return F.sigmoid(self.W @ x + self.b)
model = Net()
for name,parameters in model.named_parameters():	# 访问参数以及对应的参数名
    print(name, ':', parameters.size())

在这里插入图片描述

  事实上,在Pytorch提供的很多模块,如torch.nn.Linear()torch.nn.Cov2d()等,在模型对象被创建时都会自动初始化参数。例如:
在这里插入图片描述
  自定义参数方式为:

torch.nn.Parameter(data, requires_grad=True)
  • data:传入一个张量作为参数。
  • requires_grad:指定参数是否需要计算梯度。当设置为True时,参数会在反向传播过程中计算梯度,并且可以通过优化器进行自动更新。

  使用nn.Parameter() 创建可训练参数的一般流程如下:

  • 1.定义一个nn.Parameter() 对象,可以通过nn.Parameter(torch.randn(size)) 构造函数传入初始化的张量,其中size是参数的形状。
  • 2.将定义的nn.Parameter() 对象作为模型的成员变量,例如通过类的属性进行定义,这样在模型的前向传播和反向传播过程中可以自动识别并更新这些参数。
  • 3.在优化器中指定需要优化的参数,例如使用optim.SGD、optim.Adam等优化器的params参数,传入模型的可训练参数列表,例如model.parameters()

  常见的参数初始化方式:

class My(nn.Module):
    def __init__(self,):
        super(My, self).__init__() 
        self.x = nn.Parameter(torch.rand(4,2)) #生成0~1之间的均匀分布
        self.w = nn.Parameter(torch.randn(size=(3,1))) #生成mean=0,std=1的正太分布
        self.u = nn.Parameter(torch.randint(10, (2, 2)).float())#生成low=0,high=10的随机整数,然后变为浮点数
        self.v = nn.Parameter(torch.normal(mean=1,std=0.05,size=(5,1)))#生成mean=1,std=0.05的正太分布

4.4.2torch.nn.ParameterList()

  torch.nn.ParameterList()是 PyTorch 中的一个类,用于将多个参数(torch.nn.Parameter对象)组合成一个参数列表,并按照列表的形式进行访问。它通常用于将一组可学习的参数组织在一起,以便在模型中使用或进行优化。

import torch
import torch.nn as nn
# 定义一个模型类,其中包含多个可学习的参数并以列表形式存储
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.parameters = nn.ParameterList([
            nn.Parameter(torch.randn(10, 20)),
            nn.Parameter(torch.randn(20, 10))
        ])
    def forward(self, x):
        # 在前向传播中使用参数
        return torch.matmul(x, self.parameters[0]) + self.parameters[1]
# 创建一个模型实例
model = MyModel()
# 打印模型结构
print(model)

在这里插入图片描述

4.4.3torch.nn.ParameterDict()

  torch.nn.ParameterDict()用于将多个参数(torch.nn.Parameter对象)组合成一个参数字典,并按照键值对的形式进行访问。类似于 Python 中的字典,ParameterDict允许通过键来访问存储的参数。

import torch
import torch.nn as nn
# 定义一个模型类,其中包含多个可学习的参数,并以字典形式存储
class MyModel(nn.Module):
    def __init__(self):
        super(MyModel, self).__init__()
        self.parameters = nn.ParameterDict({
            'weight1': nn.Parameter(torch.randn(10, 20)),
            'weight2': nn.Parameter(torch.randn(20, 10))
        })
    def forward(self, x):
        # 在前向传播中使用参数
        return torch.matmul(x, self.parameters['weight1']) + self.parameters['weight2']
# 创建一个模型实例
model = MyModel()
# 打印模型结构
print(model)

在这里插入图片描述

五、自动求导

  在深度学习中,我们通常需要训练一个模型来最小化损失函数。这个过程可以通过梯度下降等优化算法来实现。梯度是函数在某一点上的变化率,可以告诉我们如何调整模型的参数以使损失函数最小化。自动求导是一种计算梯度的技术,它允许我们在定义模型时不需要手动推导梯度计算公式。PyTorch 提供了自动求导的功能,使得梯度的计算变得非常简单和高效。

5.1自动求导函数

  PyTorch中有反向传播backward()实现梯度计算与调用torch.autograd.grad()函数实现梯度计算两种方式。在计算梯度之前,需要首先明确哪个变量需要计算梯度,故在创建需要计算梯度的张量时就应当指定参数requires_grad=True
  案例:计算 y = x 2 − 2 x + 1 y=x^2-2x+1 y=x22x+1 x = 2 x=2 x=2处的梯度,易知该梯度值为2。

5.1.1backward()

  对于需要求梯度的张量需要提前指定,再给出表达变量之间依赖关系的表达式后即可通过backward()求出梯度大小:

import torch

# f(x) = x**2-2x+1的导数
x = torch.tensor(2., requires_grad=True)  #设置x需要被求导
y = torch.pow(x, 2) - 2 * x + 1

y.backward()  #计算梯度
print(x.grad)  #查看x=2时的梯度

在这里插入图片描述

5.1.2torch.autograd.grad()

  通过torch.autograd.grad()函数可自动求出张量的梯度,并且

import numpy as np
import torch

# f(x) = a*x**2 + b*x + c的导数
x = torch.tensor(2.0, requires_grad=True)  # x需要被求导
y = torch.pow(x, 2) - 2 * x + 1

# create_graph 设置为 True 将允许创建更高阶的导数
grad1 = torch.autograd.grad(y, x, create_graph=True)[0]
print('在x=2处一阶导数为:', grad1)
grad2 = torch.autograd.grad(grad1, x, create_graph=True)[0]
print('在x=2处二阶导数为:', grad2)

在这里插入图片描述

5.2计算图机制

5.2.1官方文档讲解

  在训练神经网络时,最常用的算法是反向传播。该算法根据损失函数对给定参数的梯度来调整参数(模型权重)。为了计算这些梯度,PyTorch 有一个内置的引擎torch.autograd。它支持任何计算图的梯度自动计算。考虑最简单的单层神经网络,输入x,参数wb,以及一些损失函数。它可以在 PyTorch 中以下列方式定义:

import torch

x = torch.ones(5)  # input tensor
y = torch.zeros(3)  # expected output
w = torch.randn(5, 3, requires_grad=True)	# 需被跟踪梯度
b = torch.randn(3, requires_grad=True)		# 需被跟踪梯度
z = torch.matmul(x, w)+b
#使用交叉熵函数作为损失函数,预测值为z,真实值为y
loss = torch.nn.functional.binary_cross_entropy_with_logits(z, y)

该代码定义了以下计算过程:
在这里插入图片描述
  在这个网络中,wb是参数(创建模型对象时会被赋予随机的初值),需要在模型训练过程中对它们进行优化,这一过程需要能够计算损失函数(交叉熵函数)相对于这些变量的梯度。为此,设置了这些张量的requires_grad=True属性,意为追踪该参数的梯度值。即使在Tensor初始化时并未指定,也可通过以下函数指定:

w.requires_grrad_(True)

  为记录张量梯度,Pytorch会根据前向传播的过程自动生成计算图,这一计算图是一个有向无环图(DAG)。该图中记录了所有的数据(张量对象)和执行的计算操作。在前向传播中,自动求导机制主要进行两步操作:

  • 1.运行对应的前向传播操作来计算结果张量。
  • 2.在DAG中保留运算操作对应的梯度函数。

当从输出结果开始进行反向传播时(调用backward()函数时),自动求导机制会进行:

  • 1.计算每个被追踪Tensor对象的属性grad_fn的梯度。
  • 2.将累加到相应的张量的.grad属性中。
  • 3.利用链式规则,一直传播到叶张量。

5.2.2计算图与梯度计算

  从官方文档中可知,PyTorch 会根据计算过程来自动生成动态图(DAG),比如,前向传播时输入的张量经过加、减、乘、除得到输出张量,那么计算图就会记录输入输出张量、加减乘除运算和一些中间变量。之后就可以根据动态图的创建过程进行反向传播,计算到每个节点的梯度值。当反向传播完成,计算图默认会被清除,即进行前向传播时记录的计算过程会被释放掉。
  有向无环图由两部分构成:

  • 运算(Operation)节点:非叶子节点,包括加减乘除、开方、幂指对、三角函数等可求导运算。表现为反向传播结束之后,非叶子节点的梯度会被释放掉。如果想要保留非叶子节点的梯度,可以使用retain_grad()方法。
  • 数据(Tensor)节点:叶子节点,由用户指定requires_grad=True的节点。表现为反向传播结束之后,叶子节点的梯度不会被释放掉。

  Tensor对象相关属性如下:

  • requires_grad:查看是否需跟踪梯度值。
  • grad_fn:查看当前Tensor对象生成时使用的运算。
  • is_leaf:查看是否是叶子节点。
  • grad:查看梯度值。

其中,对于需要求梯度值的Tensor对象在创建时会指定requires_grad=True,也可使用requires_grad_()函数进行指定:

x = torch.tensor(1.).requires_grad_() # requires_grad=True

  例如, y = ( x + w ) ∗ ( w + 1 ) y=(x+w)*(w+1) y=(x+w)(w+1)的有向无环图表示为:
在这里插入图片描述

  • 叶子节点: x 、 w x、w xw
  • 非叶子节点: a 、 b 、 y a、b、y aby

其中, a = x + w 、 b = w + 1 a=x+w、b=w+1 a=x+wb=w+1 y = a ∗ b y=a*b y=ab。采用计算图来描述运算不仅让运算更加简洁,也同样使梯度求导更加方便。例如,求 y y y w w w的偏导数:
∂ y ∂ w = ​ ∂ y ∂ a ∂ a ∂ w + ∂ y ∂ b ∂ b ∂ w = b ∗ 1 + a ∗ 1 = b + a = ( w + 1 ) + ( x + w ) = 2 w + x + 1 \frac{∂y}{∂w}=​\frac{∂y}{∂a}\frac{∂a}{∂w}+\frac{∂y}{∂b}\frac{∂b}{∂w}=b*1+a*1=b+a=(w+1)+(x+w)\\ =2w+x+1 wy=aywa+bywb=b1+a1=b+a=(w+1)+(x+w)=2w+x+1
假设要求的是 ∂ y ∂ w ∣ ( x = 2 , w = 1 ) \frac{∂y}{∂w}|_{(x=2,w=1)} wy(x=2,w=1),可计算图模拟求该偏导数的过程:
在这里插入图片描述
可见,只需找到 y y y w w w的所有路径,并将路径上的导数求和即可。最终得到结果 ∂ y ∂ w ∣ ( x = 2 , w = 1 ) = 5 \frac{∂y}{∂w}|_{(x=2,w=1)}=5 wy(x=2,w=1)=5
  使用代码模拟这一过程:

import torch
w = torch.tensor([1.], requires_grad=True)  #由于需要计算梯度,所以requires_grad设置为True
x = torch.tensor([2.], requires_grad=True)  #由于需要计算梯度,所以requires_grad设置为True

a = torch.add(w, x)     # a = w + x
b = torch.add(w, 1)     # b = w + 1
y = torch.mul(a, b)     # y = a * b

y.backward()    #对y进行反向传播
print(w.grad)   #输出w的梯度

在这里插入图片描述
  叶子节点是整个计算图的根基,例如前面求导的计算图,在前向传导中的a、b和y都要依据创建的叶子节点x和w进行计算的。同样,在反向传播过程中,所有梯度的计算都要依赖叶子节点。设置叶子节点主要是为了节省内存,在梯度反向传播结束之后,非叶子节点的梯度都会被释放掉。在张量对象中有属性is_leaf用于判断是否是叶子节点:

#查看叶子结点
dict={'w':w,'x':x,'a':a,'b':b,y':y}
for key,value in dict.items():
    if value.is_leaf==True:
        print(key,'是叶子节点')

#查看梯度(非叶子节点的梯度会被自动释放,值为None)
for key,value in dict.items():
    if value.grad!=None:
        print(key,'.grad=',value.grad)

在这里插入图片描述
  而若想要使用非叶子节点的梯度,可使用retain_grad()保留梯度:

import torch

w = torch.tensor([1.], requires_grad=True)  #由于需要计算梯度,所以requires_grad设置为True
x = torch.tensor([2.], requires_grad=True)  #由于需要计算梯度,所以requires_grad设置为True

a = torch.add(w, x)     # a = w + x
a.retain_grad()
b = torch.add(w, 1)     # b = w + 1
y = torch.mul(a, b)     # y = a * b

y.backward()    #对y进行反向传播
#查看叶子结点
dict={'w':w,'x':x,'a':a,'b':b,y':y}
for key,value in dict.items():
    if value.is_leaf==True:
        print(key,'是叶子节点')
#查看梯度
for key,value in dict.items():
    if value.grad!=None:
        print(key,'.grad=',value.grad)

在这里插入图片描述
  Tensor对象还有属性grad_fn,用于记录创建该张量时所用的方法(函数)。例如在上面提到的例子中,y.grad_fn会记录 y y y是由 a 、 b a、b ab相乘得到,故在求解a和b的梯度的时候就会用到乘法的求导法则去求解a和b的梯度。同理,由于a和b是通过加法得到的,反向传播时也会按照加法的求导法则求解梯度。

dict={'w':w,'x':x,'a':a,'b':b,'y':y}
for key,value in dict.items():
    print(key,'.grad_fn=',value.grad_fn)

在这里插入图片描述
  可以看到w和x的grad_fn都是None,因为w和x都是用户创建的,没有通过任何方法任何函数去生成这两个张量,所以两个叶子节点的属性为None,这些属性都是在梯度求导中用到的。

5.2.3梯度清空grad.zero_()

  在PyTorch中,通常在执行反向传播(调用.backward())之后,需要清空节点的梯度。这是因为在神经网络训练过程中,每次迭代都会计算梯度。如果不清空梯度,那么在下一次迭代时,新的梯度会与之前的梯度累加。这会导致梯度值非常大,从而使得模型参数更新过大,影响模型的收敛。示例代码:

for inputs, targets in dataloader:
    optimizer.zero_grad()  # 清空梯度
    outputs = model(inputs)
    loss = loss_function(outputs, targets)
    loss.backward()  # 反向传播,计算梯度
    optimizer.step()  # 更新模型参数

5.2.4反向传播模拟模型训练过程

  此处真实函数为 y = 3 x + 0.8 y=3x+0.8 y=3x+0.8,模型训练过程如下:

  • 1.准备样本数据,以及样本数据对应的真实值。
  • 2.定义模型参数 w 、 b w、b wb,设置随机的初始值。
  • 3.开始训练:
    • (1)计算当前参数 w 、 b w、b wb对样本数据的预测值,并求出对应的损失函数值(此处使用均方误差函数)。
    • (2)利用反向传播求出损失函数对参数 w 、 b w、b wb的偏导数。
    • (3)利用偏导数更新参数 w 、 b w、b wb,重新开始执行(1)。
import torch
 
# 1.准备数据
# y=3x+0.8
learning_rate = 0.01    #定义学习率
x = torch.rand([500,1])  #准备500个随机数据
y_true = x*3 + 0.8  #定义真实模型
 
#2.定义w和b,并随机生成初始值
w = torch.rand([1,1],requires_grad = True)
b = torch.tensor(0,requires_grad = True,dtype = torch.float32)

 
#3.通过循环,反向传播,更新参数
for i in range(5000):
    #4.通过模型计算y_predict
    y_predict = torch.matmul(x,w) + b
 
    #5.计算损失函数(均方误差函数)
    loss = (y_true-y_predict).pow(2).mean()
    
    if w.grad is not None:  #梯度清零
        w.grad.data.zero_()
    if b.grad is not None:  #梯度清零
        b.grad.data.zero_()
        
    loss.backward() #反向传播,并获取梯度值以更新参数
    w.data = w.data - learning_rate*w.grad
    b.data = b.data - learning_rate*b.grad
    #每1000轮输出一次参数更新结果
    if i%1000 == 0:
        print("w,b,loss",w.item(),b.item(),loss.item())

可见,最终 w 、 b w、b wb逼近于真实值。需要注意的是,在清零梯度前需判断if w.grad is not None,因为系统初始化参数时,其grad值初始化为None,若直接执行w.grad.data.zero_(),则会报错、

六、优化器

  在深度学习中,我们通常会使用优化算法来调整神经网络的权重和偏差,以便模型能够更好地拟合训练数据,如,在上文中使用梯度下降法更新模型参数。torch.optimPyTorch中的一个模块,它提供了各种优化算法的实现,用于自动化地优化神经网络的参数。而优化算法,是用于训练神经网络的核心工具之一。这些算法的主要目标是找到使损失函数最小化的参数值,从而使模型能够更好地拟合数据。损失函数是一个衡量模型预测与实际值之间差距的函数,我们的目标是最小化这个差距。torch.optim实现了许多常用的优化算法,包括随机梯度下降(SGD)、Adam、RMSprop等。每种算法都有其独特的特点和适用场景,我们可以根据任务需求选择合适的优化算法。
  在模型训练过程中,在损失函数中会得到一个loss值,即模型输出与真实值之间的差异。pytorch中的自动求导autograd模块会求出模型当中的参数的梯度grad,优化器拿到梯度grad后,进行一系列的优化策略去更新模型参数,使得loss值不断下降。相关概念如下:

  • 导数:函数在指定坐标轴上的变化率。
  • 方向导数:指定方向上的变化率。
  • 梯度:一个向量,方向为方向导数取得最大值的方向。
  • 梯度下降:朝着梯度下降的负方向去变化,下降是最快的。

6.1官方文档讲解

1.定义神经网络模型

import torch
from torch import nn
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision.transforms import ToTensor

# 下载数据集
training_data = datasets.FashionMNIST(
    root="data",
    train=True,
    download=True,
    transform=ToTensor()
)

test_data = datasets.FashionMNIST(
    root="data",
    train=False,
    download=True,
    transform=ToTensor()
)

# 加载数据集
train_dataloader = DataLoader(training_data, batch_size=64)
test_dataloader = DataLoader(test_data, batch_size=64)

# 定义神经网络模型
class NeuralNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        self.flatten = nn.Flatten()
        self.linear_relu_stack = nn.Sequential(
            nn.Linear(28*28, 512),
            nn.ReLU(),
            nn.Linear(512, 512),
            nn.ReLU(),
            nn.Linear(512, 10),
        )

    def forward(self, x):
        x = self.flatten(x)
        logits = self.linear_relu_stack(x)
        return logits

model = NeuralNetwork()
# 查看模型参数(仅Linear有参数)
for name,parameter in model.named_parameters():
    print(name,':',parameter.data.shape,'requires_grad=',parameter.requires_grad)

在这里插入图片描述
2.设置超参数

# 设置学习率
learning_rate = 1e-3
# 设置批大小(模型加载器中已设置)
batch_size = 64
# 设置轮次
epochs = 5

3.选择优化算法
  在设置超参数后,就可通过一个优化循环来训练和优化模型,优化循环的每一次迭代成为一个epoch(轮)。每一轮主要包含两种操作:

  • 训练集循环:在训练数据集上迭代,并尝试收敛到最优参数。
  • 测试集循环:迭代测试数据集以检查模型性能是否有所改进。

  当给出一些训练数据时,我们未经训练的网络可能不会给出正确的答案。损失函数度量得到的结果与目标值的差异程度,而训练的目的就是不断更新参数,使得损失函数值最小化。常见的损失函数包括用于回归任务的nn.MSEloss(均方误差)和用于分类的nn.NLLloss(负对数似然)。初始化损失函数:

loss_fn = nn.CrossEntropyLoss()

  优化,是在每个训练步骤中调整模型参数以减小模型误差的过程,而优化算法定义了这个过程是如何执行的(官方文档中使用了随机梯度下降)。Pytorch将优化算法的优化逻辑都封装在优化器对象中,常见的优化器模块包括ADAMRMSProp,它们可以更好地适用于不同类型的模型和数据。本例中使用SGD优化器,在初始化优化器时需注册模型的参数,并传入超参数:

optimizer = torch.optim.SGD(model.parameters(), lr=learning_rate)

在训练集循环中,优化分为三个步骤:

  • 1.调用Optimizer.zero _ grad ()来重置模型参数的梯度。在默认情况下,梯度是累加的,为了防止重复计数,我们在每次迭代时显式地为它们取零。
  • 2.通过调用loss.backward()反向传播获取参数梯度值。
  • 3.获取到梯度后,调用Optimizer.step()来调整参数。

实现训练集循环、测试集循环代码:


6.2torch.optim.Optimizer

6.2.1常用功能函数

  torch.optim模块是PyTorch中用于实现优化算法的组件,主要用于训练神经网络和其他机器学习模型。而torch.optim.Optimizer是所有优化器的父类,其定义了优化器的基本行为和接口,常见的优化器如,torch.optim.SGD, torch.optim.Adam, torch.optim.AdamW, torch.optim.RMSprop都是其子类。构造函数:

optimizer = torch.optim.Optimizer(params, **defaults)
  • params:包含张量的列表或生成器,代表模型中的可训练参数。
  • defaults:一系列关键字参数,用来设置优化器的具体超参数。
函数名功能
zero_grad()清零所有优化参数上的梯度,为下一轮前向传播与反向传播做准备。
step()更新参数。
add_param_group()添加额外参数组,每个参数组可以有不同的超参数设置,如学习率等。
state_dict()获取优化器当前状态信息字典,优化器维护了一个内部状态,其中包括参数的状态以及优化器自身的状态(例如学习率)。
load_state_dict()加载状态信息字典。

  Optimizer的核心功能在于step()方法,通常在前向传播后计算完损失函数的梯度后调用,该方法会遍历模型的所有参数,并根据相应的优化策略应用梯度更新。不同的优化器有不同的参数更新规则。例如:

  • SGD:简单地将梯度乘以学习率后累加到参数上。
  • Adam:结合了指数移动平均的梯度和二阶矩,同时对学习率进行动态调整。
属性名功能
defaults优化器的超参数。
state参数的缓存
param_groups管理的参数组,是由字典对象构成的列表,键为参数名,其中包含了普通参数、超参数。
_step_count记录更新次数,以便于调整训练策略,例如每更新100次参数,对学习率调整。

导入包:

import torch
from torch import nn
from torch.utils.data import DataLoader
from torch.optim import Optimizer

1.初始化Optimizer对象

# 1.构建可学习参数和学习率
weight=torch.randn((2,2),requires_grad=True)
weight.grad=torch.ones((2,2))    #初始化参数值为全1
optimizer = torch.optim.SGD([weight], lr=0.1)    #传入weight交于优化器管理,设置学习率为0.1

2.step()函数

# 2.梯度下降更新参数
print('weight更新前为:',weight.data)
optimizer.step()
print('weight更新后为:',weight.data)

在这里插入图片描述
3.zero_grad()函数

# 3.梯度清零,避免累加
print('weight.grad更新前为:',weight.grad)
optimizer.zero_grad()
print('weight.grad更新后为:',weight.grad)

在这里插入图片描述
4.add_param_group函数

# 4.增加参数组
## 查看当前参数组(包括普通参数、超参数)
for dict in optimizer.param_groups:
    for key,value in dict.items():
        print(key,":",value)
print("------------------------------------------")
bias=torch.randn((3, 3), requires_grad=True)
optimizer.add_param_group({"params": bias, 'lr': 0.0001})
for dict in optimizer.param_groups:
    for key,value in dict.items():
        print(key,":",value)

在这里插入图片描述
5.state_dict()函数

optimizer = torch.optim.SGD([weight], lr=0.1, momentum=0.9)
opt_state_dict = optimizer.state_dict()
 
print("迭代更新前的状态信息:")
for key,value in opt_state_dict.items():
    print(key,":",value)
for i in range(10):
    optimizer.step()
print("------------------------------------------")
print("迭代更新后的状态信息:", optimizer.state_dict())
for key,value in opt_state_dict.items():
    print(key,":",value)

在这里插入图片描述

6.2.2实战:拟合线性函数

  假设 x = 4 x = 4 x=4为起点,求 y = ( x − 5 ) 2 y = (x-5)^{2} y=(x5)2的最小值点:

 
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optim
 
x_values=[i for i in range(11)]
x_train=np.array(x_values,dtype=np.float32)
x_train=x_train.reshape(-1,1)
 
y_values=[2*i +1 for i in x_values]
y_values=np.array(y_values,dtype=np.float32)
y_train=y_values.reshape(-1,1)
 
#这里线性回归就相当于不加激活函数的全连接层
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1)
 
    def forward(self, x):
        return self.linear(x)
 
 
#使用GPU训练
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
 
# 创建模型实例和优化器
model = LinearRegression()
model.to(device)
optimizer = optim.SGD(model.parameters(), lr=0.01)
 
# 定义损失函数
criterion = nn.MSELoss()
for epoch in range(100):
    # 创建数据集
    inputs = torch.from_numpy(x_train).to(device)
    targets = torch.from_numpy(y_train).to(device)
    # 前向传播
    outputs = model(inputs)
    loss = criterion(outputs, targets)
 
    # 反向传播和优化器更新
    #梯度清零每一次迭代
    optimizer.zero_grad()
    #反向传播
    loss.backward()
    #更新权重参数
    optimizer.step()
    #每10轮,打印一下损失函数
    if epoch%10==0:
        print("epoch {}, loss {}".format(epoch,loss.item()))

#使用训练完的模型进行数据的预测
predicted=model(torch.from_numpy(x_train).to(device))
print(predicted)
print(targets)

在这里插入图片描述

七、模型的保存与加载

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值