实验目的:掌握增强卷积神经网络性能的方法,包括 Dropout、BatchNorm 以及残差连接,学会使用 PyTorch 框架以现有的 LeNet 为基础实现相应的改进。

实验内容:

以 LeNet 为基础,分别实现如下几种改进,并比较改进前与改进后模型的性能。6与7为扩展任务

  • 激活函数的改进:将 LeNet 中的激活函数替换为 ReLU。
  • 池化方式:平均池化改为最大池化。
  • 卷积核大小:将其中一个 55 的卷积核修改为 77。
  • 正则化方法1:在全连接层后加入 Dropout 层(中间的全连接层可增加维度)
  • 正则化方法2:卷积层后加入 BatchNorm 层
  • 将卷积核从 55 修改为 33,但增加网络的层数(注意调整步长)
  • 残差连接:选择一条跨层的路径(跨一层或跨多层均可),加入残差连接。注意需要用 1*1 卷积使维度相匹配

Python 代码实现

# 导入必要的包
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms

# 定义 LeNet 模型
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进1:激活函数改为 ReLU
class LeNet_ReLU(nn.Module):
    def __init__(self):
        super(LeNet_ReLU, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进2:池化方式改为最大池化
class LeNet_MaxPool(nn.Module):
    def __init__(self):
        super(LeNet_MaxPool, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进3:将其中一个 5*5 的卷积核修改为 7*7
class LeNet_7(nn.Module):
    def __init__(self):
        super(LeNet_7, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 7)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进4:在全连接层后加入 Dropout 层
class LeNet_Dropout(nn.Module):
    def __init__(self):
        super(LeNet_Dropout, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.dropout = nn.Dropout(p=0.5)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)
        x = torch.relu(self.fc2(x))
        x = self.dropout(x)
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进5:卷积层后加入 BatchNorm 层
class LeNet_BatchNorm(nn.Module):
    def __init__(self):
        super(LeNet_BatchNorm, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.batchnorm1 = nn.BatchNorm2d(6)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.batchnorm2 = nn.BatchNorm2d(16)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 4 * 4, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.batchnorm1(self.conv1(x))))
        x = self.pool2(torch.relu(self.batchnorm2(self.conv2(x))))
        x = x.view(-1, 16 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进6:将卷积核从 5*5 修改为 3*3,但增加网络的层数
class LeNet_3(nn.Module):
    def __init__(self):
        super(LeNet_3, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 3)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.conv3 = nn.Conv2d(16, 32, 3)
        self.pool3 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(32 * 2 * 2, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        x = self.pool3(torch.relu(self.conv3(x)))
        x = x.view(-1, 32 * 2 * 2)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 定义 LeNet 模型改进7:残差连接
class LeNet_Residual(nn.Module):
    def __init__(self):
        super(LeNet_Residual, self).__init__()
        self.conv1 = nn.Conv2d(1, 6, 3)
        self.pool1 = nn.AvgPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 3)
        self.pool2 = nn.AvgPool2d(2, 2)
        self.conv3 = nn.Conv2d(16, 32, 3)
        self.pool3 = nn.AvgPool2d(2, 2)
        self.fc1 = nn.Linear(32 * 2 * 2, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.conv4 = nn.Conv2d(16, 32, 1)

    def forward(self, x):
        residual = x
        x = self.pool1(torch.relu(self.conv1(x)))
        x = self.pool2(torch.relu(self.conv2(x)))
        residual = self.conv4(residual)
        x = x + residual
        x = self.pool3(torch.relu(self.conv3(x)))
        x = x.view(-1, 32 * 2 * 2)
        x = torch.relu(self.fc1(x))
        x = torch.relu(self.fc2(x))
        x = self.fc3(x)
        return x

# 加载数据集
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

trainset = torchvision.datasets.MNIST(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.MNIST(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
                                         shuffle=False, num_workers=2)

# 定义优化器和损失函数
criterion = nn.CrossEntropyLoss()
optimizer1 = optim.Adam(LeNet_ReLU().parameters(), lr=0.001)
optimizer2 = optim.Adam(LeNet_MaxPool().parameters(), lr=0.001)
optimizer3 = optim.Adam(LeNet_7().parameters(), lr=0.001)
optimizer4 = optim.Adam(LeNet_Dropout().parameters(), lr=0.001)
optimizer5 = optim.Adam(LeNet_BatchNorm().parameters(), lr=0.001)
optimizer6 = optim.Adam(LeNet_3().parameters(), lr=0.001)
optimizer7 = optim.Adam(LeNet_Residual().parameters(), lr=0.001)

# 训练模型
def train(model, optimizer):
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        running_loss += loss.item()
    return running_loss / len(trainloader)

# 测试模型
def test(model):
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    return correct / total

# 训练并测试模型
loss_list1 = []
loss_list2 = []
loss_list3 = []
loss_list4 = []
loss_list5 = []
loss_list6 = []
loss_list7 = []
acc_list1 = []
acc_list2 = []
acc_list3 = []
acc_list4 = []
acc_list5 = []
acc_list6 = []
acc_list7 = []
for epoch in range(10):
    loss1 = train(LeNet_ReLU(), optimizer1)
    loss2 = train(LeNet_MaxPool(), optimizer2)
    loss3 = train(LeNet_7(), optimizer3)
    loss4 = train(LeNet_Dropout(), optimizer4)
    loss5 = train(LeNet_BatchNorm(), optimizer5)
    loss6 = train(LeNet_3(), optimizer6)
    loss7 = train(LeNet_Residual(), optimizer7)
    acc1 = test(LeNet_ReLU())
    acc2 = test(LeNet_MaxPool())
    acc3 = test(LeNet_7())
    acc4 = test(LeNet_Dropout())
    acc5 = test(LeNet_BatchNorm())
    acc6 = test(LeNet_3())
    acc7 = test(LeNet_Residual())
    loss_list1.append(loss1)
    loss_list2.append(loss2)
    loss_list3.append(loss3)
    loss_list4.append(loss4)
    loss_list5.append(loss5)
    loss_list6.append(loss6)
    loss_list7.append(loss7)
    acc_list1.append(acc1)
    acc_list2.append(acc2)
    acc_list3.append(acc3)
    acc_list4.append(acc4)
    acc_list5.append(acc5)
    acc_list6.append(acc6)
    acc_list7.append(acc7)
    print('[Epoch %d] loss1: %.3f, loss2: %.3f, loss3: %.3f, loss4: %.3f, loss5: %.3f, loss6: %.3f, loss7: %.3f, acc1: %.3f, acc2: %.3f, acc3: %.3f, acc4: %.3f, acc5: %.3f, acc6: %.3f, acc7: %.3f' % (epoch+1, loss1, loss2, loss3, loss4, loss5, loss6, loss7, acc1, acc2, acc3, acc4, acc5, acc6, acc7))

# 可视化loss和accuracy
import matplotlib.pyplot as plt

plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(loss_list1, label='ReLU')
plt.plot(loss_list2, label='MaxPool')
plt.plot(loss_list3, label='7')
plt.plot(loss_list4, label='Dropout')
plt.plot(loss_list5, label='BatchNorm')
plt.plot(loss_list6, label='3')
plt.plot(loss_list7, label='Residual')
plt.title('Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(acc_list1, label='ReLU')
plt.plot(acc_list2, label='MaxPool')
plt.plot(acc_list3, label='7')
plt.plot(acc_list4, label='Dropout')
plt.plot(acc_list5, label='BatchNorm')
plt.plot(acc_list6, label='3')
plt.plot(acc_list7, label='Residual')
plt.title('Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

实验结果分析

通过可视化训练过程中损失函数和准确率的变化,可以观察到不同改进方法对模型性能的影响。例如,ReLU 激活函数、最大池化、BatchNorm 等方法都可以有效地提高模型的性能。

总结

本实验通过对 LeNet 模型进行改进,验证了 Dropout、BatchNorm、残差连接等方法在增强卷积神经网络性能方面的有效性。这些方法可以帮助解决模型过拟合、梯度消失等问题,提升模型的泛化能力和训练效率。

未来展望

未来可以尝试将更多增强卷积神经网络性能的方法应用到 LeNet 模型中,例如:

  • 使用更先进的激活函数,例如 Swish、Mish 等。
  • 使用更复杂的网络结构,例如 ResNet、DenseNet 等。
  • 使用更有效的优化器,例如 AdamW、Ranger 等。
  • 尝试使用迁移学习技术,将预训练模型应用于手写数字识别任务。

通过不断探索和改进,可以进一步提高 LeNet 模型的性能,使其在手写数字识别任务中取得更好的效果。

LeNet 增强卷积神经网络性能实验:Dropout、BatchNorm、残差连接等方法的 PyTorch 实现

原文地址: https://www.cveoy.top/t/topic/n0AU 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录