小项目:手写数字识别,可识别小数点(三)(2020.7.3)

小项目:手写数字识别,可识别小数点(三)

完整代码已经上传 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()

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值