手写数字识别—Kaggle Digit Recognizer

发布于 2023-07-15  97 次阅读


刚学了一点CNN,就来练练手

点我传送

前言

MNIST(Modified National Institute of Standards and Technology database)是一个常用的手写数字识别数据集,常被用作机器学习和深度学习的基准数据集。它的历史可以追溯到20世纪80年代末和90年代初。当时,美国国家标准与技术研究所(NIST)收集了大量手写数字图像,并将其用于开发和评估基于光学字符识别(OCR)的技术和算法。

MNIST数据集包含了大量的手写数字图片,每张图片都是28x28像素的灰度图像。数据集被划分为训练集(training set)和测试集(test set),其中训练集包含60,000个样本,测试集包含10,000个样本。这些样本都是由来自美国国家标准与技术研究所(NIST)的员工和美国人口普查局的高中生手写而成。

总结一下,MNIST是一个包含手写数字图像的数据集,常用于机器学习和深度学习中的手写数字识别任务。它是一个相对简单的数据集,被广泛用于算法验证和学术研究。而这个Kaggle的比赛的就是基于MNIST的一个入门项目。

数据处理

虽然pytorch里面已经提供了MNIST数据集,但是还是需要下载一下。这里用的是Kaggle的比赛数据集。我们拿pytorch的MNIST数据集来测试模型的准确率。

Kaggle的比赛数据是一个CSV文件,该文件有28x28 + 1列,除第一列外,每一列代表一个像素点的灰度值。第一列则代表图片的结果。

我们可以简单看一下这些图片。

import pandas as pd
import matplotlib.pyplot as plt

# 读取CSV文件
data = pd.read_csv('train.csv')

# 获取图像数据和标签
image_data = data.iloc[:, 1:].values
labels = data.iloc[:, 0].values

# 将图像数据转换为图像格式
images = image_data.reshape(-1, 28, 28)

# 显示图像
fig, axes = plt.subplots(5, 5, figsize=(8, 8))

for i, ax in enumerate(axes.flat):
    ax.imshow(images[i], cmap='gray')
    ax.axis('off')
    ax.set_title(f"Label: {labels[i]}")

plt.tight_layout()
plt.show()

如图所示:


大概就是这样的,没有倒置,没有左右旋转的毒瘤操作。

模型设计瞎搞(不是

虽然torchvision里面已经提供了一些常用的模型,像ResNet,VGG,AlexNet等,但是考虑到第一次写,确定手动定义一个,考虑到我的训练时间问题,最终选择了AlexNet。电脑太拉了

AlexNet的大体结构图如下,但是由于我们只需要处理28x28的数据,最后类别数为10,所以我们简单胡乱改一下

# myNet.py
import torch.nn as nn

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()

        # 定义神经网络的层结构
        self.features = nn.Sequential(
            nn.Conv2d(1, 96, 11, 1, 5), nn.ReLU(inplace=True),  # 第一个卷积层
            nn.MaxPool2d(kernel_size=3, stride=2),  # 第一个最大池化层
            nn.Conv2d(96, 256, 5, 1, 2), nn.ReLU(inplace=True),  # 第二个卷积层
            nn.MaxPool2d(kernel_size=3, stride=2),  # 第二个最大池化层
            nn.Conv2d(256, 384, 3, 1, 1), nn.ReLU(inplace=True),  # 第三个卷积层
            nn.Conv2d(384, 384, 3, 1, 1), nn.ReLU(inplace=True),  # 第四个卷积层
            nn.Conv2d(384, 256, 3, 1, 1), nn.ReLU(inplace=True),  # 第五个卷积层
            nn.MaxPool2d(kernel_size=3, stride=2),  # 第三个最大池化层
            nn.Flatten(),  # 将多维输入展平为一维
            nn.Linear(1024, 256), nn.ReLU(inplace=True),  # 第一个全连接层
            nn.Dropout(p=0.5),  # Dropout层,随机失活一部分神经元
            nn.Linear(256, 84), nn.ReLU(inplace=True),  # 第二个全连接层
            nn.Dropout(p=0.5),  # Dropout层,随机失活一部分神经元
            nn.Linear(84, 10)  # 第三个全连接层
        )

    def forward(self, x):
        x = self.features(x)
        return x

大概就是这样了,没啥经验,层的超参数我乱设的,比原本的就小了一点点~~

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import *
from torchvision.datasets import MNIST
from torchvision.transforms import ToTensor
import pandas as pd
from myNet import *

# 加载训练数据
train_data = pd.DataFrame(pd.read_csv('train.csv'))
image_train = torch.tensor(
    train_data.iloc[:, 1:].values, dtype=torch.float32).reshape(-1, 1, 28, 28)
outputs_train = torch.tensor(train_data.iloc[:, 0].values, dtype=torch.long)
train_dataset = TensorDataset(image_train, outputs_train)

# 加载测试数据,这里使用自带的
test_data = MNIST(root='data/', train=False,
                  download=True, transform=ToTensor())

# 小批量数据加载
data_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)
test_loader = DataLoader(test_data, batch_size=32, shuffle=False)

# 定义模型,在myNet.py中
net = Net()

# 定义损失函数和优化器
loss = nn.CrossEntropyLoss()
opt = optim.Adam(net.parameters(), lr=0.001)

# 我是Mac设备,加速用的mps
device = torch.device('mps')
net.to(device)

cnt = 20
for epoch in range(cnt):
    # 模型训练
    net.train()
    train_loss = 0.0
    train_acc = 0.0

    for image, number in data_loader:
        image = image.to(device)
        number = number.to(device)

        # 前向传播
        outputs = net(image)
        l = loss(outputs, number)

        # 反向传播
        opt.zero_grad()
        l.backward()
        opt.step()

        train_loss += l.item()
        _, train_ans = torch.max(outputs.data, 1)
        train_acc += (train_ans == number).sum().item()

    train_loss /= (len(data_loader))
    train_acc = 100 * train_acc / (len(train_dataset))

    # 模型测试
    net.eval()
    test_acc = 0.0
    test_loss = 0.0
    with torch.no_grad():
        for image, number in test_loader:
            image = image.to(device)
            number = number.to(device)

            outputs = net(image)
            l = loss(outputs, number)

            test_loss += l.item()
            _, test_ans = torch.max(outputs.data, 1)
            test_acc += (test_ans == number).sum().item()

        test_loss /= len(test_loader)
        test_acc = 100 * test_acc / len(test_data)

    print(f'Epoch {epoch+1}/{epoch}:')
    print(f'训练 Loss: {train_loss:.4f}, 训练精准度: {train_acc:.2f}%')
    print(f'测试 Loss: {test_loss:.4f}, 测试精准度: {test_acc:.2f}%')

torch.save(net.state_dict(), 'model_final.pth')
Epoch 1:
训练 Loss: 0.5177, 训练精准度: 84.43%
测试 Loss: 2.2420, 测试精准度: 30.26%
Epoch 2:
训练 Loss: 0.1744, 训练精准度: 96.22%
测试 Loss: 2.2501, 测试精准度: 36.55%
Epoch 3:
训练 Loss: 0.1589, 训练精准度: 96.75%
测试 Loss: 2.2226, 测试精准度: 31.70%
Epoch 4:
训练 Loss: 0.1490, 训练精准度: 96.91%
测试 Loss: 2.2276, 测试精准度: 20.78%
Epoch 5:
训练 Loss: 0.1385, 训练精准度: 97.00%
测试 Loss: 2.1943, 测试精准度: 32.70%
......

不知道为什么,测试数据精确度比较低,可能是比赛的数据强度比较低,是在不行可以使用自带的训练,我试过用自带的训练得分高0.01(

还有,不要重复太多次,我有一次循环了70多次结果梯度消失了(

生成答案

import torch
from torch.utils.data import *
from myNet import *
import pandas as pd

data = pd.DataFrame(pd.read_csv('test.csv'))
image_train = torch.tensor(
    data.iloc[:, :].values, dtype=torch.float32).reshape(-1, 1, 28, 28)

dataset = TensorDataset(image_train)
data_loader = DataLoader(dataset, batch_size=32, shuffle=False)

# 定义模型并加载
net = Net()
net.load_state_dict(torch.load('model_final.pth'))
device = torch.device('mps')
net.to(device)

number = []
label = []
net.eval()
with torch.no_grad():
    for image in data_loader:
        image = image[0].to(device)

        outputs = net(image)
        _, ans = torch.max(outputs.data, 1)

        for i in ans:
            number.append(cnt)
            # 要把数据从GPU移动到CPU
            label.append(i.data.cpu().numpy())
            cnt += 1

answer = pd.DataFrame({'ImageId': number, 'Label': label})
answer.to_csv("answer.csv", index=False, sep=',')

还能怎么玩——用tkinter来做GUI手写识别

得意于我薄弱的GUI编程知识,我用AI生成了GUI绘图的代码并魔改(

首先程序分为前端和后端,前端使用tkinter来绘图,并将图片数据传给pytorch来处理,pytorch加载之前训练好的模型参数。可能是书写习惯的缘故?我写的数字6好像识别的不太好诶(也可能是我写的太丑了)

项目放github上了:DigitRecognizer