|
|
@@ -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;
|
|
|
+ }
|
|
|
}
|