Pytorch:卷积神经网络-LeNet

2022-09-27 12:46:33

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 10001000 像素的彩色照片(含 3 33 个通道)。即使全连接层输出个数仍是 256 256256 ,该层权重参数的形状是 3000000 × 2563000000 × 256 3000000\times2563000000\times2563000000×2563000000×256 :它占用了大约 3 33 GB的内存或显存。这带来过复杂的模型和过高的存储开销。

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

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

Lenet 模型

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

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

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

在这里插入图片描述

import numpyas npimport pandasas pdfrom sklearn.metricsimport accuracy_score, confusion_matriximport matplotlib.pyplotas pltimport seabornas snsimport copyimport timeimport torchimport torch.nnas nnfrom torch.optimimport Adamimport torch.utils.dataas Datafrom torchvisionimport transformsfrom torchvision.datasetsimport 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)inenumerate(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 iin 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 卷积神经网络搭建

classLeNet(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))defforward(self, img):
        feature= self.conv(img)
        output= self.fc(feature.view(img.shape[0],-1))return output
# 定义卷积网络对象
mylenet= LeNet().to(device)
from torchsummaryimport 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 torchvizimport 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 6000060000 张图像, 938 938938 个 batch ,使用 80 % 80\%80% 的 batch 用于模型训练, 20 % 20\%20% 的用于模型验证

# 定义网络训练过程函数deftrain_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 epochinrange(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=0for step,(b_x, b_y)inenumerate(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()- sinceprint('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
  • 作者:宅家的小魏
  • 原文链接:https://blog.csdn.net/weixin_44979150/article/details/122778759
    更新时间:2022-09-27 12:46:33