1、神经网络结构
class NetWork(nn.Module):
def __init__(self):
super(NetWork, self).__init__()
# 第一层卷积
self.conv1 = nn.Conv2d(1, 20, 5, 1) # 输入通道1,输出通道20,卷积核大小5,步长1
self.conv2 = nn.Conv2d(20, 50, 5, 1) # 输入通道20,输出通道50,卷积核大小5,步长1
# 池化层
self.pool = nn.MaxPool2d(2, 2) # 池化核大小2,步长2
self.fc1 = nn.Linear(4 * 4 * 50, 500) # 假设图像被压缩到4x4,每个通道50个特征图
self.fc2 = nn.Linear(500, 10) # 输出层,10个类别
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 4 * 4 * 50) # 展平
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
1.1 卷积层 (Conv2d
)
- 第一层卷积 (
self.conv1
):- 输入: 图像数据,假设是灰度图,因此输入通道数为1。
- 输出: 20个特征图(或称为卷积后的图像),每个特征图是通过一个5x5的卷积核在输入图像上滑动并计算得到的。这一步骤旨在提取图像中的低级特征,如边缘、角点等。
- 参数: 卷积核大小为5x5,步长为1,意味着卷积核在输入图像上每次移动1个像素。
- 第二层卷积 (
self.conv2
):- 输入: 第一层卷积输出的20个特征图。
- 输出: 50个特征图,每个特征图通过另一个5x5的卷积核在第一层输出的特征图上滑动并计算得到。这一步进一步提取更高级别的特征。
- 参数: 同样使用5x5的卷积核,步长为1。
1.2. 池化层 (MaxPool2d
)
- 作用: 池化层(在这里是最大池化)用于减少特征图的空间尺寸(即高度和宽度),从而减少计算量和参数数量,同时保留重要特征。
- 实现: 通过一个2x2的窗口在特征图上滑动,每次取窗口内的最大值作为输出。这有助于减少特征图的维度,同时保留最重要的特征。
- 在网络中的应用: 在两层卷积之后各应用了一个池化层,以减小特征图的尺寸。
1.3. 全连接层 (Linear
)
- 第一层全连接 (
self.fc1
):- 输入: 池化层输出的特征图被展平成一维向量。假设经过两次卷积和池化后,特征图的尺寸为4x4,且有50个通道,因此输入向量的长度为4450。
- 输出: 500个神经元的输出,这一层通常用于学习特征之间的非线性组合。
- 作用: 将学到的“分布式特征表示”映射到样本的标记空间。
- 第二层全连接 (
self.fc2
):- 输入: 第一层全连接的输出,即500个神经元的输出。
- 输出: 10个神经元的输出,对应于10个类别的得分或概率(在分类任务中)。
- 作用: 最终的分类层,输出每个类别的预测得分或概率。
代码上可以用nn.Sequential进行优化
2、数据加载以及数据预处理
2.1 数据转换流程(transform
)
-
transforms.ToTensor()
:这个转换将PIL Image或者NumPy ndarray(HxWxC)转换成形状为[C, H, W]的FloatTensor,并且值会被缩放到[0.0, 1.0]的范围内。这里的C、H、W分别代表颜色通道数、高度、宽度。对于MNIST数据集(灰度图像),颜色通道数C为1。 -
transforms.Normalize((0.5,), (0.5,))
:这个转换会对Tensor进行标准化处理,使其均值为0,标准差为1。这里的(0.5,)
和(0.5,)
分别是均值和标准差。对于灰度图像(范围[0, 1]),将它们减去0.5然后除以0.5(实际上这相当于将范围从[0, 1]转换到[-1, 1]),但这种处理方式不严格符合传统的均值为0、标准差为1的标准化定义,因为这里的均值和标准差是人为设定的,而不是根据数据集的统计特性计算得出的。
2.2 加载数据集
-
使用
datasets.MNIST
类来加载MNIST数据集。这个类会自动从互联网下载数据集(如果本地没有且download=True
),并存储到指定的根目录(这里是'./data'
)。 -
通过设置
train=True
来加载训练集,train=False
来加载测试集。 -
transform
参数用于指定数据预处理流程。
2.3 打印数据集大小
- 使用
len(train_dataset)
和len(test_dataset)
来获取训练集和测试集的大小(即包含的图片数量),并打印出来。
2.4 输出结果
输出结果将显示训练集和测试集的大小,对于MNIST数据集,训练集通常包含60000张图片,测试集包含10000张图片。因此,输出大致如下:
train dataset size 60000
test dataset size 10000
3、数据加载
通过DataLoader
为训练集(train_dataset
)和测试集(test_dataset
)创建了数据加载器(train_dataloader
和test_dataloader
),并指定了每个批次(batch)的大小为64。
# 利用dataloader加载数据
train_dataloader = DataLoader(train_dataset, batch_size=64)
test_dataloader = DataLoader(test_dataset, batch_size=64)
4、训练前置准备
# 实例网络模型
mnist_model = NetWork()
# 定义损失函数
loss_f = nn.CrossEntropyLoss()
# 优化器
learning_rate = 1e-2
optimizer = torch.optim.SGD(mnist_model.parameters(), lr=learning_rate)
# 设置训练网络参数
# 记录训练测试
total_train_step = 0
total_test_step = 0
# 训练的轮数
echo = 10
writer = SummaryWriter("./logs")
初始化 mnist_model
的网络模型,定义了损失函数 loss_f
为交叉熵损失(适合多分类问题),配置了优化器为随机梯度下降(SGD)并设置了学习率。此外,设置训练的一些基础参数,包括训练步数、测试步数的计数器,训练的轮数(这里通过变量 echo
表示,但通常我们使用 epochs
(爱剖析龙) 或类似的命名),以及创建了一个 SummaryWriter
实例 writer
用于记录训练过程中的各种指标(如损失、准确率等),这些指标可以通过TensorBoard进行可视化。
TensorBoard的使用就是训练完或者训练中 新开终端执行命令
➜ ~ tensorboard --logdir=./logs --port=6007
5、训练步骤
设置了训练步骤的开始,包括将模型设置为训练模式(mnist_model.train()
),这是非常重要的,因为某些层(如Dropout
和BatchNorm
)在训练和评估模式下会有不同的行为。接下来,遍历训练数据加载器(train_dataloader
),对每批数据进行前向传播、计算损失、反向传播和优化,并且还包含了记录训练步数和损失的逻辑。
# 训练步骤开始
mnist_model.train() # 针对某些特定的层生效
for data in train_dataloader:
imgs, targets = data
print(imgs.shape)
outputs = mnist_model(imgs)
loss = loss_f(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step += 1
if total_train_step % 100 == 0:
print("训练次数:{},loss:{}".format(total_train_step, loss))
writer.add_scalar("train_loss", loss.item(), total_train_step)
6、评估
# 测试步骤
mnist_model.eval() # 针对某些特定层生效
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
outputs = mnist_model(imgs)
loss = loss_f(outputs, targets)
total_test_loss += loss
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print("整体测试集上的loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy / test_dataset_size))
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy / test_dataset_size, total_test_step)
total_test_step += 1
设置了模型为评估模式(mnist_model.eval()
),在测试时,我们通常不需要计算梯度,因此使用torch.no_grad()
上下文管理器来禁用梯度计算是一个好习惯,这可以帮助节省内存和计算资源。
7、模型保存
torch.save(mnist_model, "model_{}.pth".format(i))
print("模型已保存!")
保存模型 方便后面在c++环境加载调用 这个后面在写
8、完整代码
# https://www.bilibili.com/video/BV134421U77t/?spm_id_from=333.337.search-card.all.click&vd_source=c49c4312f98c9a2bcaf5cb1b07324412
import torch
from torch import nn
import torch.nn.functional as F
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter
class NetWork(nn.Module):
def __init__(self):
super(NetWork, self).__init__()
# 第一层卷积
self.conv1 = nn.Conv2d(1, 20, 5, 1) # 输入通道1,输出通道20,卷积核大小5,步长1
self.conv2 = nn.Conv2d(20, 50, 5, 1) # 输入通道20,输出通道50,卷积核大小5,步长1
# 池化层
self.pool = nn.MaxPool2d(2, 2) # 池化核大小2,步长2
self.fc1 = nn.Linear(4 * 4 * 50, 500) # 假设图像被压缩到4x4,每个通道50个特征图
self.fc2 = nn.Linear(500, 10) # 输出层,10个类别
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 4 * 4 * 50) # 展平
x = F.relu(self.fc1(x))
x = self.fc2(x)
return x
if __name__ == '__main__':
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
# 下载并加载训练集
train_dataset = datasets.MNIST(root='./data', train=True, download=True, transform=transform)
# 下载并加载测试集
test_dataset = datasets.MNIST(root='./data', train=False, download=True, transform=transform)
train_dataset_size = len(train_dataset)
test_dataset_size = len(test_dataset)
print("train dataset size {}".format(train_dataset_size))
print("test dataset size {}".format(test_dataset_size))
# 利用dataloader加载数据
train_dataloader = DataLoader(train_dataset, batch_size=64)
test_dataloader = DataLoader(test_dataset, batch_size=64)
# 实例网络模型
mnist_model = NetWork()
# 定义损失函数
loss_f = nn.CrossEntropyLoss()
# 优化器
learning_rate = 1e-2
optimizer = torch.optim.SGD(mnist_model.parameters(), lr=learning_rate)
# 设置训练网络参数
# 记录训练测试
total_train_step = 0
total_test_step = 0
# 训练的轮数
echo = 10
writer = SummaryWriter("./logs")
for i in range(echo):
print("-------- 第 {} 轮训练开始 --------".format(i + 1))
# 训练步骤开始
mnist_model.train() # 针对某些特定的层生效
for data in train_dataloader:
imgs, targets = data
print(imgs.shape)
outputs = mnist_model(imgs)
loss = loss_f(outputs, targets)
optimizer.zero_grad()
loss.backward()
optimizer.step()
total_train_step += 1
if total_train_step % 100 == 0:
print("训练次数:{},loss:{}".format(total_train_step, loss))
writer.add_scalar("train_loss", loss.item(), total_train_step)
# 测试步骤
mnist_model.eval() # 针对某些特定层生效
total_test_loss = 0
total_accuracy = 0
with torch.no_grad():
for data in test_dataloader:
imgs, targets = data
outputs = mnist_model(imgs)
loss = loss_f(outputs, targets)
total_test_loss += loss
accuracy = (outputs.argmax(1) == targets).sum()
total_accuracy += accuracy
print("整体测试集上的loss:{}".format(total_test_loss))
print("整体测试集上的正确率:{}".format(total_accuracy / test_dataset_size))
writer.add_scalar("test_loss", total_test_loss, total_test_step)
writer.add_scalar("test_accuracy", total_accuracy / test_dataset_size, total_test_step)
total_test_step += 1
torch.save(mnist_model, "model_{}.pth".format(i))
print("模型已保存!")
writer.close()