文章目录
从零开始搭建一个PyTorch模型
为了更好的理解PyTorch模型的架构,本文使用手写数字识别的例子,从零开始一步步搭建一个标准的前馈神经网络。
目标:训练一个3层的神经网络,以手写数字图像作为输入,经过神经网络的计算,识别出图像中的数字。
1.神经网络的搭建思路?
为了设计一个处理图像数据的神经网络,首先需要明确输入的图像数据的大小和格式。我们要处理的图像数据是28*28像素的灰色图像。
这样的灰色图像包括了 28 ∗ 28 = 784 28*28=784 28∗28=784个数据点,我们要先将它展平为 1 ∗ 784 1*784 1∗784大小的向量,然后将这个向量输入到神经网络中。
我们将使用一个三层的神经网络来处理图片对应的向量x,输入层需要接收784维的图片向量x,x中的每个维度的数据都有一个神经元来接收,故输入层要包含784个神经元。
隐藏层用于特征提取,将输入的特征向量处理为更高级的特征向量。因为手写数字识别并不复杂,故将隐藏层的神经元个数设置为256。这样输入层与隐藏层之间就会有一个 784 ∗ 256 784*256 784∗256大小的线性层。它可以将输入为784维的输入向量转换为256维的输出向量。该输出向量继续向前传播到达输出层。
由于最终的输出要对应0~9这10个数字,所以输出层要定义10个神经元来对应这10个数字。
这样,256维的向量经过隐藏层和输出层的线性层计算后,就得到了10维的输出结果。这个10维的向量就代表了10个数字的预测得分。
为了继续得到10个数字的预测概率,将输出层的输出输入到Softmax层,Softmax层会将10维向量转换为10个概率值 p 0 − p 9 p_0-p_9 p0−p9。每个概率值都对应一个数字,也就是输入图片是某一个数字的可能性。
2.模型搭建流程
2.1 加载数据集
"""
第一步:加载数据集-构建训练数据和测试数据各自的Dataloader。
"""
# 对图片进行预处理,将图片数据转换为张量,并进行归一化
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,), )])
# 下载数据集并预处理
train_dataset = dataset.MNIST(root="./MINST", train=True, download=True, transform=transform)
test_dataset = dataset.MNIST(root="./MINST", train=False, download=True, transform=transform)
# 构建dataloader,实现小批量的数据读取
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
2.2 定义模型(搭建网络结构)
"""
第二步:定义模型,包括模型结构的初始化`__init__(self)`和前向传播`forward(self, x)`。
* __init__(self)函数:用于定义前向传播需要用到的网络结构。
* forward(self, x)函数:用于实现前向传播的逻辑。
"""
class Model(nn.Module):
def __init__(self, input_size, hideen_size, output_size):
super().__init__()
# 线性层1,输入层和隐藏层之间的线性层
self.layer1 = nn.Linear(input_size, hideen_size)
# 激活函数
self.relu = nn.ReLU()
# 线性层2,隐藏层和输出层之间的线性层
self.layer2 = nn.Linear(hideen_size, output_size)
def forward(self, x):
# 将输入的图片数据x展平成一维数据
x = x.view(-1, input_size)
# 前向传播
x = self.layer1(x)
x = self.relu(x)
x = self.layer2(x)
return x
2.3 创建三个对象(模型本身、优化器、损失函数)
"""
第三步:创建三个对象(模型本身、优化器、损失函数)
"""
# 实例化模型
model = Model(input_size, hidden_size, output_size)
# 优化器
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 损失函数
criterion = nn.CrossEntropyLoss()
2.4 模型训练
"""
第四步:模型训练(两种情况)- 手写数字识别案例的数据集划分为训练集-测试集
* 1.数据集划分为训练集-验证集-测试集:在训练集上训练出模型,将该模型在验证集上进行验证,以保存效果最好的模型。
* 2.数据集划分为训练集-测试集:在训练集上训练出模型(往往保存后面的epoch所对应的模型,根据自己情况所定)。
"""
for epoch in range(total_epochs): # 外层循环,代表了整个训练数据集的遍历次数,每次迭代进行一次训练与验证
for batch_index, (data, labels) in enumerate(train_loader): # 内部循环包括训练与验证,每次加载一个batch
# 设置模型为训练模式
model.train()
"""
训练步骤包括五步:
* 1. 模型前向传播
* 2. 计算损失
* 3. 利用optimizer.zero_grad()将梯度清零
* 4. 利用loss.backward()计算梯度
* 5. 利用optimizer.step更新模型参数
"""
output = model(data) # 模型前向传播
loss = criterion(output, labels) # 计算损失
optimizer.zero_grad() # 利用optimizer.zero_grad()将梯度清零
loss.backward() # 利用loss.backward()计算梯度
optimizer.step() # 利用optimizer.step更新模型参数
# 每迭代100个batch打印一次训练信息
if (batch_index + 1) % 100 == 0:
print("Epoch [{}/{}], Step [{}/{}], Train Loss:{:.4f}".format(epoch + 1, total_epochs, batch_index + 1,
len(train_loader),
loss.item()))
if epoch >= total_epochs - 1:
# 保存模型
torch.save(model.state_dict(), "model.pth")
"""
验证步骤包括两步:
* 验证不需要计算梯度,所以用`with torch.no_grad():`包裹代码
* 1. 模型前向传播
* 2. 计算损失
"""
with torch.no_grad():
# 设置模型为评估模式
model.eval()
test_loss = 0.0 # 初始化验证数据集的总损失为 0
count = 0 # 初始化正确预测的样本数为 0
for data, labels in test_loader:
outputs = model(data) # 模型前向传播
loss = criterion(outputs, labels) # 计算损失
count += torch.sum(outputs.argmax(1) == labels).item() # 计算正确预测的样本数
# 为了计算整个验证数据集的总损失,将当前批次的损失乘以该批次的样本数,然后累加到 test_loss。
test_loss += loss.item() * data.size(0)
# 将累加的总损失除以测试数据集的总样本数,获得平均损失。
test_loss /= len(test_loader.dataset)
# 计算精确度
acc = count / len(test_loader.dataset)
if acc > check: # 当精确度有提升时,保存模型
torch.save(model.state_dict(), "model.pth")
# 每次迭代结束,打印模型在验证集上的平均损失。
print("Epoch [{}/{}], Valid Loss:{:.4f}".format(epoch + 1, total_epochs, test_loss))
2.5 模型测试
"""
第五步:模型测试
* 1. 初始化模型
* 2. 加载训练好的模型
* 3. 验证模型性能
"""
model = Model(input_size, hidden_size, output_size) # 初始化模型
model.load_state_dict(torch.load("model.pth")) # 加载模型
print("load successfully!")
count = 0 # 初始化正确预测的样本数为 0
# 验证模型
for data, labels in test_loader:
outputs = model(data)
count += torch.sum(outputs.argmax(1) == labels).item() # 计算正确预测的样本数
acc = count / len(test_loader.dataset)
print("Test Accuracy:{:.4f}".format(acc))
3.手写数字识别可执行代码
"""
coding:utf-8
* @Author:FHTT-Tian
* @name:tutorial.py
* @Time:2024/7/23 星期二 19:57
* @Description: 手写数字识别案例:从零开始构建一个模型,用于手写数字识别
"""
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
from torch import nn, optim
from torch.utils.data import DataLoader
# 超参数设置
input_size = 28 * 28
hidden_size = 256
output_size = 10
batch_size = 128
learning_rate = 0.0001
total_epochs = 10
check = 0 # 保存效果最好的模型的评估指标
"""
第一步:加载数据集-构建训练数据和测试数据各自的Dataloader。
"""
# 对图片进行预处理,将图片数据转换为张量,并进行归一化
transform = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.5,), (0.5,), )])
# 下载数据集并预处理
train_dataset = dataset.MNIST(root="./MINST", train=True, download=True, transform=transform)
test_dataset = dataset.MNIST(root="./MINST", train=False, download=True, transform=transform)
# 构建dataloader,实现小批量的数据读取
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
"""
第二步:定义模型,包括模型结构的初始化`__init__(self)`和前向传播`forward(self, x)`。
* __init__(self)函数:用于定义前向传播需要用到的网络结构。
* forward(self, x)函数:用于实现前向传播的逻辑。
"""
class Model(nn.Module):
def __init__(self, input_size, hideen_size, output_size):
super().__init__()
# 线性层1,输入层和隐藏层之间的线性层
self.layer1 = nn.Linear(input_size, hideen_size)
# 激活函数
self.relu = nn.ReLU()
# 线性层2,隐藏层和输出层之间的线性层
self.layer2 = nn.Linear(hideen_size, output_size)
def forward(self, x):
# 将输入的图片数据x展平成一维数据
x = x.view(-1, input_size)
# 前向传播
x = self.layer1(x)
x = self.relu(x)
x = self.layer2(x)
return x
"""
第三步:创建三个对象(模型本身、优化器、损失函数)
"""
# 实例化模型
model = Model(input_size, hidden_size, output_size)
# 优化器
optimizer = optim.Adam(model.parameters(), lr=learning_rate)
# 损失函数
criterion = nn.CrossEntropyLoss()
"""
第四步:训练模型(两种情况)- 手写数字识别案例的数据集划分为训练集-测试集
* 1.数据集划分为训练集-验证集-测试集:在训练集上训练出模型,将该模型在验证集上进行验证,以保存效果最好的模型。
* 2.数据集划分为训练集-测试集:在训练集上训练出模型(往往保存后面的epoch所对应的模型,根据自己情况所定)。
"""
for epoch in range(total_epochs): # 外层循环,代表了整个训练数据集的遍历次数,每次迭代进行一次训练与验证
for batch_index, (data, labels) in enumerate(train_loader): # 内部循环包括训练与验证,每次加载一个batch
# 设置模型为训练模式
model.train()
"""
训练步骤包括五步:
* 1. 模型前向传播
* 2. 计算损失
* 3. 利用optimizer.zero_grad()将梯度清零
* 4. 利用loss.backward()计算梯度
* 5. 利用optimizer.step更新模型参数
"""
output = model(data) # 模型前向传播
loss = criterion(output, labels) # 计算损失
optimizer.zero_grad() # 利用optimizer.zero_grad()将梯度清零
loss.backward() # 利用loss.backward()计算梯度
optimizer.step() # 利用optimizer.step更新模型参数
# 每迭代100个batch打印一次训练信息
if (batch_index + 1) % 100 == 0:
print("Epoch [{}/{}], Step [{}/{}], Train Loss:{:.4f}".format(epoch + 1, total_epochs, batch_index + 1,
len(train_loader),
loss.item()))
if epoch >= total_epochs - 1:
# 保存模型
torch.save(model.state_dict(), "model.pth")
"""
第五步:测试模型
* 1. 初始化模型
* 2. 加载训练好的模型
* 3. 验证模型性能
"""
model = Model(input_size, hidden_size, output_size) # 初始化模型
model.load_state_dict(torch.load("model.pth")) # 加载模型
print("load successfully!")
count = 0 # 初始化正确预测的样本数为 0
# 验证模型
for data, labels in test_loader:
outputs = model(data)
count += torch.sum(outputs.argmax(1) == labels).item() # 计算正确预测的样本数
acc = count / len(test_loader.dataset)
print("Test Accuracy:{:.4f}".format(acc))
参考
😃😃😃