卷积神经网络实战——LeNet-5(PyTorch)

一、LeNet-5

1、LeNet-5

以Yann LeCun为第一作者的论文《Gradient-Based Learning Applied to Document Recognition》在1998年提出了LeNet-5,达到了手写数字识别在当时最先进的结果。

本文使用PyTorch实现LeNet-5识别MNIST手写数字数据集。

2、LeNet-5网络结构

LeNet-5包含了卷积层块和全连接层块。

  • 卷积层块

卷积层块包含了卷积层池化层。其中,卷积层提取图像的局部特征池化层降低参数的数量,并且降低卷积层对位置的敏感性

  • 全连接层块

全连接层块包含了全连接层全连接层将多维特征向量化,用于分类。

 本文根据PyTorch方法改动部分LeNet-5原文网络结构,具体如下表。

参数输出形状
输入层-(1,28,28)
C1卷积层
in_channels=1, out_channels=6,
kernel_size=5, stride=1, padding=2
(6,28,28)
Sigmoid激活函数-(6,28,28)
S2最大池化层
kernel_size=2, stride=2
(6,14,14)
C3卷积层
in_channels=6, out_channels=16,
kernel_size=5, stride=1, padding=0
(16,10,10)
Sigmoid激活函数-(16,10,10)
S4最大池化层kernel_size=2, stride=2(16,5,5)
F5全连接层层
in_features=400, out_features=120
(120)
Sigmoid激活函数-(120)
F6全连接层
in_features=120, out_features=84
(84)
Sigmoid激活函数-(84)
输出层(全连接层)
in_features=84, out_features=10
(10)

二、PyTorch实现

 1、数据准备

 PyTorch直接加载MNIST数据集,并将图像转换为张量

train_dataset = torchvision.datasets.MNIST(root='./data', train=True,
                                           transform=transform.ToTensor())  # 加载MNIST数据集作为训练集,将图像转换为张量

 Hold-out,划分数据集和验证集

train_data, valid_data, train_target, valid_target = train_test_split(train_dataset.data, train_dataset.targets,
                                                                      test_size=0.33,
                                                                      random_state=23)  # 将训练集的数据和标签分割为训练集和验证集,验证集占比为0.33,随机种子为23

增加通道数维度与卷积层维度对齐

train_data.resize_(train_data.size(0), 1, train_data.size(1), train_data.size(2))  # 将训练集的数据调整为四维张量
valid_data.resize_(valid_data.size(0), 1, valid_data.size(1), valid_data.size(2))  # 将验证集的数据调整为四维张量

将训练集和验证集的数据和标签合并为张量数据集

注意,在PyTorch中,神经网络层的权重通常为Float32,因此需要将数据转换为float。

train_dataset = TensorDataset(train_data.float(), train_target)  # 将训练集的数据和标签封装成一个张量数据集
valid_dataset = TensorDataset(valid_data.float(), valid_target)  # 将验证集的数据和标签封装成一个张量数据集

 分别加载训练集和验证集

BATCH_SIZE = 256
train_dataloader = DataLoader(dataset=train_dataset, batch_size=BATCH_SIZE, shuffle=True)
valid_dataloader = DataLoader(dataset=valid_dataset, batch_size=BATCH_SIZE, shuffle=False)

2、训练模型

编写设备无关代码,根据是否有可用GPU,选择是否使用cuda加速。

device = "cuda" if torch.cuda.is_available() else "cpu"  # 判断是否有可用的GPU设备,如果有则使用GPU,否则使用CPU

 定义一个LeNet-5类,根据网络结构定义层次。

class LeNet5(nn.Module):
    def __init__(self):
        super(LeNet5, self).__init__()
        # 定义一个卷积神经网络模块
        self.CNNs = nn.Sequential(
            # 卷积层C1,输入为(1,28,28),输出为(6,28,28)
            nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2, bias=True),
            nn.Sigmoid(),
            # 池化层S2,使用最大池化,输入为(6,28,28),输出为(6,14,14)
            nn.MaxPool2d(kernel_size=2, stride=2),

            # 卷积层C3,输入为(6,14,14),输出为(16,10,10)
            nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0, bias=True),
            nn.Sigmoid(),
            # 池化层S4,使用最大池化,输入为(16,10,10),输出为(16,5,5)
            nn.MaxPool2d(kernel_size=2, stride=2)
        )

        # 定义一个全连接网络模块
        self.FCs = nn.Sequential(
            # 线性层F5,输入为(400),输出为(120)
            nn.Linear(in_features=16 * 5 * 5, out_features=120, bias=True),
            nn.Sigmoid(),

            # 线性层F6,输入为(120),输出为(84)
            nn.Linear(in_features=120, out_features=84, bias=True),  # 第二个全连接层,输入特征数为120,输出特征数为84,有偏置项
            nn.Sigmoid(),

            # 输出层,输入为(84),输出为(10)
            nn.Linear(in_features=84, out_features=10, bias=True)
        )

定义LeNet-5类的前向传播方法。由于卷积层块的输出和全连接层块输入维度不同,将卷积层块输出转换成一维向量。

# 定义类的前向传播函数
def forward(self, data):
    data = self.CNNs(data)
    data = data.view(data.size(0), -1)  # 将数据展平成一维向量
    data = self.FCs(data)
    return data

 创建模型、优化器和损失函数

model = LeNet5().to(device)
model.zero_grad()

optimizer = torch.optim.Adam(model.parameters(), lr=0.001)  # 创建一个Adam优化器
lossFn = nn.CrossEntropyLoss()  # 创建一个交叉熵损失函数

训练模型

EPOCH = 45
train_loss_epoch = []  # 存储每轮训练后的平均损失
valid_loss_epoch = []  # 存储每轮验证后的平均损失
for epoch in range(EPOCH):
    print("Epoch : {}".format(epoch))
    model.train()  # 将模型设置为训练模式

    train_loss_batch = []  # 存储每批训练后的损失
    for images, labels in train_dataloader:
        images = images.to(device)
        labels = labels.to(device)

        optimizer.zero_grad()  # 将优化器的梯度清零

        outputs = model(images)
        loss = lossFn(outputs, labels)  # 计算输出结果和真实标签之间的损失
        loss.backward()  # 反向传播,计算梯度
        optimizer.step()  # 更新参数

        train_loss_batch.append(loss.item())
    mean_train_loss = np.mean(train_loss_batch)
    train_loss_epoch.append(mean_train_loss)
    print("Loss of train-set : {:.4f}".format(mean_train_loss))

    model.eval()  # 将模型设置为评估模式

    valid_loss_batch = []  # 存储每批验证后的损失
    with torch.no_grad():  # 不计算梯度
        for images, labels in valid_dataloader:
            images = images.to(device)
            labels = labels.to(device)

            outputs = model(images)
            loss = lossFn(outputs, labels)  # 计算输出结果和真实标签之间的损失

            valid_loss_batch.append(loss.item())
    mean_valid_loss = np.mean(valid_loss_batch)
    valid_loss_epoch.append(mean_valid_loss)
    print("Loss of valid-set : {:.4f}".format(mean_valid_loss))

可视化训练过程中损失函数的变化。

plt.plot(np.arange(EPOCH), train_loss_epoch, label='Train loss')
plt.plot(np.arange(EPOCH), valid_loss_epoch, label='Valid loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.savefig('./fig/loss_' + str(EPOCH) + '.png')
plt.clf()

 结果如下:

  保存模型

torch.save(model, './model/model_' + str(EPOCH) + '.pth')

3、评估模型

同训练集和验证集数据准备,先准备测试集数据

test_dataset = torchvision.datasets.MNIST(root='./data', train=False,
                                          transform=transform.ToTensor())  # 加载MNIST数据集作为测试集,将图像转换为张量
test_data = test_dataset.data
test_data.resize_(test_data.size(0), 1, test_data.size(1), test_data.size(2))  # 将测试集的数据调整为四维张量
test_dataset = TensorDataset(test_data.float(), test_dataset.targets)  # 将测试集的数据和标签封装成一个张量数据集

BATCH_SIZE = 256
test_dataloader = DataLoader(dataset=test_dataset, batch_size=BATCH_SIZE, shuffle=False)

加载模型

EPOCH = 45
model = torch.load('./model/model_' + str(EPOCH) + '.pth')  # 加载训练好的模型

测试模型。预测值根据模型输出得分计算max()得到,max()输出结果形式为(max_data,index)

model.eval()  # 将模型设置为评估模式
test_predicts = torch.LongTensor()
test_predicts = test_predicts.to(device)
test_labels = torch.LongTensor()
test_labels = test_labels.to(device)
with torch.no_grad():  # 不计算梯度
    for images, labels in test_dataloader:
        images = images.to(device)
        labels = labels.to(device)

        outputs = model(images)
        predicts = outputs.max(1, keepdim=True)[1]  # 获取输出结果中概率最大的类别作为预测结果
        test_predicts = torch.cat((test_predicts, predicts), dim=0)  # 将预测结果拼接
        test_labels = torch.cat((test_labels, labels), dim=0)  # 将真实标签拼接

计算模型在测试集上准确率

test_predicts = test_predicts.view(test_predicts.size(0))  # 将预测结果的张量调整为一维
accuracy = (test_predicts == test_labels).sum().item() / test_labels.size(0)  # 计算准确率
print("Accuracy of test-set using {} EPOCH : {:.4f}".format(EPOCH, accuracy))

 结果如下:

Accuracy of test-set using 45 EPOCH : 0.9885

 使用混淆矩阵(confusion-matrix)评估模型。

print(pd.crosstab(test_labels.to('cpu').numpy(), test_predicts.to('cpu').numpy(), rownames=['Predict Value'],
                  colnames=['True Value']))  # 生成一个混淆矩阵,展示每个类别的预测情况

 结果如下:

True Value

0123456789
Predict Value
0972030003110
10113000002210
200

1026

0100230
3004999030130
4100096705117
5100908792010
6121102950010
70280000101512
8313201029611
90310103141986
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习啊ZzZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值