上一篇文章解决了MATLAB在训练CNN网络时遇到的问题,现在要将网络搬到FPGA中去实现,这篇文章主要介绍该网络在FPGA中的实现方式,供大家学习,后续也会将该项目开源出来。但是该项目只设计了卷积核以及激活函数,因为在后续开发的过程中发现,使用传统的CNN网络在综合时会消耗很多资源,因此后续会考虑更换更加轻量级的Mobilenet V2网络。卷积神经网络(Convolutional Neural Network, CNN)是深度学习中的一种经典模型,特别适用于处理图像、语音和视频等数据。
上一篇文章解决了MATLAB在训练CNN网络时遇到的问题,现在要将网络搬到FPGA中去实现,这篇文章主要介绍该网络在FPGA中的实现方式,供大家学习,后续也会将该项目开源出来。
但是该项目只设计了卷积核以及激活函数,因为在后续开发的过程中发现,使用传统的CNN网络在综合时会消耗很多资源,因此后续会考虑更换更加轻量级的Mobilenet V2网络。
一、CNN模块介绍
卷积神经网络(Convolutional Neural Network, CNN)是深度学习中的一种经典模型,特别适用于处理图像、语音和视频等数据。CNN通过模拟生物视觉系统的工作原理来提取数据中的特征,在计算机视觉和图像识别领域取得了显著的成功。
在本项目中完成了CNN模块中的两个部分,卷积层以及激活函数层:
卷积层 (Convolutional Layer)
卷积层是CNN的核心。其主要作用是从输入数据中提取局部特征。卷积操作通过一个小的滤波器(或称卷积核)与输入数据进行滑动窗口式的计算,得到一个特征图。每个卷积核可以学习到不同的特征(例如边缘、纹理、形状等)。通过多层卷积,网络能够学习到从低级到高级的特征。
激活函数层 (Activation Layer)
卷积层的输出通常会经过一个激活函数(如ReLU、Sigmoid、Tanh等),以引入非线性因素,使网络能够学习和表示更加复杂的特征。ReLU(Rectified Linear Unit)是最常用的激活函数,它通过将负值设置为零,保留正值,有助于加速训练并避免梯度消失问题。
二、基础模块介绍
万丈高楼平地起,在设计过程中需要很多基础的运算模块,比如浮点数的加减运算模块
2.1 浮点数相加减
2.1.1 浮点数是什么
浮点数是与定点数相对的概念,计算机中的定点数约定小数点的位置不变,即人为约定俗成地规定了一个数小数点的位置。例如定点纯整数约定了小数点在数值位的最后。定点纯小数约定了数值位的最高位在小数点后面。
由于计算机字长的限制,当需要表示的数据有很大的数值范围时,他们不能直接用定点小数或者定点整数表示
在本项目中使用浮点数进行操作,能够最大程度的提高精度,提高卷积操作的效果。
2.1.2 单精度浮点数的形式
IEEE 754规定单精度浮点数的真值一般表示为
单精度浮点数编码格式由三个字段构成:
数符s为1位,表示浮点数的正负
尾数编码f为23位(采用原码表示)
阶码编码e为8位(含1位阶符,采用移码表示,偏移量127)
2.1.3 单精度浮点数的相加的verilog代码
假设有两个浮点数:
对阶:尾数右移,小阶对大阶,阶码小的尾数右移,阶码+1
尾数加减法运算:使用补码运算,减法也采用补码加法实现
规格化:尾数加减法运算后,结果可能是非规格化数。如果结果的真值M不满足:1 / 2 ≤ M < 1 ,则是非规格化数需要规格化处理。
代码如下:
always @ (floatA or floatB) begin
exponentA = floatA[30:23];
exponentB = floatB[30:23];
fractionA = {1'b1,floatA[22:0]};
fractionB = {1'b1,floatB[22:0]};
exponent = exponentA;
if (floatA == 0) begin //处理0现象
sum = floatB;
end else if (floatB == 0) begin
sum = floatA;
end else if (floatA[30:0] == floatB[30:0] && floatA[31]^floatB[31]==1'b1) begin // 刚好相反
sum=0;
end else begin
// 指数
if (exponentB > exponentA) begin // 比较指数
shiftAmount = exponentB - exponentA; // 计算指数差
fractionA = fractionA >> (shiftAmount); // 还原有效值
exponent = exponentB; // 大的是指数
end else if (exponentA > exponentB) begin
shiftAmount = exponentA - exponentB;
fractionB = fractionB >> (shiftAmount);
exponent = exponentA;
end
// 根据符号处理有效位数
if (floatA[31] == floatB[31]) begin //same sign
{cout,fraction} = fractionA + fractionB;
if (cout == 1'b1) begin
{cout,fraction} = {cout,fraction} >> 1;
exponent = exponent + 1;
end
sign = floatA[31];
end else begin //different signs
if (floatA[31] == 1'b1) begin
{cout,fraction} = fractionB - fractionA;
end else begin
{cout,fraction} = fractionA - fractionB;
end
sign = cout;
if (cout == 1'b1) begin
fraction = -fraction;
end
// 处理有效位
if (fraction [23] == 0) begin
if (fraction[22] == 1'b1) begin
fraction = fraction << 1;
exponent = exponent - 1;
end else if (fraction[21] == 1'b1) begin
fraction = fraction << 2;
exponent = exponent - 2;
...
end else if (fraction[0] == 1'b1) begin
fraction = fraction << 23;
exponent = exponent - 23;
end
end
end
mantissa = fraction[22:0];
sum = {sign,exponent,mantissa};
end
end
endmodule
这边简单分析一下代码逻辑,首先是对特殊情况进行处理,然后进行指数对其A如果两个浮点数的指数不同,较小的指数需要通过移位对齐。移位的数目是两个指数之间的差值。如果 eB > eA ,则 fA 需要右移 eB - eA 位,反之亦然。最后进行尾数的规范化,如果加法结果的尾数的最高位(即第 23 位)为 0(意味着结果不是规范化的),则需要对尾数进行左移操作,并相应地调整指,使其符合规范化格式。
2.2 浮点数相乘
浮点乘法的运算过程可以分为以下几个步骤,具体分析如下:
求乘积的阶码
乘法运算中,结果的阶码 Ez 是两个乘数阶码之和,即:Ez=Ex+Ey。其中,Ex 和 Ey 分别是乘数 x 和 y 的阶码部分。如果 Ez 超出了浮点数表示范围(根据规格化范围决定),则会出现溢出。
求乘积的尾数
乘法的核心是尾数部分的相乘。由于浮点数是规范化的,乘法时直接计算尾数的乘积:Mz=Mx×My,其中,Mx 和 My 分别是乘数 x 和 y 的尾数部分。尾数部分可能会有进位或溢出。
规格化乘积的尾数
由于乘法中尾数的积可能大于等于 1,尾数通常需要右移一次。如果尾数大于 1,需要右规,并且在右移的过程中,可以使用舍入算法来确保结果的精度。常见的舍入算法包括四舍五入,向零舍入,或向最近偶数舍入等。
左规(左移):如果尾数的最高有效位(即最左边的非零位)后面还有有效位(即尾数部分有进位),则需要左移尾数以满足规范化要求。
<