- 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
- 🍖 原作者:K同学啊 | 接辅导、项目定制
- 🚀 文章来源:K同学的学习圈子
一、前期准备
1.设置GPU
代码知识点
torch.device(...):
这是PyTorch中的一个函数,用于创建一个新的设备对象。这个对象可以用于指定张量(tensor)应该在哪个设备上进行计算。
Torchvision
是PyTorch的一个子库,提供了丰富的功能以支持计算机视觉任务。主要可以划分为以下四个部分:
torchvision.datasets
: 提供常用的数据集,如MNIST、ImageNet、CIFAR-10等,并且设计上继承自torch.utils.data.Dataset
接口。
torchvision.models
: 提供深度学习中各种经典的网络结构以及预训练好的模型,例如Alex-Net、VGG、ResNet、Inception等。
torchvision.transforms
: 提供图像转换的功能,包括常见的图像增强方法,如裁剪、旋转、翻转等。
torchvision.utils
: 提供一些实用的工具函数,例如保存和加载模型、可视化数据等。
import torch
import torch.nn as nn
import matplotlib.pyplot as plt
import torchvision
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device
输出
device(type=‘cuda’)
2.导入数据
代码知识点
'data':
这是数据集下载和保存的目录路径。在这个例子中,数据集将被下载到名为"data"的文件夹中。
train=True:
这个参数指定了我们想要加载的是训练数据集。如果设置为False,则加载的是测试数据集。
transform=torchvision.transforms.ToTensor():
这个参数指定了在加载数据集之前对图像进行转换的操作。在这里,使用torchvision.transforms.ToTensor()将图像转换为张量(tensor)。
download=True:
这个参数指定了是否自动下载数据集。如果设置为True,则会检查是否存在已下载的数据集,如果不存在,则会从互联网上下载并保存到指定的目录中。
train_ds = torchvision.datasets.MNIST('data',
train = True,
transform=torchvision.transforms.ToTensor(),
download = True)
test_ds = torchvision.datasets.MNIST('data',
train = False,
transform=torchvision.transforms.ToTensor(),
download = True)
代码知识点
torch.utils.data.DataLoader
是Pytorch自带的一个数据加载器,结合了数据集和取样器,并且可以提供多个线程处理数据集。
参数说明:
dataset(string) :
加载的数据集
batch_size (int,optional) :
每批加载的样本大小(默认值:1)
shuffle(bool,optional) :
如果为True,每个epoch重新排列数据。
sampler (Sampler or iterable, optional) :
定义从数据集中抽取样本的策略。 可以是任何实现了 len 的 Iterable。 如果指定,则不得指定 shuffle 。
batch_sampler (Sampler or iterable, optional) :
类似于sampler,但一次返回一批索引。与 batch_size、shuffle、sampler 和 drop_last 互斥。
num_workers(int,optional) :
用于数据加载的子进程数。 0 表示数据将在主进程中加载(默认值:0)。
pin_memory (bool,optional) :
如果为 True,数据加载器将在返回之前将张量复制到设备/CUDA 固定内存中。 如果数据元素是自定义类型,或者collate_fn返回一个自定义类型的批次。
drop_last(bool,optional) :
如果数据集大小不能被批次大小整除,则设置为 True 以删除最后一个不完整的批次。 如果 False 并且数据集的大小不能被批大小整除,则最后一批将保留。 (默认值:False)
timeout(numeric,optional) :
设置数据读取的超时时间 , 超过这个时间还没读取到数据的话就会报错。(默认值:0)
worker_init_fn(callable,optional) :
如果不是 None,这将在步长之后和数据加载之前在每个工作子进程上调用,并使用工作 id([0,num_workers - 1] 中的一个 int)的顺序逐个导入。 (默认:None)
batch_size = 16
train_dl = torch.utils.data.DataLoader(train_ds,
batch_size = batch_size,
shuffle = True)
test_dl = torch.utils.data.DataLoader(test_ds,
shuffle = True)
代码知识点
iter(train_dl)
返回一个迭代器,next()函数则用于获取迭代器的下一个元素。
imgs,labels = next(iter(train_dl))
这行代码将获取到的数据分解为两部分:imgs和labels。
imgs,labels = next(iter(train_dl))
imgs.shape
输出
torch.Size([32, 1, 28, 28])
3.数据可视化
代码知识点
plt.figure(figsize=(20, 5))
: 创建一个大小为20x5英寸的图形窗口。plt
是Matplotlib库的常用别名,用于绘图。for i, imgs in enumerate(imgs[:20]):
: 使用enumerate()
函数遍历imgs
列表的前20个元素。enumerate()
函数返回一个枚举对象,其中包含索引和对应的值。在这里,索引表示图像的位置,值表示图像本身。npimg = np.squeeze(imgs.numpy())
: 将图像转换为NumPy数组,并使用np.squeeze()
函数删除数组中的单维度条目。这样可以确保图像数据以正确的形状进行处理。
单维度条目,是指数组中形状为1的维度。例如,对于一个一维数组,只有一个0轴,对于一个二维数组(shape(2,2)),有0轴和1轴,对于三维数组(shape(2,2,3)),有0, 1, 2轴。在NumPy库中,np.squeeze()函数就是用来删除这些单维度条目的。如果数组的形状中有一个单维度条目,np.squeeze()函数就会返回一个维度较小的新数组;如果该数组没有单维度条目,那么这个数组就不会发生变化。同时,我们可以通过指定axis参数来删除特定轴上的单维度条目。
plt.subplot(2, 10, i+1)
: 创建一个子图,将其放置在2行10列的网格中,当前位置由i+1
确定。这里,2
表示行数,10
表示列数,i+1
表示当前图像在网格中的位置。plt.imshow(npimg, cmap=plt.cm.binary)
: 使用imshow()
函数显示图像。npimg
是要显示的图像数据,cmap=plt.cm.binary
指定了颜色映射,这里使用了二值颜色映射。plt.axis('off')
: 关闭坐标轴。这可以使得图像看起来更加简洁。
import numpy as np
plt.figure(figsize=(20, 5))
for i, imgs in enumerate(imgs[:20]):
npimg = np.squeeze(imgs.numpy())
plt.subplot(2, 10, i+1)
plt.imshow(npimg, cmap=plt.cm.binary)
plt.axis('off')
输出
二、构建简单的CNN网络
代码知识点
import torch.nn.functional as F
这行代码导入了PyTorch库中的函数模块torch.nn.functional,它包含了一些常用的激活函数和损失函数。
x = torch.flatten(x, start_dim = 1)
这行代码将卷积层的输出结果展平,以便后续进行全连接层的计算。start_dim=1表示从第二个维度开始展平。
x = F.relu(self.fcl(x)) x = self.fc2(x)
这两行代码分别对展平后的输出结果进行了两个全连接层的计算。第一个全连接层使用ReLU激活函数,第二个全连接层不使用激活函数.
import torch.nn.functional as F
num_classes = 10
class Model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(1,32,kernel_size = 3)
self.pool1 = nn.MaxPool2d(2)
self.conv2 = nn.Conv2d(32,64,kernel_size = 3)
self.pool2 = nn.MaxPool2d(2)
#分类网络
self.fcl = nn.Linear(1600,64)
self.fc2 = nn.Linear(64, num_classes)
#前向传播
def forward(self,x):
x = self.pool1(F.relu(self.conv1(x)))
x = self.pool2(F.relu(self.conv2(x)))
x = torch.flatten(x, start_dim = 1)
x = F.relu(self.fcl(x))
x = self.fc2(x)
return x
from torchinfo import summary
model = Model( ).to(device)
summary(model)
输出
三、训练模型
1.设置超参数
代码知识点
这段代码是使用PyTorch库定义了一个神经网络模型的损失函数、学习率和优化器。下面是逐行解释:
loss_fn = nn.CrossEntropyLoss()
: 这行代码创建了一个交叉熵损失函数对象,用于计算模型预测结果与真实标签之间的损失。
learn_rate = 0.3e-2
: 这行代码定义了学习率为0.3乘以10的负2次方,即0.003。学习率决定了模型参数更新的速度。
opt = torch.optim.SGD(model.parameters(),lr = learn_rate)
: 这行代码创建了一个随机梯度下降(SGD)优化器对象,用于更新模型的参数。model.parameters()
表示模型的所有参数,lr = learn_rate
指定了学习率为之前定义的0.003。总结起来,这段代码定义了一个交叉熵损失函数、一个学习率和一个随机梯度下降优化器,用于训练神经网络模型。
loss_fn = nn.CrossEntropyLoss()
learn_rate = 0.3e-2
opt = torch.optim.SGD(model.parameters(),lr = learn_rate)
2.编写训练函数
代码知识点
1.optimizer.zero_grad()
函数会遍历模型的所有参数,通过内置方法截断反向传播的梯度流,再将每个参数的梯度值设为0,即上一次的梯度记录被清空。2.
loss.backward()
PyTorch的反向传播(即tensor.backward()
)是通过autograd包来实现的,autograd包会根据tensor进行过的数学运算来自动计算其对应的梯度。具体来说,torch.tensor是autograd包的基础类,如果你设置tensor的requires_grads为True,就会开始跟踪这个tensor上面的所有运算,如果你做完运算后使用tensor.backward(),所有的梯度就会自动运算,tensor的梯度将会累加到它的.grad属性里面去。
更具体地说,损失函数loss是由模型的所有权重w经过一系列运算得到的,若某个w的requires_grads为True,则w的所有上层参数(后面层的权重w)的.grad_fn属性中就保存了对应的运算,然后在使用loss.backward()后,会一层层的反向传播计算每个w的梯度值,并保存到该w的.grad属性中。
如果没有进行tensor.backward()的话,梯度值将会是None,因此loss.backward()要写在optimizer.step()之前。
optimizer.step() step()
函数的作用是执行一次优化步骤,通过梯度下降法来更新参数的值。因为梯度下降是基于梯度的,所以在执行optimizer.step()函数前应先执行loss.backward()函数来计算梯度。
注意:optimizer只负责通过梯度下降进行优化,而不负责产生梯度,梯度是tensor.backward()方法产生的。
3.编写测试函数
代码知识点
这段代码是一个用于测试神经网络模型的函数。下面是逐行解释:
这段代码定义了一个名为test
的函数,它接受三个参数:dataloader
、model
和loss_fn
。dataloader
是一个数据加载器对象,用于从数据集中加载图像和标签;model
是一个神经网络模型,用于对输入的图像进行预测;loss_fn
是一个损失函数对象,用于计算预测结果与真实标签之间的损失。函数首先获取测试集的大小(即总共有多少张图像),并计算批次数目(即总共有多少个批次)。然后,初始化测试损失和测试准确率为0。
接下来,使用
with torch.no_grad()
语句来停止梯度更新。这是因为在测试过程中,我们不需要计算梯度,因此可以节省计算内存消耗。在
with torch.no_grad()
语句块中,遍历数据加载器中的每个批次。对于每个批次,将图像和标签转移到指定的设备上(例如GPU或CPU),然后使用模型对图像进行预测。接着,使用损失函数计算预测结果与真实标签之间的损失,并将其累加到测试损失中。同时,计算预测结果中正确分类的数量,并将其累加到测试准确率中。
for imgs, target in dataloader:
:这是一个循环,用于遍历数据加载器(dataloader)中的所有批次。在每个批次中,imgs
表示图像数据,target
表示对应的标签。
imgs, target = imgs.to(device), target.to(device)
:这行代码将图像数据和标签转移到指定的设备上(例如GPU或CPU)。这样可以确保计算过程在相同的设备上进行,提高计算效率。
target_pred = model(imgs)
:这行代码使用神经网络模型对输入的图像数据进行预测,得到预测结果target_pred
。
loss = loss_fn(target_pred, target)
:这行代码计算预测结果与真实标签之间的损失。loss_fn
是一个损失函数对象,它接受预测结果和真实标签作为输入,并返回一个标量值表示损失。
test_loss += loss.item()
:这行代码将当前批次的损失累加到测试损失变量test_loss
中。loss.item()
将损失转换为Python标量值。
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
:这行代码计算预测结果中正确分类的数量,并将其累加到测试准确率变量test_acc
中。首先,target_pred.argmax(1)
找到预测结果中概率最大的类别索引,然后将其与真实标签进行比较,得到一个布尔值张量。接着,使用type(torch.float)
将布尔值张量转换为浮点数张量,最后使用sum().item()
计算正确分类
的数量。最后,将测试准确率除以测试集的大小,得到平均准确率;将测试损失除以批次数目,得到平均损失。函数返回平均准确率和平均损失作为输出。
def test (dataloader, model, loss_fn):
size = len(dataloader.dataset) # 测试集的大小,一共10000张图片
num_batches = len(dataloader) # 批次数目,313(10000/32=312.5,向上取整)
test_loss, test_acc = 0, 0
# 当不进行训练时,停止梯度更新,节省计算内存消耗
with torch.no_grad():
for imgs, target in dataloader:
imgs, target = imgs.to(device), target.to(device)
# 计算loss
target_pred = model(imgs)
loss = loss_fn(target_pred, target)
test_loss += loss.item()
test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
test_acc /= size
test_loss /= num_batches
return test_acc, test_loss