掘金 人工智能 07月04日 14:28
Pytorch实现mnist手写数字识别
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文详细介绍了使用PyTorch配置环境,构建卷积神经网络(CNN)模型,并在MNIST数据集上进行手写数字识别的完整过程。内容涵盖环境配置、数据加载、模型构建、训练、测试以及预测等关键步骤,并提供了预测自己手写数字的实践,最后对结果进行了总结与分析。

⚙️首先,配置PyTorch环境是进行手写数字识别的第一步。文章详细阐述了如何在PyCharm中配置Python 3.10环境,并安装了PyTorch框架。确定CUDA版本后,通过pip命令安装了torch、torchvision和torchaudio。

💾接下来,文章展示了如何下载并加载MNIST数据集。使用torchvision.datasets.MNIST,设置root、train、transform和download参数,将数据转换为张量格式。然后,使用torch.utils.data.DataLoader加载数据,设置batch_size、shuffle等参数,为后续模型训练做准备。

🧠随后,文章构建了一个CNN模型。模型包含卷积层、池化层和全连接层。通过nn.Conv2d、nn.MaxPool2d和nn.Linear等模块定义网络结构,并在forward函数中定义数据流向。最后,将模型转移到GPU中。

📈文章详细介绍了模型的训练过程。定义损失函数和优化器,编写train和test函数,循环训练模型,并记录训练过程中的准确率和损失。通过可视化训练结果,可以直观地了解模型的性能。

✍️最后,文章介绍了如何预测自己手写的数字。通过加载训练好的模型,对预处理后的手写数字图片进行预测,并展示了预测结果。文章还分析了图片预处理对预测准确率的影响。

目标

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

具体实现

(一)环境

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

(二)具体步骤

**1.**配置Pytorch环境

打开官网PyTorch,Get started:接下来是选择安装版本,最难的就是确定Compute Platform的版本,是否要使用GPU。所以先要确定CUDA的版本。会发现,pytorch官网根本没有对应12.7的版本,先安装最新的试试呗,选择12.4:安装命令:pip3 install torch torchvision torchaudio --index-url 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%).。但是看图片,还是不清晰。

(三)总结

    epochs=5,预测的准确性达到97%,如果增加迭代的次数到10,准确性提升接近到99%。迭代20次则达到99.3,提升不明显。batch_size如何从32调整到64,准确性差不太多后续研究图片增强

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

PyTorch MNIST 手写数字识别 CNN 深度学习
相关文章