
LeNet的提出并非出于纯粹的理论兴趣,而是为了解决一个非常具体且具有巨大商业价值的实际问题:自动识别银行支票上的手写数字。
在20世纪80年代末到90年代,银行每天需要处理海量的支票,手工录入数字效率低下、成本高昂且容易出错。因此,如何让机器“看懂”手写数字,实现自动化处理,成为了一个紧迫的工业界需求。
在当时(20世纪90年代),传统方法依赖“人工设计特征”,流程复杂且效果有限。Yann LeCun等人的突破性想法是:让模型自己从数据中学习特征。
为此,他们设计了第一个成功应用的卷积神经网络(CNN)——LeNet。其意义在于:
因此,LeNet不仅是手写数字识别问题的解决方案,更开创了现代深度学习在计算机视觉领域的先河,为后续所有CNN模型奠定了基础。
核心流程:输入 → 卷积 → 池化 → 卷积 → 池化 → 全连接 → 输出

输入:32×32 的灰度图像。
C1 - 卷积层:
S2 - 池化层:
C3 - 卷积层:
S4 - 池化层:
C5 - 全连接层:
F6 - 全连接层:
输出层:
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.c1=nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,stride=1,padding=2)
self.sig=nn.Sigmoid()
self.s2=nn.AvgPool2d(kernel_size=2,stride=2)
self.c3=nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5)
self.s4=nn.AvgPool2d(kernel_size=2,stride=2)
self.flatten=nn.Flatten()
self.f5=nn.Linear(16*5*5,120)
self.f6 = nn.Linear(120, 84)
self.f7 = nn.Linear(84, 10)
def forward(self,x):
x=self.sig(self.c1(x))
x=self.s2(x)
x=self.sig(self.c3(x))
x=self.s4(x)
x=self.flatten(x)
x=self.f5(x)
x=self.f6(x)
x=self.f7(x)
return x卷积层
self.c1: 第一个卷积层,输入通道数为1(灰度图像),输出通道数为6,卷积核大小5×5,步长1,填充2
self.c3: 第二个卷积层,输入通道数为6,输出通道数为16,卷积核大小5×5
激活函数
self.sig: Sigmoid激活函数,用于引入非线性变换
池化层
self.s2: 平均池化层,池化窗口大小2×2,步长2
self.s4: 平均池化层,池化窗口大小2×2,步长2
全连接层
self.f5: 线性层,输入维度为16*5*5=400,输出维度为120
self.f6: 线性层,输入维度为120,输出维度为84
self.f7: 线性层,输入维度为84,输出维度为10(分类数量)
辅助层
self.flatten: 展平层,将多维张量转换为一维向量
验证代码
if __name__=='__main__':
device=torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model=LeNet().to(device)
print(summary(model,(1,28,28)))执行结果如下

def train_val_data_process():
train_data = FashionMNIST(root='./data',
train=True,
download=True,
transform=transforms.Compose([transforms.Resize(size=227), transforms.ToTensor()]))
train_data, val_data = Data.random_split(train_data, [round(0.8 * len(train_data)),round(0.2 * len(train_data))])
train_dataloader = Data.DataLoader(dataset=train_data,
batch_size=32,
shuffle=True,
num_workers=2)
val_dataloader = Data.DataLoader(dataset=train_data,
batch_size=32,
shuffle=True,
num_workers=2)
return train_dataloader,val_dataloader加载FashionMNIST训练数据集,设置图像大小为227×227并转换为张量
将数据集按8:2比例随机分割为训练集和验证集
创建训练和验证数据加载器,批量大小为32,启用数据混洗和2个工作进程
def train_model_process(model,train_dataloader,val_dataloader,num_epochs):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
optimizer=torch.optim.Adam(model.parameters(),lr=0.001)
#交叉熵z失函数
criterion=nn.CrossEntropyLoss()
model=model.to(device)
#复制当前模型的参数
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
train_loss_all = []
val_loss_all = []
val_acc_all= []
train_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_correct = 0.0
val_loss = 0.0
val_correct = 0.0
train_num = 0
val_num = 0
for step,(b_x,b_y) in enumerate(train_dataloader):
b_x,b_y=b_x.to(device),b_y.to(device)
model.train()
#前向传播
output=model(b_x)
#查找概率最大的索引
pre_lab=torch.argmax(output,dim=1)
loss = criterion(output, b_y)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss+=loss.item() * b_x.size(0)
train_correct+=torch.sum(pre_lab == b_y)
train_num+=b_x.size(0)
for step,(b_x,b_y) in enumerate(val_dataloader):
b_x,b_y=b_x.to(device),b_y.to(device)
model.eval()
# 前向传播
output = model(b_x)
pre_lab = torch.argmax(output, dim=1)
loss = criterion(output, b_y)
# 查找概率最大的索引
pre_lab = torch.argmax(output, dim=1)
loss = criterion(output, b_y)
val_loss += loss.item() * b_x.size(0)
val_correct += torch.sum(pre_lab == b_y)
val_num += b_x.size(0)
#计算并保持每一轮次迭代的loss值
train_loss_all.append(train_loss / train_num)
train_acc_all.append(train_correct.double().item() / train_num)
val_loss_all.append(val_loss / val_num)
val_acc_all.append(val_correct.double().item() / val_num)
print('train_loss:{:.4f},train_acc:{:.4f}'.format(train_loss_all[-1],train_acc_all[-1]))
print('val_loss:{:.4f},val_acc:{:.4f}'.format(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('模型训练和验证耗费的时间:{:.0f}m {:.0f}s'.format(time_use // 60, time_use % 60))
#加载权重参数并保存
torch.save(best_model_wts,'C:/Users/28604/Desktop/AlexNet/best_model.pth')
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 train_process
函数首先初始化了设备、优化器(Adam)和损失函数(交叉熵),然后在每个epoch中分别处理训练和验证数据。在训练阶段,模型设置为训练模式(model.train()),进行前向传播、损失计算和反向传播更新参数。在验证阶段,模型切换到评估模式(model.eval()),不进行梯度更新。
def matplot_acc_loss(train_process):
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("loss")
plt.legend()
plt.show()函数创建一个12x4英寸的画布,分为两个子图:左侧子图绘制训练损失和验证损失随epoch的变化曲线,右侧子图绘制训练准确率和验证准确率随epoch的变化曲线,便于监控模型训练效果。
if __name__ == '__main__':
LeNet=AlexNet()
train_dataloader,val_dataloader=train_val_data_process()
train_process = train_model_process(LeNet,train_dataloader,val_dataloader,num_epochs=5)
matplot_acc_loss(train_process)
测试数据加载
def test_val_data_process():
test_data = FashionMNIST(root='./data',
train=False,
download=True,
transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]))
test_data, val_data = Data.random_split(test_data, [round(0.8 * len(test_data)),round(0.2 * len(test_data))])
test_dataloader = Data.DataLoader(dataset=test_data,
batch_size=1,
shuffle=True,
num_workers=0)
return test_dataloader1)加载FashionMNIST测试数据集,进行尺寸调整和张量转换;
2)将数据集按8:2比例随机分割为测试集和验证集;
3)创建测试数据的数据加载器,设置批次大小为1并启用随机打乱;
4)返回测试数据加载器。
具体测试过程
def test_model_process(model,test_dataloader):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model=model.to(device)
test_correct = 0.0
test_num = 0
#测试不进行梯度计算
with torch.no_grad():
for test_data_x, test_data_y in test_dataloader:
test_data_x,test_data_y=test_data_x.to(device),test_data_y.to(device)
model.eval()
test_output = model(test_data_x)
test_lab = torch.argmax(test_output, dim=1)
#计算正确几率
test_correct += torch.sum(test_lab == test_data_y)
test_num += test_data_x.size(0)
print('test_acc:{:.4f}'.format(test_correct.double().item() / test_num))
def test_model_process1(model,test_dataloader):
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model=model.to(device)
test_correct = 0.0
test_num = 0
asse=[u'T恤/上衣' , u'裤子' , u'套衫' , u' Dress' , u'外套' , u'凉鞋' , u'衬衫' , u'运动鞋' , u'包' , u'短靴']
#测试不进行梯度计算
with torch.no_grad():
for test_data_x, test_data_y in test_dataloader:
test_data_x,test_data_y=test_data_x.to(device),test_data_y.to(device)
model.eval()
test_output = model(test_data_x)
test_lab = torch.argmax(test_output, dim=1)
result=test_lab.item()
label=test_data_y.item()
print('预测结果为:{},真实结果为:{}'.format(asse[result],asse[label]))
#计算正确几率
test_correct += torch.sum(test_lab == test_data_y)
test_num += test_data_x.size(0)
print('test_acc:{:.4f}'.format(test_correct.double().item() / test_num))
test_model_process 函数
这个函数用于对训练好的模型进行测试,计算准确率。首先设置设备(自动选择GPU或CPU),然后将模型移动到指定设备并设置为评估模式。在 with torch.no_grad(): 上下文中遍历测试数据集,将输入数据和标签移动到设备,获取模型预测结果,并计算预测准确率,最后打印整体测试准确率。
test_model_process1 函数
这个函数在基础测试功能上增加了预测结果的可视化输出。除了与基础版本相同的准确率统计功能外,还定义了FashionMNIST数据集的10个类别名称列表 asse,包括T恤/上衣、裤子、套衫、Dress、外套、凉鞋、衬衫、运动鞋、包和短靴。函数会输出每张图片的预测结果和真实标签,便于查看模型的预测效果。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。