最近在温习神经网络,决定做一件之前从未长久坚持过的事情——将近期复习成果保存并分享给更多的人,欢迎大家一起交流探讨。
本系列文章的目的不是为了1:1复现论文,而是增强对各个模块的熟悉,加入自己的idea,比如文章中有一些已经过时的老旧方法,会替换成一些效果好的方案。通过本系列文章分享,提升对深度学习框架的熟练程度,练习深度学习相关部署工具,如ONNX,TensorRT,OpenVino,NCNN等。
学习本系列需要具有一定的深度学习了解,Python、C++编程基础。
作为CNN早期论文,Gradient-based learning applied to document recognition在深度学习领域带来重大影响,也是很多人入门必看论文之一,为后续各种模型奠定了基础。从Google Scholar的引用数量也可以看出它的启发性。
关于论文的创新和重要性,大家可以自行下载论文阅读。我们仅需要了解网络的结构并进行实现。
可以看出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())
以上就是全部实现代码,有问题欢迎大家一起探讨。