2.1 pytorch官方demo(Lenet)_代码详解
model.py
import torch.nn as nn
import torch.nn.functional as F
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 输入channel为:3,卷积核个数为:16,卷积核大小为:5*5
self.conv1 = nn.Conv2d(3, 16, 5)#得到的结果为(16,28,28)
# 池化核大小为:2,步距为:2
self.pool1 = nn.MaxPool2d(2, 2)#得到的结果为(16,14,14)
# 输入channel为:16,卷积核个数为:32,卷积核大小为:5*5
self.conv2 = nn.Conv2d(16, 32, 5)#得到的结果为(32,10,10)
# 池化核大小为:2,步距为:2
self.pool2 = nn.MaxPool2d(2, 2)#得到的结果为(32,5,5)
# 特征向量展平为一维向量,即数据节点个数为:32*5*5,输出节点个数为:120
self.fc1 = nn.Linear(32*5*5, 120)
self.fc2 = nn.Linear(120, 84)#84个输出节点
self.fc3 = nn.Linear(84, 10)#10个输出节点(根据分类任务决定)
def forward(self, x):
x = F.relu(self.conv1(x)) # input(3, 32, 32) output(16, 28, 28)
x = self.pool1(x) # output(16, 14, 14)
x = F.relu(self.conv2(x)) # output(32, 10, 10)
x = self.pool2(x) # output(32, 5, 5)
# 使用view进行展平。-1: 表示自动计算该维度的大小(通常用于批量大小)
x = x.view(-1, 32*5*5) # output(32*5*5)
x = F.relu(self.fc1(x)) # output(120)
x = F.relu(self.fc2(x)) # output(84)
x = self.fc3(x) # output(10)
return x
train.py
import torch
import torchvision
import torch.nn as nn
from model import LeNet
import torch.optim as optim
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import numpy as np
def main():
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("Using device:", device)
# transforms.Compose它将多个预处理操作组合在一起,形成一个可调用的对象。
# 当这个管道被应用于图像时,会按顺序执行其中定义的所有操作。
# transforms.ToTensor()将PIL图片或NumPy数组转换为PyTorch张量,并将像素值从[0, 255]范围缩放到[0, 1]之间。
# 具体行为:
# 如果输入是RGB图片,每个像素值会除以255,转为浮点数。
# 数据维度从 (H, W, C) (高度、宽度、通道)变为 (C, H, W)(通道、高度、宽度),这是PyTorch处理图像数据的标准格式。
# transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
# 对张量数据按通道标准化:
# 每个通道的数据减去均值(0.5, 0.5, 0.5),再除以标准差(0.5, 0.5, 0.5)。
# 将原本[0, 1]范围的值进一步映射到[-1, 1]范围。
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# 50000张训练图片
# 第一次使用时要将download设置为True才会自动去下载数据集
# train_set 是一个封装了所有训练数据和标签的对象,包括预处理方法
train_set = torchvision.datasets.CIFAR10(root='./data', train=True,
download=False, transform=transform)
# train_loader 是一个帮助分批次加载 train_set 数据的工具,通过它可以方便地迭代读取数据和标签用于模型训练。
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64,
shuffle=True, num_workers=4)
# 10000张验证图片
# 第一次使用时要将download设置为True才会自动去下载数据集
val_set = torchvision.datasets.CIFAR10(root='./data', train=False,
download=False, transform=transform)
val_loader = torch.utils.data.DataLoader(val_set, batch_size=10000,
shuffle=False, num_workers=4)
# 将数据加载器 val_loader 转为迭代器。
val_data_iter = iter(val_loader)
# 取出迭代器的下一批数据:调用 next(),从 val_data_iter 中取出一批次的数据。
# 由于 val_loader 的 batch_size=10000,所以这实际上取出的是验证集中的全部数据。
val_image, val_label = next(val_data_iter)
val_image, val_label = val_image.to(device), val_label.to(device)
# 定义CIFAR-10数据集的10个类别。
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# # Helper function for inline image display
# def matplotlib_imshow(img, one_channel=False):
# if one_channel:
# img = img.mean(dim=0)
# img = img / 2 + 0.5 # unnormalize
# npimg = img.numpy()
# if one_channel:
# plt.imshow(npimg, cmap="Greys")
# else:
# plt.imshow(np.transpose(npimg, (1, 2, 0)))
#
# # Create a grid from the images and show them
# img_grid = torchvision.utils.make_grid(val_image)
# matplotlib_imshow(img_grid, one_channel=False)
# print(' '.join(classes[val_label[j]] for j in range(4)))
# plt.show() # 显示图片
# 实例化LeNet模型
net = LeNet().to(device)
# 使用交叉熵作为损失函数
loss_function = nn.CrossEntropyLoss()
# 使用Adam优化器更新模型的权重
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 训练过程
for epoch in range(5): # 模型遍历训练数据集5次
running_loss = 0.0
for step, data in enumerate(train_loader, start=0):
# get the inputs; data is a list of [inputs, labels]
# 每次加载一个批次的训练数据,包括inputs和labels。
inputs, labels = data[0].to(device), data[1].to(device)
# zero the parameter gradients
# 每次训练前清除梯度,避免梯度累积。
optimizer.zero_grad()
# forward + backward + optimize
# 前向传播:将输入数据通过网络,得到模型的预测输出。
outputs = net(inputs)
# 计算当前批次的损失
loss = loss_function(outputs, labels)
# 反向传播
loss.backward()
# 根据Adam优化器规则,更新模型参数
optimizer.step()
# print statistics
# 累积每个批次的损失值,用于计算平均训练损失
running_loss += loss.item()
if step % 500 == 499: # print every 500 mini-batches
# 禁用梯度计算,节省内存和计算资源。验证时不需要更新模型参数,只需要计算输出。
with torch.no_grad():
# 使用验证数据进行前向传播,得到预测结果。
outputs = net(val_image) # [batch, 10]
# 从模型输出中选择预测概率最大的类别,作为模型的预测结果。
predict_y = torch.max(outputs, dim=1)[1]
# 比较预测值 predict_y 和真实标签 val_label,计算正确预测的数量并求出验证集的准确率。
accuracy = torch.eq(predict_y, val_label).sum().item() / val_label.size(0)
# 打印当前轮次、批次、平均训练损失以及验证集准确率。
print('[%d, %5d] train_loss: %.3f test_accuracy: %.3f' %
(epoch + 1, step + 1, running_loss / 500, accuracy))
running_loss = 0.0
print('Finished Training')
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
if __name__ == '__main__':
main()
predict.py
import torch
import torchvision.transforms as transforms
from PIL import Image
from model import LeNet
def main():
# Resize((32, 32)): 将图片缩放到 32×32 大小, LeNet 的输入要求。
# ToTensor(): 将图片转换为 PyTorch 张量,值范围从 [0, 255] 转为 [0, 1]。
# Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)): 按通道对图片进行标准化,结果范围从 [0, 1] 映射到 [-1, 1]。
transform = transforms.Compose(
[transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
# CIFAR-10 数据集的标签类别,按顺序与模型的输出对应
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
# LeNet(): 创建模型实例。
# torch.load('Lenet.pth'): 加载训练好的模型权重。
# load_state_dict: 将加载的权重应用到模型中。
net = LeNet()
net.load_state_dict(torch.load('Lenet.pth'))
# Image.open('1.jpg'): 使用 PIL 打开图片。
# transform(im): 应用前面定义的预处理,将图片转为 [C, H, W] 格式的张量。
# torch.unsqueeze(im, dim=0): 增加一个批次维度,将形状从 [C, H, W] 变为 [N, C, H, W],以符合模型输入格式。
im = Image.open('1.jpg')
im = transform(im) # [C, H, W]
im = torch.unsqueeze(im, dim=0) # [N, C, H, W]
# 禁用梯度计算,加速推理并节省显存。
with torch.no_grad():
# 使用模型对图片进行前向传播,得到输出张量 [N, num_classes]。
outputs = net(im)
# 获取每张图片预测的类别索引。
predict = torch.max(outputs, dim=1)[1].numpy()
# 根据类别索引找到对应的类别名称,并输出。
print(classes[int(predict)])
if __name__ == '__main__':
main()