在运用Vivado HLS生成IP Core的过程中,HLS只能综合C或者C++语言,之前用于编写卷积神经网络的一直都是运用Python语言,虽然学习过C语言但是对它已经很陌生了,这里详细记录以下用C语言实现lenet-5的过程,希望对大家有所帮助。下面的代码是我在github上下载下来进行阅读的。
1、对卷积的定义
#include "lenet.h"
#include <memory.h>
#include <time.h>
#include <stdlib.h>
#include <math.h>
#define GETLENGTH(array) (sizeof(array)/sizeof(*(array)))
#define GETCOUNT(array) (sizeof(array)/sizeof(double))
#define FOREACH(i,count) for (int i = 0; i < count; ++i)
#define CONVOLUTE_VALID(input,output,weight)
{
FOREACH(o0,GETLENGTH(output))
FOREACH(o1,GETLENGTH(*(output)))
FOREACH(w0,GETLENGTH(weight))
FOREACH(w1,GETLENGTH(*(weight)))
(output)[o0][o1] += (input)[o0 + w0][o1 + w1] * (weight)[w0][w1];
以上代码中,反复用到了GETLENGTH这个函数,由于对C语言目前比较生疏,我重新翻看相关知识,其中涉及到了指针、sizeof等。
sizeof
sizeof运算符以字节为单位返回运算对象的大小,运算对象可以是具体的数据对象或类型。指针
指针是一个值为内存地址的变量。需要用到间接运算符 * ,如果前面有带 * ,这表示是相应地址的值。
具体的知识可以通过网络或者书籍进行查询。
#define GETLENGTH(array) (sizeof(array)/sizeof(*(array)))
这个语句中,对GETLENGTH函数进行了定义,输入的是一个矩阵,输出的是矩阵中的行数。sizeof计算的是整个矩阵的字节长度,而*(array)表示的是第一行有多少个字节,因为每一行的字节数都相等,所以两者相除求出的就是有多少行。
#define FOREACH(i,count) for (int i = 0; i < count; ++i)
本句代表定义一个FOREACH函数来遍历每个数。
#define CONVOLUTE_VALID(input,output,weight)
{
FOREACH(o0,GETLENGTH(output))
FOREACH(o1,GETLENGTH(*(output)))
FOREACH(w0,GETLENGTH(weight))
FOREACH(w1,GETLENGTH(*(weight)))
(output)[o0][o1] += (input)[o0 + w0][o1 + w1] * (weight)[w0][w1];
}
本段是进行卷积操作,padding=VALID类型的定义,运用了四个for循环,计算出最终卷积的结果,这里的定义十分巧妙,花时间去理解。
#define CONVOLUTE_FULL(input,output,weight) \
{ \
FOREACH(i0,GETLENGTH(input)) \
FOREACH(i1,GETLENGTH(*(input))) \
FOREACH(w0,GETLENGTH(weight)) \
FOREACH(w1,GETLENGTH(*(weight))) \
(output)[i0 + w0][i1 + w1] += (input)[i0][i1] * (weight)[w0][w1];
}
这段代码是对padding=SAME的定义。
#define CONVOLUTION_FORWARD(input,output,weight,bias,action)
{
for (int x = 0; x < GETLENGTH(weight); ++x)
for (int y = 0; y < GETLENGTH(*weight); ++y)
CONVOLUTE_VALID(input[x], output[y], weight[x][y]);
FOREACH(j, GETLENGTH(output))
FOREACH(i, GETCOUNT(output[j]))
((double *)output[j])[i] = action(((double *)output[j])[i] + bias[j]);
}
本段代码中加了偏置和激活函数,并且对前面定义的卷积运算进行了调用,得到的是前向传播过程中卷积加上偏置还有激活函数处理后所得到的结果。