Bläddra i källkod

Update OpenVINO support (#191)

Lutz Roeder 6 år sedan
förälder
incheckning
9bb78077b6
2 ändrade filer med 318 tillägg och 298 borttagningar
  1. 44 38
      src/openvino-metadata.json
  2. 274 260
      src/openvino.js

+ 44 - 38
src/openvino-metadata.json

@@ -160,6 +160,12 @@
       "support_level": "default"
     }
   },
+  {
+    "name": "BinaryConvolution",
+    "schema": {
+      "category": "Layer"
+    }
+  },
   {
     "name": "Pooling",
     "schema": {
@@ -659,7 +665,7 @@
           "description": " *epsilon* is the number to be added to the variance to avoid division by zero when normalizing the value. For example, *epsilon* equal 0.001 means that 0.001 is added to the variance.",
           "name": "epsilon",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         }
       ],
       "category": "Normalization",
@@ -692,7 +698,7 @@
           "description": " *eps* is the epsilon used to avoid division by zero when normalizing the value. For example, *eps* equals 0.001 means that 0.001 is used if all the values in normalization are equal to zero.",
           "name": "eps",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         }
       ],
       "category": "Normalization",
@@ -803,7 +809,7 @@
           "description": " *scale_all_sizes* is a flag that denotes type of inference. For example, *scale_all_sizes* equals 0 means that priorbox layer is inferd in MXNet-like manner. In particular, *max_size* parameter is ignored.",
           "name": "scale_all_sizes",
           "option": "required",
-          "type": "\n    * 0 - *max_size* is ignored\n    * 1 - default value. *max_size* is used"
+          "type": "int32"
         }
       ],
       "description": "**Short description**: *PriorBox* layer generates prior boxes of specified sizes and aspect ratios across all dimensions.\n**Parameters**: *PriorBox* layer parameters should be specified as the `data` node, which is a child of the layer node.\n**Mathematical Formulation**:\n*PriorBox* computes coordinates of prior boxes by following:\n1.  First calculates *center_x* and *center_y* of prior box:\n    \\f[\n    W \\equiv Width \\quad Of \\quad Image\n    \\f]\n    \\f[\n    H \\equiv Height \\quad Of \\quad Image\n    \\f]\n    *   If step equals 0:\n        \\f[\n        center_x=(w+0.5)\n        \\f]\n        \\f[\n        center_y=(h+0.5)\n        \\f]\n    *   else:\n        \\f[\n        center_x=(w+offset)*step\n        \\f]\n        \\f[\n        center_y=(h+offset)*step\n        \\f]\n        \\f[\n        w \\subset \\left( 0, W \\right )\n        \\f]\n        \\f[\n        h \\subset \\left( 0, H \\right )\n        \\f]\n2.  Then, for each \\f$ s \\subset \\left( 0, min_sizes \\right ) \\f$ calculates coordinates of priorboxes:\n    \\f[\n    xmin = \\frac{\\frac{center_x - s}{2}}{W}\n    \\f]\n    \\f[\n    ymin = \\frac{\\frac{center_y - s}{2}}{H}\n    \\f]\n    \\f[\n    xmax = \\frac{\\frac{center_x + s}{2}}{W}\n    \\f]\n    \\f[\n    ymin = \\frac{\\frac{center_y + s}{2}}{H}\n    \\f]\n**Example**\n\n```html\n<layer ... type=\"PriorBox\" ... >\n    <data step=\"64.000000\" min_size=\"162.000000\" max_size=\"213.000000\" offset=\"0.500000\" flip=\"1\" clip=\"0\" aspect_ratio=\"2.000000,3.000000\" variance=\"0.100000,0.100000,0.200000,0.200000\" />\n    <input> ... </input>\n    <output> ... </output>\n</layer>\n```",
@@ -821,42 +827,42 @@
           "description": " *pre_nms_topn (post_nms_topn)* is the quantity of bounding boxes before (after) applying NMS operation. For example, *pre_nms_topn (post_nms_topn)* equals 15 means that the minimum (maximum) box size is 15.",
           "name": "pre_nms_topn (post_nms_topn)",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *cls_threshold* is the minimum value of the proposal to be taken into consideration. For example, *cls_threshold* equal 0.5 means that all boxes with prediction probability less than 0.5 are filtered out.",
           "name": "cls_threshold",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         },
         {
           "default": 1,
           "description": " *iou_threshold* is the minimum ratio of boxes overlapping to be taken into consideration. For example, *iou_threshold* equal 0.7 means that all boxes with overlapping ratio less than 0.7 are filtered out.",
           "name": "iou_threshold",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         },
         {
           "default": 1,
           "description": " *feat_stride* is the step size to slide over boxes (in pixels). For example, *feat_stride* equal 16 means that all boxes are analyzed with the slide 16.",
           "name": "feat_stride",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *min_bbox_size* is the minimum size of box to be taken into consideration. For example, *min_bbox_size* equal 35 means that all boxes with box size less than 35 are filtered out.",
           "name": "min_bbox_size",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *scale* is array of scales for anchor boxes generating.",
           "name": "scale",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         }
       ],
       "category": "Layer",
@@ -914,10 +920,10 @@
         },
         {
           "default": 1,
-          "description": " type of coding method for bounding boxes",
+          "description": " type of coding method for bounding boxes. caffe.PriorBoxParameter.CENTER_SIZE and others.",
           "name": "code_type",
           "option": "required",
-          "type": " caffe.PriorBoxParameter.CENTER_SIZE and others"
+          "type": "int32"
         },
         {
           "default": 1,
@@ -938,14 +944,14 @@
           "description": " threshold to be used in NMS stage",
           "name": "nms_threshold",
           "option": "required",
-          "type": " floating point values"
+          "type": "float32"
         },
         {
           "default": 1,
           "description": " only consider detections whose confidences are larger than a threshold. If not provided, consider all boxes.",
           "name": "confidence_threshold",
           "option": "required",
-          "type": " floating point values"
+          "type": "float32"
         }
       ],
       "description": "**Short description**: *DetectionOutput* layer performs non-maximum suppression to generate the detection output using information on location and confidence predictions.\n**Detailed description**: [Reference](https://arxiv.org/pdf/1512.02325.pdf)\n**Parameters**: *DetectionOutput* layer parameters should be specified as the `data` node, which is a child of the layer node.\n**Mathematical Formulation**\nAt each feature map cell, *DetectionOutput* predicts the offsets relative to the default box shapes in the cell, as well as the per-class scores that indicate the presence of a class instance in each of those boxes. Specifically, for each box out of k at a given location, *DetectionOutput* computes class scores and the four offsets relative to the original default box shape. This results in a total of \\f$(c + 4)k\\f$ filters that are applied around each location in the feature map, yielding \\f$(c + 4)kmn\\f$ outputs for a m \u00d7 n feature map.\n**Example**\n\n```html\n<layer ... type=\"DetectionOutput\" ... >\n    <data num_classes=\"21\" share_location=\"1\" background_label_id=\"0\" nms_threshold=\"0.450000\" top_k=\"400\" eta=\"1.000000\" output_directory=\"\" output_name_prefix=\"\" output_format=\"\" label_map_file=\"\" name_size_file=\"\" num_test_image=\"0\" prob=\"1.000000\" resize_mode=\"caffe.ResizeParameter.WARP\" height=\"0\" width=\"0\" height_scale=\"0\" width_scale=\"0\" pad_mode=\"caffe.ResizeParameter.CONSTANT\" pad_value=\"#\" interp_mode=\"#\" code_type=\"caffe.PriorBoxParameter.CENTER_SIZE\" variance_encoded_in_target=\"0\" keep_top_k=\"200\" confidence_threshold=\"0.010000\" visualize=\"0\" visualize_threshold=\"0.000000\" save_file=\"\"/>\n    <input> ... </input>\n    <output> ... </output>\n</layer>\n```",
@@ -963,21 +969,21 @@
           "description": " *id* is the id of the pair of *Memory* layers. For example, *id* equals r_27-28 means that layers with id 27 and 28 are in one pair.",
           "name": "id",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *index* represents if the given layer is input or output. For example, *index* equal 0 means this layer is output one.",
           "name": "index",
           "option": "required",
-          "type": "\n    * 0 - current layer is output one\n    * 1 - current layer is input one"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *size* represents the size of the group. For example, *size* equals 2 means this group is a pair.",
           "name": "size",
           "option": "required",
-          "type": " only 2 is supported"
+          "type": "int32"
         }
       ],
       "description": "**Short description**: *Memory* layer represents delay layer in terms of LSTM terminology. To read more about LSTM topologies please refer this [link](http://colah.github.io/posts/2015-08-Understanding-LSTMs).\n**Detailed description**: *Memory* layer saves state between two infer requests. In the topology, it is the single layer, however, in the Intermediate Representation, it is always represented as a pair of **Memory** layers. One of these layers does not have outputs and another does not have inputs (in terms of the Intermediate Representation).\n**Parameters**: *Memory* layer parameters should be specified as the `data` node, which is a child of the layer node.\n**Mathematical Formulation**\n*Memory* save data from the input blob.\n**Example**\n\n```html\n<layer ... type=\"Memory\" ... >\n    <data id=\"r_27-28\" index=\"0\" size=\"2\" />\n    <input> ... </input>\n    <output> ... </output>\n</layer>\n```",
@@ -1020,14 +1026,14 @@
           "description": " if *out_max_val* equals 1, output is a vector of pairs *(max_ind, max_val)*, unless axis is set. Then output is *max_val* along the specified axis.",
           "name": "top_k",
           "option": "required",
-          "type": " positive integer number\n 0 or 1"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " if *out_max_val* equals 1, output is a vector of pairs *(max_ind, max_val)*, unless axis is set. Then output is *max_val* along the specified axis.",
           "name": "top_k",
           "option": "required",
-          "type": " positive integer number\n 0 or 1"
+          "type": "int32"
         },
         {
           "default": 1,
@@ -1052,21 +1058,21 @@
           "description": " pooled output channel number",
           "name": "output_dim",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " number of groups to encode position-sensitive score maps",
           "name": "group_size",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " multiplicative spatial scale factor to translate ROI coordinates from their input scale to the scale used when pooling",
           "name": "spatial_scale",
           "option": "required",
-          "type": " positive floating point value"
+          "type": "float32"
         }
       ],
       "category": "Pool",
@@ -1085,7 +1091,7 @@
           "description": " *bias* is added to the variance.",
           "name": "bias",
           "option": "required",
-          "type": " floating point value"
+          "type": "float32"
         }
       ],
       "category": "Normalization",
@@ -1104,7 +1110,7 @@
           "description": " *channel_shared* shows if negative slope shared across channels or not.",
           "name": "channel_shared",
           "option": "required",
-          "type": " 0 or 1"
+          "type": "int32"
         },
         {
           "description": " *filler_type* defines initialization type for negative slope.",
@@ -1171,14 +1177,14 @@
           "description": " *do_softmax* is a flag which specifies the method of infer",
           "name": "do_softmax",
           "option": "required",
-          "type": "\n    * *0* - softmax is not performed\n    * *1* - softmax is performed"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *anchors* coordinates regions",
           "name": "anchors",
           "option": "required",
-          "type": " floating point values"
+          "type": "float32[]"
         },
         {
           "default": 1,
@@ -1199,14 +1205,14 @@
           "description": " *axis* is the number of the dimension from which flattening is performed. For example, *axis* equals 1 means that flattening is started from the 1st dimension.",
           "name": "axis",
           "option": "required",
-          "type": " positive number greater or equal to 0"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *end_axis* is the number of the dimension on which flattening is ended. For example, *end_axis* equals -1 means that flattening is performed till the last dimension.",
           "name": "end_axis",
           "option": "required",
-          "type": " positive number greater or equal to 0"
+          "type": "int32"
         }
       ],
       "category": "Layer",
@@ -1320,21 +1326,21 @@
           "description": " *across_channels* is a flag that denotes if mean values are shared across channels. For example, *across_channels* equal 0 means that mean values are not shared across channels.",
           "name": "across_channels",
           "option": "required",
-          "type": "\n    * 0 - mean values are not shared across channels\n    * 1 - mean values are shared across channels"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *normalize_variance* is a flag that denotes whether to perform variance normalization.",
           "name": "normalize_variance",
           "option": "required",
-          "type": "\n    * 0 - variance normalization is not performed\n    * 1 - variance normalization is performed"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *eps* is the number to be added to the variance to avoid division by zero when normalizing the value. For example, *epsilon* equal 0.001 means that 0.001 is added to the variance.",
           "name": "eps",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         }
       ],
       "category": "Normalization",
@@ -1353,7 +1359,7 @@
           "description": " *ctc_merge_repeated* is a flag for collapsing the repeated labels during the ctc calculation.",
           "name": "ctc_merge_repeated",
           "option": "required",
-          "type": " 0 or 1"
+          "type": "int32"
         }
       ],
       "category": "Layer",
@@ -1372,49 +1378,49 @@
           "description": " *pre_nms_topn (post_nms_topn)* is the quantity of bounding boxes before (after) applying NMS operation. For example, *pre_nms_topn (post_nms_topn)* equal 15 means that the minimum (maximum) box size is 15.",
           "name": "pre_nms_topn (post_nms_topn)",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *nms_thresh* is the minimum value of the proposal to be taken into consideration. For example, *nms_thresh* equal 0.5 means that all boxes with prediction probability less than 0.5 are filtered out.",
           "name": "nms_thresh",
           "option": "required",
-          "type": " positive floating point number"
+          "type": "float32"
         },
         {
           "default": 1,
           "description": " *feat_stride* is the step size to slide over boxes (in pixels). For example, *feat_stride* equal 16 means that all boxes are analyzed with the slide 16.",
           "name": "feat_stride",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *min_size* is the minimum size of box to be taken into consideration. For example, *min_size* equal 35 means that all boxes with box size less than 35 are filtered out.",
           "name": "min_size",
           "option": "required",
-          "type": " positive integer number"
+          "type": "int32"
         },
         {
           "default": 1,
           "description": " *ratio* is the ratios for anchor generation.",
           "name": "ratio",
           "option": "required",
-          "type": " array of float numbers"
+          "type": "float32[]"
         },
         {
           "default": 1,
           "description": " *ratio* is the ratios for anchor generation.",
           "name": "ratio",
           "option": "required",
-          "type": " array of float numbers"
+          "type": "float32[]"
         },
         {
           "default": 1,
           "description": " *scale* is the scales for anchor generation.",
           "name": "scale",
           "option": "required",
-          "type": " array of float numbers"
+          "type": "float32[]"
         }
       ],
       "category": "Layer",

+ 274 - 260
src/openvino.js

@@ -69,12 +69,10 @@ openvino.ModelFactory = class {
                 if (errors || xmlDoc.documentElement == null || xmlDoc.getElementsByTagName('parsererror').length > 0) {
                     throw new openvino.Error("File format is not OpenVINO.");
                 }
-                const net = xmlDoc.documentElement;
-                if (!net || net.nodeName != 'net' ||
-                    openvino.Node.children(net, 'layers').length != 1 ||
-                    openvino.Node.children(net, 'edges').length != 1) {
+                if (!xmlDoc.documentElement || xmlDoc.documentElement.nodeName != 'net') {
                     throw new openvino.Error("File format is not OpenVINO IR.");
                 }
+                const net = openvino.XmlReader.read(xmlDoc.documentElement);
                 return new openvino.Model(metadata, net, bin);
             }
             catch (error) {
@@ -89,8 +87,12 @@ openvino.ModelFactory = class {
 openvino.Model = class {
 
     constructor(metadata, net, bin) {
-        let graph = new openvino.Graph(metadata, net, bin);
-        this._graphs = [ graph ];
+        this._name = net.name || '';
+        this._graphs = [ new openvino.Graph(metadata, net, bin) ];
+    }
+
+    get name() {
+        return this._name;
     }
 
     get format() {
@@ -106,55 +108,35 @@ openvino.Model = class {
 openvino.Graph = class {
 
     constructor(metadata, net, bin) {
-        this._name = net.getAttribute('name') || '';
-        this._batch = net.getAttribute('batch') || '';
-        this._version = net.getAttribute('version') || '';
+        this._name = net.name || '';
         this._nodes = [];
         this._inputs = [];
         this._outputs = [];
         this._arguments = {};
 
-        const layersElement = openvino.Node.children(net, 'layers')[0];
-        const edgesElement = openvino.Node.children(net, 'edges')[0];
-
-        const layers = openvino.Node.children(layersElement, 'layer');
-        const edges = openvino.Node.children(edgesElement, 'edge');
-
-        const edgeMap = this._collectEdges(edges);
-
-        for (const  layer of layers) {
-            const operator = layer.getAttribute('type');
-            switch (operator) {
+        for (const layer of net.layers) {
+            const inputs = layer.inputs.map((input) => this._argument(layer.id, layer.precision, input, net.edges));
+            const outputs = layer.outputs.map((output) => this._argument(layer.id, output.precision || layer.precision, output, null));
+            switch (layer.type) {
                 case 'Input': {
-                    let args = [];
-                    const name = layer.getAttribute('name') || '';
+                    const name = layer.name || '';
                     // precision is a part of OpenVINO IR layers of IR v6 and earlier
                     // in IR v7 and newer the port is no longer an attribute of the layer but of each output port
-                    const precision = layer.getAttribute('precision');
-                    const id = layer.getAttribute('id');
-                    for (const  outputElement of openvino.Node.children(layer, 'output')) {
-                        for (const  portElement of openvino.Node.children(outputElement, 'port')) {
-                            const portPrecision = portElement.getAttribute('precision') || precision;
-                            args.push(this._argument(id, portPrecision, portElement, null));
-                        }
-                    }
                     // IR input is not just a placeholder, it is conceptually the legitimate layer
                     // in order not to break compatibility with the overall approach
                     // with openvino.Parameter for inputs and openvino.Node for outputs
                     // input openvino.Node would be stored as an optional attribute of openvino.Parameter
-                    const inputNode = new openvino.Node(this, metadata, bin, layer, edgeMap);
-                    const inputParameter = new openvino.Parameter(name, args);
-                    inputParameter._realNode = inputNode;
-                    this._inputs.push(inputParameter);
+                    this._inputs.push(new openvino.Parameter(name, outputs));
                     break;
                 }
-                default:
-                    this._nodes.push(new openvino.Node(this, metadata, bin, layer, edgeMap));
+                default: {
+                    this._nodes.push(new openvino.Node(this, metadata, bin, layer, inputs, outputs));
                     break;
+                }
             }
         }
 
-        this._replaceTensorIteratorWithSubgraph(metadata, bin, layers, edges, edgeMap);
+        this._replaceTensorIteratorWithSubgraph(metadata, bin, net.layers, net.edges);
         delete this._arguments;
 
         // Validation
@@ -163,25 +145,32 @@ openvino.Graph = class {
         // "Input" layers are already moved to inputs when we parse a graph
         // if there are any layers that do not have input arguments and they are no Const ones
         // this means that this graph was not properly processed by the graph building logic
-        const allNodesOutputs = this._nodes.reduce((acc, node) => {
-            const nodesRes = this._collectConnectionsIds(node._outputs);
-            acc = acc.concat(nodesRes);
-            return acc;
-        }, []);
-        const allInputsOutputs = this._collectConnectionsIds(this._inputs);
-        const outputSet = new Set([...allNodesOutputs, ...allInputsOutputs]);
-        const nodesWithNonExistentInputs = this._nodes.reduce((acc, node) => {
-            const nodesInputs = this._collectConnectionsIds(node._inputs);
-            if (nodesInputs.filter((value) => !outputSet.has(value)).length > 0) {
-                acc.push(node);
+        let outputSet = new Set();
+        for (const node of this._nodes) {
+            for (const output of node.outputs) {
+                for (const argument of output.arguments) {
+                    outputSet.add(argument.id);
+                }
             }
-            return acc;
-        }, []);
-
-        if (nodesWithNonExistentInputs.length !== 0){
-            const layerNames = nodesWithNonExistentInputs.map((n) => n.name).join(',');
-            const message = `Graph seems to contain ${nodesWithNonExistentInputs.length} connected components. Not connected layers: ${layerNames}`;
-            throw new openvino.Error(message);
+        }
+        for (const input of this.inputs) {
+            for (const argument of input.arguments) {
+                outputSet.add(argument.id);
+            }
+        }
+        let nodesWithNonExistentInputs = new Set();
+        for (const node of this._nodes) {
+            for (const input of node.inputs) {
+                for (const argument of input.arguments) {
+                    if (!argument.initializer && !outputSet.has(argument.id)) {
+                        nodesWithNonExistentInputs.add(node);
+                    }
+                }
+            }
+        }
+        if (nodesWithNonExistentInputs.size !== 0){
+            const layerNames = Array.from(nodesWithNonExistentInputs).map((node) => node.name);
+            throw new openvino.Error('Graph contains not connected layers ' + JSON.stringify(layerNames) + '.');
         }
     }
 
@@ -202,50 +191,41 @@ openvino.Graph = class {
     }
 
     _argument(layer, precision, port, map) {
-        let id = layer + ':' + port.getAttribute('id');
+        let id = layer + ':' + port.id;
         if (map) {
             id = map[id];
         }
         let argument = this._arguments[id];
         if (!argument) {
-            let dimensions = [];
-            for (const dimElement of Array.prototype.slice.call(port.getElementsByTagName('dim'))) {
-                dimensions.push(parseInt(dimElement.textContent.trim()));
-            }
-            const shape = (dimensions.length == 0) ? null : new openvino.TensorShape(dimensions);
+            const shape = port.dims.length == 0 ? null : new openvino.TensorShape(port.dims);
             argument = new openvino.Argument(id, new openvino.TensorType(precision, shape), null);
         }
         return argument;
     }
 
-    _replaceTensorIteratorWithSubgraph(metadata, bin, layers, edges, edgeMap) {
-        const tiNodes = layers.filter((node) => node.getAttribute('type') === 'TensorIterator');
-        for (const  singleTensorIteratorNode of tiNodes) {
-            const singleTensorIteratorNodeId = singleTensorIteratorNode.getAttribute("id");
-            const tiNode = this._nodes.find((n) => n._id === `${singleTensorIteratorNodeId}`);
-            const body = openvino.Node.children(singleTensorIteratorNode, 'body')[0];
-            const layersContainer = openvino.Node.children(body, 'layers')[0];
-            const edgesContainer = openvino.Node.children(body, 'edges')[0];
-            const iteratorLayers = openvino.Node.children(layersContainer, 'layer');
-            const iteratorEdges = openvino.Node.children(edgesContainer, 'edge');
-            const iteratorEdgeMap = this._collectEdges(iteratorEdges);
-            const iteratorBackEdgesContainer = openvino.Node.children(singleTensorIteratorNode, 'back_edges')[0];
-            const iteratorBackEdges = openvino.Node.children(iteratorBackEdgesContainer, 'edge')
-            const iteratorBackEdgesMap = this._collectEdges(iteratorBackEdges);
+    _replaceTensorIteratorWithSubgraph(metadata, bin, layers, edges) {
+        const tiNodes = layers.filter((node) => node.type === 'TensorIterator');
+        for (const singleTensorIteratorNode of tiNodes) {
+            const singleTensorIteratorNodeId = singleTensorIteratorNode.id;
+            const tiNode = this._nodes.find((n) => n._id === singleTensorIteratorNodeId);
+            const iteratorLayers = singleTensorIteratorNode.body.layers;
+            const iteratorEdgeMap = singleTensorIteratorNode.body.edges;
+            const iteratorBackEdgesMap = singleTensorIteratorNode.back_edges;
             const iteratorAllEdges = Object.assign({}, iteratorEdgeMap, iteratorBackEdgesMap);
-            const mappingForNestedIR = this._parseMappingBlock(singleTensorIteratorNode);
+            const mappingForNestedIR = singleTensorIteratorNode.port_map;
             for (const nestedLayer of iteratorLayers) {
-                let nestedNode = new openvino.Node(this, metadata, bin, nestedLayer, iteratorAllEdges);
-                nestedNode._id = `${singleTensorIteratorNodeId}_${nestedLayer.getAttribute('id')}`;
+                const inputs = nestedLayer.inputs.map((input) => this._argument(nestedLayer.id, nestedLayer.precision, input, iteratorAllEdges));
+                const outputs = nestedLayer.outputs.map((output) => this._argument(nestedLayer.id, nestedLayer.precision || output.precision, output, null));
+                let nestedNode = new openvino.Node(this, metadata, bin, nestedLayer, inputs, outputs);
+                nestedNode._id = singleTensorIteratorNodeId + '_' + nestedLayer.id;
                 for (const input of nestedNode._inputs) {
                     for (const input_argument of input.arguments) {
                         // we had a argument with id: 0:1  - meaning from layer "0" and its port "1"
                         // now as we rename all internal nodes to have an id of the TI included
                         // e.g. internal layer with id "0" and TI with id "14" results in internal layer to get id "14_0"
-                        if (!input_argument._id){
-                            continue;
+                        if (input_argument.id){
+                            input_argument._id = singleTensorIteratorNodeId + '_' + input_argument.id;
                         }
-                        input_argument._id = `${singleTensorIteratorNodeId}_${input_argument._id}`;
                     }
                 }
 
@@ -254,31 +234,25 @@ openvino.Graph = class {
                         // we had a argument with id: 1:1  - meaning from me with id "1" and my port "1"
                         // now as we rename all internal nodes to have an id of the TI included
                         // e.g. my layer with id "1" and TI with id "14" results in internal layer to get id "14_1"
-                        if (!output_argument._id){
-                            continue;
+                        if (output_argument.id){
+                            output_argument._id = singleTensorIteratorNodeId + '_' + output_argument.id;
                         }
-                        output_argument._id = `${singleTensorIteratorNodeId}_${output_argument._id}`;
                     }
                 }
                 
                 this._nodes.push(nestedNode);
             }
 
-            // We know for sure that edges that appeared in the nested IR are not
-            // aware of the external context
+            // We know for sure that edges that appeared in the nested IR are not aware of the external context
             for (const nestedInput of mappingForNestedIR.input) {
-                const nestedNode = this._nodes.find((n) => n._id === `${singleTensorIteratorNodeId}_${nestedInput.internal_layer_id}`);
-                const candidate_edges = edges.filter((edge) => {
-                    return edge.getAttribute('to-layer') === singleTensorIteratorNodeId && edge.getAttribute('to-port') === nestedInput.external_port_id;
-                });
-                if (!candidate_edges.length){
-                    continue;
-                }
-                for (const candidate_edge of candidate_edges) {
-                    const parentLayerID = candidate_edge.getAttribute('from-layer');
-                    const parentPortID = candidate_edge.getAttribute('from-port');
-                    
-                    const parentNode = this._nodes.find((n) => n._id === `${parentLayerID}`);
+                const nestedNode = this._nodes.find((n) => n._id === singleTensorIteratorNodeId + '_' + nestedInput.internal_layer_id);
+
+                const candidate_edge = edges[singleTensorIteratorNodeId + ':' + nestedInput.external_port_id];
+                if (candidate_edge) {
+                    const parts = candidate_edge.split(':');
+                    const parentLayerID = parts[0];
+                    const parentPortID = parts[1];
+                    const parentNode = this._nodes.find((n) => n._id === parentLayerID);
                     if (!parentNode) {
                         // its parent is a TensorIterator that was removed on the previous cycle
                         // information is still present in the inputs of the current TensorIterator node
@@ -287,10 +261,10 @@ openvino.Graph = class {
                             return;
                         }
                         const inputWithoutId = nestedNode._inputs.find((input) => {
-                            return Boolean(input._arguments.find((argument) => !argument._id));
+                            return Boolean(input.arguments.find((argument) => !argument.id));
                         });
                         if (inputWithoutId) {
-                            const argumentWithoutId = inputWithoutId._arguments.find((argument) => !argument._id);
+                            const argumentWithoutId = inputWithoutId.arguments.find((argument) => !argument.id);
                             if (argumentWithoutId){
                                 argumentWithoutId._id = potentialParentInput.arguments[0].id;
                             } 
@@ -298,10 +272,10 @@ openvino.Graph = class {
                     } 
                     else {
                         if (!nestedNode._inputs){
-                            throw new openvino.Error(`Tensor Iterator node with name ${nestedNode._name} does not have inputs.`);
+                            throw new openvino.Error("Tensor Iterator node with name '" + nestedNode._name + "' does not have inputs.");
                         }
                         
-                        const newId = `${parentLayerID}:${parentPortID}`;
+                        const newId = parentLayerID + ':' + parentPortID;
                         const inputWithoutId = nestedNode._inputs.find((input) => {
                             return Boolean(input._arguments.find((argument) => !argument._id));
                         });
@@ -313,12 +287,9 @@ openvino.Graph = class {
                         }
                         else {
                             // TODO: no tensor information in the new argument - passed as null for now
-                            const inputNode = new openvino.Node(this, metadata, bin, singleTensorIteratorNode, edgeMap);
-                            const inputParameter = new openvino.Parameter((nestedNode._inputs.length+1).toString(), [
+                            nestedNode._inputs.push(new openvino.Parameter((nestedNode._inputs.length + 1).toString(), [
                                 new openvino.Argument(newId, null, null)
-                            ]);
-                            inputParameter._realNode = inputNode;
-                            nestedNode._inputs.push(inputParameter);
+                            ]));
                         }
                     }
                 }
@@ -326,14 +297,10 @@ openvino.Graph = class {
 
             for (const nestedOutput of mappingForNestedIR.output) {
                 const nestedNode = this._nodes.find((n) => n._id === `${singleTensorIteratorNodeId}_${nestedOutput.internal_layer_id}`);
-                const candidate_edges = edges.filter((edge) => {
-                    return edge.getAttribute('from-layer') === singleTensorIteratorNodeId && edge.getAttribute('from-port') === nestedOutput.external_port_id;
-                });
-                if (candidate_edges.length === 0){
-                    continue;
-                }
+                const toEdge = singleTensorIteratorNodeId + ':' + nestedOutput.external_port_id;
+                const candidate_edges = Object.keys(edges).filter((key) => edges[key] === toEdge)
                 for (const candidate_edge of candidate_edges) {
-                    const childLayerID = candidate_edge.getAttribute('to-layer');
+                    const childLayerID = candidate_edge.split(':')[0];
                     const child = this._nodes.find((layer) => layer._id === childLayerID);
                     if (!child._inputs || (child._inputs && child._inputs.length === 0)){
                         continue;
@@ -345,7 +312,7 @@ openvino.Graph = class {
                                     continue;
                                 }
                                 const myPort = nestedNode._outputs[0]._arguments[0]._id.split(':')[1];
-                                argument._id = `${nestedNode.id}:${myPort}`;
+                                argument._id = nestedNode.id + ':' + myPort;
                             }
                         }
                     }
@@ -355,154 +322,99 @@ openvino.Graph = class {
             this._nodes = this._nodes.filter((node) => node.id !== singleTensorIteratorNode.id);
         }
     }
-
-    _collectEdges(edges){
-        let edgeMap = {};
-        for (const edge of edges) {
-            const fromLayer = edge.getAttribute('from-layer');
-            const fromPort = edge.getAttribute('from-port');
-            const toLayer = edge.getAttribute('to-layer');
-            const toPort = edge.getAttribute('to-port');
-            edgeMap[toLayer + ':' + toPort] = fromLayer + ':' + fromPort;
-        }
-        return edgeMap;
-    }
-
-    _collectPortsInformation(ports) {
-        return ports.reduce((acc, port) => {
-            acc.push({
-                axis: port.getAttribute("axis"),
-                external_port_id: port.getAttribute("external_port_id"),
-                internal_layer_id: port.getAttribute("internal_layer_id"),
-                internal_port_id: port.getAttribute("internal_port_id")
-            });
-            return acc;
-        }, []);
-    }
-
-    _parseMappingBlock(singleTensorIteratorNode) {
-        const portMap = openvino.Node.children(singleTensorIteratorNode, 'port_map')[0];
-        const inputs = openvino.Node.children(portMap, 'input');
-        const outputs = openvino.Node.children(portMap, 'output');
-        return {
-            input: this._collectPortsInformation(inputs),
-            output: this._collectPortsInformation(outputs)
-        };
-    }
-
-    _collectConnectionsIds(where) {
-        return where.reduce((accOutput, output) => {
-            const res = output._arguments.reduce((accConn, argument) => {
-                accConn.push(argument._id);
-                return accConn;
-            }, []);
-            accOutput = accOutput.concat(res);
-            return accOutput;
-        }, []);
-    }
 };
 
 openvino.Node = class {
 
-    constructor(graph, metadata, bin, layer, edgeMap) {
+    constructor(graph, metadata, bin, layer, inputs, outputs) {
         this._metadata = metadata;
-        this._type = layer.getAttribute('type');
-        this._name = layer.getAttribute('name') || '';
-        this._id = layer.getAttribute('id');
+        this._type = layer.type;
+        this._name = layer.name || '';
+        this._id = layer.id;
         this._inputs = [];
         this._outputs = [];
         this._initializers = [];
         this._attributes = [];
-        const precision = layer.getAttribute('precision');
+        const precision = layer.precision;
         let inputIndex = 0;
-        const input = openvino.Node.children(layer, 'input')[0];
-        if (input) {
-            for (const port of openvino.Node.children(input, 'port')) {
-                const inputName = (inputIndex == 0) ? 'input' : inputIndex.toString(); 
-                this._inputs.push(new openvino.Parameter(inputName, [
-                    graph._argument(this._id, precision, port, edgeMap)
-                ]));
-                inputIndex++;
-            }
+        for (const input of inputs) {
+            const inputName = (inputIndex == 0) ? 'input' : inputIndex.toString(); 
+            this._inputs.push(new openvino.Parameter(inputName, [ input ]));
+            inputIndex++;
         }
         let outputIndex = 0;
-        const output = openvino.Node.children(layer, 'output')[0];
-        if (output) {
-            for (const portElement of openvino.Node.children(output, 'port')) {
-                const outputName = (outputIndex == 0) ? 'output' : outputIndex.toString();
-                const portPrecision = portElement.getAttribute('precision') || precision;
-                this._outputs.push(new openvino.Parameter(outputName, [
-                    graph._argument(this._id, portPrecision, portElement, null)
-                ]));
-                outputIndex++;
-            }
+        for (const output of outputs) {
+            const outputName = (outputIndex == 0) ? 'output' : outputIndex.toString();
+            this._outputs.push(new openvino.Parameter(outputName, [ output ]));
+            outputIndex++;
         }
         let attributes = {};
-        const data = openvino.Node.children(layer, 'data')[0];
-        if (data && data.attributes) {
-            for (const attribute of Array.from(data.attributes)) {
-                attributes[attribute.name] = attribute.value;
-                this._attributes.push(new openvino.Attribute(metadata, this, attribute.name, attribute.value));
-            }
+        for (const attribute of layer.data) {
+            attributes[attribute.name] = attribute.value;
+            const attributeSchema = metadata.attribute(this.operator, attribute.name);
+            this._attributes.push(new openvino.Attribute(attributeSchema, attribute.name, attribute.value));
         }
-        const blobs = openvino.Node.children(layer, 'blobs')[0];
-        if (blobs){
-            for (const blob of Array.from(blobs.childNodes).filter((node) => node.nodeName != '#text')) {
-                if (blob.getAttribute && typeof blob.getAttribute === 'function') {
-                    const name = blob.nodeName;
-                    let data = null;
-                    let shape = null;
-                    const blobPrecision = blob.getAttribute('precision') || precision;
-                    if (bin) {
-                        const offset = parseInt(blob.getAttribute('offset'));
-                        const size = parseInt(blob.getAttribute('size'));
-                        if ((offset + size) <= bin.length) {
-                            data = bin.slice(offset, offset + size);
-                        }
-                        const precisionMap = {
-                            'FP16': 2, 'FP32': 4,
-                            'I8': 1, 'I16': 2, 'I32': 4, 'I64': 8,
-                            'U8': 1, 'U16': 2, 'U32': 4, 'U64': 8
-                        };
-                        if (precisionMap[blobPrecision]) {
-                            let itemSize = precisionMap[blobPrecision];
-                            switch (this._type + ':' + name) {
-                                case 'FullyConnected:weights': {
-                                    const outSize = parseInt(attributes['out-size'], 10);
-                                    shape = [ size / (outSize * itemSize), outSize ];
-                                    break;
-                                }
-                                case 'FullyConnected:biases': {
-                                    shape = [ parseInt(attributes['out-size'], 10) ];
-                                    break;
-                                }
-                                case 'ScaleShift:weights': 
-                                case 'ScaleShift:biases': 
-                                case 'Convolution:biases': {
-                                    shape = [ Math.floor(size / itemSize) ];
-                                    break;
-                                }
-                                case 'Const:custom': {
-                                    if (this._outputs.length > 0 &&
-                                        this._outputs[0].arguments.length > 0 &&
-                                        this._outputs[0].arguments[0].type &&
-                                        this._outputs[0].arguments[0].type.shape &&
-                                        this._outputs[0].arguments[0].type.shape.dimensions) {
-                                        shape = this._outputs[0].arguments[0].type.shape.dimensions;
-                                    }
-                                    break;
-                                }
-                            }
-                        }
+        for (const blob of layer.blobs) {
+            const name = blob.name;
+            const offset = blob.offset;
+            const size = blob.size;
+            const data = (bin && (offset + size) <= bin.length) ? bin.slice(offset, offset + size) : null;
+            let shape = null;
+            const blobPrecision = blob.precision || precision;
+            const precisionMap = {
+                'FP16': 2, 'FP32': 4,
+                'I8': 1, 'I16': 2, 'I32': 4, 'I64': 8,
+                'U8': 1, 'U16': 2, 'U32': 4, 'U64': 8
+            };
+            if (precisionMap[blobPrecision]) {
+                let itemSize = precisionMap[blobPrecision];
+                switch (this._type + ':' + name) {
+                    case 'FullyConnected:weights': {
+                        const outSize = parseInt(attributes['out-size'], 10);
+                        shape = [ size / (outSize * itemSize), outSize ];
+                        break;
+                    }
+                    case 'FullyConnected:biases': {
+                        shape = [ parseInt(attributes['out-size'], 10) ];
+                        break;
                     }
-                    if (shape) {
-                        shape = new openvino.TensorShape(shape);
+                    case 'Convolution:weights':
+                    case 'Deconvolution:weights': {
+                        const c = this.inputs[0].arguments[0].type.shape.dimensions[1];
+                        const group = parseInt(attributes['group'] || '1', 10);
+                        const kernel = attributes['kernel-x'] && attributes['kernel-y'] ?
+                            [ parseInt(attributes['kernel-x'], 10), parseInt(attributes['kernel-y'], 10) ] :
+                            attributes['kernel'].split(',').map((v) => parseInt(v.trim(), 10));
+                        const n = parseInt(attributes['output'], 10);
+                        shape = [ Math.floor(c / group), n ].concat(kernel);
+                        break;
+                    }
+                    case 'ScaleShift:weights': 
+                    case 'ScaleShift:biases': 
+                    case 'Convolution:biases':
+                    case 'Normalize:weights':
+                    case 'PReLU:weights': {
+                        shape = [ Math.floor(size / itemSize) ];
+                        break;
+                    }
+                    case 'Const:custom': {
+                        if (this._outputs.length > 0 &&
+                            this._outputs[0].arguments.length > 0 &&
+                            this._outputs[0].arguments[0].type &&
+                            this._outputs[0].arguments[0].type.shape &&
+                            this._outputs[0].arguments[0].type.shape.dimensions) {
+                            shape = this._outputs[0].arguments[0].type.shape.dimensions;
+                        }
+                        break;
                     }
-                    this._initializers.push(new openvino.Parameter(name, [
-                        new openvino.Argument('', null, new openvino.Tensor(blobPrecision, shape, data))
-                    ]));
                 }
             }
+            if (shape) {
+                shape = new openvino.TensorShape(shape);
+            }
+            this._initializers.push(new openvino.Parameter(name, [
+                new openvino.Argument('', null, new openvino.Tensor(blobPrecision, shape, data))
+            ]));
         }
     }
 
@@ -537,18 +449,6 @@ openvino.Node = class {
     get outputs() {
         return this._outputs;
     }
-
-    static children(element, name) {
-        let children = [];
-        let child = element.firstChild;
-        while (child != null) {
-            if (child.nodeType == 1 && child.nodeName == name) {
-                children.push(child);
-            }
-            child = child.nextSibling;
-        }
-        return children;
-    }
 };
 
 openvino.Parameter = class {
@@ -556,7 +456,6 @@ openvino.Parameter = class {
     constructor(name, args) {
         this._name = name;
         this._arguments = args;
-        this._realNode = null;
     }
 
     get name() {
@@ -575,6 +474,9 @@ openvino.Parameter = class {
 openvino.Argument = class {
 
     constructor(id, type, initializer) {
+        if (typeof id !== 'string') {
+            // throw new openvino.Error("Invalid argument identifier '" + JSON.stringify(id) + "'.");
+        }
         this._id = id;
         this._type = type || null;
         this._initializer = initializer || null;
@@ -598,11 +500,9 @@ openvino.Argument = class {
 
 openvino.Attribute = class {
 
-    constructor(metadata, node, name, value) {
-        this._node = node;
+    constructor(schema, name, value) {
         this._name = name;
         this._value = value;
-        const schema = metadata.attribute(node.operator, name);
         if (schema) {
             if (Object.prototype.hasOwnProperty.call(schema, 'type')) {
                 this._type = schema.type;
@@ -963,6 +863,120 @@ openvino.Metadata = class {
     }
 };
 
+openvino.XmlReader = class {
+
+    static read(element) {
+        const children = (parent, name) => {
+            let children = [];
+            let child = parent.firstChild;
+            while (child != null) {
+                if (child.nodeType == 1 && child.nodeName == name) {
+                    children.push(child);
+                }
+                child = child.nextSibling;
+            }
+            return children;
+        };
+        const child = (parent, name) => {
+            const elements = children(parent, name);
+            if (elements.length > 1) {
+                throw new openvino.Error("Element '" + parent.nodeName + "' has multiple '" + name + "' elements.");
+            }
+            return elements.length > 0 ? elements[0] : null;
+        }
+        const ports = (parent, name) => {
+            const elements = child(parent, name);
+            if (elements) {
+                return children(elements, 'port').map((element) => {
+                    return {
+                        id: element.getAttribute('id'),
+                        precision: element.getAttribute('precision'),
+                        dims: Array.prototype.slice.call(element.getElementsByTagName('dim')).map((dim) => parseInt(dim.textContent.trim(), 10))
+                    };
+                });
+            }
+            return [];
+        };
+        const layers = (parent) => {
+            const elements = child(parent, 'layers');
+            if (elements) {
+                return children(elements, 'layer').map((element) => {
+                    const data = child(element, 'data');
+                    const blobs = child(element, 'blobs');
+                    let layer = {
+                        id: element.getAttribute('id'),
+                        name: element.getAttribute('name'),
+                        type: element.getAttribute('type'),
+                        precision: element.getAttribute('precision'),
+                        data: !data ? [] : Array.from(data.attributes).map((attribute) => {
+                            return { name: attribute.name, value: attribute.value};
+                        }),
+                        blobs: !blobs ? [] : Array.from(blobs.childNodes).filter((node) => node.nodeType === 1).map((blob) => {
+                            return { 
+                                name: blob.nodeName,
+                                precision: blob.getAttribute('precision'),
+                                offset: parseInt(blob.getAttribute('offset'), 10),
+                                size: parseInt(blob.getAttribute('size'), 10)
+                            } 
+                        }),
+                        inputs: ports(element, 'input'),
+                        outputs: ports(element, 'output'),
+                    };
+                    if (layer.type === 'TensorIterator') {
+                        layer.back_edges = edges(element, 'back_edges');
+                        const body = child(element, 'body');
+                        if (body) {
+                            layer.body = {
+                                layers: layers(body),
+                                edges: edges(body)
+                            };
+                        }
+                        const port_map = child(element, 'port_map');
+                        if (port_map) {
+                            layer.port_map = { input: [], output: [] };
+                            for (const port of Array.from(port_map.childNodes).filter((element) => element.nodeType === 1)) {
+                                const item = {
+                                    axis: port.getAttribute("axis"),
+                                    external_port_id: port.getAttribute("external_port_id"),
+                                    internal_layer_id: port.getAttribute("internal_layer_id"),
+                                    internal_port_id: port.getAttribute("internal_port_id")
+                                };
+                                switch (port.nodeName) {
+                                    case 'input': layer.port_map.input.push(item); break;
+                                    case 'output': layer.port_map.output.push(item); break;
+                                }
+                            }
+                        }
+                    }
+                    return layer;
+                });
+            }
+            return [];
+        }
+        const edges = (parent, name) => {
+            let map = {};
+            const elements = child(parent, name || 'edges');
+            if (elements) {
+                for (const element of children(elements, 'edge')) {
+                    const fromLayer = element.getAttribute('from-layer');
+                    const fromPort = element.getAttribute('from-port');
+                    const toLayer = element.getAttribute('to-layer');
+                    const toPort = element.getAttribute('to-port');
+                    map[toLayer + ':' + toPort] = fromLayer + ':' + fromPort;
+                }
+            }
+            return map;
+        }
+        return {
+            name: element.getAttribute('name'),
+            batch: element.getAttribute('batch'),
+            version: element.getAttribute('version'),
+            layers: layers(element),
+            edges: edges(element)
+        };
+    }
+}
+
 openvino.Error = class extends Error {
 
     constructor(message) {