首页 > 其他分享 >深度学习入门:3.手写数字分类

深度学习入门:3.手写数字分类

时间:2024-05-28 19:59:57浏览次数:18  
标签:loss 入门 模型 batch train 深度 test 手写 data

这一章将基于Pytorch在只用全连接网络情况下实现手写数字识别,让大家基本了解怎么实现模型训练和预测使用。完整代码在最下面。

一.MNIST数据集

1.数据集介绍

MNIST 数据集包含 60,000 个训练样本和 10,000 个测试样本,每个样本都是 28x28 像素的灰度图像,表示一个 0 到 9 的手写数字。每个图像都有一个对应的标签,表示图像中的数字。样例如下:

尽管手写数字识别是一个复杂的任务,但 MNIST 数据集相对简单,因为所有图像都是经过归一化和中心化的,且背景干净,没有复杂的背景或噪声。在实现分类上也比较容易,模型收敛较快,就算用CPU训练模型也可以在短时间内收敛。

2.MNIST数据集下载与预处理

train_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=True, download=True,    #训练集下载
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),       #转换数据类型为Tensor
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))    #数据标准化
                               ])),
    batch_size=batch_size_train, shuffle=True)  #批数量和样本随机
test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=False, download=True,   #测试集下载
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_test, shuffle=True)

Pytorch已经提供了MNIST数据集,只要调用相关代码进行下载调用即可,下载后还需要进行预处理才能训练模型,需要将数据集的数据类型转换为张量;再将数据标准化,加快模型收敛速度。数据标准化所用的均值0.1307和标准差0.3081这些系数是数据提供方计算好的数据。

二、神经网络模型

注:本章只是让大家简单了解模型的训练和使用,神经网络模型只使用了全连接层,并未加入卷积神经网络。

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 196)
        self.fc2 = nn.Linear(196, 49)
        self.fc3 = nn.Linear(49, 10)
    def forward(self, x):
        x = x.view(-1, 784)  # 降维
        x = F.relu(self.fc1(x))  # 激活函数relu
        x = F.dropout(x, training=self.training)  #dropout正则化,降低过拟合
        x = self.fc2(x)
        x = self.fc3(x)
        return F.log_softmax(x)

如上代码所示,构建的神经网络模型主要包括三个全连接层(fc1,fc2,fc3)。由于输入张量的维度为(28,28,1),全连接层只能处理一维向量,所以需要将张量进行降维处理。使用x.view(-1,784)进行降维(28*28*1=784)。

激活函数作用:

  1. 非线性化:神经网络中的线性组合(如权重乘以输入加上偏置)本身是线性的。为了捕获数据中的非线性关系,需要引入非线性元素,这就是激活函数的作用。
  2. 增强模型的表达能力:通过引入非线性,激活函数允许神经网络学习复杂的数据表示和模式。
  3. 控制输出范围:某些激活函数(如Sigmoid和Tanh)将输出限制在特定的范围内,这有助于标准化输出,并可能使某些优化问题更容易解决。
  4. 引入稀疏性:某些激活函数(如ReLU及其变体)在输入为负时输出为零,这有助于引入稀疏性,即许多神经元的输出为零。这可以提高计算效率,并可能有助于防止过拟合。
  5. 优化梯度传播:某些激活函数(如ReLU)在输入为正时具有恒定的梯度,这有助于缓解梯度消失问题,特别是在深度网络中。

dropout正则化,对某一层使用dropout,就是在训练过程中随机将该层的一些输出特征舍弃(即舍弃的特征值设置为0),有效的降低模型的过拟合问题。

网络结构如下:

三、优化器和损失函数

优化器:使用SGD优化器,在PyTorch中,optim.SGD 是用于实现随机梯度下降(Stochastic Gradient Descent, SGD)优化算法的类。这个类用于更新和计算模型(如神经网络)的参数,以便在训练过程中最小化损失函数。

损失函数:负对数似然损失(Negative Log Likelihood Loss,简称NLL Loss)是用于分类任务的一种损失函数,尤其是在处理具有softmax或log-softmax输出的神经网络时。它的基本思想是衡量模型预测的类别概率分布与真实类别概率分布之间的差异。

假设有 N 个样本,每个样本的真实标签为 yi​,对应的模型预测概率为 pyi​​,则平均负对数似然损失可以表示为:

L = -\frac{1}{N} \sum_{i=1}^{N} \log(p_{y_i})

四、训练模型

训练模型可以选择CPU或者GPU训练,CPU训练速度比GPU慢很多,但数据集和模型规模都比较小,CPU训练也不会花费太长时间。若使用GPU训练模型,需要先将输入和模型加载到GPU上,才能使用CUDA加速训练。

def train(epoch,epochs):
    #训练模型
    train_loss=0
    network.train()
    pbar = tqdm(total=len(train_loader), desc=f'Epoch {epoch + 1}/{epochs}', mininterval=0.3)
    for batch_idx, (data, target) in enumerate(train_loader): #批次,输入数据,标签
        data = data.to(device)
        target=target.to(device)
        optimizer.zero_grad()   #清空优化器中的梯度
        output = network(data)  #前向传播,获得当前模型的预测值
        loss = F.nll_loss(output, target) #真实值和预测值之前的损失
        loss.backward() #反向传播,计算损失函数关于模型中参数梯度
        optimizer.step() #更新模型中参数
        #输出当前训练轮次,批次,损失等
        train_loss +=loss.item()
        '''torch.save(network.state_dict(), './model.pth')
        torch.save(optimizer.state_dict(), './optimizer.pth')'''
        pbar.set_postfix(**{'train loss'  : train_loss/(batch_idx+1)})
        pbar.update(1)
    return train_loss/(batch_idx+1)

def eval(epoch,epochs):
    #测试模型
    network.eval()
    pbar = tqdm(total=len(eval_loader), desc=f'Epoch {epoch + 1}/{epochs}', mininterval=0.3)
    eval_loss = 0
    with torch.no_grad(): #仅测试模型,禁用梯度计算
        for batch_idx, (data, target) in enumerate(eval_loader):
            data=data.to(device)
            target=target.to(device)
            output = network(data)
            eval_loss += F.nll_loss(output, target).item()
            pbar.set_postfix(**{'eval loss': eval_loss / (batch_idx + 1)})
            pbar.update(1)
    return eval_loss/(batch_idx + 1)

训练模型代码如上,训练集用于训练模型,验证集用于评估模型在未见过的数据上的性能,从而帮助选择最佳的模型架构和超参数。最后将在验证集上损失最小的模型权重保存下来。

五、测试模型

测试集是模型在训练和验证过程中从未见过的数据,通过在测试集上评估模型,我们可以获得对模型泛化能力的真实估计,即模型对未见数据的预测能力。

def test():
    #如果已经训练好了权重,模型直接加载权重文件进行测试#
    model_test=Net()
    model_test.load_state_dict(torch.load('model.pth'))
    model_test.eval()
    model_test=model_test.to(device)
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data=data.to(device)
            target=target.to(device)
            output = model_test(data)
            test_loss += F.nll_loss(output, target, size_average=False).item()
            pred = output.data.max(1, keepdim=True)[1]  #模型的分类结果
            correct += pred.eq(target.data.view_as(pred)).sum() #模型的分类结果和真实类别对比,统计正确的个数
    test_loss /= len(test_loader.dataset)
    print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))


    #------------------------------------------------------------------------------------------------------------#
    #                展示一些测试样本,直接用cpu运行,如果继续用cuda后续还需要将结果转到cpu再转numpy数据类型,非常麻烦           #
    # ----------------------------------------------------------------------------------------------------------#
    model_test=model_test.to('cpu')
    examples = enumerate(test_loader)
    batch_idx, (example_data, example_targets) = next(examples)
    with torch.no_grad():
        output = model_test(example_data)
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        plt.tight_layout()
        plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
        plt.title("Prediction: {}".format(output.data.max(1, keepdim=True)[1][i].item()))
        plt.xticks([])
        plt.yticks([])
    plt.show()

最后几个样本的预测结果如下。

完整代码如下

import numpy as np
import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import matplotlib.pyplot as plt
from tqdm import tqdm

batch_size_train = 64    #训练模型时,一个批次的数据(当前代表64个MNIST图片),用cuda的可以相应将参数设置大些
# 意味着在每次权重更新时,你会从训练集中选择64个样本(也称为一个“批次”)来计算梯度,并使用这些梯度来更新模型的权重。
batch_size_eval = 200   #测试模型时,一次批次的数据(当前代表200个MNIST图片)
batch_size_test = 200  #测试模型时,一次批次的数据(当前代表200个MNIST图片)
learning_rate = 0.01 #学习率
momentum = 0.5  #使用optim.SGD(随机梯度下降)优化器时,momentum是一个重要的参数。它代表了动量(Momentum)的大小,是动量优化算法中的一个关键概念。
log_interval = 10
random_seed = 1
torch.manual_seed(random_seed) #用于设置随机数生成器种子(seed)的函数。设置种子可以确保在每次运行代码时,与随机数生成相关的操作(如权重初始化、数据打乱等)都会生成相同的随机数序列,从而使结果具有可复现性。

trainset =torchvision.datasets.MNIST('./data/', train=True, download=True,    #训练集下载
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),       #转换数据类型为Tensor
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))    #数据标准化
                               ]))

#------------------------------------------------------------------#
#       将训练集再划分为训练集和测试集(训练集:测试集=4:1)    #
#------------------------------------------------------------------#
train_size = len(trainset)
indices = list(range(train_size))

# 划分索引
split = int(0.8 * train_size)
train_indices, val_indices = indices[:split], indices[split:]

# 创建训练集和验证集的子集
trainset_subset = torch.utils.data.Subset(trainset, train_indices)
valset_subset = torch.utils.data.Subset(trainset, val_indices)

# 创建数据加载器
train_loader = torch.utils.data.DataLoader(trainset_subset, batch_size=64, shuffle=True)
eval_loader = torch.utils.data.DataLoader(valset_subset, batch_size=64, shuffle=False)



test_loader = torch.utils.data.DataLoader(
    torchvision.datasets.MNIST('./data/', train=False, download=True,   #测试集下载
                               transform=torchvision.transforms.Compose([
                                   torchvision.transforms.ToTensor(),
                                   torchvision.transforms.Normalize(
                                       (0.1307,), (0.3081,))
                               ])),
    batch_size=batch_size_test, shuffle=True)


def show_MNIST():
    #展示MNIST图片
    examples = enumerate(test_loader)
    batch_idx, (example_data, example_targets) = next(examples)
    fig = plt.figure()
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        plt.tight_layout()
        plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
        plt.title("Ground Truth: {}".format(example_targets[i]))
        plt.xticks([])
        plt.yticks([])
    plt.show()


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(784, 196)
        self.fc2 = nn.Linear(196, 49)
        self.fc3 = nn.Linear(49, 10)
    def forward(self, x):
        x = x.view(-1, 784)  # 降维
        x = F.relu(self.fc1(x))  # 激活函数relu
        x = F.dropout(x, training=self.training)  #dropout正则化,降低过拟合
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return F.log_softmax(x,dim=1)



network = Net()
optimizer = optim.SGD(network.parameters(), lr=learning_rate, momentum=momentum)  #优化器
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") #检测电脑是否能使用cuda训练,不行则使用cpu
network=network.to(device)

def train(epoch,epochs):
    #训练模型
    train_loss=0
    network.train()
    pbar = tqdm(total=len(train_loader), desc=f'Epoch {epoch + 1}/{epochs}', mininterval=0.3)
    for batch_idx, (data, target) in enumerate(train_loader): #批次,输入数据,标签
        data = data.to(device)
        target=target.to(device)
        optimizer.zero_grad()   #清空优化器中的梯度
        output = network(data)  #前向传播,获得当前模型的预测值
        loss = F.nll_loss(output, target) #真实值和预测值之前的损失
        loss.backward() #反向传播,计算损失函数关于模型中参数梯度
        optimizer.step() #更新模型中参数
        #输出当前训练轮次,批次,损失等
        train_loss +=loss.item()
        '''torch.save(network.state_dict(), './model.pth')
        torch.save(optimizer.state_dict(), './optimizer.pth')'''
        pbar.set_postfix(**{'train loss'  : train_loss/(batch_idx+1)})
        pbar.update(1)
    return train_loss/(batch_idx+1)

def eval(epoch,epochs):
    #测试模型
    network.eval()
    pbar = tqdm(total=len(eval_loader), desc=f'Epoch {epoch + 1}/{epochs}', mininterval=0.3)
    eval_loss = 0
    with torch.no_grad(): #仅测试模型,禁用梯度计算
        for batch_idx, (data, target) in enumerate(eval_loader):
            data=data.to(device)
            target=target.to(device)
            output = network(data)
            eval_loss += F.nll_loss(output, target).item()
            pbar.set_postfix(**{'eval loss': eval_loss / (batch_idx + 1)})
            pbar.update(1)
    return eval_loss/(batch_idx + 1)


def model_fit(epochs):
    best_loss=1e7
    for epoch in range(epochs):
        train_loss=train(epoch,epochs)
        eval_loss=eval(epoch,epochs)
        print('\nEpoch: {}\tTrain Loss: {:.6f}\tEval Loss: {:.6f}'.format(epoch+1,train_loss,eval_loss))
        if eval_loss<best_loss:
            best_loss=eval_loss
            torch.save(network.state_dict(), 'model.pth')

def test():
    #如果已经训练好了权重,模型直接加载权重文件进行测试#
    model_test=Net()
    model_test.load_state_dict(torch.load('model.pth'))
    model_test.eval()
    model_test=model_test.to(device)
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data=data.to(device)
            target=target.to(device)
            output = model_test(data)
            test_loss += F.nll_loss(output, target, size_average=False).item()
            pred = output.data.max(1, keepdim=True)[1]  #模型的分类结果
            correct += pred.eq(target.data.view_as(pred)).sum() #模型的分类结果和真实类别对比,统计正确的个数
    test_loss /= len(test_loader.dataset)
    print('\nTest set: Avg. loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))


    #------------------------------------------------------------------------------------------------------------#
    #                展示一些测试样本,直接用cpu运行,如果继续用cuda后续还需要将结果转到cpu再转numpy数据类型,非常麻烦           #
    # ----------------------------------------------------------------------------------------------------------#
    model_test=model_test.to('cpu')
    examples = enumerate(test_loader)
    batch_idx, (example_data, example_targets) = next(examples)
    with torch.no_grad():
        output = model_test(example_data)
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        plt.tight_layout()
        plt.imshow(example_data[i][0], cmap='gray', interpolation='none')
        plt.title("Prediction: {}".format(output.data.max(1, keepdim=True)[1][i].item()))
        plt.xticks([])
        plt.yticks([])
    plt.show()


if __name__=="__main__":
    #model_fit(100) #训练100轮
    test() #测试最终保存的模型


标签:loss,入门,模型,batch,train,深度,test,手写,data
From: https://blog.csdn.net/weixin_43946736/article/details/138998825

相关文章

  • Keras深度学习框架第三十一讲:KerasTuner定制搜索空间
    1、绪论在本文中我们将深入探讨如何在不直接修改HyperModel代码的情况下,定制KerasTuner的搜索空间。在深度学习的超参数优化过程中,搜索空间的定制是一个关键的步骤,因为它决定了Tuner将尝试哪些不同的配置组合。通过定制搜索空间,我们可以更有效地探索那些可能对模型性能产......
  • 深度学习——自己的训练集——测试模型(CNN)
    测试模型1.导入新图片名称2.加载新的图片3.加载图片4.使用模型进行预测5.获取最可能的类别6.显示图片和预测的标签名称7.图像加载失败输出导入新的图像,显示图像和预测的类别标签。1.导入新图片名称new_image_path='456.jpg'2.加载新的图片new_image=cv2.i......
  • 普通程序员深度学习教程(fastai及PyTorch)1深度学习快速入门-1简介
    1深度学习快速入门本章介绍深度学习背后的关键概念,并在不同的任务中训练我们的第一个模型。如果你不是技术或数学专业出身,也没有关系,我们从工程应用的角度入手,而不是数学科学。1.1深度学习没那么难多数深度学习不需要:高深的数据基础,实际高中数学已经够用大量数据:实际最低小......
  • 如何使用navigator对象,手写一个正则表达式验证邮箱
    1:如何使用navigator对象navigator对象是JavaScript中的一个内置对象,用于获取浏览器和操作系统的信息。以下是一些常用的navigator属性和方法:navigator.userAgent:返回用户代理字符串,可以用于检测浏览器类型和版本。navigator.platform:返回操作系统平台。na......
  • 学习方法--NLP入门
    1.了解NLP的最基本知识        Jurafsky和Martin的SpeechandLanguageProcessing是领域内的经典教材,对于NLP任务有基本认识,遇到问题知道在书的哪个地方还是非常有意义的。去做一个语言模型的问题,实现神经网络模型之前,第一步要去写一个bigram或者trigram的语言模......
  • 黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了
    黑客入门教程(非常详细)从零基础入门到精通,看完这一篇就够了这篇文章没有什么套路。就是一套自学理论和方向,具体的需要配合网络黑白去学习。毕竟是有网络才会有黑白!有自学也有培训!黑客进阶资源资料包1.打死也不要相信什么分分钟钟教你成为大黑阔的,各种包教包会的教程,就算......
  • 【go从入门到精通】精通并发编程-使用atomic管理状态和同步的无锁技术
    了解原子计数器        在Go中,原子计数器是多个goroutine可以同时访问的共享变量。术语“原子”是指在计数器上执行的操作的不可分割的性质。在Go中,原子计数器允许多个goroutine安全地更改共享变量,而无需使用锁或任何其他显式同步,这可确保数据完整性并避免竞......
  • 【舞台灯方案】LED驱动恒流芯片pwm深度调光APS54085降压IC
    产品描述APS54085是一款PWM工作模式,高效率、外围简单、内置功率MOS管,适用于5-100V输入的高精度降压LED恒流驱动芯片。最大电流2.0A。APS54085可实现线性调光和PWM调光,线性调光有效电压范围0.52-2.55V.PWM调光频率范围100HZ-30KHZ。APS54085工作频率可以通过R......
  • 网关路由快速入门
    在SpringBoot中整合SpringCloudGateway是一个常见的需求,尤其是当需要构建一个微服务架构的应用程序时。SpringCloudGateway是SpringCloud生态系统中的一个项目,它提供了一个API网关,用于处理服务之间的请求路由、安全、监控和限流等功能。使用引入依赖<dependency>......
  • 【秒杀系统】秒杀系统实战(四):缓存与数据库双写一致性深度分析
    【秒杀系统】秒杀系统实战(四):缓存与数据库双写一致性深度分析前言微笑挖坑,努力填坑。————已经拥有黑眼圈,但还没学会小猪老师时间管理学的蛮三刀同学本文是秒杀系统的第四篇,我们来讨论秒杀系统中缓存热点数据的问题,进一步延伸到数据库和缓存的双写一致性问题,并且给......