Pytorch:卷积神经网络-LeNet

Pytorch: 卷积神经网络-LeNet

Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School of Artificial and Intelligence, Huazhong University of Science and Technology

Pytorch教程专栏链接


本教程不商用,仅供学习和参考交流使用,如需转载,请联系本人。

Reference

LeNet 原文链接

前言

在前面的多层感知机里我们构造了一个含隐藏层的多层感知机模型来完成分类和回归任务。然而,这种分类方法有一定的局限性。

图像在同一列邻近的像素在这个向量中可能相距较远。它们构成的模式可能难以被模型识别。

对于大尺寸的输入图像,使用全连接层容易造成模型过大。假设输入是高和宽均为 1000 1000 1000 像素的彩色照片(含 3 3 3 个通道)。即使全连接层输出个数仍是 256 256 256 ,该层权重参数的形状是 3000000 × 2563000000 × 256 3000000\times2563000000\times256 3000000×2563000000×256 :它占用了大约 3 3 3 GB的内存或显存。这带来过复杂的模型和过高的存储开销。

卷积层尝试解决这两个问题。一方面,卷积层保留输入形状,使图像的像素在高和宽两个方向上的相关性均可能被有效识别;另一方面,卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。

卷积神经网络就是含卷积层的网络。本节里我们将介绍一个早期用来识别手写数字图像的卷积神经网络:LeNet 。这个名字来源于 LeNet 论文的第一作者 Yann LeCun 。LeNet 展示了通过梯度下降训练卷积神经网络可以达到手写数字识别在当时最先进的结果。这个奠基性的工作第一次将卷积神经网络推上舞台,为世人所知。

Lenet 模型

LeNet 分为卷积层块和全连接层块两个部分。下面我们分别介绍这两个模块。

卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用 5 × 55 × 5 5×55×5 5×55×5 的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为 6 6 6 ,第二个卷积层输出通道数则增加到 16 16 16 。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。卷积层块的两个最大池化层的窗口形状均为 2 × 22 × 2 2×22×2 2×22×2 ,且步幅为 2 2 2 。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。

卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。全连接层块含 3 3 3 个全连接层。它们的输出个数分别是 120 120 120 84 84 84 10 10 10 ,其中 10 10 10 为输出的类别个数。

在这里插入图片描述

import numpy as np
import pandas as pd
from sklearn.metrics import accuracy_score, confusion_matrix
import matplotlib.pyplot as plt 
import seaborn as sns 
import copy 
import time
import torch
import torch.nn as nn
from torch.optim import Adam
import torch.utils.data as Data 
from torchvision import transforms 
from torchvision.datasets import FashionMNIST
# 模型加载选择GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
print(torch.cuda.device_count())
print(torch.cuda.get_device_name(0))
cuda
1
GeForce MX250
图像数据准备

调用 sklearn 的 datasets 模块的 FashionMNIST 的 API 函数读取。

# 使用 FashionMNIST 数据,准备训练数据集
train_data = FashionMNIST(
    root = './data/FashionMNIST',
    train = True,
    transform = transforms.ToTensor(),
    download = False
)

# 定义一个数据加载器
train_loader = Data.DataLoader(
    dataset = train_data,   # 数据集
    batch_size = 64,    # 批量处理的大小
    shuffle = False,   # 不打乱数据
    num_workers = 2,    # 两个进程
)

# 计算 batch 数
print(len(train_loader))
938

上述程序块定义了一个数据加载器,批量的数据块为 64.

接下来我们进行数据可视化分析,将 tensor 数据转为 numpy 格式,然后利用 imshow 进行可视化。

# 获得 batch 的数据

for step, (b_x, b_y) in enumerate(train_loader):
    if step > 0:
        break
# 可视化一个 batch 的图像
batch_x = b_x.squeeze().numpy()
batch_y = b_y.numpy()
label = train_data.classes
label[0] = 'T-shirt'

plt.figure(figsize = (12, 5))
for i in np.arange(len(batch_y)):
    plt.subplot(4, 16, i + 1)
    plt.imshow(batch_x[i, :, :], cmap = plt.cm.gray)
    plt.title(label[batch_y[i]], size = 9)
    plt.axis('off')
    plt.subplots_adjust(wspace = 0.05)


在这里插入图片描述

# 处理测试集
test_data = FashionMNIST(
    root = './data/FashionMNIST',
    train = False, # 不使用训练数据集
    download = False
)

# 为数据添加一个通道维度,并且取值范围归一化
test_data_x = test_data.data.type(torch.FloatTensor) / 255.0
test_data_x = torch.unsqueeze(test_data_x, dim = 1)
test_data_y = test_data.targets # 测试集标签

print(test_data_x.shape)
print(test_data_y.shape)
torch.Size([10000, 1, 28, 28])
torch.Size([10000])
LeNet 卷积神经网络搭建
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(1, 6, 5), # in_channels, out_channels, kernel_size
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2), # kernel_size, stride
            nn.Conv2d(6, 16, 5),
            nn.Sigmoid(),
            nn.MaxPool2d(2, 2)
        )
        self.fc = nn.Sequential(
            nn.Linear(16*4*4, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )

    def forward(self, img):
        feature = self.conv(img)
        output = self.fc(feature.view(img.shape[0], -1))
        return output
# 定义卷积网络对象
mylenet = LeNet().to(device)
from torchsummary import summary
summary(mylenet, input_size=(1, 28, 28))
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
            Conv2d-1            [-1, 6, 24, 24]             156
           Sigmoid-2            [-1, 6, 24, 24]               0
         MaxPool2d-3            [-1, 6, 12, 12]               0
            Conv2d-4             [-1, 16, 8, 8]           2,416
           Sigmoid-5             [-1, 16, 8, 8]               0
         MaxPool2d-6             [-1, 16, 4, 4]               0
            Linear-7                  [-1, 120]          30,840
           Sigmoid-8                  [-1, 120]               0
            Linear-9                   [-1, 84]          10,164
          Sigmoid-10                   [-1, 84]               0
           Linear-11                   [-1, 10]             850
================================================================
Total params: 44,426
Trainable params: 44,426
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.08
Params size (MB): 0.17
Estimated Total Size (MB): 0.25
----------------------------------------------------------------
# 输出网络结构
from torchviz import make_dot

x = torch.randn(1, 1, 28, 28).requires_grad_(True)
y = mylenet(x.to(device))
myCNN_vis = make_dot(y, params=dict(list(mylenet.named_parameters()) + [('x', x)]))
myCNN_vis

在这里插入图片描述

LeNet 网络训练和预测

训练集整体有 60000 60000 60000 张图像, 938 938 938 个 batch ,使用 80 % 80\% 80% 的 batch 用于模型训练, 20 % 20\% 20% 的用于模型验证

# 定义网络训练过程函数
def train_model(model, traindataloader, train_rate, criterion, optimizer, num_epochs = 25):
    '''
    模型,训练数据集(待切分),训练集百分比,损失函数,优化器,训练轮数
    '''
    # 计算训练使用的 batch 数量
    batch_num = len(traindataloader)
    train_batch_num = round(batch_num * train_rate)
    # 复制模型参数
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0
    train_loss_all = []
    val_loss_all =[]
    train_acc_all = []
    val_acc_all = []
    since = time.time()
    # 训练框架
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)
        train_loss = 0.0 
        train_corrects = 0
        train_num = 0
        val_loss = 0.0
        val_corrects = 0
        val_num = 0
        for step, (b_x, b_y) in enumerate(traindataloader):
            b_x, b_y = b_x.to(device), b_y.to(device)
            if step < train_batch_num:
                model.train()   # 设置为训练模式
                output = model(b_x)
                pre_lab = torch.argmax(output, 1)
                loss = criterion(output, b_y)   # 计算误差损失
                optimizer.zero_grad()   # 清空过往梯度
                loss.backward() # 误差反向传播
                optimizer.step()    # 根据误差更新参数
                train_loss += loss.item() * b_x.size(0)
                train_corrects += torch.sum(pre_lab == b_y.data)
                train_num += b_x.size(0)
            else:
                model.eval()    # 设置为验证模式
                output = model(b_x)
                pre_lab = torch.argmax(output, 1)
                loss = criterion(output, b_y)
                val_loss += loss.cpu().item() * b_x.size(0)
                val_corrects += torch.sum(pre_lab == b_y.data)
                val_num += b_x.size(0)
        # ======================小循环结束========================

        # 计算一个epoch在训练集和验证集上的损失和精度
        train_loss_all.append(train_loss / train_num)
        train_acc_all.append(train_corrects.double().item() / train_num)
        val_loss_all.append(val_loss / val_num)
        val_acc_all.append(val_corrects.double().item() / val_num)
        print('{} Train Loss: {:.4f} Train Acc: {:.4f}'.format(epoch, train_loss_all[-1], train_acc_all[-1]))
        print('{} Val Loss: {:.4f} Val Acc: {:.4f}'.format(epoch, val_loss_all[-1], val_acc_all[-1]))

        # 拷贝模型最高精度下的参数
        if val_acc_all[-1] > best_acc:
            best_acc = val_acc_all[-1]
            best_model_wts = copy.deepcopy(model.state_dict())
        time_use = time.time() - since
        print('Train and Val complete in {:.0f}m {:.0f}s'.format(time_use // 60, time_use % 60))
    # ===========================大循环结束===========================


    # 使用最好模型的参数
    model.load_state_dict(best_model_wts)
    train_process = pd.DataFrame(
        data = {'epoch': range(num_epochs),
                'train_loss_all': train_loss_all,
                'val_loss_all': val_loss_all,
                'train_acc_all': train_acc_all,
                'val_acc_all': val_acc_all})
    return model, train_process

模型的损失和识别精度组成数据表格 train_process 输出,使用 copy.deepcopy() 将模型最优的参数保存在 best_model_wts 中,最终所有的训练结果通过 model.load_state_dict(best_model_wts) 将最优的参数赋给最终的模型

下面对模型和优化器进行训练:

# 模型训练
optimizer = Adam(mylenet.parameters(), lr = 0.001)   # Adam优化器
criterion = nn.CrossEntropyLoss()   # 损失函数
myconvnet, train_process = train_model(mylenet, train_loader, 0.8, criterion, optimizer, num_epochs = 25)
Epoch 0/24
----------
0 Train Loss: 1.3207 Train Acc: 0.5002
0 Val Loss: 0.8333 Val Acc: 0.6904
Train and Val complete in 0m 16s
Epoch 1/24
----------
1 Train Loss: 0.7443 Train Acc: 0.7220
1 Val Loss: 0.6562 Val Acc: 0.7502
Train and Val complete in 0m 29s
Epoch 2/24
----------
2 Train Loss: 0.6337 Train Acc: 0.7557
2 Val Loss: 0.5853 Val Acc: 0.7726
Train and Val complete in 0m 43s
Epoch 3/24
----------
3 Train Loss: 0.5743 Train Acc: 0.7765
3 Val Loss: 0.5447 Val Acc: 0.7833
Train and Val complete in 0m 55s
Epoch 4/24
----------
4 Train Loss: 0.5339 Train Acc: 0.7921
4 Val Loss: 0.5160 Val Acc: 0.7925
Train and Val complete in 1m 7s
Epoch 5/24
----------
5 Train Loss: 0.5021 Train Acc: 0.8060
5 Val Loss: 0.4914 Val Acc: 0.8082
Train and Val complete in 1m 19s
Epoch 6/24
----------
6 Train Loss: 0.4777 Train Acc: 0.8157
6 Val Loss: 0.4719 Val Acc: 0.8207
Train and Val complete in 1m 32s
Epoch 7/24
----------
7 Train Loss: 0.4583 Train Acc: 0.8266
7 Val Loss: 0.4565 Val Acc: 0.8271
Train and Val complete in 1m 45s
Epoch 8/24
----------
8 Train Loss: 0.4416 Train Acc: 0.8341
8 Val Loss: 0.4432 Val Acc: 0.8324
Train and Val complete in 1m 56s
Epoch 9/24
----------
9 Train Loss: 0.4264 Train Acc: 0.8410
9 Val Loss: 0.4308 Val Acc: 0.8387
Train and Val complete in 2m 7s
Epoch 10/24
----------
10 Train Loss: 0.4128 Train Acc: 0.8455
10 Val Loss: 0.4191 Val Acc: 0.8434
Train and Val complete in 2m 18s
Epoch 11/24
----------
11 Train Loss: 0.4006 Train Acc: 0.8498
11 Val Loss: 0.4083 Val Acc: 0.8475
Train and Val complete in 2m 30s
Epoch 12/24
----------
12 Train Loss: 0.3896 Train Acc: 0.8540
12 Val Loss: 0.3983 Val Acc: 0.8503
Train and Val complete in 2m 43s
Epoch 13/24
----------
13 Train Loss: 0.3795 Train Acc: 0.8580
13 Val Loss: 0.3894 Val Acc: 0.8531
Train and Val complete in 2m 53s
Epoch 14/24
----------
14 Train Loss: 0.3704 Train Acc: 0.8614
14 Val Loss: 0.3812 Val Acc: 0.8552
Train and Val complete in 3m 3s
Epoch 15/24
----------
15 Train Loss: 0.3621 Train Acc: 0.8641
15 Val Loss: 0.3739 Val Acc: 0.8582
Train and Val complete in 3m 14s
Epoch 16/24
----------
16 Train Loss: 0.3545 Train Acc: 0.8669
16 Val Loss: 0.3674 Val Acc: 0.8613
Train and Val complete in 3m 25s
Epoch 17/24
----------
17 Train Loss: 0.3474 Train Acc: 0.8696
17 Val Loss: 0.3615 Val Acc: 0.8628
Train and Val complete in 3m 36s
Epoch 18/24
----------
18 Train Loss: 0.3409 Train Acc: 0.8720
18 Val Loss: 0.3563 Val Acc: 0.8652
Train and Val complete in 3m 47s
Epoch 19/24
----------
19 Train Loss: 0.3347 Train Acc: 0.8742
19 Val Loss: 0.3516 Val Acc: 0.8678
Train and Val complete in 3m 58s
Epoch 20/24
----------
20 Train Loss: 0.3289 Train Acc: 0.8761
20 Val Loss: 0.3473 Val Acc: 0.8699
Train and Val complete in 4m 10s
Epoch 21/24
----------
21 Train Loss: 0.3234 Train Acc: 0.8783
21 Val Loss: 0.3434 Val Acc: 0.8718
Train and Val complete in 4m 22s
Epoch 22/24
----------
22 Train Loss: 0.3182 Train Acc: 0.8803
22 Val Loss: 0.3397 Val Acc: 0.8742
Train and Val complete in 4m 36s
Epoch 23/24
----------
23 Train Loss: 0.3132 Train Acc: 0.8821
23 Val Loss: 0.3362 Val Acc: 0.8761
Train and Val complete in 4m 47s
Epoch 24/24
----------
24 Train Loss: 0.3085 Train Acc: 0.8838
24 Val Loss: 0.3331 Val Acc: 0.8771
Train and Val complete in 4m 58s
# 可视化训练过程
plt.figure(figsize = (12, 4))
plt.subplot(1, 2, 1)
plt.plot(train_process.epoch, train_process.train_loss_all, 'ro-', label = 'Train loss')
plt.plot(train_process.epoch, train_process.val_loss_all, 'bs-', label = 'Val loss')
plt.legend()
plt.xlabel('epoch')
plt.ylabel('Loss')

plt.subplot(1, 2, 2)
plt.plot(train_process.epoch, train_process.train_acc_all, 'ro-', label = 'Train acc')
plt.plot(train_process.epoch, train_process.val_acc_all, 'bs-', label = 'Val acc')
plt.legend()
plt.xlabel('epoch')
plt.ylabel('Acc')

plt.show()


在这里插入图片描述

同时我们计算模型的泛化能力,使用输出的模型在测试集上进行预测:

# 测试集预测,并可视化预测效果
mylenet.eval()
output = mylenet(test_data_x.to(device))
pre_lab = torch.argmax(output, 1)
acc = accuracy_score(test_data_y, pre_lab.cpu())
print(test_data_y)
print(pre_lab)
print('测试集上的预测精度为', acc)
tensor([9, 2, 1,  ..., 8, 1, 5])
tensor([9, 2, 1,  ..., 8, 1, 5], device='cuda:0')
测试集上的预测精度为 0.8677
# 计算测试集上的混淆矩阵并可视化
conf_mat = confusion_matrix(test_data_y, pre_lab.cpu())
df_cm = pd.DataFrame(conf_mat, index = label, columns = label)
heatmap = sns.heatmap(df_cm, annot = True, fmt = 'd', cmap = 'YlGnBu')
heatmap.yaxis.set_ticklabels(heatmap.yaxis.get_ticklabels(), rotation = 0, ha = 'right')
heatmap.xaxis.set_ticklabels(heatmap.xaxis.get_ticklabels(), rotation = 45, ha = 'right')
plt.ylabel('True label')
plt.xlabel('Predicted label')
plt.show()


在这里插入图片描述

我们发现,最容易预测错误的是 T-shirt 和 Shirt ,相互预测出错的样本量为 154 154 154 个。

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是基于PyTorch实现LeNet网络的代码,包括了平均池化和正则化: ```python import torch import torch.nn as nn import torch.optim as optim import torchvision.datasets as dsets import torchvision.transforms as transforms from torch.autograd import Variable # 定义LeNet网络结构 class LeNet(nn.Module): def __init__(self): super(LeNet, self).__init__() self.conv1 = nn.Conv2d(1, 6, kernel_size=5, padding=2) self.avgpool1 = nn.AvgPool2d(kernel_size=2, stride=2) self.conv2 = nn.Conv2d(6, 16, kernel_size=5) self.avgpool2 = nn.AvgPool2d(kernel_size=2, stride=2) self.fc1 = nn.Linear(16*5*5, 120) self.fc2 = nn.Linear(120, 84) self.fc3 = nn.Linear(84, 10) self.relu = nn.ReLU() self.dropout = nn.Dropout(p=0.5) def forward(self, x): out = self.relu(self.conv1(x)) out = self.avgpool1(out) out = self.relu(self.conv2(out)) out = self.avgpool2(out) out = out.view(out.size(0), -1) out = self.relu(self.fc1(out)) out = self.relu(self.fc2(out)) out = self.dropout(out) out = self.fc3(out) return out # 加载MNIST数据集 train_dataset = dsets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True) test_dataset = dsets.MNIST(root='./data', train=False, transform=transforms.ToTensor()) # 定义超参数 batch_size = 100 learning_rate = 0.001 num_epochs = 10 # 加载数据集 train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True) test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False) # 实例化模型 model = LeNet() # 定义损失函数和优化器 criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=learning_rate, weight_decay=0.01) # 训练模型 for epoch in range(num_epochs): for i, (images, labels) in enumerate(train_loader): images = Variable(images) labels = Variable(labels) # 前向传播 outputs = model(images) loss = criterion(outputs, labels) # 反向传播和优化 optimizer.zero_grad() loss.backward() optimizer.step() if (i+1) % 100 == 0: print('Epoch [%d/%d], Step [%d/%d], Loss: %.4f' % (epoch+1, num_epochs, i+1, len(train_dataset)//batch_size, loss.item())) # 测试模型 model.eval() # 设置模型为评估模式 correct = 0 total = 0 for images, labels in test_loader: images = Variable(images) outputs = model(images) _, predicted = torch.max(outputs.data, 1) total += labels.size(0) correct += (predicted == labels).sum() print('Accuracy of the model on the test images: %d %%' % (100 * correct / total)) ``` 在上面的代码中,我们使用了`nn.AvgPool2d`实现了平均池化,并在LeNet的全连接层中使用了`nn.Dropout`实现了正则化。运行上面的代码将会训练一个LeNet网络,并在MNIST测试集上进行测试,输出测试集上的准确率结果。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值