【深度学习】识别手写数字项目实战(含GUI界面且准确率高达99%!!!)

一.项目介绍

1.概述

        识别手写数字项目是我们学习深度学习的基础项目,非常适合刚起步的初学者实践的好项目。此项目采用五层卷积神经网络作为网络模型,准确率高达99%以上。同时此项目还设计了一个GUI界面,能够快速识别现场手写数字,快来试试吧 ~ ~

2.运行效果图:

图1:这里可以看到准确率高达99%

图2:测试实况

图3:抗干扰测试

3.你能学到什么

通过自己亲手撸完代码后,我觉得自己收获还是很大的,我在这里做一个小总结:

a.了解卷积网络的基本构架:例如本项目采用了2个卷积层,2个ReLu激活层,2个最大池化层,1个全连接层。

b.对于模型训练的基本套路有了一个基本的了解:数据准备、选择模型架构、初始化模型、选择损失函数(详见【机器学习】损失函数的选择总结-CSDN博客)、选择优化方法、训练模型、模型评估、调参和优化、保存模型。(详细总结请见本人另一篇博客基于经典网络架构训练图像分类模型(超详细批注和完整思路!!!)-CSDN博客

c.简单了解如何封装网络模型、数据加载器和模型评估函数。

d.了解如何使用GPU训练数据集,以及如何对训练好的模型进行保存和加载使用。

e.初步体验设计一个简单的GUI界面。

二.代码展示

train:(这个文件用来训练模型并保存效果最好的模型文件“best_module.pth”)

# 创作者:余潇
# 创作时间:2023-10-28  17:46
import time
import torch
from torchvision import transforms
from torch.utils.data import DataLoader
import torchvision
import torch.nn as nn


class Net(torch.nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.conv = nn.Sequential(
            # [BATCH_SIZE, 1, 28, 28]
            nn.Conv2d(1, 32, 5, 1, 2),
            # [BATCH_SIZE, 32, 28, 28]
            nn.ReLU(),
            nn.MaxPool2d(2),
            # [BATCH_SIZE, 32, 14, 14]
            nn.Conv2d(32, 64, 5, 1, 2),
            # [BATCH_SIZE, 64, 14, 14]
            nn.ReLU(),
            nn.MaxPool2d(2)
            # [BATCH_SIZE, 64, 7, 7]
        )
        self.fc = nn.Linear(64 * 7 * 7, 10)

    def forward(self, x):
        x = self.conv(x)
        x = x.view(x.size(0), -1)
        y = self.fc(x)
        return y


def get_data_loader(is_train):
    to_tensor = transforms.Compose([transforms.ToTensor()])  # 数据集被转换为张量
    data_set = torchvision.datasets.MNIST("", is_train, transform=to_tensor, download=True)  # 加载 MNIST 数据集
    return DataLoader(data_set, batch_size=15, shuffle=True)  # 按照每批 15 个样本进行随机打乱


# evaluate函数用于评估模型的性能
def evaluate(test_data, net):
    correct_num = 0
    total_num = 0
    with torch.no_grad():  # 禁用梯度计算(torch.no_grad()):因为在评估阶段我们不需要计算梯度,所以可以通过这个上下文管理器来禁用梯度计算,以提高运行效率。
        for inputs, labels in test_data:
            inputs = inputs.to(device)
            labels = labels.to(device)
            outputs = net(inputs.view(-1, 1, 28, 28))  # 将输入数据的形状重塑为[batch_size, 1, 28, 28],其中1表示通道数,因为灰度图像只有一个通道。
            loss_func = torch.nn.CrossEntropyLoss()
            loss_func = loss_func.to(device)
            loss = loss_func(outputs, labels)
            outputs = net.forward(inputs.view(-1, 1, 28, 28))
            for i, output in enumerate(outputs):  # torch.argmax(output) 返回的是输出张量 output 中最大元素的索引,这个索引可以被视为模型的预测类别
                if torch.argmax(output) == labels[i]:
                    correct_num += 1
                total_num += 1
        accuracy = correct_num / total_num

    return accuracy, loss


# 是否用GPU训练
train_on_gpu = torch.cuda.is_available()
if not train_on_gpu:
    print('CUDA is not available.  Training on CPU ...')
else:
    print('CUDA is available!  Training on GPU ...')

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

net = Net()
net = net.to(device)


def main(net):
    best_acc = 0
    train_data = get_data_loader(is_train=True)
    test_data = get_data_loader(is_train=False)
    since = time.time()
    print("initial accuracyL:{}".format(evaluate(test_data, net)[0]))
    optimizer = torch.optim.Adam(net.parameters(), lr=0.001)  # 创建了一个Adam优化器(torch.optim.Adam),用于更新神经网络的参数。学习率设置为0.001。
    for epoch in range(10):
        print("----------第{}轮训练开始----------".format(epoch))
        for inputs, labels in train_data:
            inputs = inputs.to(device)
            labels = labels.to(device)
            net.zero_grad()  # 将神经网络模型 net 中的梯度清零
            outputs = net.forward(inputs.view(-1, 1, 28, 28))
            loss_func = torch.nn.CrossEntropyLoss()  # 使用负对数似然损失(Negative Log-Likelihood Loss),它通常用于多类别分类问题
            loss_func = loss_func.to(device)
            loss = loss_func(outputs, labels)
            loss.backward()  # 反向传播的开始,用于计算如何更新模型参数以减小损失。
            optimizer.step()  # 使用优化器 optimizer 来更新模型的权重和偏置,以最小化损失。
        now = time.time()
        use_time = now - since
        print("Epoch:{}, Accuracy:{}, Time:{}min {:.2f}s, Loss:{:.6f}".format(epoch, evaluate(test_data, net)[0],
                                                                              use_time // 60,
                                                                              use_time % 60,
                                                                              evaluate(test_data, net)[1]))

        epoch_acc = evaluate(test_data, net)[0]
        if epoch_acc > best_acc:
            best_acc = epoch_acc
            best_model = net.state_dict()  # 保留模型参数

    torch.save(best_model, "best_model.pth")


if __name__ == "__main__":
    main(net)

main:(这个文件主要用来设计GUI界面)

import tkinter as tk
import numpy as np
from PIL import Image, ImageDraw
import torch
import train
from tkinter import font

# 加载训练好的模型
model = train.Net()
model.load_state_dict(torch.load('best_model.pth', map_location=torch.device('cpu')))
model.eval()

# 创建画布和绘图工具
canvas_width = 280
canvas_height = 280


def clear_canvas():
    canvas.delete('all')  # 删除画布上的所有元素
    img_draw.rectangle([(20, 0), (300, 280)], fill='black')  # 在图像上绘制一个黑色的矩形覆盖原有内容
    output_label.config(text='')


def draw(event):
    x, y = event.x, event.y
    canvas.create_text(x, y, text='●', font='Helvetica 20', fill='white')  # 将椭圆填充颜色设为白色
    img_draw.ellipse([(x - 5, y - 5), (x + 5, y + 5)], fill='white')  # 将椭圆描边颜色设为白色


def recognize_digit():
    # 从画布中获取图像,调整尺寸为模型所需的大小
    image = img.crop((0, 0, 300, 300)).resize((28, 28)).convert('L')
    # 数据预处理
    input_data = np.array(image).reshape((1, 1, 28, 28)).astype('float32')
    input_data = input_data / 255.0  # 将像素值缩放到0到1之间

    input_tensor = torch.from_numpy(input_data)
    # 使用模型进行预测
    with torch.no_grad():
        output = model(input_tensor)
        prediction = output.argmax(dim=1).item()
    result_font = font.Font(size=15)
    # 显示预测结果
    output_label.config(text=f'\n识别结果: {prediction}', font=result_font)


root = tk.Tk()
root.title("手写数字识别")

# 计算窗口位置
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
window_width = 320
window_height = 350
x_position = int((screen_width - window_width) / 2)
y_position = int((screen_height - window_height) / 2)
root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}")

canvas = tk.Canvas(root, width=canvas_width, height=canvas_height, bg='black')


# 固定窗口大小
root.resizable(False, False)
root.geometry(f"{window_width}x{window_height}+{x_position}+{y_position}")

canvas = tk.Canvas(root, width=canvas_width, height=canvas_height, bg='black')
canvas.pack()

img = Image.new('RGB', (canvas_width, canvas_height), 'black')
img_draw = ImageDraw.Draw(img)

canvas.bind('<B1-Motion>', draw)

recognize_button = tk.Button(root, text="识别", command=recognize_digit, width=10, height=2)
recognize_button.pack(side=tk.LEFT, padx=10, pady=10)

clear_button = tk.Button(root, text="清除", command=clear_canvas, width=10, height=2)
clear_button.pack(side=tk.RIGHT, padx=10, pady=10)

output_label = tk.Label(root, text='', font=('Arial', 20))
output_label.pack()

root.mainloop()

  • 8
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值