TensorFlow框架的Resnet50移植

TensorFlow框架的Resnet50移植

本教程针对TensorFlow框架,从模型转化到推理逐步解析Resnet50的移植过程
难易程度: |实验人次:112

前言

本文主要梳理了整体移植过程,分成以下几个流程:

1、模型从ckpt转换成pb

2、CPU推理用于验证

3、模型量化

4、MLU在线推理

5、MLU离线推理

 

实验环境:sdk1.6.1+python3.5+tensorflow 

代码参考链接:https://github.com/CambriconECO/TensorFlow_Resnet50_Inference

一、模型从ckpt转换成pb

MLU在线推理时使用的是量化后的pb,但Tensorflow官网中提供的原始模型大部分都是checkpoint的形式,所以先要将ckpt格式的模型转换为pb格式。如果已经有pb模型可以直接跳到第2步开始。

下载ckpt文件:http://download.tensorflow.org/models/resnet_v1_50_2016_08_28.tar.gz

进入到Cambrcion TensorFlow源码目录中:

① 生成没有权值的pb 

在tensorflow_models/research/slim目录下执行:

python export_inference_graph.py \
  --alsologtostderr \
  --model_name=resnet_v1_50 \
  --image_size=224 \ 
  --output_file=path/to/resnet50_v1.pb \          (要改成自己的输出路径)           
  --labels_offset=1 \  (不同分类网络对应的类别不同,需注意,默认为0)
  --batch_size=1

②与下载好的ckpt结合生成带权值的pb文件

在tensorflow目录下执行:

bazel-bin/tensorflow/python/tools/freeze_graph \                                                                               
  --input_graph=/path/to/resnet50_v1.pb \                             第①步生成的pb路径
  --input_checkpoint=/path/to/resnet_v1_50.ckpt \                     下载好的ckpt路径
  --input_binary=true \
  --output_graph=/path/to/frozen_resnet50_v1.pb \                     待生成的有权重的pb路径
  --output_node_names=resnet_v1_50/predictions/Reshape_1              pb的最后节点信息

上述步骤执行完则可以生成待量化的有权重pb文件。

注意:以下CPU推理、模型量化、MLU在线推理使用同一套代码执行,只要选择不同的选项即可

Cambricon TensorFlow对底层进行了修改,用户只要对自己原有的demo和代码做简单的修改,设置相应的环境变量就能够完成CPU推理、模型量化和在线推理。

二、CPU推理

①如何运行

在tf_resnet50_v1目录下执行:

# 对一张图片进行推理,输出top5类别及概率,概率从小到大输出
python tf_forward.py --input_pb resnet50_v1.pb --mode cpu

    运行结果:

---------------------TOP_5-------------------------------------------
[273270279271281]
[0.0256091860.079628910.1216833140.292274420.39985612]
['273:coyote, prairie wolf, brush wolf, Canis latrans''270:timber wolf, grey wolf, gray wolf, Canis lupus''279:kit fox, Vulpes macrotis''271:white wolf, Arctic wolf, Canis lupus tundrarum''281:grey fox, gray fox, Urocyon cinereoargenteus']

 

②关键代码

# 设置对应环境变量
if FLAGS.mode=='cpu':
    os.environ['MLU_VISIBLE_DEVICES']=''    #表示使用CPU进行推理
 
# 前处理
if FLAGS.mode=='cpu':
    image = image - mean
# 推理
image=image[np.newaxis, :]
images = np.repeat(image, FLAGS.batch_size, axis=0)
out = session.run(output, feed_dict={input[0]:images})

 

三、模型量化

在MLU在线推理时,需要使用到量化的pb模型。所以在推理前,需要进行量化操作。

①如何运行

在tf_resnet50_v1目录下执行:

cp resnet50_v1_quant_param.txt_param resnet50_v1_quant_param.txt     # 设置量化配置初始文件
python tf_forward.py --input_pb resnet50_v1.pb --mode quant       # 量化

执行完成后,会将量化参数写入resnet50_v1_quant_param.txt文件,保存了对应层的量化参数,在后续的在线推理中会设置环境变量引用到该文件。

 

②量化初始配置文件内容

bit_width:8                                    # 量化位宽,8或者16
quant_mode:naive                               # 量化模式,目前只支持naive模式
 
 
# 这个表示开启convfirst
convfirst_param:
{
node_name: resnet_v1_50/conv1/Conv2D           # 网络中第一个conv节点的名称
mean:124116127                             # 对应通道均值
std: 1,1,1                                     # 方差
color_mode: rgb                     
}

注意:

若⼀个⽹络要使⽤ convfirst,要满⾜以下⼏个条件:

  • ⽹络的前处理不能包含在 graph 中
  • ⽹络的前处理必须是以下形式或者可以转换为以下形式:input = ( input – mean ) / std
  • ⽹络的第⼀层必须是 Conv2D,输⼊图⽚必须是 3 通道

 

③量化关键代码

# 设置环境变量
if FLAGS.mode=='quant':
    os.environ['MLU_VISIBLE_DEVICES']=''                               # 量化仍使用CPU推理
    os.environ['MLU_QUANT_PARAM']='resnet50_v1_quant_param.txt'        # 设置量化参数环境变量
    os.environ['MLU_RUNNING_MODE']='0'                                 # 0表示量化,1表示在线推理
 
# 前处理同CPU,后续的在线推理无须在CPU上做减均值除方差的前处理,因为已经挪到第一层conv2d上
if FLAGS.mode=='quant':
    image = image - mean
# 推理
image=image[np.newaxis, :]
images = np.repeat(image, FLAGS.batch_size, axis=0)
out = session.run(output, feed_dict={input[0]:images})

四、MLU在线推理

①如何运行

在tf_resnet50_v1目录下执行:

  在线逐层推理:

python tf_forward.py --input_pb resnet50_v1.pb --mode online_layer

  运行结果:

---------------------TOP_5-------------------------------------------
[270281279278271]
[0.0660772250.0903699550.130020930.159065430.4323373]
['270:timber wolf, grey wolf, gray wolf, Canis lupus''281:grey fox, gray fox, Urocyon cinereoargenteus''279:kit fox, Vulpes macrotis''278:red fox, Vulpes vulpes''271:white wolf, Arctic wolf, Canis lupus tundrarum']

    在线融合推理:

python tf_forward.py --input_pb resnet50_v1.pb --mode online_fusion

   运行结果:

---------------------TOP_5-------------------------------------------
[273270279271281]
[0.0258178710.083435060.0861206050.266357420.46118164]
['273:coyote, prairie wolf, brush wolf, Canis latrans''270:timber wolf, grey wolf, gray wolf, Canis lupus''279:kit fox, Vulpes macrotis''271:white wolf, Arctic wolf, Canis lupus tundrarum''281:grey fox, gray fox, Urocyon cinereoargenteus']

 

②关键代码

# 设置环境变量
if FLAGS.mode=='online_layer':
    os.environ['MLU_VISIBLE_DEVICES']='0'                            # 0表示使用MLU推理
    os.environ['MLU_QUANT_PARAM']='resnet50_v1_quant_param.txt'      # 使用量化参数文件
    os.environ['MLU_RUNNING_MODE']='1'                               # 1表示运行推理
    os.environ['MLU_STATIC_NODE_FUSION']='false'                     # 逐层模式设置为false  
 
if FLAGS.mode=='online_fusion':
    os.environ['MLU_VISIBLE_DEVICES']='0'
    os.environ['MLU_QUANT_PARAM']='resnet50_v1_quant_param.txt'
    os.environ['MLU_RUNNING_MODE']='1'
    os.environ['MLU_STATIC_NODE_FUSION']='true'                      # 融合模式设置为true
 
# 推理,无须在cpu上做减均值除方差的前处理
image=image[np.newaxis, :]
images = np.repeat(image, FLAGS.batch_size, axis=0)
out = session.run(output, feed_dict={input[0]:images})

 

五、MLU离线推理

①生成离线模型,和上述推理使用同一套代码

在tf_resnet50_v1目录下执行:

python tf_forward.py --input_pb resnet50_v1.pb --mode offline

运行后即可在当前目录生成.cambricon和.cambricon_twins文件。.cambricon为生成的离线模型,.cambricon_twins存储了离线模型相关信息,可以打开进行查看离线模型融合情况。

 关键代码

if FLAGS.mode=='offline':
    os.environ['MLU_VISIBLE_DEVICES']='0'
    os.environ['MLU_QUANT_PARAM']='resnet50_v1_quant_param.txt'
    os.environ['MLU_RUNNING_MODE']='1'
    os.environ['MLU_STATIC_NODE_FUSION']='true'
 
if FLAGS.mode=='offline':
    config.mlu_options.save_offline_model = True                       # 保存离线模型
    config.mlu_options.offline_model_name = "resnet50_v1.cambricon"    # 离线模型名

 

② 离线推理

使用CNRT接口调用生成的离线模型.cambricon完成推理

在cnrt_resnet50_demo目录下执行:

# 编译
make
# 运行
./cnrt_resnet50_demo ../tf_resnet50_v1/resnet50_v1.cambricon subnet0 0 0 ../tf_resnet50_v1/fox.jpg 1

运行结果:

---------------------------TOP_5-----------------------------
275 272 281 279 278
0.00621033 0.02034 0.0823364 0.131348 0.756836
275:dhole, Cuon alpinus
272:red wolf, maned wolf, Canis rufus, Canis niger
281:grey fox, gray fox, Urocyon cinereoargenteus
279:kit fox, Vulpes macrotis
278:red fox, Vulpes vulpes
-------------------------------------------------------------

 

关键代码解析:

 折叠源码
unsigned int            dev_num=0;
  
 CNRT_CHECK(cnrtInit(0)); 
 CNRT_CHECK(cnrtGetDeviceCount(&dev_num));
 if (dev_num == 0)
     return -1;
  
 const char              *model_path=argv[1];        //模型路径
 const char              *function_name=argv[2];     //离线模型中的Function Name
 unsigned                dev_id=atoi(argv[3]);       //使用的MLU 设备ID
 int                     dev_channel=atoi(argv[4]);  //使用MLU设备的通道号 -1 为自动分配
 const char              *image_path=argv[5];        //测试图片的路径
 int                     is_rgb=atoi(argv[6]);
                                                      
 
 int                     input_width;                //网络输入的宽度
 int                     input_height;               //网络输入的高度
 int                     batch_size;                 //网络的batch
                                                      
 int64_t                 *inputSizeS;                //网络输入数据大小,用于分配内存
 int64_t                 *outputSizeS;               //网络输出数据量大小,用于分配内存
                                                      
 cnrtDataType_t          *inputTypeS;                //网络输入的数据类型
 cnrtDataType_t          *outputTypeS;               //网络输出的数据类型
                                                      
 vector<int>           output_count;
                                                      
                                                      
 cnrtQueue_t             queue;                      //cnrt queue
                                                      
 cnrtModel_t             model;                      //离线模型
 cnrtFunction_t          function;                   //离线模型中的Function
 cnrtDev_t               dev;                        //MLU设备句柄
 cnrtRuntimeContext_t    ctx;                        //推理上下文
 
 int                     *dimValues;                 //保存维度shape            
 int                     dimNum;                     //保存维度大小
  
 int                     inputNum;                   //输入节点个数
 int                     outputNum;                  //输出节点个数
 
 void                    **param;                    //保存推理时,mlu上内存地址的指针
 void                    **inputCpuPtrS;             //输入数据的CPU指针
 void                    **outputCpuPtrS;            //输出数据的CPU指针
 void                    **outputCpuNchwPtrS;        //用来保存transpose后的NCHW数据
 
 void                    **inputMluPtrS;             //输入数据的MLU指针
 void                    **outputMluPtrS;            //输出数据的MLU指针
 
 cnrtNotifier_t          notifier_start;             //用来记录硬件时间
 cnrtNotifier_t          notifier_end;
 unsigned int            affinity=1<<dev_channel;    //设置通道亲和性,使用指定的MLU cluster做推理
 cnrtInvokeParam_t       invokeParam;                //invoke参数
  
  
 //获取指定设备的句柄
 CNRT_CHECK(cnrtGetDeviceHandle(&dev, dev_id));
  
 //设置当前使用的设备,作用于线程上下文
 CNRT_CHECK(cnrtSetCurrentDevice(dev));
 
 //加载离线模型
 CNRT_CHECK(cnrtLoadModel(&model, model_path));
  
 //创建function
 CNRT_CHECK(cnrtCreateFunction(&function));
  
 //从离线模型中提取指定的function,离线模型可以存储多个function
 CNRT_CHECK(cnrtExtractFunction(&function, model, function_name));
  
 //调用cnrtSetCurrentChannel之后 CNRT 仅在指定的通道上分配MLU内存,否则采用交织的方式分配
 if(dev_channel>=0)
     CNRT_CHECK(cnrtSetCurrentChannel((cnrtChannelType_t)dev_channel));
 
 //创建运行时
 CNRT_CHECK(cnrtCreateRuntimeContext(&ctx,function,NULL));
 
 //设置运行时使用的设备ID
 CNRT_CHECK(cnrtSetRuntimeContextDeviceId(ctx,dev_id));
  
 //初始化运行时
 CNRT_CHECK(cnrtInitRuntimeContext(ctx,NULL));
 
 //创建队列
 CNRT_CHECK(cnrtRuntimeContextCreateQueue(ctx,&queue));
  
 //获取模型输入/输出 的数据大小及节点个数
 CNRT_CHECK(cnrtGetInputDataSize(&inputSizeS,&inputNum,function));
 CNRT_CHECK(cnrtGetOutputDataSize(&outputSizeS,&outputNum,function));
  
 //获取模型输入/输出 的数据类型
 CNRT_CHECK(cnrtGetInputDataType(&inputTypeS,&inputNum,function));
 CNRT_CHECK(cnrtGetOutputDataType(&outputTypeS,&outputNum,function));
 
 //分配 存放CPU端输入/输出地址的 指针数组
 inputCpuPtrS = (void **)malloc(sizeof(void *) * inputNum);
 outputCpuPtrS = (void **)malloc(sizeof(void *) * outputNum);
 outputCpuNchwPtrS = (void **)malloc(sizeof(void *) * outputNum);
 
 //分配 存放MLU端输入/输出地址的 指针数组
 outputMluPtrS = (void **)malloc(sizeof(void *) * outputNum);
 inputMluPtrS = (void **)malloc(sizeof(void *) * inputNum);
 
 //为输入节点 分配CPU/MLU内存
 for (int i = 0; i < inputNum; i++)
 {  
     CNRT_CHECK(cnrtMalloc(&inputMluPtrS[i],inputSizeS[i])); //分配MLU上内存
     inputCpuPtrS[i] = (void *)malloc(inputSizeS[i]); //分配CPU上的内存
      
     //获取输入的维度信息 NHWC
     CNRT_CHECK(cnrtGetInputDataShape(&dimValues,&dimNum,i,function));                       
     printf("input shape:\n");
     for(int y=0;y<dimNum;y++)
     {
         printf("%d ",dimValues[y]);
     }
     printf("\n");
 
     input_width=dimValues[2];
     input_height=dimValues[1];
     batch_size=dimValues[0];
     free(dimValues);
 }
 
 //为输出节点 分配CPU/MLU内存
 for (int i = 0; i < outputNum; i++) {
     CNRT_CHECK(cnrtMalloc(&outputMluPtrS[i],outputSizeS[i])); //分配MLU上内存   
     outputCpuPtrS[i] = (void *)malloc(outputSizeS[i]); //分配CPU上的内存
      
     //获取输出的维度信息 NHWC
     CNRT_CHECK(cnrtGetOutputDataShape(&dimValues,&dimNum,i,function));     
     int count=1;
     printf("output shape:\n");
     for(int y=0;y<dimNum;y++)
     {
         printf("%d ",dimValues[y]);
         count=count*dimValues[y];
     }
     printf("\n");      
     outputCpuNchwPtrS[i] = (void *)malloc(count*sizeof(float)); //将输出转为float32类型,方便用户后处理
     output_count.push_back(count);
     free(dimValues);
 }
 
 //创建事件
 CNRT_CHECK(cnrtRuntimeContextCreateNotifier(ctx,&ifier_start));
 CNRT_CHECK(cnrtRuntimeContextCreateNotifier(ctx,&ifier_end));
 
 //配置MLU输入/输出 地址的指针
 param = (void **)malloc(sizeof(void *) * (inputNum + outputNum));
 for (int i = 0; i < inputNum; i++) {
     param[i] = inputMluPtrS[i];
 }
 for (int i = 0; i < outputNum; i++) {
     param[i + inputNum] = outputMluPtrS[i];
 }
      
 //设置invoke的参数
 invokeParam.invoke_param_type=CNRT_INVOKE_PARAM_TYPE_0;
 invokeParam.cluster_affinity.affinity=&affinity;
  
 //设置输入/输出的节点 索引
 int input_idx=0;
 int output_idx=0;
 
 unsigned char *ptr=(unsigned char *)inputCpuPtrS[input_idx];
 for(int i=0;i<batch_size;i++)
 {      
     //conv first的网络要求输入的数据类型为 8UC4
      
     cv::Mat input_image=cv::imread(image_path);
     cv::Mat input_image_resized;//(input_height,input_width,CV_8UC4,ptr);
     cv::resize(input_image,input_image_resized,cv::Size(input_width,input_height));    
 
     // 我们选择的是1
     if(is_rgb==1)
     {
         cv::Mat net_input_data_rgba(input_height,input_width,CV_8UC4,ptr); 
         cv::cvtColor(input_image_resized, net_input_data_rgba, CV_BGR2RGBA);
         ptr+=(input_height*input_width*4);
     }
     else if(is_rgb==0)
     {
         cv::Mat net_input_data_rgba(input_height,input_width,CV_8UC4,ptr); 
         cv::cvtColor(input_image_resized, net_input_data_rgba, CV_BGR2BGRA);
         ptr+=(input_height*input_width*4);
     }
     else if(is_rgb==2)
     {
         cv::Mat net_input_data_rgba(input_height,input_width,CV_8UC4,ptr); 
         cv::cvtColor(input_image_resized, net_input_data_rgba, CV_BGR2GRAY);
         ptr+=(input_height*input_width);
     }
     else if(is_rgb==3)
     {
         cv::Mat net_input_data_rgba(input_height,input_width,CV_8UC3,ptr); 
         cv::cvtColor(input_image_resized, net_input_data_rgba, CV_BGR2RGB);
         ptr+=(input_height*input_width*3);
     }      
     else
     {
         cv::Mat net_input_data_rgba(input_height,input_width,CV_8UC2,ptr); 
         cv::Mat gray;
         cv::cvtColor(input_image_resized, gray, CV_BGR2GRAY);
         vector<cv::Mat> channels;
         channels.push_back(gray);
         channels.push_back(gray);
         cv::merge(channels,net_input_data_rgba);
         ptr+=(input_height*input_width*2);         
     }      
 }
      
 //拷贝输入数据到MLU内存
 auto t0=GetTickCount();
  
 CNRT_CHECK(cnrtMemcpy(inputMluPtrS[input_idx],inputCpuPtrS[input_idx],inputSizeS[input_idx],CNRT_MEM_TRANS_DIR_HOST2DEV));
      
 CNRT_CHECK(cnrtPlaceNotifier(notifier_start, queue));
 CNRT_CHECK(cnrtInvokeRuntimeContext(ctx,param,queue,&invokeParam));
 CNRT_CHECK(cnrtPlaceNotifier(notifier_end, queue));   
 CNRT_CHECK(cnrtSyncQueue(queue));  
 
 //拷贝MLU输出到CPU内存
 CNRT_CHECK(cnrtMemcpy(outputCpuPtrS[output_idx],outputMluPtrS[output_idx],outputSizeS[output_idx],CNRT_MEM_TRANS_DIR_DEV2HOST));
 auto t1=GetTickCount();
  
 float hwtime;
 CNRT_CHECK(cnrtNotifierDuration(notifier_start, notifier_end, &hwtime));
  
 printf("HardwareTime:%f(ms) E2ETime:%f(ms)\n",hwtime/1000.0,t1-t0);
  
 int dim_order[4] = {0, 3, 1, 2};
 CNRT_CHECK(cnrtGetOutputDataShape(&dimValues,&dimNum,output_idx,function));
  
  
 //NHWC->NCHW half->float32
 CNRT_CHECK(cnrtTransOrderAndCast(reinterpret_cast<void*>(outputCpuPtrS[output_idx]), outputTypeS[output_idx],
                                      reinterpret_cast<void*>(outputCpuNchwPtrS[output_idx]), CNRT_FLOAT32,
                                      nullptr, dimNum, dimValues, dim_order));
  
 free(dimValues);
 
// 打印推理结果
// .....见源码

 

申 请 试 用