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.56252.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,则: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;
目前对于这个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)<
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=”<
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