Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)

1、环境依赖

torch=1.6.0+cu101
CUDA: 10.1
cuDNN: 7.6.2

2、多通道卷积操作

在许多应用程序中,我们处理的是具有多个通道的图像。一个典型的例子是RGB图像。每个RGB通道强调原始图像的不同方面,如图1所示。
Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)_第1张图片
多通道卷积就如图2所示。每个核被应用到前一层的输入通道上,以生成一个输出通道。这是一个核扩展的过程。我们对所有核重复这样的过程,以生成多个通道。然后将这些通道中的每一个相加,形成一个单独的输出通道。下图过程应该更清晰。
这里输入层是一个5 x 5 x 3矩阵,有3个通道。滤波器是一个3 x 3 x 3矩阵。首先,滤波器中的每个核分别应用于输入层中的三个通道。进行三次卷积,产生3个尺寸为3 x 3的通道。
Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)_第2张图片
然后将这三个通道相加(元素相加),形成一个单通道(3 x 3 x 1)。该通道是使用滤波器(3 x 3 x 3矩阵)对输入层(5 x 5 x 3矩阵)进行卷积的结果。
Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)_第3张图片

3、LeNet神经网络

LeNet 是卷积神经网络的奠基人 Yann LeCun 在1998 年提出的,用于解决手写数字识别的视觉任务。它的结构非常简单,我们通过这个简单的网络来学习时下最为流行的卷积神经网络结构。如今各大深度学习框架中使用的 LeNet 都是经过简化改进过的 LeNet-5N(5 表示网络具有5个层),如图1所示,和原始的 LeNet 有些许不同,激活函数数也从原来的tanh 改为现在常用的 ReLU。
如今的 LeNet 网络与卷积层之后紧跟池化层再接ReLU 层的套路不同,而是卷积层 1一ReLU一池化层1一卷积层 2一 ReLU一 池化层2再接全连接层,但卷积层后紧接池化层的模式依旧不变。在PyTorch 中默认使用的padding模式是 valid而不是 same (same 指的是输人特征图和输出特征图的尺寸保持一致,vali模式的输出特征图经过滤波器后可能会变小)。

Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)_第4张图片

4、过程实现

4.1 引入依赖

import torch
import torch.nn.functional as F
from torchvision import datasets, transforms

4.2实现卷积函数

def Conv2d(x, weight, bias, stride, padding):
    '''
    x:输入特征图
    weight:卷积核
    bias:偏置值
    stride:步长
    padding:填充
    '''
    # 批样本量(batch size), 输入通道,输入的长和宽
    n, c, h_in, w_in = x.shape
    # 输出通道,输入通道,卷积核尺寸(k, j)
    d, c, k, j = weight.shape
    
    if padding != 0: # padding不等于0时,做补零操作
        x_padding = torch.zeros(n, c, h_in + 2*padding, w_in + 2*padding).to(x.device)
        x_padding[:, :, padding:-padding, padding:-padding] = x # 对输入进行补零操作
    if padding == 0: # padding等于0时,不做补零操作
        x_padding = x
        
    x_padding = x_padding.unfold(2, k, stride)# 按照滑动窗展开
    x_padding = x_padding.unfold(3, j, stride)
    out = torch.einsum( # 按照滑动窗相乘,并将所有输入通道上卷积结果相加
        'nchwkj, dckj->ndhw',
        x_padding, weight
    )
    out = out + bias.view(1, -1, 1, 1) # 添加偏置值
    return out

4.3 实现激活函数ReLU

def ReLU(x):
    out = torch.clamp(x, min=0) # 截断元素值至[0, x]之间
    return out

4.4 实现全连接层

def Linear(x, weight, bias):
    out = torch.matmul(x, weight) + bias.view(1, -1) # 做矩阵乘法,加上偏置值
    return out

4.5 构建LeNet

def model(x, params):
    x = Conv2d(x=x, weight=params[0], bias=params[1], stride=1, padding=2)
    x = F.max_pool2d_with_indices(input=x, kernel_size=2, stride=2)[0] # max_pool2d_with_indices函数返回一个元组,元组内是一个tensor类型
    x = Conv2d(x=x, weight=params[2], bias=params[3], stride=1, padding=2)
    x = ReLU(x)
    x = F.max_pool2d_with_indices(input=x, kernel_size=2, stride=2)[0]
    x = x.view(x.size(0), -1)
    x = Linear(x, params[4], params[5])
    x = ReLU(x)
    x = Linear(x, params[6], params[7])
    return x

4.6 卷积核与偏置值

init_std = 0.1
params = [
    torch.randn(20,1,5,5) * init_std,      # Conv1的卷积核(weight)与偏置值(bias)
    torch.zeros(20),
    torch.randn(50,20,5,5) * init_std,      # Conv2的卷积核(weight)与偏置值(bias)
    torch.zeros(50),
    
    torch.randn(2450, 500) * init_std,      # Linear1的weight与偏置值(bias)
    torch.zeros(500),
    torch.randn(500, 10) * init_std,      # Linear2的weight与偏置值(bias)
    torch.zeros(10)
]
for p in params:                          # 设置所有的参数可导便于训练
    p.requires_grad = True

4.7 加载数据集

batch_size_train = 64 # 训练批样本量
batch_size_test = 64 # 测试批样本量

train_dataset = datasets.MNIST(root="./data/mnist", # 数据集下载后的存放路径
                                  train=True,
                                  transform = transforms.Compose([
                                      transforms.ToTensor(),
                                      transforms.Normalize((0.1307,), (0.3081,)),
                                      
                                      
                                  ]),
                                  download=True
                              )

test_dataset = datasets.MNIST(root="./data/mnist", # 数据集下载后的存放路径
                                  train=False,
                                  transform = transforms.Compose([
                                      transforms.ToTensor(),
                                      transforms.Normalize((0.1307,), (0.3081,)),
                                      
                                  ])
                              
                                  
                              )

train_loader = torch.utils.data.DataLoader(dataset=train_dataset,
                                           # # 从数据集中每次抽出batch_size个样本
                                          batch_size=batch_size_train,
                                          shuffle=True)

test_loader = torch.utils.data.DataLoader(dataset=test_dataset,
                                          batch_size=batch_size_test,
                                          shuffle=False)

4.8 训练代码

lr = 0.01 # 学习率
epochs = 10 # 训练周期
log_interval = 100 # 显示训练结果间隔
for epoch in range(epochs):
    for idx, (data, label) in enumerate(train_loader):
        output = model(data, params) # 前向计算
        loss = F.cross_entropy(output, label) # 计算交叉熵损失函数
        for p in params:
            if p.grad is not None:
                p.grad.zero_() # 注意:一定在反向传播前清除梯度值
        
        loss.backward() # 反向传播
        for p in params:
            p.data = p.data - lr * p.grad.data # SGD更新参数,只能用.data,否则是对于可微变量的赋值 破坏前向计算,导致不可微分
        
        if idx % log_interval == 0:
            print("Epoch %3d [%3d/%3d]\tLoss: %.4f" % (epoch, idx, len(train_loader), loss.item()))
            
    correct_num = 0
    total_num = 0
        
    with torch.no_grad():   # 测试时关闭自动微分
        for data, label in test_loader:
            output = model(data, params)
            pred = output.max(1)[1] # 求出置信度最高的结果,按行求最大值
            # 将预测结果predicts与标签labels对比(两个数组比较相同元素个数),求出预测结果predicts中与标签labels相同的元素的个数
            correct_num += (pred == label).sum().item()
            total_num += len(data)
        
    acc = correct_num / total_num # 计算测试集正确率 输出测试结果
    print("...Testing @ Epoch %03d\tAcc: %.4f" % (epoch, acc))    
        

5、训练结果

直到训练循环结束。可以看到每一轮测试的准确率在逐步提升。以我的实验结果来看,10轮训练周期后就可以达到 98%的淮确率。大家可以自我探索网络结构和超参数设置,训练达到更高准确率的水平。
Pytorch张量与自动微分:纯过程式搭建LeNet实现MNIST图像分类(附源码)_第5张图片

6、源码链接

链接: https://pan.baidu.com/s/1jgbH_H5oviSStlpHqJlL4w
提取码: 47id

你可能感兴趣的:(pytorch,分类,深度学习)