Commit 0ffa7cb7 authored by liucong's avatar liucong
Browse files

提交ResNet50示例代码和文档

parents
Pipeline #332 canceled with stages
#! /bin/sh
############### Ubuntu ###############
# 参考:https://docs.opencv.org/3.4.11/d7/d9f/tutorial_linux_install.html
# apt-get install build-essential -y
# apt-get install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev -y
# apt-get install python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev -y # 处理图像所需的包,可选
############### CentOS ###############
yum install gcc gcc-c++ gtk2-devel gimp-devel gimp-devel-tools gimp-help-browser zlib-devel libtiff-devel libjpeg-devel libpng-devel gstreamer-devel libavc1394-devel libraw1394-devel libdc1394-devel jasper-devel jasper-utils swig python libtool nasm -y
\ No newline at end of file
############################ 在线安装依赖 ###############################
#cd ./3rdParty
#pip install rbuild-master.tar.gz
############################ 离线安装依赖 ###############################
# 安装依赖
cd ./3rdParty/rbuild_depend
pip install click-6.6-py2.py3-none-any.whl
pip install six-1.15.0-py2.py3-none-any.whl
pip install subprocess32-3.5.4.tar.gz
pip install cget-0.1.9.tar.gz
# 安装rbuild
cd ../
pip install rbuild-master.tar.gz
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.5)
# 设置项目名
project(ResNet50)
# 设置编译器
set(CMAKE_CXX_COMPILER g++)
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -std=c++17) # 2.2版本以上需要c++17
set(CMAKE_BUILD_TYPE release)
# 添加头文件路径
set(INCLUDE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/Src/
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/
${CMAKE_CURRENT_SOURCE_DIR}/Src/Resnet50/
$ENV{DTKROOT}/include/
${CMAKE_CURRENT_SOURCE_DIR}/depend/include/)
include_directories(${INCLUDE_PATH})
# 添加依赖库路径
set(LIBRARY_PATH ${CMAKE_CURRENT_SOURCE_DIR}/depend/lib64/
$ENV{DTKROOT}/lib/)
link_directories(${LIBRARY_PATH})
# 添加依赖库
set(LIBRARY opencv_core
opencv_imgproc
opencv_imgcodecs
opencv_dnn
migraphx_ref
migraphx
migraphx_c
migraphx_device
migraphx_gpu
migraphx_onnx)
link_libraries(${LIBRARY})
# 添加源文件
set(SOURCE_FILES ${CMAKE_CURRENT_SOURCE_DIR}/Src/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Resnet50/resnet50.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/CommonUtility.cpp
${CMAKE_CURRENT_SOURCE_DIR}/Src/Utility/Filesystem.cpp)
# 添加可执行目标
add_executable(ResNet50 ${SOURCE_FILES})
# 分类器
本示例通过ResNet50模型说明如何使用MIGraphX C++ API进行分类模型的推理,包括如何预处理、推理并获取推理结果。
## 模型简介
本示例使用了经典的ResNet50模型,onnx文件在/Resource/Models/Classifier文件夹下,模型结构可以通过netron (https://netron.app/) 查看,该模型的输入shape为[1,3,224,224] ,数据排布为NCHW。
## 预处理
在将数据输入到模型之前,需要对图像做如下预处理操作:
- resize到224x224
- 将像素值归一化到[0.0, 1.0]
- 转换数据排布为NCHW
本示例代码采用了OpenCV的cv::dnn::blobFromImages()函数实现了预处理操作:
```c++
ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
...
// 预处理
cv::Mat inputBlob;
cv::dnn::blobFromImages(srcImages, // 输入数据,支持多张图像
inputBlob, // 输出数据
scale, // 缩放系数,这里为1/255.0
inputSize, // 模型输入大小,这里为28x28
meanValue, // 均值,这里不需要减均值,所以设置为0.0
swapRB, // 多通道图像,这里设置为1
false);
...
}
```
cv::dnn::blobFromImages()函数支持多个输入图像,首先将输入图像resize到inputSize,然后减去均值meanValue,最后乘以scale并转换为NCHW,最终将转换好的数据保存到inputBlob中,然后就可以输入到模型中执行推理了。
## 推理
完成预处理后,就可以执行推理了:
```c++
ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
...
// 预处理
// 输入数据
std::unordered_map<std::string, migraphx::argument> inputData;
inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
// 推理
std::vector<migraphx::argument> results = net.eval(inputData);
// 获取输出节点的属性
migraphx::argument result = results[0]; // 获取第一个输出节点的数据
...
}
```
- inputData表示MIGraphX的输入数据,inputData是一个映射关系,每个输入节点名都会对应一个输入数据,如果有多个输入,则需要为每个输入节点名创建数据,inputName表示输入节点名,migraphx::argument{inputShape, (float*)inputBlob.data}表示该节点名对应的数据,这里是通过前面预处理的数据inputBlob来创建的,第一个参数表示数据的shape,第二个参数表示数据指针。
- net.eval(inputData)返回模型的推理结果,由于这里只有一个输出节点,所以std::vector中只有一个数据,results[0]表示第一个输出节点,这里对应resnetv24_dense0_fwd节点,获取输出数据。
# 分类器
本示例通过ResNet50模型说明如何使用MIGraphX Python API进行分类模型的推理,包括预处理、推理并获取推理结果。
## 模型简介
本示例使用了经典的ResNet50模型,onnx文件在/Resource/Models/Classifier文件夹下,模型结构可以通过netron (https://netron.app/) 查看,该模型的输入shape为[1,3,224,224] ,数据排布为NCHW,输出是1000个类别的概率(未归一化)。
## 预处理
在将数据输入到模型之前,需要对图像做如下预处理操作:
- 转换数据排布为NCHW
- 将像素值归一化到[0.0, 1.0]
- 调整输入数据的尺寸为(1, 3, 224, 224)
本示例代码采用了OpenCV实现了预处理操作:
```
def Preprocessing(pathOfImage):
image = cv2.imread(pathOfImage,cv2.IMREAD_COLOR)
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# 调整图像的尺寸
image = cv2.resize(image, (224,224))
# 维度转换HWC->CHW
image = image.transpose(2, 0, 1)
# 维度拓展,增加batch维度
image = np.expand_dims(image, 0)
image = np.ascontiguousarray(image)
image = image.astype(np.float32)
# 归一化
input = image / 255
return input
```
## 推理
完成预处理后,就可以执行推理了:
```
if __name__ == '__main__':
...
# 预处理
pathOfImage ="../Resource/Images/mnist_9.jpg"
image = Preprocessing(pathOfImage)
# 推理
results = model.run({inputName: migraphx.argument(image)}) # 推理结果,list类型
# 获取输出节点属性
result=results[0] # 获取第一个输出节点的数据,migraphx.argument类型
outputShape=result.get_shape() # 输出节点的shape,migraphx.shape类型
outputSize=outputShape.lens() # 每一维大小,维度顺序为(N,C,H,W),list类型
numberOfOutput=outputShape.elements() # 输出节点元素的个数
# 获取分类结果
result=results[0].tolist() # 将migraphx.argument转换为list
result=np.array(result)
# 打印1000个类别的输出值
print(result)
```
- Preprocessing()函数返回输入数据(numpy类型),然后通过{inputName: migraphx.argument(image)}构造一个字典输入模型执行推理,如果模型有多个输入,则在字典中需要添加多个输入数据。
- model.run()返回模型的推理结果,返回结果是一个list类型,results[0]表示第一个输出节点的输出,是一个migraphx.argument类型,由于示例模型只有一个输出节点,所以results[0]对应resnetv24_dense0_fwd节点,如果想将migraphx.argument类型转换为list类型,可以通过tolist()方法实现。最后,打印出1000个类别的输出值。
# -*- coding: utf-8 -*-
"""
分类器示例
"""
import cv2
import numpy as np
import migraphx
def Preprocessing(pathOfImage):
image = cv2.imread(pathOfImage,cv2.IMREAD_COLOR) # cv2.IMREAD_COLOR:彩色图,cv2.IMREAD_GRAYSCALE:灰度图
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
image = cv2.resize(image, (224,224))
image = image.transpose(2, 0, 1)
image = np.expand_dims(image, 0)
image = np.ascontiguousarray(image)
image = image.astype(np.float32)
input = image / 255
return input
if __name__ == '__main__':
# 加载模型
model = migraphx.parse_onnx("../Resource/Models/resnet50-v2-7.onnx")
inputName=model.get_parameter_names()[0]
inputShape=model.get_parameter_shapes()[inputName].lens()
print("inputName:{0} \ninputShape:{1}".format(inputName,inputShape))
# FP16
# migraphx.quantize_fp16(model)
# 编译
model.compile(t=migraphx.get_target("gpu"),device_id=0) # device_id: 设置GPU设备,默认为0号设备
# 预处理并转换为NCHW
pathOfImage ="../Resource/Images/ImageNet_01.jpg"
image = Preprocessing(pathOfImage)
# 推理
results = model.run({inputName: image}) # 推理结果,list类型
# 获取输出节点属性
result=results[0] # 获取第一个输出节点的数据,migraphx.argument类型
outputShape=result.get_shape() # 输出节点的shape,migraphx.shape类型
outputSize=outputShape.lens() # 每一维大小,维度顺序为(N,C,H,W),list类型
numberOfOutput=outputShape.elements() # 输出节点元素的个数
# 获取分类结果
result=results[0].tolist() # 将migraphx.argument转换为list
result=np.array(result)
# 打印1000个类别的输出
print(result)
\ No newline at end of file
opencv-python
numpy
\ No newline at end of file
# ResNet50
## 模型介绍
使用MIGraphX推理框架对ResNet50模型进行推理。
## 模型结构
ResNet50模型包含了49个卷积层、一个全连接层。
## Python版本推理
下面介绍如何运行python代码示例,具体推理代码解析,在Doc/Tutorial_Python目录中有详细说明。
### 构建安装
在光源可拉取推理的docker镜像,ResNet50模型推理的镜像如下:
```python
docker pull image.sourcefind.cn:5000/dcu/admin/base/custom:ort1.14.0_migraphx3.0.0-dtk22.10.1
```
### 推理示例
1.参考《MIGraphX教程》设置好PYTHONPATH
2.安装依赖:
```python
# 进入migraphx samples工程根目录
cd <path_to_migraphx_samples>
# 进入示例程序目录
cd Python/
# 安装依赖
pip install -r requirements.txt
```
3.在Python目录下执行如下命令运行该示例程序:
```python
python Classifier.py
```
输出结果中,每个值分别对应每个label的输出值。
```
[ 1.25075293e+00 1.78420877e+00 -2.56109548e+00 -3.44433069e+00
-2.66113567e+00 1.52841401e+00 7.93735325e-01 -1.26759931e-02
...
4.39746976e-02 3.43239784e-01 2.75328755e+00 1.70684290e+00
8.54880095e-01 2.12219620e+00 2.35758686e+00 -1.06204104e+00]
```
## C++版本推理
下面介绍如何运行C++代码示例,具体推理代码解析,在Doc/Tutorial_Cpp目录中有详细说明。
参考Python版本推理中的构建安装,在光源中拉取推理的docker镜像。
### 安装Opencv依赖
```python
cd <path_to_migraphx_samples>
sh ./3rdParty/InstallOpenCVDependences.sh
```
### 修改CMakeLists.txt
- 如果使用ubuntu系统,需要修改CMakeLists.txt中依赖库路径:
将"${CMAKE_CURRENT_SOURCE_DIR}/depend/lib64/"修改为"${CMAKE_CURRENT_SOURCE_DIR}/depend/lib/"
- **MIGraphX2.3.0及以上版本需要c++17**
### 安装OpenCV并构建工程
```
rbuild build -d depend
```
### 设置环境变量
将依赖库依赖加入环境变量LD_LIBRARY_PATH,在~/.bashrc中添加如下语句:
**Centos**:
```
export LD_LIBRARY_PATH=<path_to_migraphx_samples>/depend/lib64/:$LD_LIBRARY_PATH
```
**Ubuntu**:
```
export LD_LIBRARY_PATH=<path_to_migraphx_samples>/depend/lib/:$LD_LIBRARY_PATH
```
然后执行:
```
source ~/.bashrc
```
### 推理示例
运行ResNet50示例程序,具体执行如下命令:
```python
# 进入migraphx samples工程根目录
cd <path_to_migraphx_samples>
# 进入build目录
cd ./build/
# 执行示例程序
./ResNet50
```
输出结果中,每个值分别对应每个label的输出值。
```
label:0,confidence:1.250770
label:1,confidence:1.784038
label:2,confidence:-2.561039
label:3,confidence:-3.444281
label:4,confidence:-2.661026
label:5,confidence:1.528260
...
label:995,confidence:1.706820
label:996,confidence:0.854793
label:997,confidence:2.121985
label:998,confidence:2.357481
label:999,confidence:-1.062007
```
## 历史版本
https://developer.hpccube.com/codes/modelzoo/classifier_migraphx
## 参考资料
https://github.com/ROCmSoftwarePlatform/AMDMIGraphX/tree/develop/examples/vision/python_resnet50
<?xml version="1.0" encoding="GB2312"?>
<opencv_storage>
<!--分类器-->
<Classifier>
<ModelPath>"../Resource/Models/resnet50-v2-7.onnx"</ModelPath>
<Scale>0.003922</Scale><!--缩放尺度-->
<MeanValue1>0.0</MeanValue1><!--均值-->
<MeanValue2>0.0</MeanValue2>
<MeanValue3>0.0</MeanValue3>
<SwapRB>1</SwapRB>
<Crop>0</Crop>
<UseInt8>0</UseInt8><!--是否使用int8,不支持-->
<UseFP16>0</UseFP16><!--是否使用FP16-->
</Classifier>
</opencv_storage>
#include <resnet50.h>
#include <migraphx/onnx.hpp>
#include <migraphx/gpu/target.hpp>
#include <migraphx/quantization.hpp>
#include <opencv2/dnn.hpp>
#include <CommonUtility.h>
#include <Filesystem.h>
#include <SimpleLog.h>
namespace migraphxSamples
{
Classifier::Classifier()
{
}
Classifier::~Classifier()
{
configurationFile.release();
}
ErrorCode Classifier::Initialize(InitializationParameterOfClassifier initializationParameterOfClassifier)
{
// 读取配置文件
std::string configFilePath=CONFIG_FILE;
if(Exists(configFilePath)==false)
{
LOG_ERROR(stdout, "no configuration file!\n");
return CONFIG_FILE_NOT_EXIST;
}
if(!configurationFile.open(configFilePath, FileStorage::READ))
{
LOG_ERROR(stdout, "fail to open configuration file\n");
return FAIL_TO_OPEN_CONFIG_FILE;
}
LOG_INFO(stdout, "succeed to open configuration file\n");
// 获取配置文件参数
FileNode netNode = configurationFile["Classifier"];
std::string modelPath=initializationParameter.parentPath+(std::string)netNode["ModelPath"];
scale=(float)netNode["Scale"];
meanValue.val[0]=(float)netNode["MeanValue1"];
meanValue.val[1]=(float)netNode["MeanValue2"];
meanValue.val[2]=(float)netNode["MeanValue3"];
swapRB=(bool)(int)netNode["SwapRB"];
crop=(bool)(int)netNode["Crop"];
useInt8=(bool)(int)netNode["UseInt8"];
useFP16=(bool)(int)netNode["UseFP16"];
// 加载模型
if(Exists(modelPath)==false)
{
LOG_ERROR(logFile,"%s not exist!\n",modelPath.c_str());
return MODEL_NOT_EXIST;
}
net = migraphx::parse_onnx(modelPath);
LOG_INFO(logFile,"succeed to load model: %s\n",GetFileName(modelPath).c_str());
// 获取模型输入属性
std::unordered_map<std::string, migraphx::shape> inputMap=net.get_parameter_shapes();
inputName=inputMap.begin()->first;
inputShape=inputMap.begin()->second;
int N=inputShape.lens()[0];
int C=inputShape.lens()[1];
int H=inputShape.lens()[2];
int W=inputShape.lens()[3];
inputSize=cv::Size(W,H);
// 设置模型为GPU模式
migraphx::target gpuTarget = migraphx::gpu::target{};
// 量化
if(useInt8)
{
// 创建量化校准数据,建议使用测试集中的多张典型图像
cv::Mat srcImage=cv::imread("../Resource/Images/ImageNet_01.jpg",1);
std::vector<cv::Mat> srcImages;
for(int i=0;i<inputShape.lens()[0];++i)
{
srcImages.push_back(srcImage);
}
cv::Mat inputBlob;
cv::dnn::blobFromImages(srcImages,
inputBlob,
scale,
inputSize,
meanValue,
swapRB,
false);
std::unordered_map<std::string, migraphx::argument> inputData;
inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
std::vector<std::unordered_map<std::string, migraphx::argument>> calibrationData = {inputData};
// INT8量化
migraphx::quantize_int8(net, gpuTarget, calibrationData);
}
if(useFP16)
{
migraphx::quantize_fp16(net);
}
// 编译模型
migraphx::compile_options options;
options.device_id=0; // 设置GPU设备,默认为0号设备
options.offload_copy=true;
net.compile(gpuTarget,options);
LOG_INFO(logFile,"succeed to compile model: %s\n",GetFileName(modelPath).c_str());
std::unordered_map<std::string, migraphx::argument> inputData;
inputData[inputName]=migraphx::argument{inputShape};
net.eval(inputData);
// log
LOG_INFO(logFile,"InputSize:%dx%d\n",inputSize.width,inputSize.height);
LOG_INFO(logFile,"InputName:%s\n",inputName.c_str());
LOG_INFO(logFile,"Scale:%.6f\n",scale);
LOG_INFO(logFile,"Mean:%.2f,%.2f,%.2f\n",meanValue.val[0],meanValue.val[1],meanValue.val[2]);
LOG_INFO(logFile,"SwapRB:%d\n",(int)swapRB);
LOG_INFO(logFile,"Crop:%d\n",(int)crop);
LOG_INFO(logFile,"UseInt8:%d\n",(int)useInt8);
LOG_INFO(logFile,"UseFP16:%d\n",(int)useFP16);
return SUCCESS;
}
ErrorCode Classifier::Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions)
{
if(srcImages.size()==0||srcImages[0].empty()||srcImages[0].depth()!=CV_8U)
{
LOG_ERROR(logFile, "image error!\n");
return IMAGE_ERROR;
}
// 预处理并转换为NCHW
cv::Mat inputBlob;
cv::dnn::blobFromImages(srcImages,
inputBlob,
scale,
inputSize,
meanValue,
swapRB,
false);
// 输入数据
std::unordered_map<std::string, migraphx::argument> inputData;
inputData[inputName]= migraphx::argument{inputShape, (float*)inputBlob.data};
// 推理
std::vector<migraphx::argument> results = net.eval(inputData);
// 获取输出节点的属性
migraphx::argument result = results[0]; // 获取第一个输出节点的数据
migraphx::shape outputShape=result.get_shape(); // 输出节点的shape
std::vector<std::size_t> outputSize=outputShape.lens(); // 每一维大小,维度顺序为(N,C,H,W)
int numberOfOutput=outputShape.elements(); // 输出节点元素的个数
float *logits=(float *)result.data(); // 输出节点数据指针
// 获取每张图像的预测结果
int numberOfClasses=numberOfOutput/srcImages.size();
for(int i=0;i<srcImages.size();++i)
{
int startIndex=numberOfClasses*i;
// 获取每幅图像对应的输出
std::vector<float> logit;
for(int j=0;j<numberOfClasses;++j)
{
logit.push_back(logits[startIndex+j]);
}
std::vector<ResultOfPrediction> resultOfPredictions;
for(int j=0;j<numberOfClasses;++j)
{
ResultOfPrediction prediction;
prediction.label=j;
prediction.confidence=logit[j];
resultOfPredictions.push_back(prediction);
}
predictions.push_back(resultOfPredictions);
}
return SUCCESS;
}
}
#ifndef __CLASSIFIER_H__
#define __CLASSIFIER_H__
#include <string>
#include <migraphx/program.hpp>
#include <opencv2/opencv.hpp>
#include <CommonDefinition.h>
namespace migraphxSamples
{
class Classifier
{
public:
Classifier();
~Classifier();
ErrorCode Initialize(InitializationParameterOfClassifier initializationParameterOfClassifier);
ErrorCode Classify(const std::vector<cv::Mat> &srcImages,std::vector<std::vector<ResultOfPrediction>> &predictions);
private:
ErrorCode DoCommonInitialization(InitializationParameterOfClassifier initializationParameterOfClassifier);
private:
cv::FileStorage configurationFile;
InitializationParameterOfClassifier initializationParameter;
FILE *logFile;
migraphx::program net;
cv::Size inputSize;
std::string inputName;
migraphx::shape inputShape;
float scale;
cv::Scalar meanValue;
bool swapRB;
bool crop;
bool useInt8;
bool useFP16;
};
}
#endif
// 常用数据类型和宏定义
#ifndef __COMMON_DEFINITION_H__
#define __COMMON_DEFINITION_H__
#include <string>
#include <opencv2/opencv.hpp>
using namespace std;
using namespace cv;
namespace migraphxSamples
{
// 路径分隔符(Linux:‘/’,Windows:’\\’)
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#else
#define PATH_SEPARATOR '/'
#endif
#define CONFIG_FILE "../Resource/Configuration.xml"
typedef struct __Time
{
string year;
string month;
string day;
string hour;
string minute;
string second;
string millisecond; // ms
string microsecond; // us
string weekDay;
}_Time;
typedef enum _ErrorCode
{
SUCCESS=0, // 0
MODEL_NOT_EXIST, // 模型不存在
CONFIG_FILE_NOT_EXIST, // 配置文件不存在
FAIL_TO_LOAD_MODEL, // 加载模型失败
FAIL_TO_OPEN_CONFIG_FILE, // 加载配置文件失败
IMAGE_ERROR, // 图像错误
}ErrorCode;
typedef struct _ResultOfPrediction
{
float confidence;
int label;
_ResultOfPrediction():confidence(0.0f),label(0){}
}ResultOfPrediction;
typedef struct _ResultOfDetection
{
Rect boundingBox;
float confidence;
int classID;
string className;
bool exist;
_ResultOfDetection():confidence(0.0f),classID(0),exist(true){}
}ResultOfDetection;
typedef struct _InitializationParameterOfClassifier
{
std::string parentPath;
std::string configFilePath;
cv::Size inputSize;
std::string logName;
}InitializationParameterOfClassifier;
}
#endif
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment