PyTorch搭建神经网络

news2024/11/26 18:30:11
  • PyTorch版本:1.12.1
  • PyTorch官方文档
  • PyTorch中文文档

PyTorch中搭建并训练一个神经网络分为以下几步:

  1. 定义神经网络
  2. 定义损失函数以及优化器
  3. 训练:反向传播、梯度下降

下面以LeNet-5为例,搭建一个卷积神经网络用于手写数字识别。

1. 模型简介——LeNet-5

LeNet-5是一个经典的深度卷积神经网络,由Yann LeCun在1998年提出用于解决手写数字识别问题。该网络是第一个被广泛应用于数字图像识别的神经网络之一,也是深度学习领域的里程碑之一,被认为是卷积神经网络的起源之一。

如下图所示,LeNet-5的结构是一个7层的卷积神经网络(不含输入层),其中包括2个卷积层、2个下采样层(池化层)、2个全连接层以及输出层。

请添加图片描述

1.1 输入层(Input layer)

输入层接收大小为 32*32 的灰度手写数字图像,像素灰度值范围为0-255。为了加快训练速度以及提高模型准确性,通常会对输入图像的像素值进行归一化。

1.2卷积层C1(Convolutional layer C1)

卷积层C1含有6个卷积核,每个卷积核的大小为 5*5 ,步长为1,填充为0。卷积层C1产生6个大小为 28*28 的特征图。

1.3 下采样层S2(Subsampling layer S2)

采样层S2采用最大池化(max-pooling)操作,这可以减少特征图的大小从而提高计算效率,并且池化操作对于轻微的位置变化可以保持一定的不变性。池化层每个窗口的大小为 2*2 ,步长为2。池化层S2产生6个大小为 14*14 的特征图。

1.4 卷积层C3(Convolutional layer C3)

卷积层C3包括16个卷积核,每个卷积核的大小为 5*5 ,步长为1,填充为0。卷积层C1产生16个大小为 10*10的特征图。

1.5 下采样层S4(Subsampling layer S4)

下采样层S4采用最大池化操作,每个窗口的大小为 2*2 ,步长为2。池化层S4产生16个大小为 5*5 的特征图。

1.6 全连接层C5(Fully connected layer C5)

C5将16个大小为 5*5 的特征图拉成一个长度为400的向量,并通过一个包括120个神经元的全连接层。120是由LeNet-5的设计者根据实验得到的最佳值。

1.7 全连接层F6(Fully connected layer F6)

全连接层F6将120个神经元连接到84个神经元。

1.8 输出层(Output layer)

输出层由10个神经元组成,每个神经元对应0-9的激活值(激活值越大,是该数字的可能性越大)。模型训练时,使用交叉熵损失函数计算输出层与样本真实标签之间的误差,然后通过反向传播算法更新模型的参数(包括卷积核和全连接层)直至模型达到指定效果或者达到指定迭代次数。

在实际应用中,通常会对LeNet-5进行一些改进,例如增加网络深度、增加卷积核数量、添加正则化等方法,以进一步提高模型的准确性和泛化能力。

2. 数据集简介——MNIST

MNIST是一个手写体数字的图片数据集,包含60,000个训练图像和10,000个测试图像,由美国国家标准与技术研究所(National Institute of Standards and Technology (NIST))发起整理,一共统计了来自250个不同的人手写数字图片,其中50%是高中生,50%来自人口普查局的工作人员。数据集中的图像都是灰度图像,大小为 28*28 像素,每个像素点的值为 0 到 255 之间的灰度值

使用torchvision中的datasets可自动下载该数据集:

train_dataset = torchvision.datasets.MNIST(root="data/", train=True, transform=transforms.ToTensor(), download=True)
test_dataset = torchvision.datasets.MNIST(root="data/", train=False, transform=transforms.ToTensor(), download=True)

其中:

  • root表示将数据集存放在当前目录下的’data’文件夹中。

  • train=True表示导入的是训练数据;train=False表示导入的是测试数据。

  • transform表示对每个数据进行的变化,这里是将其变为Tensor,Tensor是PyTorch中存储数据的主要格式。

  • download表示是否将数据下载到本地。

3. 定义神经网络

PyTorch中主要有以下两种方式定义神经网络

3.1 使用前馈神经网络方式

这种方法需要继承torch.nn.Module并且实现__init__()和forward()这两个方法。其中__init__()可以用于做一些初始化工作,比如定义输入数据、隐藏层、激活函数等;forward()是实现前向传播的核心函数,用于定义神经网络的结构和参数,在前向传播的过程中,输入的数据将按照该函数定义的神经网络结构进行计算并得到最终的输出。

import torch.nn.functional as F
from torch import nn


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

        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1)  # 定义卷积核
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)  # 定义最大池化层
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(in_features=16 * 4 * 4, out_features=120)  # 定义全连接层
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x1 = self.conv1(x)  # 卷积层C1
        x2 = F.relu(x1)  # 激活函数
        x3 = self.pool1(x2)  # 下采样层S2

        x4 = self.conv2(x3)  # 卷积层C3
        x5 = F.relu(x4)
        x6 = self.pool2(x5)  # 下采样层S4

        x7 = x.reshape(x6.shape[0], -1)  # 二维变成一维,以输入到全连接层
        x8 = self.fc1(x7)  # 全连接层C5
        x9 = F.relu(x8)
        x10 = self.fc2(x9)  # 全连接层F6
        x11 = F.relu(x10)
        x12 = self.fc3(x11)  # 输出层

        return x12

代码解释

  • __init__():

    定义了用到的卷积核、池化层以及全连接层,其中:

    • nn.Conv2d,定义二维卷积核。in_channels,输入通道数量;out_channels,输出通道数量;kernel_size,卷积核大小;stride,卷积时的步长。
    • nn.MaxPool2d,定义二维最大池化层。kernel_size,池化的窗口大小;stride,池化时的步长。
    • nn.Linear,定义全连接层。in_features,输入数据的大小;out_features,输出数据的大小。
  • forward():

    __init__()函数中仅仅是定义了各个层,但并未将它们连接起来搭建出一个神经网络,forward()函数的作用就是搭建一个神经网络,使得输入的数据沿着指定的结构进行前向传播:

    • forward除了self之外,还接收一个参数x作为输入数据。
    • x = self.conv1(x):输入的x经过卷积计算后得到x1,对应于卷积层C1。
    • x2 = F.relu(x1) :对卷积后的数据进行ReLU激活操作。
    • x3 = self.pool1(x2) :对数据进行池化,对应于下采样层S2。
    • ……
    • 与上面类似,数据依次经过卷积层C3、下采样层S4、全连接层C5、全连接层F6以及输出层,从而使输入x沿着指定的路径得到最终的输出。

    注:

    • 为了更好的展示数据如何沿着神经网络进行前向传播,这里对每一层的输出设置了不同的变量命名,实际应用时,可以将x1~x12都写作x,只要不影响前向传播即可。

    • 二维卷积以及池化操作得到的是二维的特则图,但全连接层需要一维的数据,因此需要对数据尺寸进行修改,即:

      x7 = x.reshape(x6.shape[0], -1)
      

3.2 使用序列化方法

这种方式使用torch.nn.Sequential方式定义模型,将神经网络以序列的方式进行连接,每个层使用前面层计算的输出作为输入,并且在内部会维护层与层之间的权重矩阵和偏置向量。

from torch import nn

in_channels = 1
model = nn.Sequential(
    nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),

    nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1),
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),

    nn.Flatten(),
    nn.Linear(in_features=16 * 4 * 4, out_features=120),
    nn.Linear(in_features=120, out_features=84),
    nn.Linear(in_features=84, out_features=10)
)

3.3总结

  1. 第一种可以更好的根据需要搭建网络结构;
  2. 第二种方式网络以序列的方式搭建网络,不适用于复杂网络;
  3. 对于一些复杂的含有重复层的网络,可将两种方式结合使用。序列化方法定义重复层,然后使用第一种方式根据网络结构进行组装。

4. 定义损失函数以及优化器

  • 损失函数

    损失函数用于计算真实值和预测值之间的差异。在PyTorch官方文档中,给出了可用的损失函数列表。

    这里,我们使用交叉熵损失函数torch.nn.CrossEntropyLoss()该损失函数内部自动加上了Softmax,用于解决多分类问题,也可用于解决二分类问题。

  • 优化器

    优化器根据损失函数求出的损失,对神经网络的参数进行更新。在PyTorch官方文档中,给出了可用的优化器。

    这里,我们使用**torch.optim.Adam()**作为我们的优化器。

from torch import nn, optim

criterion = nn.CrossEntropyLoss()  # 损失函数
optimizer = optim.Adam(model.parameters())  # 优化器

其中:

  • model.parameters()是待优化的参数。

5.训练模型

模型的训练主要包括3部分:

  • 前向传播
  • 反向传播
  • 梯度下降

简单的说就是取出数据,放到模型里面跑一次得到预测值,计算与真实值之间的损失,然后计算梯度,根据梯度更新一次网络。

代码实现如下:

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = MyCNN(1).to(device)  # 加载模型到设备

num_epochs = 100
for epoch in range(num_epochs):
    for batch_idx, (data, label) in enumerate(train_loader):
        data = data.to(device=device)  # 加载数据到设备
        label = label.to(device=device)

        # 前向传播
        pre = model(data)
        loss = criterion(pre, label)

        # 反向传播
        optimizer.zero_grad()
        loss.backward()

        # 梯度下降
        optimizer.step()

其中:

  • torch.device(‘cuda’ if torch.cuda.is_available() else ‘cpu’):选择使用GPU或者CPU训练,若电脑有GPU且配置正确,则使用GPU训练,否则使用CPU训练(模型和数据必须都放在GPU或者CPU上)。

  • for epoch in range(num_epochs):模型训练次数。

  • for batch_idx, (data, label) in enumerate(train_loader):mini-batch对数据进行小批量训练。

  • 前向传播:

    • pre = model(data):将数据放入模型中训练。
    • loss = criterion(pre, label):通过损失函数得到本次训练的损失。
  • 反向传播:

    • optimizer.zero_grad():将梯度归零。训练时通常使用mini-batch方法,如果不将梯度清零的话,梯度会与上一个batch的梯度相关,因此该函数要写在反向传播和梯度下降之前
    • loss.backward():反向传播。计算得到每个参数的梯度。
  • 梯度下降

    optimizer.step():执行一次优化步骤,对参数进行更新。注意:optimizer.step()只负责通过梯度下降对参数进行优化,并不负责产生梯度,梯度是loss.backward()方法产生的。

6. 测试模型

模型训练完毕后,可以使用测试集对模型进行测试:

loss = 0

with torch.no_grad():  # 关闭梯度计算
    model.eval()  # 评估模式
    for batch_idx, (data, label) in enumerate(test_loader):
        data = data.to(device=device)
        label = label.to(device=device)

        pre = model(data)
        loss += criterion(pre, label).item()

model.train()  # 训练模式
loss = loss / len(test_loader.dataset)

其中:

  • with torch.no_grad():关闭梯度计算。在训练模型时,需要计算根据反向传播计算梯度以更新参数,但在对验证集或者测试集进行预测时,并不需要更新参数,因此也就不需要计算梯度。因此,为了避免浪费计算资源,在模型评估时最后关闭梯度计算。

  • model.eval():将模型切换到评估模式。在神经网络中,出于防止过拟合等目的,一般会加入Dropout和Batch Normalization层,在模型训练阶段,根据输入数据的变化,这些层的参数也会发生变化。在评估模式下,Dropout层会让所有的网络节点都生效,而Batch Normalization层会停止计算和更新均值和方差,直接使用在训练阶段已经学出的均值和方差。

  • model.train():将模型切换到训练模式。此时Dropout层使网络中的节点以一定概率失效,Batch Normalization层根据输入的数据更新均值和方差。在将模型切换到评估模式之后,在下一次训练之前必须再切换到训练模式。

  • 注意with torch.no_grad()和model.eval()的区别:

    with torch.no_grad()关闭的是梯度计算,和神经网络整体有关;而model.eval()和梯度没有关系,只和Dropout和Batch Normalization这两层有关系。

7. 整体代码

以下是最终的代码(使用前馈神经网络的方式定义神经网络)。由于这里仅仅是为了介绍如何搭建一个模型,另外出于篇幅考虑,对于一些细节方面未做具体改进,主要包括以下几点:

  • 除了训练集和测试集之外,还可以使用验证集评估模型性能以设置早停
  • 为了得到更好的模型性能,一般会对数据进行归一化
import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
from torch import optim
from torch.utils.data import DataLoader
from torchvision import transforms


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

        self.conv1 = nn.Conv2d(in_channels=in_channels, out_channels=6, kernel_size=5, stride=1)  # 定义卷积核
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)  # 定义最大池化层
        self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, stride=1)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)
        self.fc1 = nn.Linear(in_features=16 * 4 * 4, out_features=120)  # 定义全连接层
        self.fc2 = nn.Linear(in_features=120, out_features=84)
        self.fc3 = nn.Linear(in_features=84, out_features=10)

    def forward(self, x):
        x = self.conv1(x)  # 卷积层C1
        x = F.relu(x)  # 激活函数
        x = self.pool1(x)  # 下采样层S2

        x = self.conv2(x)  # 卷积层C3
        x = F.relu(x)
        x = self.pool2(x)  # 下采样层S4

        x = x.reshape(x.shape[0], -1)  # 二维变成一维,以输入到全连接层
        x = self.fc1(x)  # 全连接层C5
        x = F.relu(x)
        x = self.fc2(x)  # 全连接层F6
        x = F.relu(x)
        x = self.fc3(x)  # 输出层

        return x


def train(model, criterion, optimizer, train_loader, device, num_epochs=200):
    for epoch in range(num_epochs):
        for batch_idx, (data, label) in enumerate(train_loader):
            data = data.to(device=device)  # 加载数据到设备
            label = label.to(device=device)

            # 前向传播
            pre = model(data)
            loss = criterion(pre, label)

            # 反向传播
            optimizer.zero_grad()
            loss.backward()

            # 梯度下降
            optimizer.step()


def test(model, criterion, test_loader, device):
    loss = 0

    with torch.no_grad():  # 关闭梯度计算
        model.eval()  # 评估模式
        for batch_idx, (data, label) in enumerate(test_loader):
            data = data.to(device=device)
            label = label.to(device=device)

            pre = model(data)
            loss += criterion(pre, label).item()

    model.train()  # 训练模式
    loss = loss / len(test_loader.dataset)

    return loss


def main():
    batch_size = 4
    num_epochs = 200

    train_dataset = torchvision.datasets.MNIST(root="data/", train=True, transform=transforms.ToTensor(),
                                               download=True)  # 下载数据集
    test_dataset = torchvision.datasets.MNIST(root="data/", train=False, transform=transforms.ToTensor(), download=True)

    train_loader = DataLoader(dataset=train_dataset, batch_size=batch_size,
                              shuffle=True)  # 将数据集(Dataset)自动分成一个个的Batch,以用于批处理
    test_loader = DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)

    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')  # 选择加载数据的设备,GPU或者CPU

    model = MyCNN(1).to(device)  # 模型和数据应加载到同一种设备上
    criterion = nn.CrossEntropyLoss()  # 损失函数
    optimizer = optim.Adam(model.parameters())  # 优化器

    train(model, criterion, optimizer, train_loader, device, num_epochs)

    print(test(model, criterion, test_loader, device))


if __name__ == '__main__':
    main()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/844573.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

日志分析和流量分析

目录 [陇剑杯 2021]日志分析(问1) [陇剑杯 2021]日志分析(问2) [陇剑杯 2021]日志分析(问3) [陇剑杯 2021]简单日志分析(问1) [陇剑杯 2021]简单日志分析(问3&#…

智慧水利整体解决方案[43页PPT]

导读:原文《智慧水利整体解决方案[43页PPT]》(获取来源见文尾),本文精选其中精华及架构部分,逻辑清晰、内容完整,为快速形成售前方案提供参考。 完整版领取方式 完整版领取方式: 如需获取完整的…

docker小白第一天

docker小白第一天 docker是什么docker理念容器与虚拟机比较docker能干什么docker官网介绍docker的基本组成docker平台架构 docker是什么 系统平滑移植,容器虚拟化技术。即源代码配置环境版本,打个包形成一个镜像文件,即软件带环境一起安装&a…

企业接口测试流程总结,你都走了哪些弯路?背锅不存在的...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 首先我们要明确&a…

LeetCode 周赛上分之旅 # 37 多源 BFS 与连通性问题

⭐️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越抽象,它能覆盖的问题域就越广,理解难度…

ArcGIS制作带蒙版的遥感影像地图

这次文章我们来介绍一下,如何通过一个系统的步骤完成ArcGIS制作带蒙版的遥感影像地图。 主要的步骤包括: 1 添加行政区划数据 2 导出兴趣去乡镇矢量范围 3 添加遥感影像底图 4 制作蒙版 5 利用自动完成面制作蒙版 6 标注乡镇带晕渲文字 7 …

Golang 包详解以及go mod

Golang 中包的介绍和定义 包(package)是多个 Go 源码的集合,是一种高级的代码复用方案,Go 语言为我们提供了 很多内置包,如 fmt、strconv、strings、sort、errors、time、encoding/json、os、io 等。 Golang 中的包可以分为三种:1、系统内置包 2、自定义包 3、第三方包…

云服务器SVN仓库搭建(以阿里云为例)

远程连接阿里云服务器 安装svn(注意需要root权限使用命令sudo su) yum install subversion 安装成功后查看svn版本 svnserve --version 创建版本库的根目录 mkdir /var/svn 创建代码仓库 svnadmin create /var/svn/test 当前生成的目录结构 此处为svn的配置文件 创建用户名…

C# App.config和Web.config加密

步骤1:创建加密命令 使用ASP.NET提供的命令工具aspnet_regiis来创建加密命令。 1、打开控制台窗口,在命令行中输入以下命令: cd C:\Windows\Microsoft.NET\Framework\v4.xxxxx aspnet_regiis.exe -pef connectionStrings "C:\MyAppFo…

【PyQt5程序的打包和发布】

【PyQt5程序的打包和发布】 1 安装Pyinstaller模块2 打包普通Python程序3 打包PyQt5程序4 打包资源文件 1 安装Pyinstaller模块 pip install Pyinstaller2 打包普通Python程序 普通Python程序由Python内部库提供,不包含第三方库模板。 使用如下命令打包&#xff1…

【Docker】数据库动态授权组件在Kubernetes集群下的测试过程记录

目录 背景 组件原理 测试设计 环境 测试脚本 脚本build为linux可执行文件 镜像构建 Dockerfile Docker build 镜像有效性验证 总结 资料获取方法 背景 我们都知道出于安全性考虑,生产环境的权限一般都是要做最小化控制,尤其是数据库的操作授…

【现网】记一次并发冲突导致流量放大的生产问题

目录 事故现象 转账 业务背景介绍 背景一:转账流程 转账流程 转账异常处理 转账异常处理流程图 背景二:账户系统合并 实际全流程: 背景三:扣内存数据库逻辑 背景四:调用方重试逻辑 问题定位 总结 资料获取…

org.apache.hadoop.hive.ql.exec.DDLTask. show Locks LockManager not specified解决

Error while processing statement: FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. show Locks LockManager not specified解决 当在Hive中执行show locks语句时,出现"LockManager not specified"错误通常是由于…

认识“协议“序列化和反序列化

目录 前言 1 应用层 2 在谈协议 3 序列化和反序列化 4 网络版计算器 4.1 指定协议 request结构体 response结构体 4.2 服务端编写 4.3 客户端的编写 5 Json for C 的序列化和反序列化使用样例 前言 之前的socket编程,都是在通过系统调用层面,…

acwing第 115 场周赛第二题题解:维护最大值和次大值

一、链接 5132. 奶牛照相 二、题目 约翰的农场有 nn 头奶牛,编号 1∼n1∼n。 其中,第 ii 头奶牛的宽度为 wiwi,高度为 hihi, 有一天,它们聚餐后决定拍照留念。 关于拍照的描述如下: 它们一共拍了 nn…

C++初阶语法——命名空间

前言:C,即cplusplus,顾名思义,是C语言promax版本,C兼容C语言。 C的诞生是因为贝尔实验室的本贾尼等大佬认为C语言的语法坑实在太多,拥有许多不足之处(比如命名冲突,)&…

Vue3 实现产品图片放大器

Vue3 实现类似淘宝、京东产品详情图片放大器功能 环境&#xff1a;vue3tsvite 1.创建picShow.vue组件 <script lang"ts" setup> import {ref, computed} from vue import {useMouseInElement} from vueuse/core/*获取父组件的传值*/ defineProps<{images:…

通信原理板块-书籍推荐及学习系列

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 关注公众号&#xff0c;后台回复通信…

在WebStorm中通过live-server插件搭建Ajax运行环境

1.下载node.js 官网: https://nodejs.cn/download/ 2.配置Node.js的HTTPS 使用淘宝的镜像&#xff1a; npm config set registry https://registry.npm.taobao.org 也可以使用cnpm npm install -g cnpm --registryhttps://registry.npm.taobao.org 配置之后可以验证是否成…

Linux(四)--包软件管理器与Linux上环境部署示例

一.包软件管理器【yum和apt】 1.先来学习使用yum命令。yum&#xff1a;RPM包软件管理器&#xff0c;用于自动化安装配置Linux软件&#xff0c;并可以自动解决依赖问题。通过yum命令我们可以轻松实现软件的下载&#xff0c;查找&#xff0c;卸载与更新等管理软件的操作。 最常用…