B站视频讲解: 深度学习入门篇:使用pytorch搭建LeNet网络并代码详解实战
上篇文章讲解了LeNet的具体细节:深度学习入门篇–来瞻仰卷积神经网络的鼻祖LeNet
这次给大家带来卷积神经网络入门级网络LeNet的代码详解,并一步步的实现,并给同学们总结出pytorch的代码套路,当你在使用神经网络的时候,直接套用代码模版就可以直接训练了
废话不多说,开整!
LeNet 作为卷积神经网络的开山之作,奠定了卷积神经网络的基本结构,包括了卷积,池化,全连接三大件
从代码的角度来讲,包含了 model.py train.py 以及 predict.py 三个python文件
model.py
我们先从第一步,设计卷积神经网络结构开始,也就是model.py文件,用来定义网络结构
class LeNet(nn.Module):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.conv1 = nn.Conv2d(3,6,5,1,0)
self.pool1 = nn.MaxPool2d(2,2,0)
self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)
self.pool2 = nn.MaxPool2d(2,2,0)
self.fc1 = nn.Linear(32*5*5,120)
self.fc2 = nn.Linear(120,84)
self.fc3 = nn.Linear(84,10)
def forward(self,x):
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
x = x.view(-1,32*5*5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
类定义:class LeNet(nn.Module)
这行定义了一个继承自 nn.Module
的子类 LeNet
,这意味着我们可以通过 LeNet
创建神经网络实例,并使用 PyTorch 的模块化特性来构建和训练模型。
__init__
方法:
__init__
是模型的构造函数,在实例化 LeNet
类时会调用。它负责定义网络的结构。
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super().__init__(*args, **kwargs)
调用了父类nn.Module
的构造函数,这样LeNet
类就继承了 PyTorchModule
的一些基础功能,比如参数管理、梯度计算等。
接下来,我们看到具体的层定义:
self.conv1 = nn.Conv2d(3, 6, 5, 1, 0)
self.pool1 = nn.MaxPool2d(2, 2, 0)
self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)
self.pool2 = nn.MaxPool2d(2, 2, 0)
self.fc1 = nn.Linear(32 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
-
self.conv1 = nn.Conv2d(3, 6, 5, 1, 0)
:nn.Conv2d
定义了一个二维卷积层。- 参数:
3
:输入通道数(例如,RGB图像有3个通道)。6
:输出通道数(卷积层将生成6个特征图)。5
:卷积核的大小为 5x5。1
:步长为1,即卷积核每次移动一个像素。0
:填充为0,即不对输入数据进行零填充(padding)。
-
self.pool1 = nn.MaxPool2d(2, 2, 0)
:nn.MaxPool2d
定义了一个最大池化层。- 参数:
2
:池化窗口的大小为 2x2。2
:池化步长为2,每次移动两个像素。0
:不使用填充。
-
self.conv2 = nn.Conv2d(6, 16, 5, 1, 0)
:- 这是另一个卷积层。输入通道是 6(来自
conv1
的输出),输出通道是 16,卷积核大小仍然是 5x5,步长和填充都为 1 和 0。
- 这是另一个卷积层。输入通道是 6(来自
-
self.pool2 = nn.MaxPool2d(2, 2, 0)
:- 第二个池化层,参数与
pool1
相同。
- 第二个池化层,参数与
-
self.fc1 = nn.Linear(32 \* 5 \* 5, 120)
:-
nn.Linear
定义了一个全连接层。 -
输入特征的数量是
32 * 5 * 5
,这个值是根据卷积层和池化层的输出大小计算的。
- 卷积后,输入图像大小会变小,所以我们需要计算卷积后图像的尺寸。假设输入图像是32x32像素,经过两次 5x5 的卷积和两次 2x2 的池化后,尺寸大约为 5x5。
-
输出的节点数是
120
。
-
-
self.fc2 = nn.Linear(120, 84)
:- 第二个全连接层,输入是 120 个节点,输出是 84 个节点。
-
self.fc3 = nn.Linear(84, 10)
:- 最后的全连接层,输入是 84 个节点,输出是 10 个节点。这里的
10
是为了分类任务(例如 MNIST 数据集,10 个类别)。
- 最后的全连接层,输入是 84 个节点,输出是 10 个节点。这里的
forward
方法:
forward
方法定义了前向传播过程,即数据如何通过网络层进行处理。
def forward(self, x):
x = F.relu(self.conv1(x))
x = self.pool1(x)
x = F.relu(self.conv2(x))
x = self.pool2(x)
x = x.view(-1, 32 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
x = F.relu(self.conv1(x))
:self.conv1(x)
:通过卷积层对输入x
进行卷积运算。F.relu(...)
:应用 ReLU 激活函数,激活函数通过将负值置为0来引入非线性。
x = self.pool1(x)
:- 对卷积后的结果
x
进行池化,减少空间维度(下采样)。
- 对卷积后的结果
x = F.relu(self.conv2(x))
:- 第二个卷积层及 ReLU 激活。
x = self.pool2(x)
:- 第二次池化。
x = x.view(-1, 32 * 5 * 5)
:x.view(-1, 32 * 5 * 5)
用于将x
的形状展平。卷积和池化操作通常会降低数据的空间维度(宽度和高度),而我们需要将这些特征传递给全连接层。-1
表示自动计算该维度的大小,32 * 5 * 5
表示展平后的特征向量长度。
x = F.relu(self.fc1(x))
:- 通过第一个全连接层,并应用 ReLU 激活。
x = F.relu(self.fc2(x))
:- 通过第二个全连接层,并应用 ReLU 激活。
x = self.fc3(x)
:- 最后通过第三个全连接层输出 10 个类别的预测值。
return x
:- 返回模型的输出,通常是一个包含类别概率的向量。
train.py
这一部分主要分为, 下载训练和测试数据 , 用设计好的神经网络来进行训练, 以及用测试数据来进行测试一下看看效果如何
数据集介绍
torchvision.datasets.CIFAR10
是一个在计算机视觉领域广泛使用的数据集,专门用于图像分类任务。它是一个小型的图像数据集,包含了 10 类不同的物体,每一类有 6000 张图像。总共有 60,000 张图像,分为训练集和测试集。数据集中的每张图像都是 32x32 像素的彩色图像(RGB 图像),适合用来进行图像分类模型的训练与测试。
CIFAR-10 数据集的特点:
-
图像大小:每张图像的分辨率是 32x32 像素。
-
图像通道:每张图像是 RGB 彩色图像,因此每张图像有 3 个通道(红、绿、蓝)
-
类别数量:共有 10 个类别,每个类别有 6,000 张图片。分类包括:
classes = (‘plane’, ‘car’, ‘bird’, ‘cat’, ‘deer’, ‘dog’, ‘frog’, ‘horse’, ‘ship’, ‘truck’)
-
训练集和测试集
- 训练集包含 50,000 张图像(每个类别 5,000 张图像)
- 测试集包含 10,000 张图像(每个类别 1,000 张图像)
数据集结构:
- 训练集(train set):用于训练模型的数据。
- 测试集(test set):用于评估模型性能的数据。
from random import shuffle
import torch
from model_official import LeNet
import torchvision
import torch.nn as nn
import torchvision.transforms as transforms
import torch.optim as optim
import torch.utils
def main():
# 使用gpu进行计算
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))]
)
train_set = torchvision.datasets.CIFAR10(root="./data",train=True,
download=True,transform=transform)
train_loader = torch.utils.data.DataLoader(train_set,batch_size=36,
shuffle=True,num_workers=0)
test_set = torchvision.datasets.CIFAR10(root="./data",train=True,
download=True,transform=transform)
test_loader = torch.utils.data.DataLoader(test_set,batch_size=5000,
shuffle=True,num_workers=0)
dataiter = iter(test_loader)
test_images, test_labels = next(dataiter)
test_images = test_images.to(device)
test_labels = test_labels.to(device)
net = LeNet()
net = net.to(device)
# 损失函数
loss_function = nn.CrossEntropyLoss()
loss_function = loss_function.to(device)
# 迭代器 optim 是pytorch的模块,其中Adam是一个优化算法
optimizer = optim.Adam(net.parameters(), lr=0.001)
#训练轮数
for epoch in range(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 = data
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = loss_function(outputs,labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if step % 500 == 499:
with torch.no_grad():
outputs = net(test_images)
predict_y = torch.max(outputs, dim=1)[1]
accuracy = torch.eq(predict_y,test_labels).sum().item() / test_labels.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()
这段代码是一个典型的使用 PyTorch 训练 LeNet 网络的完整流程,主要包括数据加载、模型训练、损失计算、优化、测试精度计算等步骤。下面我将逐行分析各个部分:
1. 选择设备:使用GPU或CPU
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
- 解释:这行代码用于检测是否有可用的GPU。如果有GPU,则使用GPU进行计算,否则回退到CPU。
torch.cuda.is_available()
:检查系统是否有可用的CUDA设备(即GPU)。torch.device('cuda')
:选择GPU进行训练。torch.device('cpu')
:如果没有GPU可用,则选择CPU。
2. 数据预处理
transform = transforms.Compose(
[transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5), (0.5,0.5,0.5))]
)
-
解释
:这部分代码定义了数据的预处理步骤。
transforms.ToTensor()
:将图像转换为PyTorch张量(Tensor)。这会把图像从PIL格式转换为一个大小为 (C, H, W) 的 Tensor(其中 C 是通道数,H 是高度,W 是宽度),并且会把像素值从[0, 255]
归一化到[0.0, 1.0]
。transforms.Normalize(mean, std)
:对图像进行标准化。标准化是深度学习中常用的操作,目的是将数据的均值和标准差调整到一个统一的范围,通常是 0 均值,1 标准差。这里(0.5, 0.5, 0.5)
是 RGB 通道的均值,(0.5, 0.5, 0.5)
是标准差。
3. 加载训练和测试数据集
train_set = torchvision.datasets.CIFAR10(root="./data", train=True, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=36, shuffle=True, num_workers=0)
test_set = torchvision.datasets.CIFAR10(root="./data", train=False, download=True, transform=transform)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=5000, shuffle=False, num_workers=0)
-
解释:
-
torchvision.datasets.CIFAR10() #这行代码用于加载CIFAR-10数据集。
-
root="./data"
:指定数据集存储路径。 -
train=True
表示加载训练集,train=False
表示加载测试集。 -
download=True
:如果数据集没有下载,会自动从互联网上下载。 -
transform=transform
:对数据集应用之前定义的预处理。 -
batch_size=36
:每个训练批次包含36个样本。 -
shuffle=True
:在训练时打乱数据集顺序。 -
num_workers=0
:用于加载数据的线程数,这里设置为0表示在主线程中加载数据。
-
-
4. 将数据加载到GPU
dataiter = iter(test_loader)
test_images, test_labels = next(dataiter)
test_images = test_images.to(device)
test_labels = test_labels.to(device)
- 解释:
dataiter = iter(test_loader)
:将test_loader
转换为迭代器。test_images, test_labels = next(dataiter)
:从迭代器中获取一个批次的测试图像和标签。test_images.to(device)
和test_labels.to(device)
:将数据迁移到选定的设备(CPU或GPU)。
5. 模型定义和迁移到设备
net = LeNet()
net = net.to(device)
- 解释:实例化LeNet模型,并将模型迁移到前面选择的设备(GPU或CPU)。
6. 定义损失函数和优化器
loss_function = nn.CrossEntropyLoss()
loss_function = loss_function.to(device)
optimizer = optim.Adam(net.parameters(), lr=0.001)
-
损失函数
nn.CrossEntropyLoss()
用于多类分类任务,计算模型预测的概率分布与真实标签之间的交叉熵损失。
loss_function.to(device)
:将损失函数迁移到相应的设备。
-
优化器:
optim.Adam()
是一个常用的优化算法,它结合了动量法和自适应学习率的优势。这里设置了学习率为 0.001。
7. 模型训练
for epoch in range(5):
running_loss = 0.0
for step, data in enumerate(train_loader, start=0):
inputs, labels = data
inputs = inputs.to(device)
labels = labels.to(device)
optimizer.zero_grad()
outputs = net(inputs)
loss = loss_function(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if step % 500 == 499:
with torch.no_grad():
outputs = net(test_images)
predict_y = torch.max(outputs, dim=1)[1]
accuracy = torch.eq(predict_y, test_labels).sum().item() / test_labels.size(0)
print('[%d, %5d] train_loss: %.3f test_accuracy:%.3f' %
(epoch+1, step+1, running_loss / 500, accuracy))
running_loss = 0.0
- 训练过程:
for epoch in range(5)
:训练 5 个周期(epochs)。for step, data in enumerate(train_loader)
:遍历每一个训练批次。inputs, labels = data
:从数据加载器获取输入图像和标签。inputs = inputs.to(device)
和labels = labels.to(device)
:将输入和标签移到 GPU 或 CPU 上。optimizer.zero_grad()
:将优化器的梯度清零,防止梯度累积。outputs = net(inputs)
:通过模型得到预测结果。loss = loss_function(outputs, labels)
:计算损失。loss.backward()
:反向传播计算梯度。optimizer.step()
:更新模型的参数。- 每 500 步输出一次训练损失和测试准确率。
- 测试准确率计算:
torch.max(outputs, dim=1)[1]
:对模型的输出进行argmax
操作,得到每个图像预测的标签。torch.eq(predict_y, test_labels).sum().item()
:计算预测标签与真实标签相等的个数。accuracy = ... / test_labels.size(0)
:计算准确率。
8. 训练完成后保存模型
print("Finished Training")
save_path = './Lenet.pth'
torch.save(net.state_dict(), save_path)
- 解释
torch.save(net.state_dict(), save_path)
:将训练好的模型保存到指定路径./Lenet.pth
中。state_dict()
是 PyTorch 中保存和加载模型的标准方法,它只保存模型的参数,而不包括优化器的状态等信息。
总结:
这段代码完成了 LeNet 模型的训练流程,主要包括:(以下都是在训练模型时候的固定套路,除了有些私有的数据集需要自己处理之外,其他的都是一样的处理思路)
- 数据预处理和加载(CIFAR-10 数据集)。
- 将数据和模型迁移到合适的设备(GPU 或 CPU)。
- 定义损失函数和优化器。
- 通过迭代训练模型,并计算每 500 步的训练损失和测试集准确率。
- 最终保存训练好的模型。
predict.py
预测部分的代码就比较简单了,首先你已经训练好了一个模型,要摩拳擦掌开始试试效果了,那么你在网上下载了一个小狗的照片,然后放在predict.py 的文件中
代码思路如下:
先对图片进行裁剪(resize到模型规定的输入大小,规格) —> 调用模型并下载训练好的参数(俗称:给模型注入灵魂) —>输入图片开始进行训练并区最大值
import torch
import torchvision.transforms as transforms
from PIL import Image
from model_official import LeNet
def main():
transform =transforms.Compose([
transforms.Resize((32,32)),
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
im = Image.open("1.png").convert("RGB")
im = transform(im) # [c,h,w]
im = torch.unsqueeze(im,dim=0) # [N,c,h,w]
classes = ('plane', 'car', 'bird', 'cat',
'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
net = LeNet()
net.load_state_dict(torch.load("Lenet.pth"))
with torch.no_grad():
outputs = net(im)
predict = torch.argmax(outputs,dim=1).item()
print(classes[predict])
if __name__ == "__main__":
main()
这段代码实现了用训练好的 LeNet 模型对一张图片进行分类。具体来说,代码执行以下几个步骤:
1. 导入所需的库
import torch
import torchvision.transforms as transforms
from PIL import Image
from model_official import LeNet
torch
: PyTorch的核心库,用于创建和操作张量。torchvision.transforms
: 用于对图像进行各种预处理操作。PIL.Image
: 用于加载和处理图片(Python Imaging Library)。LeNet
:自定义的LeNet模型(假设定义在model_official.py
文件中)。
2. 图像预处理
transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
transforms.Resize((32, 32))
:将输入图像调整为 32x32 像素。LeNet模型通常处理32x32的输入图像。transforms.ToTensor()
:将图像从PIL格式转换为PyTorch的Tensor格式,并且将图像像素值缩放到[0, 1]的范围。transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
:对图像进行归一化操作。这里的均值和标准差是0.5,通常是为了加速训练和改善模型的收敛。输入图像的每个通道(RGB)都会减去均值0.5并除以标准差0.5,使其值大致在[-1, 1]范围内。
3. 读取和处理输入图片
im = Image.open("1.png").convert("RGB")
im = transform(im) # [c,h,w]
im = torch.unsqueeze(im, dim=0) # [N,c,h,w]
Image.open("1.png")
:打开文件名为1.png
的图片。.convert("RGB")
:确保图像是RGB格式,如果是灰度图像,则会转换为RGB三通道。transform(im)
:对图像进行预处理,应用上面定义的所有变换。torch.unsqueeze(im, dim=0)
:将图像张量的维度从[c, h, w]
(单张图片的3D张量)增加一维,变成[N, c, h, w]
,其中N=1
表示批次大小为1。这个操作是因为PyTorch模型期望输入是一个批次的图像,而不是单张图像。
4. 加载并使用LeNet模型
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
net = LeNet()
net.load_state_dict(torch.load("Lenet.pth"))
classes
:这是一个包含10个类标签的元组,对应 CIFAR-10 数据集中的类别。模型将根据输入图像预测这些类别中的一个。LeNet()
:实例化一个LeNet模型。假设这个模型已经在model_official.py
文件中定义。net.load_state_dict(torch.load("Lenet.pth"))
:加载训练好的LeNet模型的权重参数(假设文件名为Lenet.pth
)。
5. 进行预测
with torch.no_grad():
outputs = net(im)
predict = torch.argmax(outputs, dim=1).item()
with torch.no_grad()
:此上下文管理器禁用梯度计算,以减少内存使用并加快推理过程,因为推理阶段不需要梯度。net(im)
:将图像输入到训练好的LeNet模型中,得到模型的输出(预测结果)。torch.argmax(outputs, dim=1).item()
:outputs
是模型的输出,通常是一个形状为[1, 10]
的张量,表示每个类别的预测得分。torch.argmax(outputs, dim=1)
会返回最大得分的索引,这就是模型预测的类别。.item()
将这个索引转换为一个纯数值。
6. 输出预测结果
print(classes[predict])
- 根据模型的预测结果,从
classes
元组中获取对应的类别名称,并打印出来。
7. 主函数执行
python复制代码if __name__ == "__main__":
main()
- 这是标准的Python主程序入口,确保只有在直接运行这个脚本时才会调用
main()
函数。
总结:
这个代码的目标是:
- 加载一张图像并对其进行预处理。
- 使用一个训练好的LeNet模型对图像进行分类。
- 输出图像属于哪个类别(例如:飞机、汽车、猫等)。