Pytorch基础
文章目录
- 零、tensorboard
- 0.1基本使用案例
- 一、数据结构:Tensor
- 1.1数据类型
- 1.2Tensor的创建方式
- 1.3张量的基本运算
- 1.4张量的属性
- 二、数据集加载器DataLoaders
- 2.0前置知识
- 2.0.1torch.scatter()、torch.scatter_()
- 2.1官方案例
- 2.1.1从TorchVision加载数据集
- 2.1.2迭代和可视化数据集
- 2.2创建自定义数据集Dataset
- 2.2.1官方文档
- 2.2.2自定义数据集
- 2.3数据集加载器DataLoader
- 2.3.1函数介绍
- 2.3.2读取数据
- 2.3.2可视化drop_last作用
- 2.3.3可视化shuffle作用
- 三、Transforms
- 3.1transforms.ToTensor()
- 3.2transforms.Normalize()
- 3.3transfoms.Resize()
- 3.4transforms.Compose()
- 四、构建神经网络
- 4.1NCHW与NHWC数据格式
- 4.2常用模块函数
- 4.2.1卷积层常用模块:torch.nn.CovXxx()
- 4.2.2池化层常用模块:torch.nn.MaxXxx()、torch.nn.MaxUnXxx()、torch.nn.AvgXxx()、torch.nn.AdaptiveXxx()
- 4.2.3常用激活函数模块
- 4.2.4常用循环层模块
- 4.2.5全连接层模块nn.Linear()
- 4.3容器类
- 4.3.1torch.nn.Module
- 4.3.2torch.nn.Sequential()
- 4.3.3torch.nn.ModuleList()
- 4.3.4torch.nn.ModuleDict()
- 4.4模型参数
- 4.4.1torch.nn.Parameter()
- 4.4.2torch.nn.ParameterList()
- 4.4.3torch.nn.ParameterDict()
- 五、自动求导
- 5.1自动求导函数
- 5.1.1backward()
- 5.1.2torch.autograd.grad()
- 5.2计算图机制
- 5.2.1官方文档讲解
- 5.2.2计算图与梯度计算
- 5.2.3梯度清空grad.zero_()
- 5.2.4反向传播模拟模型训练过程
- 六、优化器
- 6.1官方文档讲解
- 6.2torch.optim.Optimizer
- 6.2.1常用功能函数
- 6.2.2实战:拟合线性函数
- 七、模型的保存与加载
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 ,这样自动求导时才会对该张量求梯度。神经网络层中的权值w 的tensor 的requires_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, Lambdatrain_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])#关闭axisplt.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,Datasetclass CustomImageDataset(Dataset):# 传入标签目录、数据目录、transform、target_transformdef __init__(self, annotations_file, img_dir, transform=None, target_transform=None):self.img_labels = pd.read_csv(annotations_file)self.img_dir = img_dirself.transform = transformself.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时传入的transform
和target_transform
。__len__
:返回数据集中样本的个数。__getitem__
:加载给定索引处的样本(包括图片、标签),根据索引识别样本在磁盘上的位置,若含有transform
和target_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 = dataprint(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 = datawriter.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 = datawriter.add_images("loader",imgs,step)step+=1
writer.close()
此时不再含有最后的16张图片。
2.3.3可视化shuffle作用
每一轮epoch之后就是分配完了一次数据,而shuffle决定了是否在新一轮epoch开始时打乱所有图片的属性进行分配。
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 = 0for data in test_dataloader:imgs, targets = datawriter.add_images("Epoch:{}".format(epoch), imgs, step)step += 1writer.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] [0,255]放缩到 [ 0 , 1 ] [0,1] [0,1],本质是除以 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的数据,这里为1nb_samples += N#获取同一batch的均值和标准差channel_mean /= nb_sampleschannel_std /= nb_samplesreturn 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() | 对输入数据使用三维转置卷积 |
跳转至别的博客进行查看:
torch.nn.Conv2d()
:Conv2d()
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.Sigmoid | Sigmoid激活函数 |
torch.nn.Tanh | Tanh激活函数 |
torch.nn.ReLU | ReLU激活函数 |
torch.nn.Softplus | Softplus激活函数 |
以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 torchmodel = 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 X0、X1、X2分别表示输入的三个行向量, W 1 、 W 2 、 W 3 、 . . . . . . 、 W 9 W_1、W_2、W_3、...... 、W_9 W1、W2、W3、......、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的初始值。所以实例化之后,我们就可以调用属性weight
和bias
来查看生成的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, transformsdevice = ("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 nnclass 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=x2−2x+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 + 1y.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
,参数w
和b
,以及一些损失函数。它可以在 PyTorch 中以下列方式定义:
import torchx = 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)
该代码定义了以下计算过程:
在这个网络中,w
和b
是参数(创建模型对象时会被赋予随机的初值),需要在模型训练过程中对它们进行优化,这一过程需要能够计算损失函数(交叉熵函数)相对于这些变量的梯度。为此,设置了这些张量的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 x、w。
- 非叶子节点: a 、 b 、 y a、b、y a、b、y。
其中, a = x + w 、 b = w + 1 a=x+w、b=w+1 a=x+w、b=w+1, y = a ∗ b y=a*b y=a∗b。采用计算图来描述运算不仅让运算更加简洁,也同样使梯度求导更加方便。例如,求 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 ∂w∂y=∂a∂y∂w∂a+∂b∂y∂w∂b=b∗1+a∗1=b+a=(w+1)+(x+w)=2w+x+1
假设要求的是 ∂ y ∂ w ∣ ( x = 2 , w = 1 ) \frac{∂y}{∂w}|_{(x=2,w=1)} ∂w∂y∣(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 ∂w∂y∣(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设置为Truea = torch.add(w, x) # a = w + x
b = torch.add(w, 1) # b = w + 1
y = torch.mul(a, b) # y = a * by.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 torchw = torch.tensor([1.], requires_grad=True) #由于需要计算梯度,所以requires_grad设置为True
x = torch.tensor([2.], requires_grad=True) #由于需要计算梯度,所以requires_grad设置为Truea = 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 * by.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 a、b相乘得到,故在求解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 w、b,设置随机的初始值。
- 3.开始训练:
- (1)计算当前参数 w 、 b w、b w、b对样本数据的预测值,并求出对应的损失函数值(此处使用均方误差函数)。
- (2)利用反向传播求出损失函数对参数 w 、 b w、b w、b的偏导数。
- (3)利用偏导数更新参数 w 、 b w、b w、b,重新开始执行(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_predicty_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.gradb.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 w、b逼近于真实值。需要注意的是,在清零梯度前需判断if w.grad is not None
,因为系统初始化参数时,其grad值初始化为None,若直接执行w.grad.data.zero_()
,则会报错、
六、优化器
在深度学习中,我们通常会使用优化算法来调整神经网络的权重和偏差,以便模型能够更好地拟合训练数据,如,在上文中使用梯度下降法更新模型参数。torch.optim
是PyTorch
中的一个模块,它提供了各种优化算法的实现,用于自动化地优化神经网络的参数。而优化算法,是用于训练神经网络的核心工具之一。这些算法的主要目标是找到使损失函数最小化的参数值,从而使模型能够更好地拟合数据。损失函数是一个衡量模型预测与实际值之间差距的函数,我们的目标是最小化这个差距。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 logitsmodel = 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将优化算法的优化逻辑都封装在优化器对象中,常见的优化器模块包括ADAM
、RMSProp
,它们可以更好地适用于不同类型的模型和数据。本例中使用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=(x−5)2的最小值点:
import torch
import numpy as np
import torch.nn as nn
import torch.optim as optimx_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)
七、模型的保存与加载
相关文章:

Pytorch基础
文章目录 零、tensorboard0.1基本使用案例 一、数据结构:Tensor1.1数据类型1.2Tensor的创建方式1.3张量的基本运算1.4张量的属性 二、数据集加载器DataLoaders2.0前置知识2.0.1torch.scatter()、torch.scatter_() 2.1官方案例2.1.1从TorchVision加载数据集2.1.2迭代…...

嵌入技术Embedding
嵌入(Embedding)是一种将高维数据映射到低维空间的技术,广泛应用于自然语言处理(NLP)、计算机视觉和推荐系统等领域。嵌入技术的核心思想是将复杂的数据表示为低维向量,使其在这个低维空间中保留尽可能多的…...

Pandas中的数据转换[细节]
今天我们看一下Pandas中的数据转换,话不多说直接开始🎇 目录 一、⭐️apply函数应用 apply是一个自由度很高的函数 对于Series,它可以迭代每一列的值操作: 二、⭐️矢量化字符串 为什么要用str属性 替换和分割 提取子串 …...

vue2面试题——路由
1. 路由的模式和区别 路由的模式:history,hash 区别: 1. 表象不同 history路由:以/为结尾,localhost:8080——>localhost:8080/about hash路由:会多个#,localhost:8080/#/——>localhost:…...

【AI应用探讨】—朴素贝叶斯应用场景
目录 文本分类 推荐系统 信息检索 生物信息学 金融领域 医疗诊断 其他领域 文本分类 垃圾邮件过滤:朴素贝叶斯被广泛用于垃圾邮件过滤任务,通过邮件中的文本内容来识别是否为垃圾邮件。例如,它可以基于邮件中出现的单词或短语的概率来…...

使用matlab的大坑,复数向量转置!!!!!变量区“转置变量“功能(共轭转置)、矩阵转置(默认也是共轭转置)、点转置
近期用verilog去做FFT相关的项目,需要用到matlab进行仿真然后和verilog出来的结果来做对比,然后计算误差。近期使用matlab犯了一个错误,极大的拖慢了项目进展,给我人都整emo了,因为怎么做仿真结果都不对,还…...

昇思25天学习打卡营第8天|保存与加载
1. 学习内容复盘 1.1 保存与加载 上一章节主要介绍了如何调整超参数,并进行网络模型训练。在训练网络模型的过程中,实际上我们希望保存中间和最后的结果,用于微调(fine-tune)和后续的模型推理与部署,本章…...

【vueUse库Animation模块各函数简介及使用方法】
vueUse库是一个专门为Vue打造的工具库,提供了丰富的功能,包括监听页面元素的各种行为以及调用浏览器提供的各种能力等。其中的Browser模块包含了一些实用的函数,以下是这些函数的简介和使用方法: vueUse库Sensors模块各函数简介及使用方法 vueUseAnimation函数1. useInter…...

汇川H5u小型PLC作modbusRTU从站设置及测试
目录 新建工程COM通讯参数配置协议选择协议配置 查看手册Modbus地址对应关系仿真测试 新建工程 新建一个H5U工程,不使用临时工程 系列选择H5U即可 COM通讯参数配置 协议选择 选择ModbusRTU从站 协议配置 端口号默认不可选择 波特率这里使用9600 数据长度&…...

基于Java的多元化智能选课系统-计算机毕业设计源码040909
摘 要 多元化智能选课系统使用Java语言的Springboot框架,采用MVVM模式进行开发,数据方面主要采用的是微软的Mysql关系型数据库来作为数据存储媒介,配合前台技术完成系统的开发。 论文主要论述了如何使用JAVA语言开发一个多元化智能选课系统&a…...

idea使用maven打包报错GBK不可映射字符
方法一:设置环境变量 打开“控制面板” > “系统和安全” > “系统”。点击“高级系统设置”。在“系统属性”窗口中,点击“环境变量”。在“系统变量”部分,点击“新建”,创建一个新的变量: 变量名:…...

解决Linux系统Root不能远程SSH登录
问题描述 在使用Linux主机或者开发板的时候远程SSH一直登录不上Root账户,只能登录其他账户。 问题解决 使用文本编辑器修改SSH的配置文件sshd_config。这个文件通常位于/etc/ssh/目录下。 sudo nano /etc/ssh/sshd_config在sshd_config文件中,找到Pe…...

【java】【控制台】【javaSE】 初级java家教管理系统控制台命令行程序项目
更多项目点击👆👆👆完整项目成品专栏 【java】【控制台】【javaSE】 初级java家教管理系统控制台命令行程序项目 获取源码方式项目说明:功能点数据库涉及到: 项目文件包含:项目运行环境 :截图其…...

(2024)豆瓣电影TOP250爬虫详细讲解和代码
(2024)豆瓣电影TOP250爬虫详细讲解和代码 爬虫目的 获取 https://movie.douban.com/top250 电影列表的所有电影的属性。并存储起来。说起来很简单就两步。 第一步爬取数据第二步存储 爬虫思路 总体流程图 由于是分页的,要先观察分页的规…...

am62x芯片安全类型确认(HS-SE, HS-FS or GP)
文章目录 芯片安全类型设置启动方式获取串口信息下载脚本运行脚本示例sk-am62x板卡参考芯片安全类型 AM62x 芯片有三个安全级别。 • GP:通用版本 • HS-FS:高安全性 - 现场安全型 • HS-SE:高安全性 - 强制安全型 在SD卡启动文件中,可以查看到, 但板上的芯片,到底是那…...

高通安卓12-在源码中查找应用的方法
1.通过搜索命令查找app 一般情况下,UI上看到的APP名称会在xml文件里面定义出来,如 搜索名字为WiGig的一个APP 执行命令 sgrep "WiGig" 2>&1|tee 1.log 将所有的搜索到的内容打印到log里面 Log里面会有一段内容 在它的前面是这段内…...

民用无人驾驶航空器运营合格证怎么申请
随着科技的飞速发展,无人机已经从遥不可及的高科技产品飞入了寻常百姓家。越来越多的人想要亲自操纵无人机,探索更广阔的天空。但是,飞行无人机可不是简单的事情,你需要先获得无人机许可证,也就是今天所要讲的叫民用无…...

[SD必备知识18]修图扩图AI神器:ComfyUI+Krita加速修手抽卡,告别低效抽卡还原光滑细腻双手,写真无需隐藏手势
🌹大家好!我是安琪!感谢大家的支持与鼓励。 krita-ai-diffusion简介 在AIGC图像生成领域的迅猛发展下,当前的AI绘图工具如Midjourney、Stable Diffusion都能够近乎完美的生成逼真富有艺术视觉效果的图像质量。然而,针…...

4.Spring Context 装载过程源码分析
Spring的ApplicationContext是Spring框架中的核心接口之一,它扩展了BeanFactory接口,提供了更多的高级特性,如事件发布、国际化支持、资源访问等。ApplicationContext的装载过程是Spring框架中非常重要的一个环节。以下是ApplicationContext装…...

mysql之数据存储单元
简介 在MySQL中,单行数据存储单元的大小并不是固定的,它取决于多种因素,如表结构中使用的数据类型以及所使用的存储引擎。 但是我们可以提供一些关于MySQL中典型行数据存储单元大小的一般性指引: 存储引擎 InnoDB(默认存储引擎) InnoDB中单行数据存储单元的大小通常在8-16…...

未来20年人工智能将如何塑造社会
照片由Brian McGowan在Unsplash上拍摄 更多资讯,请访问 2img.ai “人工智能会成为我们的救星还是我们的末日?” 几十年来,这个问题一直困扰着哲学家、科学家和科幻爱好者。 当我们踏上技术革命的边缘时,是时候透过水晶球&#x…...

Maven的依赖传递、依赖管理、依赖作用域
在Maven项目中通常会引入大量依赖,但依赖管理不当,会造成版本混乱冲突或者目标包臃肿。因此,我们以SpringBoot为例,从三方面探索依赖的使用规则。 1、 依赖传递 依赖是会传递的,依赖的依赖也会连带引入。例如在项目中…...

ArcGIS定义1.5度带坐标系与投影转换
点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 对于ArcGIS如何定义高斯克吕格3度带、6度带,我相信大部分人都是比较清楚的࿰…...

艺术与科技的精湛融合:探讨AI绘画与AI动画的交汇点
前言 艺术与科技的精湛融合:探讨AI绘画与AI动画的交汇点 在当代社会中,艺术和科技的结合呈现出了从来灭有的创新和可能性。随着人工智能技术的不断发展,AI绘画与AI动画的融合愈发引人瞩目。这一融合不仅给艺术家们带来了更多创作的可能&…...

【移动应用开发期末复习】第五/六章
系列文章 第一章——Android平台概述 第一章例题 第二章——Android开发环境 第二章例题 第三章 第三章例题 第四章 系列文章界面布局设计线性布局表格布局帧布局相对布局约束布局控制视图界面的其他方法代码控制视图界面数据存储与共享首选项信息数据文件SQLite数据库Content…...

excel FORMULA
在Excel中,FORMULA 实际上是一个拼写错误。您可能是指 FORMULA 的正确拼写 FORMULA(这在Excel中不是有效的函数或关键字),但更可能是您想要讨论的是FORMULA(公式)的创建或使用。 在Excel中,您可…...

【学习】开发板接口
工作用到机器的开发板 有如上三个接口 。最右是仿真器,中间是RS232串口,最左是电源线 仿真器 这个是仿真器 接入机器那端用的是SWD模式,另一端通过USB接电脑(这小肥手拍的怪好看)仿真口连接了四条线分别是 VCC&#…...

主干网络篇 | YOLOv5/v7 更换骨干网络之 EfficientNet | 卷积神经网络模型缩放的再思考
主干网络篇 | YOLOv5/v7 更换骨干网络之 EfficientNet | 卷积神经网络模型缩放的再思考 1. 简介 近年来,深度卷积神经网络(CNN)在图像识别、目标检测等领域取得了巨大进展。然而,随着模型复杂度的不断提升,模型训练和…...

如何测试Java应用的性能?
如何测试Java应用的性能? 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 在开发Java应用程序的过程中,性能测试是一个不可忽视的重要环…...

css 动画
当涉及到CSS动画时,有几种方式可以实现动画效果。以下是一些常见的CSS动画技术: 使用keyframes规则:keyframes规则允许你创建一个动画序列,定义动画的关键帧和属性值。例如,你可以创建一个旋转动画,让一个…...