目录

CNNNetReader API列表

CNNNetReader构造函数

CNNNetReaderImpl构造函数

xml网络拓扑文件结构

CNNNetReader::ReadNetwork

CNNNetReaderImpl::ReadNetwork

读取xml文件

获取网络拓扑文件版本

获取解析器

FormatParser::Parse 解析xml节点数据

验证该网络合法性

CNNNetReader::ReadWeights

CNNNetReaderImpl::ReadWeights

SetWeights

FormatParser::SetWeights

总结


OpenVINO中的inference Engine 中做推理的第一步首先就是要读取IR(.xml和bin)文件,将整个网络拓扑架构xml和相关权重bin文件读取到内存中,才能为接下来做推理打下基础。所以在一般的深度学习框架中,理解整个网络怎么管理的是非常重要的,而理解整个网络是怎么组织的,则可以从读取网络拓扑结构开始,OpenVINO的处理也逃不过该套路。

OpenVINO对外提供的读取IR文件接口统一封装到CNNNetReader类中,该类位于inference-engine\include\cpp\ie_cnn_net_reader.h文件中

CNNNetReader API列表

CNNNetReader类对外提供的接口如下:

Method Descript
CNNNetReader() CNNNetReader类默认构造函数
void ReadNetwork(const std::string &filepath) 读取xml网络拓扑结构文件
void ReadNetwork(const void *model, size_t size) 读取xml网络拓扑结构文件
void SetWeights(const TBlob<uint8_t>::Ptr &weights) 设置权重
void ReadWeights(const std::wstring &filepath) 读取权重(.bin)文件
CNNNetwork getNetwork()  获取到读取到内存中的网络CNNNetwork结构
bool isParseSuccess() 分析网络是否合法
std::string getDescription() 获取到网络数据精度
std::string getName() 获取到网络名称
int getVersion() 获取到版本

该API列表对外提供了基本的对网络操作的功能,下面来看下具体实现。

CNNNetReader构造函数

CNNNetReader默认构造函数代码如下:

该构造函数体现了OpenVINO的一个一般处理技巧,调用InferenceEngine::CreateCNNNetReader,创建一个CNNNetReader记录实现类,将接口和具体实现进行分类:

CNNNetReader类的具有实现是在CNNNetReaderImpl类中,调用CNNNetReaderImpl类的构造函数创建一个新类。

CNNNetReaderImpl构造函数

CNNNetReaderImpl构造函数如下:

CNNNetReaderImpl构造函数中将_creator指针参数保存在parserCreator变量中,而CNNNetReader的构造函数中传递的是V2FormatParserCreator类,所以_creator保存的是V2FormatParserCreator类地址。

而V2FormatParserCreato中create返回的是FormatParser。

CNNNetReader构造函数嵌套的类比较深,最后具体的实现是在 CNNNetReaderImpl类中,并将该类的实现地址保存到actual。这样设计的好处是将接口与实现进行了分离,减少了之前的耦合度,坏处也是显而易见,增加了代码的复杂度。

xml网络拓扑文件结构

在这里先补充下预备知识,openvino的xml网络拓扑结构文件内容大概框架如下:

<net  batch="xxx"  nane="XXXX" versiob="2">

<layers>

<layer id="xxx"  name="XXX" precision="FP32" type="XXXX">

<data  XXXX />

<input>

<port id="x">

<dim> XX</dim>

<dim> XX</dim>

...

<dim> XX</dim>

</input>

<output>

<port id="x">

<dim> XX</dim>

<dim> XX</dim>

...

<dim> XX</dim>

</output>

<blobs>

<weights offset="xxxx" size="xxxxx"/>

<biases offset="11424" size="64"/>

</blobs>

</layer >

...

<layer >

...

</layer >

</layers>

<edges>

<edge from-layer="0"t from-por="0" to-layer="1" to-port="0"/>

....

<edge from-layer="10" from-port="1" to-layer="11" to-port="0"/>

</edges>

</net>

该网络拓扑结构采用xml层级规划管理,每个网络使用<net>..</net>标签,该标签表明为一个网络,标签后有name代表网络名称,batch代表处理的batch 大小,version代表的是openvino支持的网络拓扑版本,目前最新的是版本2,当然还有其他的标签例如precision代表的数据精度。

网络中所所有的层在<layers> ... ...</layers>标签内,具体的每一层使用 <layer >... ...  </layer >标签,layer内的id代表的是layer id , name代表的是layer name,precision代表的是该层处理的数据精度, type代表的是网络类型,具体的每层的类型与各个常见的深度学习框架(caffe,TensorFlow等)layer类型有一一对应的关系,具体的对应关系可以查看官方文档说明:Supported Framework Layers。

每一层<layer>内的标签输入<data>代表的是该层的一些参数,<input>代表的是该层的输入,<ouput>代表的是该层的输出,每个维度的大小使用<dim>标签, <blob>代表的是一些权重参数,基本能够表达出每层的需要。

所有的layer表述完之后是 <edges>标签,该标签代表的是整个网络层的组织, from-layer代表的链接的上一层,to-layer代表的本层,from-port代表的是上层的第几个输出参数作为本层的输入,to-port代表的是本层的输出。

与caffe中使用prototxt文件描述网络拓扑结构不同,openvino使用xml文件描述网络拓扑结构,层次化更加明显。

CNNNetReader::ReadNetwork

ReadNetwork()函数的处理的本质就是将上述拓扑网络结构读取到内存中,与caffe中的init函数功能差不多。

该函数实现较为简单直接调用CALL_STATUS_FNC宏,该宏的实现如下:

直接调用actual指针下的相对应的函数, 而由CNNNetReader构造函数可知,actual挂载的实际对应的类为CNNNetReaderImpl,通过该方法将接口与实现进行分离.

CNNNetReaderImpl::ReadNetwork

CNNNetReaderImpl::ReadNetwork为ReadNetwork的真正实现

读取xml文件

该步骤比较简单主要使用第三方pugixml软件将xml网络拓扑结构按照xml文件格式读取到xmlDoc内存中

接下来是调用ReadNetwork接口对该xml节点数据进行提取,组织成后续所需要的网络格式。

获取网络拓扑文件版本

获取到net节点中version 版本号,最新的一般为2,比较老的openvino版本使用的是版本1

GetFileVersion()函数实现如下:

获取解析器

获取解析器,其实质就是获取到解析的类:

_parser = parserCreator->create(_version);

parserCreator指向的结构指针由构造函数可知,parserCreator指向的是V2FormatParserCreator类地址:

可知得到的是FormatParser解析类。

FormatParser::Parse 解析xml节点数据

使用生成的解析器,解析读取到的xml节点数据:

network = _parser->Parse(root);

FormatParser类中的Parse方法为:

CNNNetworkImplPtr FormatParser::Parse(pugi::xml_node& root) {_network.reset(new CNNNetworkImpl()); //生成一个CNNNetworkImpl类用于记录各种数据_network->setName(GetStrAttr(root, "name", ""));//设置网络名称_defPrecision = Precision::FromStr(GetStrAttr(root, "precision", "UNSPECIFIED"));_network->setPrecision(_defPrecision);//设置网络江都// parse the input DataDataPtr inputData;// parse the graph layersauto allLayersNode = root.child("layers");//获取到所有的layers下的layer节点std::vector< CNNLayer::Ptr> inputLayers;int nodeCnt = 0;std::map<int, CNNLayer::Ptr> layerById;bool identifyNetworkPrecision = _defPrecision == Precision::UNSPECIFIED;for (auto node = allLayersNode.child("layer"); !node.empty(); node = node.next_sibling("layer")) {//遍历所有的节点,对每层数据单独提取LayerParseParameters lprms;ParseGenericParams(node, lprms);//解析layer节点下的数据到LayerParseParameters结构中CNNLayer::Ptr layer = CreateLayer(node, lprms);//生成对应的Layerif (!layer) THROW_IE_EXCEPTION << "Don't know how to create Layer type: " << lprms.prms.type;layersParseInfo[layer->name] = lprms;//将每层的数据保存到 layersParseInfo中_network->addLayer(layer);//添加该layer到CNNNetwork中layerById[lprms.layerId] = layer;//以layer id为key 保存每层layer到layerById中if (equal(layer->type, "input")) {inputLayers.push_back(layer);//如果是输入层,则将该层保证到inputLayers中}if (identifyNetworkPrecision) {//设置网络精度if (!_network->getPrecision()) {_network->setPrecision(lprms.prms.precision);}if (_network->getPrecision() != lprms.prms.precision) {_network->setPrecision(Precision::MIXED);identifyNetworkPrecision = false;}}for (int i = 0; i < lprms.outputPorts.size(); i++) {//将每层的输出保存到layer->outData以及_portsToData中const auto &outPort = lprms.outputPorts[i];const auto outId = gen_id(lprms.layerId, outPort.portId);const std::string outName = lprms.outputPorts.size() == 1? lprms.prms.name: lprms.prms.name + "." + std::to_string(i);DataPtr& ptr = _network->getData(outName.c_str());if (!ptr) {ptr.reset(new Data(outName, outPort.dims, outPort.precision, TensorDesc::getLayoutByDims(outPort.dims)));ptr->setDims(outPort.dims);}_portsToData[outId] = ptr;if (ptr->getCreatorLayer().lock())THROW_IE_EXCEPTION << "two layers set to the same output [" << outName << "], conflict at offset "<< node.offset_debug();ptr->getCreatorLayer() = layer;layer->outData.push_back(ptr);}nodeCnt++;}// connect the edgespugi::xml_node edges = root.child("edges");//获取网络连接信息FOREACH_CHILD(_ec, edges, "edge") {int fromLayer = GetIntAttr(_ec, "from-layer");int fromPort = GetIntAttr(_ec, "from-port");int toLayer = GetIntAttr(_ec, "to-layer");int toPort = GetIntAttr(_ec, "to-port");const auto dataId = gen_id(fromLayer, fromPort);auto targetLayer = layerById[toLayer];if (!targetLayer)THROW_IE_EXCEPTION << "Layer ID " << toLayer << " was not found while connecting edge at offset "<< _ec.offset_debug();SetLayerInput(*_network, dataId, targetLayer, toPort);//根据每层的链接信息,设置其每层的输入}auto keep_input_info = [&] (DataPtr &in_data) {InputInfo::Ptr info(new InputInfo());info->setInputData(in_data);Precision prc = info->getPrecision();// Convert precision into native format (keep element size)prc = prc == Precision::Q78 ? Precision::I16 :prc == Precision::FP16 ? Precision::FP32 :static_cast<Precision::ePrecision>(prc);info->setPrecision(prc);_network->setInputInfo(info);};// Keep all data from InputLayersfor (auto inLayer : inputLayers) {if (inLayer->outData.size() != 1)THROW_IE_EXCEPTION << "Input layer must have 1 output. ""See documentation for details.";keep_input_info(inLayer->outData[0]);//将输入层的数据保存到CNNNetworkImpl中的_inputData}// Keep all data which has no creator layer//创建每层的输入数据内存空间for (auto &kvp : _network->allLayers()) {const CNNLayer::Ptr& layer = kvp.second;auto pars_info = layersParseInfo[layer->name];if (layer->insData.empty())layer->insData.resize(pars_info.inputPorts.size());for (int i = 0; i < layer->insData.size(); i++) {if (!layer->insData[i].lock()) {std::string data_name = (layer->insData.size() == 1)? layer->name: layer->name + "." + std::to_string(i);DataPtr data(new Data(data_name,pars_info.inputPorts[i].dims,pars_info.inputPorts[i].precision,TensorDesc::getLayoutByDims(pars_info.inputPorts[i].dims)));data->setDims(pars_info.inputPorts[i].dims);layer->insData[i] = data;data->getInputTo()[layer->name] = layer;const auto insId = gen_id(pars_info.layerId, pars_info.inputPorts[i].portId);_portsToData[insId] = data;keep_input_info(data);}}}auto statNode = root.child("statistics");ParseStatisticSection(statNode);//处理network statistics节点if (!_network->allLayers().size())THROW_IE_EXCEPTION << "Incorrect model! Network doesn't contain layers.";size_t inputLayersNum(0);CaselessEq<std::string> cmp;for (const auto& kvp : _network->allLayers()) {const CNNLayer::Ptr& layer = kvp.second;if (cmp(layer->type, "Input") || cmp(layer->type, "Const"))inputLayersNum++;//统计其输入层有多少个}if (!inputLayersNum && !cmp(root.name(), "body"))THROW_IE_EXCEPTION << "Incorrect model! Network doesn't contain input layers.";// check all input ports are occupiedfor (const auto& kvp : _network->allLayers()) {const CNNLayer::Ptr& layer = kvp.second;const LayerParseParameters& parseInfo = layersParseInfo[layer->name];size_t inSize = layer->insData.size();if (inSize != parseInfo.inputPorts.size())THROW_IE_EXCEPTION << "Layer " << layer->name << " does not have any edge connected to it";for (unsigned i = 0; i < inSize; i++) {if (!layer->insData[i].lock()) {THROW_IE_EXCEPTION << "Layer " << layer->name.c_str() << " input port "<< parseInfo.inputPorts[i].portId << " is not connected to any data";}}layer->validateLayer();}// parse mean imageParsePreProcess(root);//进行预处理_network->resolveOutput();// Set default output precision to FP32 (for back-compatibility)OutputsDataMap outputsInfo;_network->getOutputsInfo(outputsInfo);//设置其输入数据精度默认为FP32 for (auto outputInfo : outputsInfo) {if (outputInfo.second->getPrecision() != Precision::FP32 &&outputInfo.second->getPrecision() != Precision::I32) {outputInfo.second->setPrecision(Precision::FP32);}}return _network;//将网络拓扑信息保存到CNNNetworkImpl类中,并返回该类指针,用于后续处理
}

整个函数处理比较复杂是CNNetwork管理的核心,对该解析的详细过程可以看<OpenVINO InferenceEngine之FormatParser>

验证该网络合法性

对该网络的合法性进行验证:

void CNNNetworkImpl::validate(int version) {std::set<std::string> layerNames;std::set<std::string> dataNames;InputsDataMap inputs;this->getInputsInfo(inputs);if (inputs.empty()) {THROW_IE_EXCEPTION << "No input layers";}bool res = CNNNetForestDFS(CNNNetGetAllInputLayers(*this), [&](CNNLayerPtr layer) {std::string layerName = layer->name;for (auto i : layer->insData) {auto data = i.lock();if (data) {auto inputTo = data->getInputTo();auto iter = inputTo.find(layerName);auto dataName = data->getName();if (iter == inputTo.end()) {THROW_IE_EXCEPTION << "Data " << data->getName() << " which inserted into the layer "<< layerName<< " does not point at this layer";}if (!data->getCreatorLayer().lock()) {THROW_IE_EXCEPTION << "Data " << dataName << " has no creator layer";}} else {THROW_IE_EXCEPTION << "Data which inserted into the layer " << layerName << " is nullptr";}}for (auto data : layer->outData) {auto inputTo = data->getInputTo();std::string dataName = data->getName();for (auto layerIter : inputTo) {CNNLayerPtr layerInData = layerIter.second;if (!layerInData) {THROW_IE_EXCEPTION << "Layer which takes data " << dataName << " is nullptr";}auto insertedDatas = layerInData->insData;auto it = std::find_if(insertedDatas.begin(), insertedDatas.end(),[&](InferenceEngine::DataWeakPtr& d) {return d.lock() == data;});if (it == insertedDatas.end()) {THROW_IE_EXCEPTION << "Layer " << layerInData->name << " which takes data " << dataName<< " does not point at this data";}}auto dataNameSetPair = dataNames.insert(dataName);if (!dataNameSetPair.second) {THROW_IE_EXCEPTION << "Data name " << dataName << " is not unique";}}auto layerSetPair = layerNames.insert(layerName);if (!layerSetPair.second) {THROW_IE_EXCEPTION << "Layer name " << layerName << " is not unique";}}, false);std::string inputType = "Input";for (auto i : inputs) {CNNLayerPtr layer = i.second->getInputData()->getCreatorLayer().lock();if (layer && !equal(layer->type, inputType)) {THROW_IE_EXCEPTION << "Input layer " << layer->name<< " should have Input type but actually its type is " << layer->type;}}if (!res) {THROW_IE_EXCEPTION << "Sorting not possible, due to existed loop.";}
}

详细代码后面在单独进行讲解。

CNNNetReader::ReadWeights

ReadWeights为读取bin文件接口:

最后是调用CNNNetReaderImpl::ReadWeights函数

CNNNetReaderImpl::ReadWeights

CNNNetReaderImp::ReadWeights()函数主要过程如下:

首先读取该权重文件大小,调用TBlob接口申请相应的内存空间,并将该权重文件数据全部保存到weightsPtr内存空间中,最后调用setWeights()函数进行具体设置

SetWeights

SetWeights()函数接口:

最后调用解析器对权重进行设置,解析器为FormatParser

FormatParser::SetWeights

FormatParser::SetWeights()是最终的处理函数:

void FormatParser::SetWeights(const TBlob<uint8_t>::Ptr& weights) {for (auto& kvp : _network->allLayers()) {auto fit = layersParseInfo.find(kvp.second->name);// todo: may check that earlier - while parsing...if (fit == layersParseInfo.end())THROW_IE_EXCEPTION << "Internal Error: ParseInfo for " << kvp.second->name << " are missing...";auto& lprms = fit->second;WeightableLayer* pWL = dynamic_cast<WeightableLayer*>(kvp.second.get());if (pWL != nullptr) {if (lprms.blobs.find("weights") != lprms.blobs.end()) {if (lprms.prms.type == "BinaryConvolution") {auto segment = lprms.blobs["weights"];if (segment.getEnd() > weights->size())THROW_IE_EXCEPTION << "segment exceeds given buffer limits. Please, validate weights file";size_t noOfElement = segment.size;SizeVector w_dims({noOfElement});typename TBlobProxy<uint8_t>::Ptr binBlob(new TBlobProxy<uint8_t>(Precision::BIN, Layout::C, weights, segment.start, w_dims));pWL->_weights = binBlob;} else {pWL->_weights = GetBlobFromSegment(weights, lprms.blobs["weights"]);}pWL->blobs["weights"] = pWL->_weights;}if (lprms.blobs.find("biases") != lprms.blobs.end()) {pWL->_biases  = GetBlobFromSegment(weights, lprms.blobs["biases"]);pWL->blobs["biases"] = pWL->_biases;}}auto pGL = kvp.second.get();if (pGL == nullptr) continue;for (auto s : lprms.blobs) {pGL->blobs[s.first] = GetBlobFromSegment(weights, s.second);}// Some layer can specify additional action to prepare weightsif (fit->second.internalWeightSet)fit->second.internalWeightSet(weights);}for (auto &kvp : _preProcessSegments) {const std::string &inputName = kvp.first;auto &segments = kvp.second;auto inputInfo = _network->getInput(inputName);if (!inputInfo)THROW_IE_EXCEPTION << "Internal error: missing input name " << inputName;auto dims = inputInfo->getTensorDesc().getDims();size_t width = 0ul, height = 0ul;if (dims.size() == 3) {height = dims.at(1);width = dims.at(2);} else if (dims.size() == 4) {height = dims.at(2);width = dims.at(3);} else if (dims.size() == 5) {height = dims.at(3);width = dims.at(4);} else {THROW_IE_EXCEPTION << inputName << " has unsupported layout " << inputInfo->getTensorDesc().getLayout();}PreProcessInfo &pp = inputInfo->getPreProcess();for (size_t c = 0; c < segments.size(); c++) {if (segments[c].size == 0)continue;Blob::Ptr blob = GetBlobFromSegment(weights, segments[c]);blob->getTensorDesc().reshape({height, width}, Layout::HW);  // to fit input image sizes (summing it is an image)pp.setMeanImageForChannel(blob, c);}}
}

后面再详细对FormatParser::SetWeights进行介绍。

总结

分析上述读取网络和权重文件可以看到最终是调用FormatParser两个主要函数对该文件进行解析,将解析出的数据最终通过ICNNNetReader进行统一管理。

OpenVINO InferenceEngine之读取IR相关推荐

  1. OpenVINO InferenceEngine之Core类

    最近因为项目需要,对intel openVINO的源码进行了解,以便为后面移植开发做准备. OpenVINO的源码在opencv的github主页上可以找到,最新的opencv 4.1.2已经全新支持 ...

  2. OpenVINO InferenceEngine之FormatParser

    目录 FormatParser::Parse 创建CNNetwork类 获取所有Layer节点 解析每层layer数据 获取到Layer id 获取到layer type 获取layer name 获 ...

  3. OpenVINO InferenceEngine之CNNNet、CNNLayer、Blob介绍

    OpenVINO IE模块对整个网络拓扑结构的管理主要是通过Net 和Layer,Data(Blob)进行管理的,看起来和caffe中的Net,Layer,Blob是一样的层次结构,是不是有点惊奇,技 ...

  4. OpenVINO 部署 YOLOv5 转换IR文件

    环境: Windows:10 YOLOv5:3.1 Python 3.7.10 torch:1.7.0+cu101 torchvision:0.8.1+cu101 OpenVINO:openvino_ ...

  5. OpenVINO InferenceEngine 之Grap file

    OpenVINO 在推理中将网络拓扑结构,最终转换成Grap,以grap file形式发送到movidius中 Grap file 文件格式为:ElfN_Ehdr + mv_blob_header + ...

  6. OpenVINO InferenceEngine framework

    最近研究OpenVINO中的推理代码,大概总结以下框架,只是自己的理解

  7. 学习OpenVINO笔记之Inference Engine

    Inference Engine是OpenVINO具体实施单元,支持CPU,GPU,FPGA,Movidius,GNA等因特尔开发的硬件平台,并提供可操作API,API为C++接口,也支持python ...

  8. openvino系列 12. Model Optimizer:PaddlePaddle 模型转化 IR 模型

    openvino系列 12. Model Optimizer:PaddlePaddle 模型转化 IR 模型 本案例展示了如何将 MobileNetV3 模型从 PaddleHub加载到本地,最终转换 ...

  9. 使用OpenVINO部署ONNX模型

    做深度学习神经网络的设计.训练与部署,往往会困扰于不同的操作系统,不同的深度学习框架,不同的部署硬件,以及不同的版本.由于相互之间的不兼容,给开发使用者造成了很大的不便. 联合使用OpenVINO和O ...

最新文章

  1. 如何使用配置的方式修改SAP C4C UI的字段标签,以及背后的工作原理
  2. 毫米波雷达_最新的7个毫米波雷达应用案例
  3. Android应用开发-图片加载库Glide
  4. 初识贪心——调度问题
  5. 明天要去面试...........
  6. TCP通信过程中各步骤的状态---(简单解释)
  7. float的比较要慎重
  8. fbx格式转gif_FBX文件打开工具
  9. 在vscode中新建html文件的两种方法
  10. 《贝叶斯统计》最大后验密度可信集(HPD)Python实现
  11. matlab 经典循环语句,经典MATLAB循环语句
  12. fluent6.3如何制作动画
  13. 基于vue的人机验证
  14. [网络流24题] 06 最长递增子序列(最多不相交路径,最大流)
  15. python读取sheet_python读取excel文件中所有sheet表格
  16. ubuntu中中文名新加卷修改为英文名新加卷
  17. 2011年回顾:改变游戏的20个HTML5网站
  18. 《CSAPP》(第3版)答案(第六章)(一)
  19. Android开发工程师笔试题
  20. spring实习总结

热门文章

  1. docker安装rabbitmq步骤
  2. Docker - 安装并持久化PostgreSQL数据
  3. package.json说明
  4. JeeWx 捷微 2.1 发布,微信管家平台
  5. 读书笔记—《销售铁军》随记7
  6. 倒排索引、分词、同义词
  7. 字典,和字典的增删改查
  8. 详谈分布式系统缓存的设计细节
  9. Spring进行面向切面编程的一个简单例子
  10. 移动开发学习记录点滴-动态增加列表行数据