第P1周:Pytorch实现mnist手写数字识别

目标

复制代码
1. 实现pytorch环境配置
2. 实现mnist手写数字识别
3. 自己写几个数字识别试试

具体实现

(一)环境

语言环境 :Python 3.10
编 译 器: PyCharm
框 架:

(二)具体步骤
**1.**配置Pytorch环境

打开官网PyTorch,Get started:

接下来是选择安装版本,最难的就是确定Compute Platform的版本,是否要使用GPU。所以先要确定CUDA的版本。

会发现,pytorch官网根本没有对应12.7的版本,先安装最新的试试呗,选择12.4:

安装命令:pip3 install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124

安装完成,我们建立python文件,输入如下代码:

复制代码
import torch  
x = torch.rand(5, 3)  
print(x)  
  
print(torch.cuda.is_available())

---------output---------------
tensor([[0.3952, 0.6351, 0.3107],
        [0.8780, 0.6469, 0.6714],
        [0.4380, 0.0236, 0.5976],
        [0.4132, 0.9663, 0.7576],
        [0.4047, 0.4636, 0.2858]])
True

从输出来看,成功了。下面开始正式的mnist手写数字识别

2. 下载数据并加载数据
复制代码
import torch  
import torch.nn as nn  
# import matplotlib.pyplot as plt  
import torchvision  
  
# 第一步:设置硬件设备,有GPU就使用GPU,没有就使用GPU  
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  
print(device)  
  
# 第二步:导入数据  
# MNIST数据在torchvision.datasets中,自带的,可以通过代码在线下载数据。  
train_ds = torchvision.datasets.MNIST(root='./data',    # 下载的数据所存储的本地目录  
                                      train=True,       # True为训练集,False为测试集  
                                      transform=torchvision.transforms.ToTensor(),  # 将下载的数据直接转换成张量格式  
                                      download=True     # True直接在线下载,且下载到root指定的目录中,注意已经下载了,第二次以后就不会再下载了  
                                      )  
test_ds = torchvision.datasets.MNIST(root='./data',  
                                     train=False,  
                                     transform=torchvision.transforms.ToTensor(),  
                                     download=True  
                                     )  
  
# 第三步:加载数据  
# Pytorch使用torch.utils.data.DataLoader进行数据加载  
batch_size = 32  
train_dl = torch.utils.data.DataLoader(dataset=train_ds, # 要加载的数据集  
                                       batch_size=batch_size, # 批次的大小  
                                       shuffle=True,     # 每个epoch重新排列数据  
                                       # 以下的参数有默认值可以不写  
                                       num_workers=0, # 用于加载的子进程数,默认值为0.注意在windows中如果设置非0,有可能会报错  
                                       pin_memory=True, # True-数据加载器将在返回之前将张量复制到设备/CUDA 固定内存中。 如果数据元素是自定义类型,或者collate_fn返回一个自定义类型的批次。  
                                       drop_last=False, #如果数据集大小不能被批次大小整除,则设置为 True 以删除最后一个不完整的批次。 如果 False 并且数据集的大小不能被批大小整除,则最后一批将保留。 (默认值:False)  
                                       timeout=0, # 设置数据读取的超时时间 , 超过这个时间还没读取到数据的话就会报错。(默认值:0)  
                                       worker_init_fn=None # 如果不是 None,这将在步长之后和数据加载之前在每个工作子进程上调用,并使用工作 id([0,num_workers - 1] 中的一个 int)的顺序逐个导入。(默认:None)  
                                       )  
  
# 取一个批次看一下数据格式,数据的shape为[batch_size, channel, height, weight]  
# batch_size是已经设定的32,channel, height和weight分别是图片的通道数,高度和宽度  
images, labels = next(iter(train_dl))  
print(images.shape)


看这个图片的shape是torch.size([32, 1, 28, 28]),可以看图MNIST的数据集里的图像我猜应该是单色的(channel=1),28 * 28大小的图片(height=28, weight=28)。

将图片可视化展示出来看看:

复制代码
# 数据可视化  
plt.figure(figsize=(20, 5)) # 指定图片大小 ,图像大小为20宽,高5的绘图(单位为英寸)  
for i , images in enumerate(images[:20]):  
    # 维度缩减,
    npimg = np.squeeze(images.numpy())  
    # 将整个figure分成2行10列,绘制第i+1个子图  
    plt.subplot(2, 10, i+1)  
    plt.imshow(npimg, cmap=plt.cm.binary)  
    plt.axis('off')  
plt.show()
**3.**构建CNN网络
(<# 复制代码
num_classes = 10 # MNIST数据集中是识别0-9这10个数字,因此是10个类别。

class Model(nn.Module):
    def __init__(self):
        super(Model, self).__init__()
        # 特征提取网络
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1) # 第一层卷积,卷积核大小3*3
        self.pool1 = nn.MaxPool2d(2)    # 池化层,池化核大小为2*2
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1) # 第二层卷积,卷积核大小3*3
        self.pool2 = nn.MaxPool2d(2)

        # 分类网络
        self.fc1 = nn.Linear(1600, 64)
        self.fc2 = nn.Linear(64, num_classes)

    def forward(self, x):
        x = self.pool1(F.relu(self.conv1(x)))
        x = self.pool2(F.relu(self.conv2(x)))

        x = torch.flatten(x, start_dim=1)

        x = F.relu(self.fc1(x))
        x = self.fc2(x)

        return x

# 第四步:加载并打印模型
# 将模型转移到GPU中
model = Model().to(device)
summary(model)>)
4.训练模型
复制代码
# 第五步:训练模型  
loss_fn = nn.CrossEntropyLoss() # 创建损失函数  
learn_rate = 1e-2   # 设置学习率  
opt = torch.optim.SGD(model.parameters(), lr=learn_rate)  
  
# 循环训练  
def train(dataloader, model, loss_fn, optimizer):  
    size = len(dataloader.dataset) # 训练集的大小  
    num_batches = len(dataloader) # 批次数目  
  
    train_loss, train_acc = 0, 0  # 初始化训练损失率和正确率都为0  
  
    for X, y in dataloader: # 获取图片及标签  
        X, y = X.to(device), y.to(device)   # 将图片和标准转换到GPU中  
  
        # 计算预测误差  
        pred = model(X) # 使用CNN网络预测输出pred  
        loss = loss_fn(pred, y) # 计算预测输出的pred和真实值y之间的差距  
  
        # 反向传播  
        optimizer.zero_grad()   # grad属性归零  
        loss.backward() # 反向传播  
        optimizer.step()    # 第一步自动更新  
  
        # 记录acc与loss  
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()  
        train_loss += loss.item()  
  
    train_acc /= size  
    train_loss /= num_batches  
  
    return train_acc, train_loss  
  
# 测试函数,注意测试函数不需要进行梯度下降,不进行网络权重更新,所以不需要传入优化器  
def test(dataloader, model, loss_fn):  
    size = len(dataloader.dataset)  
    num_batches = len(dataloader)  
    test_loss, test_acc = 0, 0  
  
    # 当不进行训练时,停止梯度更新,节省计算内存消耗  
    with torch.no_grad():  
        for imgs, targets in dataloader:  
            imgs, target = imgs.to(device), targets.to(device)  
  
            # 计算 loss            target_pred = model(imgs)  
            loss = loss_fn(target_pred, target)  
  
            test_loss += loss.item()  
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()  
  
    test_acc /= size  
    test_loss /= num_batches  
  
    return test_acc, test_loss  
  
# 正式训练  
epochs = 5  
train_loss, train_acc, test_loss, test_acc = [], [], [], []  
  
for epoch in range(epochs):  
    model.train()  
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, opt)  
  
    model.eval()  
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)  
  
    train_acc.append(epoch_train_acc)  
    test_acc.append(epoch_test_acc)  
    train_loss.append(epoch_train_loss)  
    test_loss.append(epoch_test_loss)  
  
    template = 'Epoch: {:2d}, Train_acc:{:.1f}%, Train_loss: {:.3f}%, Test_acc: {:.1f}%, Test_loss: {:.3f}%'  
    print(template.format(epoch+1, epoch_train_acc*100, epoch_train_loss, epoch_test_acc*100, epoch_test_loss))  
print('Done')
复制代码
# 可见化一下训练结果  
warnings.filterwarnings("ignore")  
plt.rcParams['font.sans-serif'] = ['SimHei']    # 显示中文不标签,不设置会显示中文乱码  
plt.rcParams['axes.unicode_minus'] = False      # 显示负号  
plt.rcParams['figure.dpi'] = 100                # 分辨率  
  
epochs_range = range(epochs)  
  
plt.figure(figsize=(12, 3))  
plt.subplot(1, 2, 1)  
  
plt.plot(epochs_range, train_acc, label='训练正确率')  
plt.plot(epochs_range, test_acc, label='测试正确率')  
plt.legend(loc='lower right')  
plt.title('训练与测试正确率')  
  
plt.subplot(1, 2, 2)  
plt.plot(epochs_range, train_loss, label='训练损失率')  
plt.plot(epochs_range, test_loss, label='测试损失率')  
plt.legend(loc='upper right')  
plt.title('训练与测试损失率')  
  
plt.show()
四:预测一下自己手写的数字

准备数据:

再手动将每个数字切割成单独的一个文件:

注意,这里并没有将每个图片的大小切割成一致,理论上切割成要求的28*28是最好。我这里用代码来重新生成28 * 28大小的图片。

复制代码
import torch  
import numpy as np  
from PIL import Image  
from torchvision import transforms  
import torch.nn as nn  
import torch.nn.functional as F  
import matplotlib.pyplot as plt  
import os, pathlib  
  
# 第一步:设置硬件设备,有GPU就使用GPU,没有就使用GPU  
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  
print(device)  
  
# 定义模型,要把模型搞过来嘛,不然加载模型会出错。  
class Model(nn.Module):  
    def __init__(self):  
        super().__init__()  
        # 特征提取网络  
        self.conv1 = nn.Conv2d(1, 32, kernel_size=3 ) # 第一层卷积,卷积核大小3*3  
        self.pool1 = nn.MaxPool2d(2)    # 池化层,池化核大小为2*2  
        self.conv2 = nn.Conv2d(32, 64, kernel_size=3) # 第二层卷积,卷积核大小3*3  
        self.pool2 = nn.MaxPool2d(2)  
  
        # 分类网络  
        self.fc1 = nn.Linear(1600, 64)  
        self.fc2 = nn.Linear(64, 10)  
  
    def forward(self, x):  
        x = self.pool1(F.relu(self.conv1(x)))  
        x = self.pool2(F.relu(self.conv2(x)))  
  
        x = torch.flatten(x, start_dim=1)  
  
        x = F.relu(self.fc1(x))  
        x = self.fc2(x)  
  
        return x  
  
# 加载模型  
model = torch.load('./models/cnn.pth')   
model.eval()  
  
transform = transforms.Compose([  
    transforms.ToTensor(),  
    transforms.Normalize((0.1307,), (0.3081,))  
])  
  
# 导入数据  
data_dir = "./mydata/handwrite"  
data_dir = pathlib.Path(data_dir)  
image_count = len(list(data_dir.glob('*.jpg')))  
print("图片总数量为:", image_count)  
  
plt.rcParams['font.sans-serif'] = ['SimHei']    # 显示中文不标签,不设置会显示中文乱码  
plt.rcParams['axes.unicode_minus'] = False      # 显示负号  
plt.rcParams['figure.dpi'] = 100                # 分辨率  
plt.figure(figsize=(10, 10))  
i = 0  
for input_file in list(data_dir.glob('*.jpg')):  
    image = Image.open(input_file)  
    image_resize = image.resize((28, 28))   # 将图片转换成 28*28  
    image = image_resize.convert('L')  # 转换成灰度图  
    image_array = np.array(image)  
    # print(image_array.shape)    # (high, weight)  
  
    image = Image.fromarray(image_array)  
    image = transform(image)  
    image = torch.unsqueeze(image, 0)   # 返回维度为1的张量  
    image = image.to(device)  
    output = model(image)  
    pred = torch.argmax(output, dim=1)  
  
    image = torch.squeeze(image, 0)     # 返回一个张量,其中删除了大小为1的输入的所有指定维度  
    image = transforms.ToPILImage()(image)  
  
    plt.subplot(10, 4, i+1)  
    plt.tight_layout()  
    plt.imshow(image, cmap='gray', interpolation='none')  
    plt.title("实际值:{},预测值:{}".format(input_file.stem[:1], pred.item()))  
    plt.xticks([])  
    plt.yticks([])  
    i += 1  
plt.show()

准确性很低,40张图片预测准确数量:6,占比:15.0%.。看图片,感觉resize成28*28和转换成灰度图后,图片本身已经失真比较严重了。先把图片像素翻转一下,其实就是反色处理,加上这段代码:

准确率上了一个台阶(40张图片预测准确数量:30,占比:75.0%).。但是看图片,还是不清晰。

(三)总结
  1. epochs=5,预测的准确性达到97%,如果增加迭代的次数到10,准确性提升接近到99%。迭代20次则达到99.3,提升不明显。

  2. batch_size如何从32调整到64,准确性差不太多

  3. 后续研究图片增强
相关推荐
sduwcgg7 分钟前
kaggle配置
人工智能·python·机器学习
DolphinScheduler社区8 分钟前
白鲸开源与亚马逊云科技携手推动AI-Ready数据架构创新
人工智能·科技·开源·aws·白鲸开源·whalestudio
__lost29 分钟前
Python图像变清晰与锐化,调整对比度,高斯滤波除躁,卷积锐化,中值滤波钝化,神经网络变清晰
python·opencv·计算机视觉
海绵波波10734 分钟前
玉米产量遥感估产系统的开发实践(持续迭代与更新)
python·flask
欣然~37 分钟前
借助 OpenCV 和 PyTorch 库,利用卷积神经网络提取图像边缘特征
人工智能·计算机视觉
谦行1 小时前
工欲善其事,必先利其器—— PyTorch 深度学习基础操作
pytorch·深度学习·ai编程
逢生博客1 小时前
使用 Python 项目管理工具 uv 快速创建 MCP 服务(Cherry Studio、Trae 添加 MCP 服务)
python·sqlite·uv·deepseek·trae·cherry studio·mcp服务
堕落似梦1 小时前
Pydantic增强SQLALchemy序列化(FastAPI直接输出SQLALchemy查询集)
python
白熊1881 小时前
【计算机视觉】CV实战项目 - 基于YOLOv5的人脸检测与关键点定位系统深度解析
人工智能·yolo·计算机视觉
nenchoumi31191 小时前
VLA 论文精读(十六)FP3: A 3D Foundation Policy for Robotic Manipulation
论文阅读·人工智能·笔记·学习·vln