海思ive ann-mlp使用说明(1)

1 概述

海思对于深度学习,提供了多层感知器的人工神经网络预测支持。其操作比较简单,加载由转换工具转换后的opencv训练产生的模型文件,组织测试数据,输入模型进行预测。

操作的时候,重点要注意的是,海思对于接收到的输入是按照s16q16的格式来解析的,所以,对于同样一个数值,给opencv训练,和给海思预测,就需要有不同的形式,以此,让它们两者从自己的角度来看,看到的是同一个数值。

2 基础知识介绍

2.1 标准数据的内存表示

2.1.1 整数的表示

参考http://blog.163.com/yql_bl/blog/static/847851692008112013117685/

2.1.2 浮点数的表示

参考http://blog.163.com/yql_bl/blog/static/847851692008112013117685/

2.2 海思的数据类型

HI_UxQyFz\HI_SxQy:

U 后面的数字 x 表示是用 x bit 无符号数据表示整数部分;

S 后面的数字 x 表示用 x bit 有符号数据表示整数部分;

Q 后面的数字 y 表示用 y bit 数据表示小数部分;

F 后面的数字 z 表示用 z bit 来表示标志位;

从左到右依次表示高 bit 位到低 bit 位。

如S16Q16表示,32bit的数据,左边(高)16bit表示的是整数数据,右边(低)16bit表示浮点数据。

那么对于一个二进制:0000000000000001 1010000000000000

就相当于是2^1+2^(-1)+2^(-3)=1.5625

2.2.1 标准数据与海思数据的转换

同样的内存内容,按照不同的格式去解析的话,会得到不同的值。

我们基于这样的前提:标准格式表达的值,是真实的值。

将一个标准表示里的值,按照海思的表达方式重新表达。或者将海思形式的值,转换为标准的值。

3 操作过程说明

3.1 利用opencv训练

这个就是正常的opencv的训练方式。

3.2 模型转换

海思提供了模型转换工具,将opencv训练产生的xml模型文件转换为二进制的模型文件,供海思加载。

3.3 模型加载

接口为:

HI_S32 HI_MPI_IVE_ANN_MLP_LoadModel(const HI_CHAR pchFileName,

IVE_ANN_MLP_MODEL_S pstAnnMlpModel)

【参数】

海思ive ann-mlp使用说明(1)

3.4 初始化相关的内存

这里的内存都涉及到了地址对齐的要求,需要使用HI_MPI_SYS_MmzAlloc接口进行内存分配。

3.4.1 输入层的内存

输入层的每一维都是一个SQ16.16的变量,32bit的长度。

输入层所需要的内存空间=(输入层的维数+1)*sizeof(SQ16.16)

地址对齐要求为16byte。

3.4.2 结果输出层的内存

输出层的每一维都是一个SQ16.16的变量,32bit的长度。

输出层所需要的内存空间=(输出层的维数)*sizeof(SQ16.16)

地址对齐要求为16byte。

3.4.3 保存原始图片的内存

原始图片的数据读取后保存的内存。这个可以有,也可以没有,看数据来源,以及怎么处理数据来源,如模拟四象限分类的数据,完全可以手工模拟出来。

这个内存中存储的是原始的数据,需要经过与opencv训练时采取的一致的特征提取方式提取(组织)特征,然后将各特征值,赋值给输入层的内存。

对齐的要求,不少于16byte,高的取决于读取的图片的宽,计算出每一行的图片实际要分配多少空间,具体为:

u16Stride=(width+(16-width%16)%16)。

具体分配的内存空间大小,首先按照每一行需要的空间的大小,然后根据图片的类型来计算。如是8位单通道的,则可以直接计算:u16Stride height;

如是420sp,则:u16Strideheight3/2

如是422sp,则:u16Strideheight2

如是16位但通道的,则:u16Strideheightsizeof(HI_U16)

如是u8c3的,则:u16Strideheight3

如是s32c1或u32c1的,则:u16Strideheightsizeof(HI_U32)

如是s64c1或u64c1的,则:u16Strideheight*sizeof(HI_U64)

完全可以不需要这样分配,可以根据opencv的接口来读取图片,进行特征提取。关键还是在于把特征传给输入层的内存。

3.5 创建查找表

激活函数的计算是指数类型的,计算较为耗时,创建了查找表以后,可以查表得到结果,加快速度。

查找表,其数据均为 S1Q15 类型数

据,最多 4096 个;鉴于当前 ANN 中支持的 Identify、Sigmoid、Gaussian 激活函数为奇或偶函数,查找表仅对输入 ∈ u [0, pstActivFuncTab→s32TabInUpper]建表及查表;对 ANN 激活函数建立查找表时用于归一化的 u8TabOutNorm 是表示移位的数目。

【定义】

typedef struct hiIVE_LOOK_UP_TABLE_S

{

IVE_MEM_INFO_S stTable;

HI_U16 u16ElemNum; /*LUTs elements number*/

HI_U8 u8TabInPreci;

HI_U8 u8TabOutNorm;

HI_S32 s32TabInLower; /*LUTs original input lower limit*/

HI_S32 s32TabInUpper; /*LUTs original input upper limit*/

}IVE_LOOK_UP_TABLE_S;

海思ive ann-mlp使用说明(1)目前对于这个u8TabOutNorm还未弄清楚其使用机制。

示例代码为:

static HI_S32 SAMPLE_IVE_Ann_Mlp_CreateTable(IVE_LOOK_UP_TABLE_S* pstTable, HI_FLOAT fAlpha, HI_FLOAT fBeta)

{

HI_U32 i;

HI_S1Q15* ps1q15Tmp;

HI_FLOAT fExpIn;

HI_DOUBLE dExpOut;

//Check table size

if (pstTable->stTable.u32Size < pstTable->u16ElemNum * sizeof(HI_S1Q15))

{

SAMPLE_PRT(“Invalid table size\n”);

return HI_FAILURE;

}

ps1q15Tmp = (HI_S1Q15*)pstTable->stTable.pu8VirAddr;

for (i = 0; i < pstTable->u16ElemNum; i++)

{

fExpIn = (HI_FLOAT)i / (1 << pstTable->u8TabInPreci);

dExpOut = (2 / (1 + exp(-fAlpha * fExpIn)) – 1) * fBeta * (1 << 15) / (1 << pstTable->u8TabOutNorm);

ps1q15Tmp[i] = (HI_CLIP(SAMPLE_IVE_Round(dExpOut), (1 << 15) – 1, -(1 << 15)));

}

return HI_SUCCESS;

}
<

调用过程为:

pstAnnInfo->stTable.s32TabInLower = 0;

pstAnnInfo->stTable.s32TabInUpper = 1;//1;

pstAnnInfo->stTable.u8TabInPreci = 8;//12;

pstAnnInfo->stTable.u8TabOutNorm = 2;//2

pstAnnInfo->stTable.u16ElemNum=(pstAnnInfo->stTable.s32TabInUpper-pstAnnInfo->stTable.s32tabInLower)<stTable.u8TabInPreci;

u32Size = pstAnnInfo->stTable.u16ElemNum * sizeof(HI_U16);

s32Ret = SAMPLE_COMM_IVE_CreateMemInfo(&(pstAnnInfo->stTable.stTable), u32Size);

3.6 特征提取与数据形式组织

特征提取的方式根据识别的要求而自定义,如人脸识别的特征提取与字符识别的特征提取方式不同。

不管用什么样的方式进行特征提取,都要保证:以相同的方式提取出来给opencv训练,给海思预测。

一种字符识别的特征提取方式描述如下:

测试(训练)图片是一张不是0就是255组成的黑白图片,没有其他灰度值。将这个图片按照nn(如44)的方式划分,每个n*n的小图片累加其灰度值,然后进行简单的归一化。这里要注意了:因为海思看待输入的方式是不同的,是以s16q16的方式来看待输入的,所以,一个同样的数值给opencv的时候,可以按照标准的方式给,但是给海思的时候,就必须要转换成s16q16的表达方式了。(或者反过来也行,以s16q16的方式去解析当前的数值,给opencv,总之就是要让两者看到的数值是一样的)。

如0.5,按照标准的float的表达的话,其二进制方式为:

0 01111110 000000000000000000000000

具体float的表达方式参考相关资料。

但是要是以s16q16的表达方式来说,其二进制,就是这样的:

0000000000000000 1000000000000000

所以,在给海思的时候,给的是下面的这个二进制值,这个二进制值对应的int值就是32768,所以,给海思的时候,给的32768!

转换某个数值为s16q16的方法,封装了一个changeFloatToS16Q16函数来进行,具体在后面的“辅助工具与方法”中。

对于正数来说,其乘于65536得到的数值的二进制表示,正好是该正数的s16q16的表达方式,所以可以看到海思预测时候的例子用的是直接乘于65536.

具体为:

static HI_VOID SAMPLE_IVE_Ann_Mlp_BinFeature_For_Predict(HI_U8* pu8GrayImg, HI_U16 u16Width, HI_U16 u16Height, HI_S16Q16* ps16q16BinFeature)

{

HI_U32 u32Step = 16;//4;

HI_U32 u16Sum = 0;

HI_U16 i, j;

HI_U16 m, n;

HI_U16 u16FeatureNum = 0;

printf(“calculate BinFeature:\n”);

for (i = 0; i < u16Height – u32Step + 1; i += u32Step)

{

for (j = 0; j < u16Width – u32Step + 1; j += u32Step)

{

u16Sum = 0;

for (m = i; m < i + u32Step; m++)

{

for (n = j; n < j + u32Step; n++)

{

u16Sum += pu8GrayImg[m * u16Width + n];

}

}

ps16q16BinFeature[u16FeatureNum++] = changeFloatToS16Q16(u16Sum*1.0/ (u32Step * u32Step * 255));

}

}

}
<

那对于训练时,给opencv训练的数值,就直接是:

ps16q16BinFeature[u16FeatureNum++] = u16Sum1.0/ (u32Step u32Step * 255);

3.7 执行预测

执行预测的接口为:

【语法】

HI_S32 HI_MPI_IVE_ANN_MLP_Predict(IVE_HANDLE pIveHandle,

IVE_SRC_MEM_INFO_S pstSrc, IVE_LOOK_UP_TABLE_S pstActivFuncTab,

IVE_ANN_MLP_MODEL_S pstAnnMlpModel, IVE_DST_MEM_INFO_S *pstDst, HI_BOOL

bInstant);

海思ive ann-mlp使用说明(1)

以上的参数是:

分配好的输入层空间,里面有以s16q16表达的值;

查找表的指针;

加载好的模型;

分配好的接收输出预测值的空间;

至于句柄(handle)与标志(bInstance)的解析为:

句柄(handle)

用户在调用算子创建任务时,系统会为每个任务分配一个 handle,用于标识不同的任务。

及时返回结果标志 bInstant

用户在创建某个任务后,希望及时得到该任务完成的信息,则需要在创建该任务时,将 bInstant 设置为 HI_TRUE。否则,如果用户不关心该任务是否完成,建议将 bInstant 设置为 HI_FALSE,这样可以与后续任务组链执行,减少中断次数,提升性能。

调用了该预测接口以后,根据返回的handle,需要进行query(需要循环),看任务的完成情况如何,进行相应的处理。如:

//创建任务

s32Ret = HI_MPI_IVE_ANN_MLP_Predict(&iveHandle,

&(pstAnnInfo->stSrc),

& (pstAnnInfo->stTable),

&(pstAnnInfo->stAnnModel),

&(pstAnnInfo->stDst),

bInstant);

if (s32Ret != HI_SUCCESS)

{

SAMPLE_PRT(“HI_MPI_IVE_ANN_MLP_Predict fail,Error(%#x)\n”, s32Ret);

break;

}

//查询任务情况

s32Ret = HI_MPI_IVE_Query(iveHandle, &bFinish, bBlock);

while (HI_ERR_IVE_QUERY_TIMEOUT == s32Ret)

{

usleep(100);

s32Ret = HI_MPI_IVE_Query(iveHandle, &bFinish, bBlock);

}

if (HI_SUCCESS != s32Ret)

{

SAMPLE_PRT(“HI_MPI_IVE_Query fail,Error(%#x)\n”, s32Ret);

break;

}

//准备输出结果

u16LayerCount = pstAnnInfo->stAnnModel.au16LayerCount[pstAnnInfo->stAnnModel.u8LayerNum – 1];

for (k = 0; k < u16LayerCount; k++)

{

printf(” ps16q16Dst[%d]=%d,H16Q16=%f\n”, k,ps16q16Dst[k],calculateS16Q16_c(ps16q16Dst[k]));

if (s16q16Response < ps16q16Dst[k])

{

s16q16Response = ps16q16Dst[k];

s32ResponseCls = k;

}

}
<

最后得到的s32ResponseCls 就是预测该测试样本所属的类别。

4 辅助工具与方法

4.1 模型文件转换工具

利用海思提供的ive_tool_xml2bin_ui.exe工具,在window下执行转换。

4.2 数据表示方式转换方法

4.2.1 获得一个short的二进制字符串

//得到一个short的二进制字符串

//返回字符串的长度

int decShortToBin(int dec,char *bstr){

int mod=0;

char tmpstr[64];

bzero(tmpstr,sizeof(tmpstr));

bzero(bstr,sizeof(bstr));

int i=0;

while(dec>0){

mod=dec%2;

dec/=2;

tmpstr[i]=mod+0;

i++;

}

//cout<<“i=”<

while(i

tmpstr[i++]=0;

}

//cout<<“tmpstr=”<

unsigned int len=strlen(tmpstr);

for(i=0;i

bstr[i]=tmpstr[len-i-1];

}

return (int)len;

}
<

4.2.2 获得一个int数据的二进制字符串

int decIntToBin(int dec,char *bstr){

int mod=0;

char tmpstr[64];

bzero(tmpstr,sizeof(tmpstr));

bzero(bstr,sizeof(bstr));

int i=0;

while(dec>0){

mod=dec%2;

dec/=2;

tmpstr[i]=mod+0;

i++;

}

//cout<<“i=”<

while(i

tmpstr[i++]=0;

}

//cout<<“tmpstr=”<

unsigned int len=strlen(tmpstr);

for(i=0;i

bstr[i]=tmpstr[len-i-1];

}

return (int)len;

}
<

4.2.3 解析一个以s16q16格式表示的内存,其真正的值是多少

float calculateS16Q16_c(int value){

char bs[64];

decIntToBin(value,bs);

//cout<<“calculateS16Q16_c:value=”<

float fout=0;

//cout<<“calculateS16Q16_c———-begin”<

int i=16;

for(i=16;i<32;i++){//the higher 16bit standard for float value———different from bitset

if(bs[i]==1){

fout+=pow(2,(i-16+1)*(-1));

//cout<<“+2^(“<<(i-16+1)*(-1)<<“)”;

}

}

//cout<<“\ncalculateS16Q16_c result=”<

for(i=0;i<16;i++){

if(bs[i]==1){

fout+=pow(2,(16-i-1));

}

}

return fout;

}

4.2.4 将一个float值,转换为S16q16的值

将一个float值,表示为以s16q16表示的形式,返回的是s16q16的形式的内存,按照正常的理解所代表的值。

int changeFloatToS16Q16(float val){

int sign=1;

if(val<0){

sign=-1;

}

int intPart=0;

float floatPart=0;

intPart=(int)abs(val);

floatPart=(val>0?val:val*(-1))-intPart;

//for float part

bitset<16> fpbs;

float tmpFl=floatPart;

for(int i=15;i>=0;i–){

tmpFl=tmpFl*2;

if(tmpFl>=1){

fpbs[i]=1;

tmpFl-=1;

}else{

fpbs[i]=0;

}

}

int sPart=intPart*sign;

bitset<16> ipbs(sPart);

//out

//

// cout<<“float val=”<

bitset<32> resultBs;

int result=0;

for(int i=0;i<16;i++){//highest one is sign

resultBs[32-16+i]=ipbs[i];

}

for(int i=0;i<16;i++){

resultBs[i]=fpbs[i];

}

// cout<<“resultBs=”<

result=resultBs.to_ulong();

return result;

}
<

原文:https://blog.csdn.net/brightming/article/details/50895356

免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:海思ive ann-mlp使用说明(1) https://www.yhzz.com.cn/a/14891.html

上一篇 2023-05-12
下一篇 2023-05-12

相关推荐

联系云恒

在线留言: 我要留言
客服热线:400-600-0310
工作时间:周一至周六,08:30-17:30,节假日休息。