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

在学了在学了! 2020-08-25 18:07:51 2473

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)
【参数】

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,则:u16Stride
height3/2
如是422sp,则:u16Stride
height2
如是16位但通道的,则:u16Stride
heightsizeof(HI_U16)
如是u8c3的,则:u16Stride
height3
如是s32c1或u32c1的,则:u16Stride
heightsizeof(HI_U32)
如是s64c1或u64c1的,则:u16Stride
height*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; /*LUT's elements number*/
HI_U8 u8TabInPreci;
HI_U8 u8TabOutNorm;
HI_S32 s32TabInLower; /*LUT's original input lower limit*/
HI_S32 s32TabInUpper; /*LUT's original input upper limit*/
}IVE_LOOK_UP_TABLE_S;


目前对于这个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)<<pstAnnInfo->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);

以上的参数是:
分配好的输入层空间,里面有以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="<<i<<endl;
while(i<sizeof(short)*8){
    tmpstr[i++]='0';
}

//cout<<"tmpstr="<<tmpstr<<endl;
unsigned int len=strlen(tmpstr);
for(i=0;i<len;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="<<i<<",sizeof(int)="<<sizeof(int)*8<<endl;
while(i<sizeof(int)*8){
    tmpstr[i++]='0';
}

//cout<<"tmpstr="<<tmpstr<<endl;
unsigned int len=strlen(tmpstr);
for(i=0;i<len;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="<<value<<",binary="<<bs<<endl;
float fout=0;
//cout<<"calculateS16Q16_c----------begin"<<endl<<"fout=";
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="<<fout<<endl;
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="<<val<<",change to S16Q16,bitset(intPart)="<<ipbs<<",floatPart="<<fpbs<<endl;

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="<<resultBs<<endl;

result=resultBs.to_ulong();

return result;

}

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

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 点赞 收藏 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
在学了在学了!
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区