Просмотр исходного кода

Caffe metadata and initializers

Lutz Roeder 8 лет назад
Родитель
Сommit
019ff6ab87
3 измененных файлов с 367 добавлено и 98 удалено
  1. 279 89
      src/caffe-model.js
  2. 87 5
      src/caffe-operator.json
  3. 1 4
      src/coreml-model.js

+ 279 - 89
src/caffe-model.js

@@ -12,7 +12,7 @@ class CaffeModel {
                 callback(err, null);
             }
             else {
-                caffe = protobuf.roots.caffe.caffe
+                caffe = protobuf.roots.caffe.caffe;
                 CaffeModel.create(buffer, identifier, host, (err, model) => {
                     callback(err, model);
                 });
@@ -35,7 +35,12 @@ class CaffeModel {
 
     constructor(netParameter) {
         if (netParameter.layers && netParameter.layers.length > 0) {
-            this._version = 1;
+            if (netParameter.layers.every((layer) => layer.hasOwnProperty('layer'))) {
+                this._version = 0;
+            }
+            else {
+                this._version = 1;
+            }
         }
         else if (netParameter.layer && netParameter.layer.length > 0) {
             this._version = 2;
@@ -45,7 +50,7 @@ class CaffeModel {
 
     get properties() {
         var results = [];
-        results.push({ name: 'Format', value: 'Caffe' + (this._version ? ' v' + this._version.toString() : '') });
+        results.push({ name: 'Format', value: 'Caffe' + (this.hasOwnProperty('_version') ? ' v' + this._version.toString() : '') });
         return results;
     }
 
@@ -64,6 +69,7 @@ class CaffeGraph {
 
         var layers = [];
         switch (version) {
+            case 0:
             case 1:
                 layers = netParameter.layers;
                 break;
@@ -135,92 +141,57 @@ class CaffeGraph {
 class CaffeNode {
 
     constructor(layer, version) {
-        this._name = layer.name;
-
-        if (version == 1) {
-            if (!CaffeNode._operatorTable) {
-                var table = {};
-                table[caffe.V1LayerParameter.LayerType.NONE] = 'None';
-                table[caffe.V1LayerParameter.LayerType.ACCURACY] = 'Accuracy';
-                table[caffe.V1LayerParameter.LayerType.BNLL] = 'BNLL';
-                table[caffe.V1LayerParameter.LayerType.CONCAT] = 'Concat'; 
-                table[caffe.V1LayerParameter.LayerType.CONVOLUTION] = 'Convolution';
-                table[caffe.V1LayerParameter.LayerType.DATA] = 'Data';
-                table[caffe.V1LayerParameter.LayerType.DROPOUT] = 'Dropout';
-                table[caffe.V1LayerParameter.LayerType.EUCLIDEAN_LOSS] = 'EuclideanLoss';
-                table[caffe.V1LayerParameter.LayerType.FLATTEN] = 'Flatten';
-                table[caffe.V1LayerParameter.LayerType.HDF5_DATA] = 'HDF5Data';
-                table[caffe.V1LayerParameter.LayerType.HDF5_OUTPUT] = 'HDF5Output';
-                table[caffe.V1LayerParameter.LayerType.IM2COL] = 'Im2col';
-                table[caffe.V1LayerParameter.LayerType.IMAGE_DATA] = 'ImageData';
-                table[caffe.V1LayerParameter.LayerType.INFOGAIN_LOSS] = 'InfogainLoss';
-                table[caffe.V1LayerParameter.LayerType.INNER_PRODUCT] = 'InnerProduct';
-                table[caffe.V1LayerParameter.LayerType.LRN] = 'LRN';
-                table[caffe.V1LayerParameter.LayerType.MULTINOMIAL_LOGISTIC_LOSS] = 'MultinomialLogisticLoss';
-                table[caffe.V1LayerParameter.LayerType.POOLING] = 'Pooling';
-                table[caffe.V1LayerParameter.LayerType.RELU] = 'ReLU';
-                table[caffe.V1LayerParameter.LayerType.SIGMOID] = 'Sigmoid';
-                table[caffe.V1LayerParameter.LayerType.SOFTMAX] = 'Softmax';
-                table[caffe.V1LayerParameter.LayerType.SOFTMAX_LOSS] = 'SoftmaxLoss';
-                table[caffe.V1LayerParameter.LayerType.SPLIT] = 'Split';
-                table[caffe.V1LayerParameter.LayerType.TANH] = 'TanH';
-                table[caffe.V1LayerParameter.LayerType.WINDOW_DATA] = 'WindowData';
-                table[caffe.V1LayerParameter.LayerType.ELTWISE] = 'Eltwise';
-                table[caffe.V1LayerParameter.LayerType.POWER] = 'Power';
-                table[caffe.V1LayerParameter.LayerType.SIGMOID_CROSS_ENTROPY_LOSS] = 'SigmoidCrossEntropyLoss';
-                table[caffe.V1LayerParameter.LayerType.HINGE_LOSS] = 'HingeLoss';
-                table[caffe.V1LayerParameter.LayerType.MEMORY_DATA] = 'HingeLoss';
-                table[caffe.V1LayerParameter.LayerType.ARGMAX] = 'ArgMax';
-                table[caffe.V1LayerParameter.LayerType.THRESHOLD] = 'Threshold';
-                table[caffe.V1LayerParameter.LayerType.DUMMY_DATA] = 'DummyData';
-                table[caffe.V1LayerParameter.LayerType.SLICE] = 'Slice';
-                table[caffe.V1LayerParameter.LayerType.MVN] = 'MVN';
-                table[caffe.V1LayerParameter.LayerType.ABSVAL] = 'AbsVal';
-                table[caffe.V1LayerParameter.LayerType.SILENCE] = 'Silence';
-                table[caffe.V1LayerParameter.LayerType.CONTRASTIVE_LOSS] = 'ContrastiveLoss';
-                table[caffe.V1LayerParameter.LayerType.EXP] = 'Exp';
-                table[caffe.V1LayerParameter.LayerType.DECONVOLUTION] = 'Deconvolution';
-                CaffeNode._operatorTable = table;
-            }
-            this._type = CaffeNode._operatorTable[layer.type];
-            if (!this._type) {
-                this._type = layer.type.toString();
-            }
-        }
-        else if (version == 2) {
-            this._type = layer.type;
-        }
 
-        this._inputs = [];
-        layer.bottom.forEach((bottom) => {
-            this._inputs.push({
-                connections: [ {
-                    id: bottom
-                } ]
-            });
-        });
-
-        this._outputs = [];
-        layer.top.forEach((top) => {
-            this._outputs.push({
-                connections: [ {
-                    id: top
-                } ]
-            });
-        });
+        switch (version) {
+            case 0:
+                this._type = layer.layer.type;
+                this._name = layer.layer.name;
+                break;
+            case 1:
+                this._type = CaffeNode.getOperator(layer.type);
+                this._name = layer.name;
+                break;
+            case 2:
+                this._type = layer.type;
+                this._name = layer.name;
+                break;
+        }
 
+        this._inputs = layer.bottom;
+        this._outputs = layer.top;
+        this._initializers = [];
         this._attributes = [];
-        Object.keys(layer).forEach((key) => {
-            if (key.endsWith('Param')) {
-                var param = layer[key];
-                if (param.constructor.name == this._type + 'Parameter') {
-                    Object.keys(param).forEach((attributeName) => {
-                        var attributeValue = param[attributeName];
-                        this._attributes.push(new CaffeAttribute(attributeName, attributeValue));
-                    });
-                }
-            }
-        });
+
+        switch (version) {
+            case 0:
+                Object.keys(layer.layer).forEach((attributeName) => {
+                    if (attributeName != 'type' && attributeName != 'name' && attributeName != 'blobs' && attributeName != 'blobsLr') {
+                        var attributeValue = layer.layer[attributeName];
+                        this._attributes.push(new CaffeAttribute(this, attributeName, attributeValue));
+                    }
+                });
+                layer.layer.blobs.forEach((blob) => {
+                    this._initializers.push(new CaffeTensor(blob));
+                });
+                break;
+            case 1:
+            case 2:
+                Object.keys(layer).forEach((key) => {
+                    if (key.endsWith('Param')) {
+                        var param = layer[key];
+                        if (param.constructor.name == this._type + 'Parameter') {
+                            Object.keys(param).forEach((attributeName) => {
+                                var attributeValue = param[attributeName];
+                                this._attributes.push(new CaffeAttribute(this, attributeName, attributeValue));
+                            });
+                        }
+                    }
+                });
+                layer.blobs.forEach((blob) => {
+                    this._initializers.push(new CaffeTensor(blob));
+                });
+                break;
+        }
     }
 
     get operator() {
@@ -236,21 +207,82 @@ class CaffeNode {
     }
 
     get inputs() {
-        return this._inputs;
+        var list = this._inputs.concat(this._initializers);
+        var inputs = CaffeOperatorMetadata.operatorMetadata.getInputs(this._type, list);
+        inputs.forEach((input) => {
+            input.connections.forEach((connection) => {
+                if (connection.id instanceof CaffeTensor) {
+                    connection.initializer = connection.id;
+                    connection.id = '';
+                }
+            });
+        });
+
+        return inputs;
     }
 
     get outputs() {
-        return this._outputs;
+        var outputs = CaffeOperatorMetadata.operatorMetadata.getOutputs(this._type, this._outputs);
+        return outputs;
     }
 
     get attributes() {
         return this._attributes;
     }
+
+    static getOperator(index) {
+        if (!CaffeNode._operatorMap) {
+            CaffeNode._operatorMap = {};
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.NONE] = 'None';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.ACCURACY] = 'Accuracy';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.BNLL] = 'BNLL';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.CONCAT] = 'Concat'; 
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.CONVOLUTION] = 'Convolution';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.DATA] = 'Data';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.DROPOUT] = 'Dropout';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.EUCLIDEAN_LOSS] = 'EuclideanLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.FLATTEN] = 'Flatten';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.HDF5_DATA] = 'HDF5Data';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.HDF5_OUTPUT] = 'HDF5Output';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.IM2COL] = 'Im2col';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.IMAGE_DATA] = 'ImageData';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.INFOGAIN_LOSS] = 'InfogainLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.INNER_PRODUCT] = 'InnerProduct';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.LRN] = 'LRN';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.MULTINOMIAL_LOGISTIC_LOSS] = 'MultinomialLogisticLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.POOLING] = 'Pooling';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.RELU] = 'ReLU';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SIGMOID] = 'Sigmoid';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SOFTMAX] = 'Softmax';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SOFTMAX_LOSS] = 'SoftmaxLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SPLIT] = 'Split';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.TANH] = 'TanH';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.WINDOW_DATA] = 'WindowData';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.ELTWISE] = 'Eltwise';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.POWER] = 'Power';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SIGMOID_CROSS_ENTROPY_LOSS] = 'SigmoidCrossEntropyLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.HINGE_LOSS] = 'HingeLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.MEMORY_DATA] = 'HingeLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.ARGMAX] = 'ArgMax';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.THRESHOLD] = 'Threshold';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.DUMMY_DATA] = 'DummyData';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SLICE] = 'Slice';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.MVN] = 'MVN';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.ABSVAL] = 'AbsVal';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.SILENCE] = 'Silence';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.CONTRASTIVE_LOSS] = 'ContrastiveLoss';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.EXP] = 'Exp';
+            CaffeNode._operatorMap[caffe.V1LayerParameter.LayerType.DECONVOLUTION] = 'Deconvolution';
+        }
+        var type = CaffeNode._operatorMap[index];
+        return type ? type : index.toString();
+    }
 }
 
 class CaffeAttribute {
 
-    constructor(name, value) {
+    constructor(owner, name, value) {
+        this._owner = owner;
         this._name = name;
         this._value = value;
     }
@@ -262,6 +294,17 @@ class CaffeAttribute {
     get value() { 
         return JSON.stringify(this._value);
     }
+
+    get hidden() {
+        return CaffeOperatorMetadata.operatorMetadata.getAttributeHidden(this._owner.operator, this._name, this._value);
+    }
+}
+
+class CaffeTensor {
+
+    constructor(blob) {
+        this._blob = blob;
+    }
 }
 
 class CaffeOperatorMetadata 
@@ -303,4 +346,151 @@ class CaffeOperatorMetadata
         }
         return null;
     }
+
+    getInputs(type, inputs) {
+        var results = [];
+        var index = 0;
+        var schema = this._map[type];
+        if (schema && schema.inputs) {
+            schema.inputs.forEach((inputDef) => {
+                if (index < inputs.length || inputDef.option != 'optional') {
+                    var input = {};
+                    input.name = inputDef.name;
+                    input.type = inputDef.type;
+                    var count = (inputDef.option == 'variadic') ? (inputs.length - index) : 1;
+                    input.connections = [];
+                    inputs.slice(index, index + count).forEach((id) => {
+                        if (id != '' || inputDef.option != 'optional') {
+                            input.connections.push({ id: id});
+                        }
+                    });
+                    index += count;
+                    results.push(input);
+                }
+            });
+        }
+        else {
+            inputs.slice(index).forEach((input) => {
+                var name = (index == 0) ? 'input' : ('(' + index.toString() + ')');
+                results.push({
+                    name: name,
+                    connections: [ { id: input } ]
+                });
+                index++;
+            });
+
+        }
+        return results;
+    }
+
+    getOutputs(type, outputs) {
+        var results = [];
+        var index = 0;
+        var schema = this._map[type];
+        if (schema && schema.outputs) {
+            schema.outputs.forEach((outputDef) => {
+                if (index < outputs.length || outputDef.option != 'optional') {
+                    var output = {};
+                    output.name = outputDef.name;
+                    var count = (outputDef.option == 'variadic') ? (outputs.length - index) : 1;
+                    output.connections = outputs.slice(index, index + count).map((id) => {
+                        return { id: id };
+                    });
+                    index += count;
+                    results.push(output);
+                }
+            });
+        }
+        else {
+            outputs.slice(index).forEach((output) => {
+                var name = (index == 0) ? 'output' : ('(' + index.toString() + ')');
+                results.push({
+                    name: name,
+                    connections: [ { id: output } ]
+                });
+                index++;
+            });
+
+        }
+        return results;
+    }
+
+    getAttributeHidden(operator, name, value) {
+        var schema = this._map[operator];
+        if (schema && schema.attributes && schema.attributes.length > 0) {
+            if (!schema.attributesMap) {
+                schema.attributesMap = {};
+                schema.attributes.forEach((attribute) => {
+                    schema.attributesMap[attribute.name] = attribute;
+                });
+            }
+            var attribute = schema.attributesMap[name];
+            if (attribute) {
+                if (attribute.hasOwnProperty('hidden')) {
+                    return attribute.hidden;
+                }
+                if (attribute.hasOwnProperty('default')) {
+                    return CaffeOperatorMetadata.isEquivalent(attribute.default, value);
+                }
+            }
+        }
+        return false;
+    }
+
+    static isEquivalent(a, b) {
+        if (a === b) {
+            return a !== 0 || 1 / a === 1 / b;
+        }
+        if (a == null || b == null) {
+            return false;
+        }
+        if (a !== a) {
+            return b !== b;
+        }
+        var type = typeof a;
+        if (type !== 'function' && type !== 'object' && typeof b != 'object') {
+            return false;
+        }
+        var className = toString.call(a);
+        if (className !== toString.call(b)) {
+            return false;
+        }
+        switch (className) {
+            case '[object RegExp]':
+            case '[object String]':
+                return '' + a === '' + b;
+            case '[object Number]':
+                if (+a !== +a) {
+                    return +b !== +b;
+                }
+                return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+            case '[object Date]':
+            case '[object Boolean]':
+                return +a === +b;
+            case '[object Array]':
+                var length = a.length;
+                if (length !== b.length) {
+                    return false;
+                }
+                while (length--) {
+                    if (!KerasOperatorMetadata.isEquivalent(a[length], b[length])) {
+                        return false;
+                    }
+                }
+                return true;
+        }
+
+        var keys = Object.keys(a);
+        var size = keys.length;
+        if (Object.keys(b).length != size) {
+            return false;
+        } 
+        while (size--) {
+            var key = keys[size];
+            if (!(b.hasOwnProperty(key) && KerasOperatorMetadata.isEquivalent(a[key], b[key]))) {
+                return false;
+            }
+        }
+        return true;
+    }
 }

+ 87 - 5
src/caffe-operator.json

@@ -3,20 +3,58 @@
     "name": "Convolution",
     "schema": {
       "category": "Layer",
+      "inputs": [
+        { "name": "input" },
+        { "name": "filter" },
+        { "name": "bias" }
+      ],
+      "ouptuts": [
+        { "name": "output" }
+      ],
       "attributes": [
+        { "name": "biasTerm", "hidden": true },
+        { "name": "weightFiller", "hidden": true },
+        { "name": "biasFiller", "hidden": true },
+        { "name": "pad", "default": [] },
+        { "name": "kernelSize", "default": [] },
+        { "name": "stride", "default": [] },
+        { "name": "dilation", "default": [] }
       ]
     }
   },
   {
     "name": "InnerProduct",
     "schema": {
-      "category": "Layer"
+      "category": "Layer",
+      "inputs": [
+        { "name": "input" },
+        { "name": "weights" },
+        { "name": "bias" }
+      ],
+      "outputs": [
+        { "name": "output" }
+      ],
+      "attributes": [
+        { "name": "biasTerm", "hidden": true },
+        { "name": "weightFiller", "hidden": true },
+        { "name": "biasFiller", "hidden": true }
+      ]
     }
   },
   {
     "name": "Scale",
     "schema": {
-      "category": "Layer"
+      "category": "Layer",
+      "inputs": [
+        { "name": "input" },
+        { "name": "scale" },
+        { "name": "bias" }
+      ],
+      "attributes": [
+        { "name": "filler", "hidden": false },
+        { "name": "biasTerm", "hidden": false },
+        { "name": "biasFiller", "hidden": false }
+      ]
     }
   },
   {
@@ -34,7 +72,17 @@
   {
     "name": "BatchNorm",
     "schema": {
-      "category": "Normalization"
+      "category": "Normalization",
+      "inputs": [
+        { "name": "input" },
+        { "name": "gamma" },
+        { "name": "beta" },
+        { "name": "mean" },
+        { "name": "variance" }
+      ],
+      "outputs": [
+        { "name": "output" }
+      ]
     }
   },
   {
@@ -43,6 +91,16 @@
       "category": "Activation"
     }
   },
+  {
+    "name": "SoftmaxLoss",
+    "schema": {
+      "category": "Activation",
+      "inputs": [
+        { "name": "input" },
+        { "name": "labels" }
+      ]
+    }
+  },
   {
     "name": "ReLU",
     "schema": {
@@ -52,13 +110,19 @@
   {
     "name": "Concat",
     "schema": {
-      "category": "Tensor"
+      "category": "Tensor",
+      "inputs": [
+        { "name": "inputs", "option": "variadic" }
+      ]
     }
   },
   {
     "name": "Split",
     "schema": {
-      "category": "Tensor"
+      "category": "Tensor",
+      "outputs": [
+        { "name": "outputs", "option": "variadic" }
+      ]  
     }
   },
   {
@@ -66,5 +130,23 @@
     "schema": {
       "category": "Pool"
     }
+  },
+  {
+    "name": "Data",
+    "schema": {
+      "category": "Data",
+      "outputs": [
+        { "name": "outputs", "option": "variadic" }
+      ]
+    }
+  },
+  {
+    "name": "ImageData",
+    "schema": {
+      "category": "Data",
+      "outputs": [
+        { "name": "outputs", "option": "variadic" }
+      ]
+    }
   }
 ]

+ 1 - 4
src/coreml-model.js

@@ -481,13 +481,10 @@ class CoreMLNode {
 
 class CoreMLAttribute {
 
-    constructor(owner, name, value, hidden) {
+    constructor(owner, name, value) {
         this._owner = owner;
         this._name = name;
         this._value = value;
-        if (hidden) {
-            this._hidden = hidden;
-        }
     }
 
     get name() {