1.背景介绍
1.1 数据集来源
本文使用的是MNIST数据集,里面包含了0到9十种数字的28*28像素规格的灰度图片,总共包含6万张训练数据集和1万张的测试数据集 由Yann LeCun、Corinna Cortes和Christopher Burges他们在1998年发布,用于手写数字的图像识别任务.
2.引入包导入数据集
2.1 引入外部包
首先先导入我们本次需要使用的所有外部的的
import torch
from torch import nn
from torch.nn import functional as F
from torch import optim
import torchvision
from util import plot_curve,plot_image,one_hot
util类的代码如下 自行编写并引入或者直接去除util类的导入直接把引入的方法写在当前类里
import torch
from matplotlib import pyplot as plt
def plot_curve(data):
"""
下降曲线的绘制
:param data: 损失值列表
:return: None
"""
fig = plt.figure()
plt.plot(range(len(data)), data, color='blue') # 绘制损失值曲线
plt.legend(['value'], loc='upper right') # 图例
plt.xlabel('step') # x轴标签
plt.ylabel('value') # y轴标签
plt.show() # 显示图形
def plot_image(img, label, name):
"""
可视化识别结果
:param img: 图像张量
:param label: 标签张量
:param name: 图像标题
:return: None
"""
fig = plt.figure()
for i in range(6): # 显示前 6 张图像
plt.subplot(2, 3, i + 1) # 创建 2 行 3 列的子图
plt.tight_layout() # 调整子图布局
plt.imshow(img[i][0] * 0.3081 + 0.1307, cmap='gray', interpolation='none') # 显示图像
plt.title("{}: {}".format(name, label[i].item())) # 设置标题
plt.xticks([]) # 不显示 x 轴刻度
plt.yticks([]) # 不显示 y 轴刻度
plt.show() # 显示图形
def one_hot(labels, depth=10):
"""
将标签转换为 one-hot 编码
:param labels: 标签张量
:param depth: 类别数(即 one-hot 编码的长度)
:return: one-hot 编码张量
"""
# 确保标签是 Long 类型
labels = labels.long()
# 创建一个全零的张量
out = torch.zeros(labels.size(0), depth)
# 将标签索引变为一列的张量
idx = labels.view(-1, 1)
# 使用 scatter 操作将值设为 1
out.scatter_(dim=1, index=idx, value=1)
return out
#plot_curve 用于绘制损失曲线,监控训练过程。
#plot_image 用于展示图像和预测结果,帮助检查模型的输出。
#one_hot 用于将标签转换为 one-hot 编码,是分类任务中常见的预处理步骤。
2.2 加载MNIST数据集
# 第一步加载数据集
def load_dataset():
#一次性处理数据集图片的数量(根据电脑性能自行调整)
batch_size = 512
#加载mnist训练数据集
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('mnist_data', train=True, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=True)
#加载mnist测试数据集
test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('mnist_data/', train=False, download=True,
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(),
torchvision.transforms.Normalize(
(0.1307,), (0.3081,))
])),
batch_size=batch_size, shuffle=False)
为了让大家有更好的理解我在这里大致阐述一下每个参数是用来做什么的
从 torch.utils.data.DataLoader 这个函数开始
参数名称 | 用途 |
mnist_data | 下载路径,用来下载指定的数据集 |
train=True | 是否是训练数据 True 为是,说明是下载的训练数据集 False为否,说明是下载的测试数据集 |
download=True | 如果本地不存在该数据集则下载 如果download=False则反之 |
transform=torchvision.transforms.Compose([ torchvision.transforms.ToTensor(), torchvision.transforms.Normalize( (0.1307,), (0.3081,)) ])) | 用于格式化数据集使用,将图像转为张量并进行归一化。 |
batch_size=batch_size | 一次加载多少张图片 batch_size是我们自定义的变量代码中是512 |
shuffle=True | 为True意味着我们将这个数据集做一个随机的打散 |
然后我们展开讲讲torchvision.transforms.Compose()这个函数
参数名称 | 用途 |
torchvision.transforms.ToTensor() | 把下载的数据集转为torch中的tensor格式的数据载体,用于后面的使用torch框架训练 |
torchvision.transforms.Normalize((0.1307,),(0.3081,)) | 正则化过程,这是什么意思呢?因为我们神经网络所接收的一个数据最好是在。0附近均匀的分配,但是我们的一个图片的一个像素,它是从0到1,也就是说它只在0的右侧分布,这样子的话,我们通过一个这样的减去0.1307,再除以一个0.3081标准差的一个这样过程。使得你的一个数据能够在零附近能够均匀的分布,这个就更加方便我们神经网络去优化,对于这一行可以不做。所以这一行你可以注释掉,你也可以不注释,通过测试发现,如果你注释掉的话,你的性能会稍微差一点,大概是70%的样子。 |
3.定义神经化网络模型
3.1代码展示
本次实验我们将采用三层神经网络模型这个 Net
类定义了一个简单的前馈神经网络(也叫全连接神经网络),包括两个隐藏层和一个输出层。每个全连接层后面跟着 ReLU 激活函数,使得网络可以学习复杂的非线性关系。这个网络设计适合处理图像分类任务,如 MNIST 手写数字识别。
# 定义神经网络模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(28*28, 256) # 输入层到隐藏层1的线性变换
self.fc2 = nn.Linear(256, 64) # 隐藏层1到隐藏层2的线性变换
self.fc3 = nn.Linear(64, 10) # 隐藏层2到输出层的线性变换
def forward(self, x):
x = F.relu(self.fc1(x)) # 应用 ReLU 激活函数
x = F.relu(self.fc2(x)) # 应用 ReLU 激活函数
x = self.fc3(x) # 输出层
return x
net = Net() # 实例化模型
3.2详细讲解
3.2.1 nn.Linear
nn.Linear
是一个全连接层,它的作用是对输入数据进行线性变换。在初始化 nn.Linear
时,需要指定输入和输出的特征数量。
作用:
比如第一个self.fc1 = nn.Linear(28*28, 256) 中 第一个参数是输入特征 28*28是固定的因为输入的x是一个28*28像素的固定图像 当他从二维的图像数据展平成一维每张图像有 28x28=784 个像素值,所以这里输入特征的数量是 784。第二个参数 256 这是输出特征的数量,表示该全连接层将输入的 784 个特征变换成 256 个特征。当然这个256个特征是自己根据经验来定义的并不是死的 你也可以不用三层可以用四层或者多层但是方法都是类似的。为了让大家更加详细的理解我绘制了如下图片
3.2.2
F.relu()
F.relu(self.fc1(x))
:F.relu
是 PyTorch 提供的 ReLU 激活函数。ReLU(Rectified Linear Unit,线性整流单元)函数将所有负值变为 0,正值保持不变。这个操作是非线性的,帮助网络学习复杂的特征。
ReLU 激活函数的公式:ReLU(x)=max(0,x)
作用:
应用 ReLU 激活函数,使得每个特征值变为非负数。通过这种方式,网络能够引入非线性,增强表达能力。这块大家可以自行搜索资料去查看 本文主要是带大家熟悉整体的流程
4.训练模型
# 定义优化器
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 使用随机梯度下降优化器,学习率为 0.001,动量为 0.9
train_loss = [] # 存储训练过程中的损失
# 训练模型
for epoch in range(3): # 训练 3 个 epoch
for batch_idx, (x, y) in enumerate(train_loader):
x = x.view(x.size(0), 28*28) # 将每张图片展平成一维向量
out = net(x) # 前向传播,获取模型输出
y_onehot = one_hot(y, 10) # 将标签转换为 one-hot 编码
loss = F.mse_loss(out, y_onehot) # 计算均方误差损失
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新权重
train_loss.append(loss.item()) # 记录损失值
if batch_idx % 10 == 0: # 每 10 个批次打印一次损失
print(epoch, batch_idx, loss.item())
# 绘制训练损失曲线
plot_curve(train_loss)
5.测试模型
# 测试模型
total_correct = 0 # 记录总的正确预测数
for x, y in test_loader:
x = x.view(x.size(0), 28*28) # 将测试数据展平成一维向量
out = net(x) # 前向传播,获取模型输出
pred = out.argmax(dim=1) # 获取预测的类别
correct = pred.eq(y).sum().float().item() # 计算正确的预测数量
total_correct += correct # 累加正确的预测数
total_num = len(test_loader.dataset) # 测试数据集中样本总数
acc = total_correct / total_num # 计算准确率
print('测试的准确率:', acc) # 打印测试准确率
# 从测试数据中取出一个批次并绘制图像
x, y = next(iter(test_loader))
out = net(x.view(x.size(0), 28*28)) # 展平图片并进行前向传播
pred = out.argmax(dim=1) # 获取预测的类别
plot_image(x, pred, 'test') # 绘制测试图像及预测结果
6.整体完整代码和测试结果
import torch
from torch import nn
from torch.nn import functional as F
from torch import optim
import torchvision
from matplotlib import pyplot as plt
from util import plot_curve, plot_image, one_hot
# 第一步加载数据集
batch_size = 512 # 设置每个批次的大小
# 加载 MNIST 训练数据集
train_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('mnist_data', train=True, download=True, # 下载并加载 MNIST 训练数据
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(), # 将图片转化为 Tensor
torchvision.transforms.Normalize(
(0.1307,), (0.3081,)) # 标准化处理
])),
batch_size=batch_size, shuffle=True) # 每批次的数据大小和是否打乱顺序
# 加载 MNIST 测试数据集
test_loader = torch.utils.data.DataLoader(
torchvision.datasets.MNIST('mnist_data/', train=False, download=True, # 下载并加载 MNIST 测试数据
transform=torchvision.transforms.Compose([
torchvision.transforms.ToTensor(), # 将图片转化为 Tensor
torchvision.transforms.Normalize(
(0.1307,), (0.3081,)) # 标准化处理
])),
batch_size=batch_size, shuffle=True) # 每批次的数据大小和是否打乱顺序
# 从测试数据加载器中获取一个批次的数据
x, y = next(iter(test_loader))
print(x.shape, y.shape, x.min(), x.max()) # 打印图片张量的形状和数值范围
# 定义神经网络模型
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(28*28, 256) # 输入层到隐藏层1的线性变换
self.fc2 = nn.Linear(256, 64) # 隐藏层1到隐藏层2的线性变换
self.fc3 = nn.Linear(64, 10) # 隐藏层2到输出层的线性变换
def forward(self, x):
x = F.relu(self.fc1(x)) # 应用 ReLU 激活函数
x = F.relu(self.fc2(x)) # 应用 ReLU 激活函数
x = self.fc3(x) # 输出层
return x
net = Net() # 实例化模型
# 定义优化器
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9) # 使用随机梯度下降优化器,学习率为 0.001,动量为 0.9
train_loss = [] # 存储训练过程中的损失
# 训练模型
for epoch in range(3): # 训练 3 个 epoch
for batch_idx, (x, y) in enumerate(train_loader):
x = x.view(x.size(0), 28*28) # 将每张图片展平成一维向量
out = net(x) # 前向传播,获取模型输出
y_onehot = one_hot(y, 10) # 将标签转换为 one-hot 编码
loss = F.mse_loss(out, y_onehot) # 计算均方误差损失
optimizer.zero_grad() # 清零梯度
loss.backward() # 反向传播计算梯度
optimizer.step() # 更新权重
train_loss.append(loss.item()) # 记录损失值
if batch_idx % 10 == 0: # 每 10 个批次打印一次损失
print(epoch, batch_idx, loss.item())
# 绘制训练损失曲线
plot_curve(train_loss)
# 测试模型
total_correct = 0 # 记录总的正确预测数
for x, y in test_loader:
x = x.view(x.size(0), 28*28) # 将测试数据展平成一维向量
out = net(x) # 前向传播,获取模型输出
pred = out.argmax(dim=1) # 获取预测的类别
correct = pred.eq(y).sum().float().item() # 计算正确的预测数量
total_correct += correct # 累加正确的预测数
total_num = len(test_loader.dataset) # 测试数据集中样本总数
acc = total_correct / total_num # 计算准确率
print('测试的准确率:', acc) # 打印测试准确率
# 从测试数据中取出一个批次并绘制图像
x, y = next(iter(test_loader))
out = net(x.view(x.size(0), 28*28)) # 展平图片并进行前向传播
pred = out.argmax(dim=1) # 获取预测的类别
plot_image(x, pred, 'test') # 绘制测试图像及预测结果
由于我电脑性能一般 3轮跑下来准确率仅有百分之60% 大家可以多跑几轮尝试一下以下为损失曲线图和结果预测图
7.总结
手写数字识别作为深度学习入门的一个小实验旨意在带大家了解pytorch和神经网络,中间有很多东西大家可以去自行探索包括搭建更多层的神经网络等,某些方面大家不了解的可以自行搜索资料。感谢大家的观看