在上一篇文章中,我们用c语言实现了一个卷积层,并查看了结果。在本文中,我们将实现其余未实现的层:全连接层、池化层和激活函数 relu。
每一层的实现
全连接层
全连接层是将输入向量x乘以权重矩阵w,然后加上偏置b的过程。下面转载第二篇的图,能按照这个图计算就可以了。
全连接层的实现如下。
void linear(const float *x, const float* weight, const float* bias, int64_t in_features, int64_t out_features, float *y) { for (int64_t i = 0; i < out_features; ++i) { float sum = 0.f; for (int64_t j = 0; j > ksize考虑到这一点,在很多情况下,全连接层参数的内存需求大大超过了卷积层。
由于fpga内部有丰富的sram缓冲区,因此擅长处理内存访问量大和内存数据相对于计算总量的大量复用。
单个全连接层不会复用权重数据,但是在视频处理等连续处理中,这是一个优势,因为要进行多次全连接。
另一方面,本文标题中也提到的边缘环境使用小型fpga,因此可能会出现sram容量不足而需要访问外部dram的情况。
如果你有足够的内存带宽,你可以按原样访问它,但如果你没有足够的内存带宽,你可以在参数调整和训练后对模型应用称为剪枝和量化的操作。
池化层
池化层是对输入图像进行缩小的过程,这次使用的方法叫做2×2 maxpooling。在这个过程中,取输入图像2x2区域的最大值作为输出图像一个像素的值。这个看第二张图也很容易理解,所以我再贴一遍。
即使在池化层,输入图像有多个通道,但池化过程本身是针对每个通道独立执行的。因此,输入图像中的通道数和输出图像中的通道数在池化层中始终相等。
池化层的实现如下所示:
void maxpool2d(const float *x, int32_t width, int32_t height, int32_t channels, int32_t stride, float *y) { for (int ch = 0; ch < channels; ++ch) { for (int32_t h = 0; h < height; h += stride) { for (int32_t w = 0; w < width; w += stride) { float maxval = -flt_max; for (int bh = 0; bh < stride; ++bh) { for (int bw = 0; bw < stride; ++bw) { maxval = std::max(maxval, x[(ch * height + h + bh) * width + w + bw]); } } y[(ch * (height / stride) + (h / stride)) * (width / stride) + w / stride] = maxval; } } }}
这个函数的接口是:
此实现省略了边缘处理,因此图像的宽度和高度都必须能被stride整除。
输入
x: 输入图像。shape=(channels, height, width)
输出
y: 输出图像。shape=(channels, height/stride, width/stride)
参数
width: 图像宽度
height: 图像高度
stride:减速比
relu
relu 非常简单,因为它只是将负值设置为 0。
void relu(const float *x, int64_t size, float *y) { for (int64_t i = 0; i 数组是为了决定在电路制作时用于访问数组的地址的位宽。
另外,in_features的值为778=392,out_将features的值固定为32。这是为了避免vivado hls 在循环次数可变时输出性能不佳。
static const std::size_t kmaxsize = 65536;void linear_hls(const float x[kmaxsize], const float weight[kmaxsize], const float bias[kmaxsize], float y[kmaxsize]) { dnnk::linear(x, weight, bias, 7*7*8, 32, y);}
linear_hls函数的综合报告中的“性能估计”如下所示:
在timing -> summary中写入了综合时指定的工作频率,此时的工作频率为5.00 ns = 200mhz。
重要的是 latency -> summary 部分,它描述了执行此函数时的周期延迟(latency(cycles))和实时延迟(latency(absolute))。看看这个,我们可以看到这个全连接层在 0.566 ms内完成。
在 latency -> detail -> loop 列中,描述了每个循环的一次迭代所需的循环次数(iteration latency)和该循环的迭代次数(trip count)。
延迟(周期)包含iteration latency * trip count +循环初始化成本的值。loop 1 是out_features循环到loop 1.1 in_features。
在loop1.1中进行sum += x[j] * weight[i * in_features + j]; 简单计算会发现需要 9 个周期才能完成 loop 1.1 所做的工作。
使用hls中的“schedule viewer”功能,可以更详细地了解哪些操作需要花费更多长时间。
下图横轴的2~10表示loop1.1的处理内容,大致分为x,weights等的加载2个循环,乘法(fmul)3个循环,加法(fadd)4个循环共计9个循环。
在使用 hls 进行开发期间通过添加#pragma hls pipeline指令,向此代码添加优化指令以指示它创建高效的硬件。
与普通的 fpga 开发类似,运算单元的流水线化和并行化经常用于优化。通过这些优化,hls 报告证实了加速:
流水线:减少迭代延迟(min=1)
并行化:减少行程次数,删除循环
正如之前也说过几次的那样,这次的课程首先是以fpga推理为目的,所以不会进行上述的优化。
最后,该函数的接口如下所示。
由于本次没有指定接口,所以数组接口如x_ 等ap_memory对应fpga上可以1个周期读写的存储器(bram/distributed ram)。
在下一篇文章中,我们将连接每一层的输入和输出,但在这种情况下,我们计划连接 fpga 内部的存储器作为每一层之间的接口,如本例所示。
总结
在本文中,我们实现了全连接层、池化层和 relu。现在我们已经实现了所有层,我们将在下一篇文章中组合它们。之后我们会实际给mnist数据,确认我们可以做出正确的推论。
WGDC2018盛大开幕,南方测绘精彩亮相群惊四座
PIC单片机复位系统模块介绍
华为手环4高清图集
恩智浦半导体三季度营收24.45亿美元 净利润同比增长1390%
pcb抄板需要什么工具
用C语言实现一个全连接层和激活函数ReLU
14000PPI红光Micro LED,这家厂商如何做到?
什么是手机信号强度?
“国产x86”宝德芯片跑分软件上代号却为英特尔芯片,“换标”疑云再起!
荣耀8X Max真机测评,三面无边框的精彩设计,屏占比高达90.05%
一个高可用的接口是该考虑哪些内容
三星电子正在越南建设Mini LED电视生产线
现在市场上有哪些手机可支持3G业务啊?
瓦斯继电器作用
车载显示设备的显示角度调节
电快速脉冲群实验及其对策综述
电容式触摸屏的工作原理是怎么样的
华为鸿蒙支持哪些手机 华为鸿蒙系统支持机型
诺基亚助力智慧城市发展,推出区块链传感系统服务
德州仪器秦小林入选“2022福布斯中国科技女性50”