经典神经网络实现——LeNet

前言

最近在温习神经网络,决定做一件之前从未长久坚持过的事情——将近期复习成果保存并分享给更多的人,欢迎大家一起交流探讨。

本系列文章的目的不是为了1:1复现论文,而是增强对各个模块的熟悉,加入自己的idea,比如文章中有一些已经过时的老旧方法,会替换成一些效果好的方案。通过本系列文章分享,提升对深度学习框架的熟练程度,练习深度学习相关部署工具,如ONNX,TensorRT,OpenVino,NCNN等。

学习本系列需要具有一定的深度学习了解,Python、C++编程基础。

LeNet介绍

作为CNN早期论文,Gradient-based learning applied to document recognition在深度学习领域带来重大影响,也是很多人入门必看论文之一,为后续各种模型奠定了基础。从Google Scholar的引用数量也可以看出它的启发性。
LeNet引用
关于论文的创新和重要性,大家可以自行下载论文阅读。我们仅需要了解网络的结构并进行实现。
LeNet网络结构
可以看出LeNet的输入为单通道手写数字图片,经过几层卷积、采样、全连接后得到网络输出,进行分类识别。

代码实现

此时我们思考一下,要实现一个神经网络需要实现哪些部分?首先要根据网络架构图构建网络的结构,其次选取合适的损失函数,优化器,评价指标,数据读取方法以及训练测试代码。

构建网络结构

首先根据上面结构图分析,论文包括两层卷积,网络第一层卷积的输入为13232的手写字体图像,其中1为通道数,32为图像的宽高,输出为62828,因此需要6个卷积核,kernel size设置为5,padding设置为2。对于这里不了解的同学可以学习下CNN原理(卷积核决定输出通道数,步长和核尺寸决定了输出的尺寸)。卷积过后经过激活函数和池化进行下采样,下采样的作用是防止网络参数量过多带来性能开销。重复进行上一步降低特征图尺寸,增加通道数,提取更细化特征。最后经过两层全连接层将输出限制在10维,代表10个类别(0~9)。

class LeNet(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.backbone = nn.Sequential(
            Reshape(),
            nn.Conv2d(1, 6, kernel_size=5, padding=2),
            nn.Sigmoid(),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Conv2d(6, 16, kernel_size=5),
            nn.Sigmoid(),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Flatten(),
            nn.Linear(16*5*5, 120),
            nn.Sigmoid(),
            nn.Linear(120, 84),
            nn.Sigmoid(),
            nn.Linear(84, 10)
        )
    def forward(self, x):
        x = self.backbone(x)
        return x

优化器

优化器使用SGD,由于是分类模型,损失函数可以使用交叉熵。

    optimizer = torch.optim.SGD(model.backbone.parameters(),lr=lr)
    loss = nn.CrossEntropyLoss()

数据集加载

关于数据集的加载可以使用d2l中的加载mnist方法。

train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)

完整代码

完整代码如下,由于考虑到Sigmoid的优缺点。。。嘿嘿,还是替换成BN+ReLU的方式了(替换后效果提升了10%左右)。

import torch
import torch.nn as nn
from d2l import torch as d2l

class Reshape(nn.Module):
    def forward(self, x):
        return x.view(-1,1,28,28)

def evaluate_accuracy_gpu(net, data_iter, device=None):
    if isinstance(net, nn.Module):
        net.eval()
        if not device:
            device = next(iter(net.parameters())).device
    metric = d2l.Accumulator(2)
    for X,y in data_iter:
        if isinstance(X, list):
            X = [x.to(device) for x in X]
        else:
            X =X.to(device)
        y = y.to(device)
        metric.add(d2l.accuracy(net(X), y), y.numel())
    return metric[0]/metric[1]

def train(model, train_iter, test_iter, epochs, lr, device):
    def init_weights(m):
        if type(m) == nn.Linear or type(m) == nn.Conv2d:
            nn.init.xavier_uniform_(m.weight)
    model.backbone.apply(init_weights)
    print('training on ', device)
    model.to(device)
    optimizer = torch.optim.SGD(model.backbone.parameters(), lr=lr)
    loss = nn.CrossEntropyLoss()
    animator = d2l.Animator(xlabel='epoch', xlim=[1,epochs],
                            legend=['train loss', 'train acc', 'test acc'])
    timer, num_batches = d2l.Timer(), len(train_iter)
    for epoch in range(epochs):
        metric = d2l.Accumulator(3)
        model.train()
        for i,(X, y) in enumerate(train_iter):
            timer.start()
            optimizer.zero_grad()
            X,y = X.to(device), y.to(device)
            y_hat = model(X)
            l = loss(y_hat, y)
            l.backward()
            optimizer.step()
            metric.add(l * X.shape[0], d2l.accuracy(y_hat, y), X.shape[0])
            timer.stop()
            train_l = metric[0] / metric[2]
            train_acc = metric[1] / metric[2]
        if (i+1)%(num_batches // 5) == 0 or i == num_batches -1:
            animator.add(epoch + (i+1) / num_batches, (train_l, train_acc, None))
        test_acc = evaluate_accuracy_gpu(model, test_iter)
        print('Epoch', epoch)
        print(f'loss {train_l:.3f}, train acc {train_acc:,.3f},' f'test acc {test_acc:.3f}')
        print(f'{metric[2] * epochs / timer.sum():.1f} examples/sec' f'on {str(device)}')
    print(f'loss {train_l:.3f}, train acc {train_acc:,.3f},' f'test acc {test_acc:.3f}')
    print(f'{metric[2] * epochs/timer.sum():.1f} examples/sec' f'on {str(device)}')
    print('finished')
    return
    
class LeNet(nn.Module):
    def __init__(self) -> None:
        super().__init__()
        self.backbone = nn.Sequential(
            Reshape(),
            nn.Conv2d(1, 6, kernel_size=5, padding=2),
            nn.BatchNorm2d(6),
            nn.ReLU(inplace=True),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Conv2d(6, 16, kernel_size=5),
            nn.BatchNorm2d(16),
            nn.ReLU(inplace=True),
            nn.AvgPool2d(kernel_size=2, stride=2),
            nn.Flatten(),
            nn.Linear(16*5*5, 120),
            nn.ReLU(inplace=True),
            nn.Linear(120, 84),
            nn.ReLU(inplace=True),
            nn.Linear(84, 10)
        )
    def forward(self, x):
        x = self.backbone(x)
        return x

if __name__=="__main__":
    x = torch.rand(size=(2,1,28,28), dtype=torch.float32)
    model = LeNet()
    #model(x)
    batch_size = 256
    train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size=batch_size)
    lr, epochs = 0.9, 10
    train(model, train_iter, test_iter, epochs, lr, d2l.try_gpu())

运行结果

运行效果图

以上就是全部实现代码,有问题欢迎大家一起探讨。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值