从FPGA说起的深度学习(二)

news2024/11/26 18:31:50


2b24d6f6840ce76a2b08ef20eeed9ce5.png

这是新的系列教程,在本教程中,我们将介绍使用 FPGA 实现深度学习的技术,深度学习是近年来人工智能领域的热门话题。

在本教程中,旨在加深对深度学习和 FPGA 的理解。

  • 用 C/C++ 编写深度学习推理代码

  • 高级综合 (HLS) 将 C/C++ 代码转换为硬件描述语言

  • FPGA 运行验证

689bfeeac8cef1fdc389fc9ac0c9e7a6.png

在上一篇文章中,谈到了深度学习是什么以及在 FPGA 上进行深度学习的好处。在本课程的后续文章中,我们将开始开发针对 FPGA 的深度学习设计。特别是,在本文中,我们将首先在 Python 上运行训练代码,并创建一个网络模型方便在后续 FPGA 上运行。

在后续文章中,我们将根据实际源码进行讲解。稍后将提供包括 FPGA 设计在内的所有代码。

要使用的数据集工具

MNIST 数据库

本课程针对的问题是通常称为图像分类的任务。在这个任务中,我们输出一个代表输入图像的标签。这里的标签例如是图像中物体的通用名称。

MNIST 数据库(http://yann.lecun.com/exdb/mnist/)是一个包含 0 到 9 的手写数字的数据集,并为每个手写数字定义了正确的标签 (0-9)。由于输入是一张28x28的灰度图,输出最多10个类,在图像分类任务中属于非常初级的任务。除了 MNIST 之外,主要的图像分类数据集包括CIFAR10(https://www.cs.toronto.edu/~kriz/cifar.html)和ImageNet(http://www.image-net.org/),并且往往会按上诉列出的顺序成为更难的问题。

MNIST 在许多深度学习教程中都有使用,因为它是一个非常简单的数据集。由于本课程的主要目的是在 FPGA 上实现深度学习,因此我们将创建一个针对 MNIST 的学习模型。

2a3c70e5fa7d7c6e3b07c87a711b879f.pngMNIST 数据库——维基百科

PyTorch

PyTorch(https://pytorch.org/)是来自 Facebook 的开源深度学习框架,也是与来自 Google 的TensorFlow(https://www.tensorflow.org/)一起使用最频繁的框架之一。在本文中,我们将在 PyTorch 上学习和创建网络模型。

PyTorch安装参考官网步骤。我使用的 Ubuntu 16.04 LTS 上安装的 Python 3.5 不支持最新的 PyTorch,所以我使用以下命令安装了旧版本。

pip install torch==1.4.0 torchvision==0.5.0

由于本文的重点不在于如何使用PyTorch,所以我将省略代码的解释,尤其是学习部分。如果有兴趣,建议尝试下面的官方教程,尽管它是英文的。

  • 使用 PYTORCH 进行深度学习:60 分钟闪电战

https://pytorch.org/tutorials/beginner/deep_learning_60min_blitz.html

卷积神经网络

卷积神经网络 (CNN) 是一种旨在在图像任务中表现出色的神经网络。本文创建的网络模型是一个卷积神经网络,该网络由以下三层和激活函数的组合组成。

  • 全连接层

  • 卷积层

  • 池化层

  • 激活函数

下面概述了每一层的处理和作用。

全连接层

全连接层是通过将输入向量乘以内部参数矩阵来输出向量的过程。如果只说神经网络没有前言比如卷积,往往指的就是这个全连接层。

5cc16cc94baffc51edced2f4d6361d2e.png

由于全连接层的输入是向量,所以无法得到图像中应该注意的与周围像素点的关系。在卷积神经网络中,全连接层主要用于将卷积层和池化层创建的压缩图像信息(特征)转换为标签数据(0-9)。

卷积层

卷积层是对图像进行卷积处理的层。如果熟悉图像处理,可能会称其为滤镜处理。

在卷积层中,对于输入图像中的每个像素,获取周围像素的像素值,每个像素值乘以一个数组(kernel),它是一个内参,求和就是像素值输出图像。这以图形方式表示,如下所示。

e56821eb4dff19ccf4d6de8bf8fc3b88.png

卷积神经网络的许多层都是由这个卷积层组成的,因为它以这种方式针对图像处理。

池化层

池化层是用于压缩由卷积层获得的信息的层。使用本次使用的参数,通过取图像中2x2块的最大值将图像大小减半。下图显示了最大值的减少处理。

b6cba22df867210eb8a2a22db277e332.png

通过应用多个池化层,聚合图像每个部分的信息,并接近表示最终图像整体的标签信息。

激活函数

激活函数是一个非线性函数,插入在卷积层和全连接层之后。这是为了避免将实际上是线性函数的两层合并为一层。已经提出了各种类型的激活函数,但近年来 ReLU 是主要使用的激活函数。

ReLU 是 Rectified Linear Unit 的缩写。该函数会将小于0的输入值置为0,大于0的值原样输出。

创建网络模型

使用的网络模型是著名网络模型LeNet的简化版(http://yann.lecun.com/exdb/publis/pdf/lecun-98.pdf)。LeNet 是早期的卷积神经网络之一,和这个一样,都是针对手写识别的。

本文使用的模型定义如下。与原来的LeNet相比,有一些不同之处,例如卷积层的核大小减小,激活函数为ReLU。

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

        
        self.conv1 = nn.Conv2d(in_channels=1, out_channels=4, kernel_size=3, padding=1)

        # 激活函数ReLU
        self.relu1 = nn.ReLU(inplace=True)

        # 
        self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 4ch -> 8ch, 14x14 -> 7x7
        self.conv2 = nn.Conv2d(in_channels=4, out_channels=8, kernel_size=3, padding=1)
        self.relu2 = nn.ReLU(inplace=True)
        self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2)

        # 全连接层
        self.fc1 = nn.Linear(8 * 7 * 7, 32)
        self.relu3 = nn.ReLU(inplace=True)

        # 全连接层
        self.fc2 = nn.Linear(32, num_output_classes)

    def forward(self, x):
   
        # 激活函数ReLU
        x = self.conv1(x)
        x = self.relu1(x)

        # 缩小
        x = self.pool1(x)

        # 2层+缩小
        x = self.conv2(x)
        x = self.relu2(x)
        x = self.pool2(x)

        #  (Batch, Ch, Height, Width) -> (Batch, Ch)
        x = x.view(x.shape[0], -1)

        # 全连接层
        x = self.fc1(x)
        x = self.relu3(x)
        x = self.fc2(x)

        return x

使用netron(https://lutzroeder.github.io/netron/)可视化此模型的数据流如下所示:

cad522441f6d45e0d187e30e99a041f8.png

按照数据的顺序流动,首先输入一张手写图像(28×28, 1ch),在第一个Conv2d层转换为(28×28, 4ch)的图像,通过ReLU变成非负的。

然后通过 Maxpool2d 层将该图像缩小为 (14×14, 4ch) 图像。随后的 Conv2d、ReLU、MaxPool2d 遵循几乎相同的程序,生成 (7×7, 8ch) 图像。将此图像视为7x7x8 = 392度的向量,应用两次全连接层最终将输出10度的向量。此 10 阶向量中具有最大值的元素的索引 (argmax) 是推断字符 (0-9)。

学习/推理

使用上述模型训练 MNIST。这里需要执行三个步骤:

  • 加载训练/测试数据

  • 循环学习

  • 测试(推理)

首先,读取数据。PyTorch 带有一个预定义的 MNIST 加载器,我们用它来加载数据(训练集/测试集)。trainloader/testloader 是定义如何读取每个数据集中数据的对象。

import torch
import torchvision
import torchvision.transforms as transforms

# 2. 定义数据集读取方法
#获取MNIST的学习测试数据
trainset = torchvision.datasets.MNIST(root='./data', train=True, download=True, transform=transforms.ToTensor())
testset = torchvision.datasets.MNIST(root='./data', train=False, download=True, transform=transforms.ToTensor())

#数据读取方法的定义
#1步骤的每个学习测试读取16张图像
trainloader = torch.utils.data.DataLoader(trainset, batch_size=16, shuffle=True)
testloader = torch.utils.data.DataLoader(testset, batch_size=16, shuffle=False)

现在数据读取已经完成,是时候开始学习了。首先,我们定义损失函数(误差函数)和优化器。在后续的学习中,我们会朝着损失值减小的方向学习。

# 损失函数,最优化器的定义
loss_func = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(), lr=0.0001)

学习循环像这样:从trainloader读取输入数据(inputs),纠正标签(labels),通过网络得到输出(outputs),得到带有正确标签的error(loss)。

然后我们使用一种称为误差反向传播 (loss.backward()) 的技术来计算每一层的学习方向(梯度)。优化器使用获得的梯度来优化模型(optimizer.step())。这是一步学习流程,在此代码中,重复学习直到够 10 个数据集。

由于我们将使用 FPGA 进行推理处理,因此即使不了解此处描述的学习过程也没有问题。如果你对我们为什么这样学习感兴趣,请参考上一篇文章中一些通俗易懂的文献。

# 循环直到使用数据集中的所有图像10次
for epoch in range(10):
    running_loss = 0

    # 在数据集中循环
    for i, data in enumerate(trainloader, 0):
        # 导入输入批(图像,正确标签)
        inputs, labels = data

        # 零初始化优化程序
        optimizer.zero_grad()

        # 通过模型获取输入图像的输出标签
        outputs = net(inputs)

        # 与正确答案的误差计算+误差反向传播
        loss = loss_func(outputs, labels)
        loss.backward()

        # 使用误差优化模型
        optimizer.step()
        running_loss += loss.item()
        if i % 1000 == 999:
            print('[%d, %5d] loss: %.3f' %
                  (epoch + 1, i + 1, running_loss / 1000))
            running_loss = 0.0

测试代码如下所示:该测试与学习循环几乎相同,但省略了学习的损失计算。scikit-learn我们还使用这里的函数来accuracy_score, confusion_matrix输出准确度和混淆矩阵。

from sklearn.metrics import accuracy_score, confusion_matrix

# 4.测试
ans = []
pred = []
for i, data in enumerate(testloader, 0):
    inputs, labels = data
    outputs = net(inputs)

    ans += labels.tolist()
    pred += torch.argmax(outputs, 1).tolist()

print('accuracy:', accuracy_score(ans, pred))
print('confusion matrix:')
print(confusion_matrix(ans, pred))

通过到目前为止的实施,可以在 PyTorch 上训练和推断 MNIST。当我在 i7 CPU 上实际运行上述代码时,该过程在大约 3 分钟内完成。日志在下面。

[n, m] loss: X这条线表示第n个epoch(使用数据集的次数)和学习数据集中第m个数据时的损失(错误)。可以看出,随着学习的进行,损失几乎接近于 0。即使是这种非常小的网络模型也可以成功学习。

测试准确率最终显示出足够高的准确率,达到 97.26%。接下来的矩阵是混淆矩阵(confusion_matrix),其中第i行第j列的值代表正确答案i和推理结果j。这次数据的准确性足够了,结果似乎没有什么明显的偏差。这一次,我们一次性得到了足够的准确率,但是我们在原来的开发中并没有得到我们想要的准确率,所以我们在这里回顾一下模型和学习方法。

[1,  1000] loss: 1.765
[1,  2000] loss: 0.655
[1,  3000] loss: 0.475
...
[10,  1000] loss: 0.106
[10,  2000] loss: 0.101
[10,  3000] loss: 0.104
accuracy: 0.9726
confusion matrix:
[[ 973    0    1    0    0    2    0    2    2    0]
 [   0 1128    2    1    0    0    2    0    2    0]
 [   3    3  997    8    0    1    2    7   10    1]
 [   1    0    6  968    0   12    0    5   10    8]
 [   1    0    3    0  960    0    1    2    2   13]
 [   3    1    0    5    0  870    2    0    7    4]
 [  10    2    1    1    7    8  927    0    2    0]
 [   1    3    9    0    1    0    0 1006    1    7]
 [   5    1    1   12    4    3    3    8  927   10]
 [   5    6    0    3   10    1    0   10    4  970]]

最后,使用以下代码保存模型。前者是从 PyTorch 重新评估的模型文件,后者用于在 PyTorch 的 C++ API(libtorch)上读取网络模型。

# 5. 保存模型
# 用于从PyTorch正常读取的模型文件
torch.save(net.state_dict(), 'model.pt')

# 保存用于从libtorch(C++API)读取的Torch Script Module
example = torch.rand(1, 1, 28, 28)
traced_script_module = torch.jit.trace(net, example)
traced_script_module.save('traced_model.pt')

总结

在本文中,我们使用 MNIST 数据集作为学习目标来创建、训练和推断网络模型。

使用基于 LeNet 的轻量级网络模型,我们能够在 MNIST 数据集上实现良好的准确性。在下一篇和后续文章中,我们的目标是在 FPGA 上运行该模型,并在考虑高级综合(HLS)的情况下开始 C++ 实现。

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

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

相关文章

Leetcode.1797 设计一个验证系统

题目链接 Leetcode.1797 设计一个验证系统 Rating : 1534 题目描述 你需要设计一个包含验证码的验证系统。每一次验证中,用户会收到一个新的验证码,这个验证码在 currentTime时刻之后 timeToLive秒过期。如果验证码被更新了,那么它会在 curr…

完成各种项目生态环评工作丨全流程基于最新导则下的生态环境影响评价技术方法及图件制作与案例

目录 专题一 生态环境影响评价框架及流程 专题二 基于遥感解译的土地利用现状图的编制 专题三 生物多样性测定及R语言分析 专题四 植被类型及植被覆盖度图的编制 专题五 生物量与净初级生产力测定:实测及模型 专题六 生态系统类型及服务价值评估 专题七 物种…

【漫漫转码路】Day 45 机器学习 day04

梯度下降 梯度下降法是常用于求解无约束情况下凸函数的极小值,是一种迭代类型的算法,因为凸函数只有一个极值点,故求解出来的极小值点就是函数的最小值点 公式 第一个θ,是更新之后的θ,第二个θ是更新之前的θ&…

数据挖掘,计算机网络、操作系统刷题笔记47

数据挖掘,计算机网络、操作系统刷题笔记47 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测开 测开的话,你就得学数据库,sql,orac…

【LeetCode】1797. 设计一个验证系统

1797. 设计一个验证系统 题目描述 你需要设计一个包含验证码的验证系统。每一次验证中,用户会收到一个新的验证码,这个验证码在 currentTime 时刻之后 timeToLive 秒过期。如果验证码被更新了,那么它会在 currentTime (可能与之…

理解HDFS工作流程与机制,看这篇文章就够了

HDFS(The Hadoop Distributed File System) 是最初由Yahoo提出的分布式文件系统,它主要用来: 1)存储大数据 2)为应用提供大数据高速读取的能力 重点是掌握HDFS的文件读写流程,体会这种机制对整个分布式系统性能提升…

训练营day16

104.二叉树的最大深度 559.n叉树的最大深度111.二叉树的最小深度222.完全二叉树的节点个数104.二叉树的最大深度 力扣题目链接 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 示…

javaEE 初阶 — UDP 协议

文章目录UDP 协议1. UDP协议报文结构1.1 一个 UDP 数据报能传输的最大数据1.2 校验和1.3 生成校验和的算法UDP 协议 1. UDP协议报文结构 16位UDP长度,表示整个数据报(UDP首部UDP数据)的最大长度,如果校验和出错,就会直…

计算机网络之http02| HTTPS HTTP1.1的优化

post与get请求的区别 get 是获取资源,Post是向指定URI提交资源,相关信息放在body里 2.http有哪些优点 (1)简单 报文只有报文首部和报文主体,易于理解 (2)灵活易拓展 URI相应码、首部字段都没有…

ORB-SLAM2编译、安装等问题汇总大全(Ubuntu20.04、eigen3、pangolin0.5、opencv3.4.10)

ORB-SLAM2编译、安装等问题汇总大全(Ubuntu20.04、eigen3、pangolin0.5、opencv3.4.10) 1:环境说明: 使用的Linux发行版本为Ubuntu 20.04 SLAM2下载地址为:git clone https://github.com/raulmur/ORB_SLAM2.git ORB_SLAM2 2&a…

Element UI框架学习篇(二)

Element UI框架学习篇(二) 1 整体布局 1.1 前提说明 el-container标签里面的标签默认是从左往右排列,若想要从上往下排列,只需要写el-header或者el-footer就行了 <el-container>&#xff1a;外层容器 <el-header>&#xff1a;顶栏容器。 <el-aside>&#…

Android框架源码分析——从设计模式角度看 Retrofit 核心源码

Android框架源码分析——从设计模式角度看 Retrofit 核心源码 Retrofit中用到了许多常见的设计模式&#xff1a;代理模式、外观模式、构建者模式等。我们将从这三种设计模式入手&#xff0c;分析 Retrofit2 的核心源码。 1. 宏观 Retrofit 是一个外观模式的设计 外观模式&am…

Intel处理器分页机制

分页模式 Intel 64位处理器支持3种分页模式&#xff1a; 32-bit分页PAE分页IA-32e分页 32-bit分页 32-bit分页模式支持两种页面大小&#xff1a;4KB以及4MB。 4KB页面的线性地址转换 4MB页面的线性地址转换 PAE分页模式 PAE分页模式支持两种页面大小&#xff1a;4KB以及…

Java 验证二叉搜索树

验证二叉搜索树中等给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。有效 二叉搜索树定义如下&#xff1a;节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉搜索树。示例 1&…

ChatGPT注册流程攻略,含验证码接收(图文步骤)

本文给大家分享一下我成功注册的流程&#xff01; 其实方法都类似&#xff0c;若无海外手机号码可用接验证码的平台&#xff08;ps&#xff1a;我之前使用的是SMS-Activate&#xff09; 必要准备 能够科学上网&#xff08;并且全局模式&#xff09; 能确认登录的电子邮箱&…

ffmpeg硬解码与软解码的压测对比

文章目录ffmpeg硬解码与软解码的压测一、基本知识二、压测实验1. 实验条件及工具说明2. 压测脚本3. 实验数据结果ffmpeg硬解码与软解码的压测 一、基本知识 本文基于intel集显进行压测 软解码&#xff1a;cpu对视频进行解码硬解码&#xff1a;显卡或者多媒体处理芯片对视频进…

Python编程自动化办公案例(1)

作者简介&#xff1a;一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 一.使用库讲解 1.xlrd 2.xlwt 二.主要案例 1.批量合并 模板如下&#xf…

Python 如何快速搭建环境?

Python可应用于多平台包括 Linux 和 Mac OS X。 你可以通过终端窗口输入 “python” 命令来查看本地是否已经安装Python以及Python的安装版本。 Unix (Solaris, Linux, FreeBSD, AIX, HP/UX, SunOS, IRIX, 等等。) Win 9x/NT/2000 Macintosh (Intel, PPC, 68K) OS/2 DOS (多个…

67. Python的绝对路径

67. Python的绝对路径 文章目录67. Python的绝对路径1. 准备工作2. 路径3. 绝对路径3.1 概念3.2 查看绝对路径的方法4. 课堂练习5. 用绝对路径读取txt文件6. 加\改写绝对路径6.1 转义字符知识回顾6.2 转义字符改写7. 总结1. 准备工作 对照下图&#xff0c;新建文件夹和txt文件…

小知识点:MySQL 的 redo log、undo log、binlog 以及 Java 监控 binlog

SQL 入库流程 服务器与 MySQL 建立连接依次经过 MySQL 服务器内存中 Server 层的分析器、优化器、执行器执行器根据执行计划操作 InnoDB 引擎InnoDB 从磁盘数据文件中将 data 读到缓冲池中修改之前&#xff0c;会写入 undo log 将 data 存起来然后将缓冲池中的 data 改成 new_d…