完整代码已经上传 GitHub(https://github.com/wwwwkd/Digit-Recognition)有帮助的话给个小星星!!!
代码是自己写的,有些不严谨,有更好的方法或者思路,希望大家之间相互指点相互进步。首先提供思路,然后在提供部分代码,然后在展示效果图。
功能要求:可以根据自己手工书写一个数字得带小数,拍照后,程序能将该手写数字转换成对应的数字。 经过查阅资料,将其大致分为一下三个部分:
① 数字的定位、分割、保存. ② 小数点的识别. ③ 网络的训练、测试和最佳模型参数保存加载.
LeNet5的网络结构示意图如图3所示:
该网络分为三部分,第一部分,输入图像;第二部,分特征提取;第三部分分类识别。 本次使用的数据集也是经典的Mnist数据集,通过引入视觉工具包torchvision其中的torchvision.datasets方法中含有多个经典数据集,其中就包含Mnist。在加载数据集(torch.utils.data.DataLoader())时选择打乱数据,也是数据增强的一个方法。 然后就是继承nn.Module类进行网络的编写,对于网络部分,复现LeNet5网络。网络的训练集loss曲线如图4所示 最终识别效果如图所示 下面仅展示部分代码只含有数字定位并不包含小数点识别,小数点的识别见下一篇,完整代码见 GitHub https://github.com/wwwwkd/Digit-Recognition 网络部分代码如下:
import torch from torch import nn from torch.nn import functional as F from utils import Flatten class LeNet5(nn.Module): ''' 1.Conv1 Block 2.Conv2 Block 3.Flatten layer1 4.Flatten layer2 ----参数设置------ 使用优化器adam:其中的 weight_decay = 0.01 学习率learning rate:lr = 1e-4 激活函数active function:relu ''' def __init__(self): super(LeNet5, self).__init__() # 初始化函数并实现 # -----Conv1 Block----- self.conv1_block = nn.Sequential( nn.Conv2d(1, 10, kernel_size=5, stride=1), nn.MaxPool2d(2) ) # -----Conv2 Block----- self.conv2_block = nn.Sequential( nn.Conv2d(10, 20, kernel_size=5, stride=1), nn.MaxPool2d(2) ) # -----Flatten layer1----- self.fc1 = nn.Sequential( Flatten(), nn.Linear(20*4*4, 120), # (b,225*1*37)=>(b,4) ) # -----Flatten layer2----- self.fc2 = nn.Sequential( nn.Linear(120, 84), # (b,225*1*37)=>(b,4) ) # -----Flatten layer3----- self.fc3 = nn.Sequential( Flatten(), nn.Linear(84, 10), # (b,225*1*37)=>(b,4) ) def forward(self, x): # 前向传播 # [b, 1, 28, 28] => [b, 10, 12, 12] x1 = F.relu(self.conv1_block(x)) #print('Conv1 Block', x1.shape) # [b, 10, 12, 12] => [b, 20, 4, 4] x2 = F.relu(self.conv2_block(x1)) #print('Conv2 Block', x2.shape) #[b, 120*4*4] => [b, 120] x3 = self.fc1(x2) #print('flatetn layer1', x3.shape) # [b, 120] => [b, 84] x4 = self.fc2(x3) #print('flatetn layer1', x4.shape) # [b, 84] => [b, 10] x5 = self.fc3(x4) #print('flatetn layer1', x5.shape) return x5 def main(): # test net = LeNet5() x = torch.randn(1, 1, 28, 28) a = net(x) print(a) if __name__ == '__main__': main()训练部分代码
import torch from torch import optim, nn from torch.utils.data import DataLoader from LeNet5 import LeNet5 from utils import plot_curve from torchvision import datasets, transforms # 视觉工具包 batch_size=200 # 一次送入200张 learning_rate=0.01 # 学习率 epochs=40 # 最大迭代次数 device = torch.device('cuda') # 设备选择cuda torch.manual_seed(1234) # 种子点 train_db = datasets.MNIST('../Digit Recognition/data', train=True, download=True, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])) #文件名 train = true 代表下载的是那60k的训练数据 而不是剩下10k的test数据 #download = true 代表当前文件无数据则自动下载 #数据格式为numpy 将其转化成 tensor格式 #normalize是正则化的一个过程 由于图像的像素是0-1 只在0的右边 将其转换到0的两侧进行均匀分布 可以提高性能 #batch_size 代表一次加载多少张数据 shuffle 代表加载数据并打散 train_loader = torch.utils.data.DataLoader( train_db, batch_size=batch_size, shuffle=True) test_db = datasets.MNIST('../Digit Recognition/data', train=False, transform=transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,)) ])) # 加载测试集10k test_loader = torch.utils.data.DataLoader(test_db, batch_size=batch_size, shuffle=True) print('train:', len(train_db), 'test:', len(test_db)) train_db, val_db = torch.utils.data.random_split(train_db, [50000, 10000]) # 将训练集在分为训练集50k和验证集10k print('db1:', len(train_db), 'db2:', len(val_db)) train_loader = torch.utils.data.DataLoader( train_db, batch_size=batch_size, shuffle=True) #在进行数据打乱,数据增强 val_loader = torch.utils.data.DataLoader( val_db, batch_size=batch_size, shuffle=True) def evalute(model, loader): ''' 测试函数:1.通过验证集评价网络最好得epoch 2.通过测试集评价网络真实准确率 注:均不用于反向传播调节网络,单纯评价 ''' model.eval() correct = 0 total = len(loader.dataset) for x,y in loader: x,y = x.to(device), y.to(device) # 改变设备类型 在GPU上进行运算 with torch.no_grad(): logits = model(x) pred = logits.argmax(dim=1) correct += torch.eq(pred, y).sum().float().item() # eq:pred与y进行比较相等的输出1 不同的输出0,然后sum累加,之后float转换成浮点数,最后item将tensor数据类型转换成numpy return correct / total # 返回平均准确率 def main(): device = torch.device('cuda:0') model = LeNet5().to(device) # 加载网络模型 optimizer = optim.SGD(model.parameters(), lr=learning_rate, momentum=0.01) # 设置优化器参数 参数选择详见网络注释 criteon = nn.CrossEntropyLoss().to(device) # 设置损失函数参数 best_acc, best_epoch = 0, 0 train_loss = [] for epoch in range(epochs): # 训练 for batch_idx, (data, target) in enumerate(train_loader): # data: [b, 1, 28, 28], target: [b] data, target = data.to(device), target.to(device) model.train() # 训练模型 logits = model(data) # 获得网络输出 loss = criteon(logits, target) # 根据损失函数得到loss train_loss.append(loss.item()) # loss是tensor数据类型 loss.item将其转换成具体数值,numpy类型 optimizer.zero_grad() # 梯度清零 loss.backward() # 反向传播,更新权重等参数信息,计算梯度 optimizer.step() # 更新梯度,注:每一个step完成的是一个batchsize,每一个epoch完成的是一整个数据集 if batch_idx % 100 == 0: # 每100个batchsize,输出一次迭代代数,已经训了训练集中多少数据,所占百分比,对应此时得loss print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format( epoch, batch_idx * len(data), len(train_loader.dataset), 100. * batch_idx / len(train_loader), loss.item())) if epoch % 1 == 0: val_acc = evalute(model, val_loader) # 每迭代一次,用validation_set进行验证 print('Average_val_acc:', val_acc, 'epoch:', epoch) if val_acc> best_acc: # 用验证集来测试网络表现最好的代数 方便进行参数保存 best_epoch = epoch best_acc = val_acc torch.save(model.state_dict(), 'best.mdl') plot_curve(train_loss) print('best acc:', best_acc, 'best epoch:', best_epoch) model.load_state_dict(torch.load('best.mdl')) # 加载在validation_set上表现最好时网络参数 print('loaded from ckpt!') test_acc = evalute(model, test_loader) # 没有训练过的新图像即测试集来体现网络得真是性能 print('test acc:', test_acc) if __name__ == '__main__': main()