Jelajahi Sumber

Deeplearning4j support (#303)

Lutz Roeder 6 tahun lalu
induk
melakukan
f247416099
8 mengubah file dengan 837 tambahan dan 3 penghapusan
  1. 1 0
      Makefile
  2. 3 1
      README.md
  3. 2 1
      setup.py
  4. 112 0
      src/dl4j-metadata.json
  5. 603 0
      src/dl4j.js
  6. 2 1
      src/view.js
  7. 70 0
      test/models.json
  8. 44 0
      tools/dl4j

+ 1 - 0
Makefile

@@ -25,6 +25,7 @@ update:
 	@./tools/coreml sync install schema
 	@./tools/cntk sync schema
 	@./tools/darknet sync
+	@./tools/dl4j sync
 	@./tools/keras sync install metadata
 	@./tools/mxnet sync metadata
 	@./tools/ncnn sync

+ 3 - 1
README.md

@@ -3,7 +3,9 @@
 
 Netron is a viewer for neural network, deep learning and machine learning models. 
 
-Netron supports **ONNX** (`.onnx`, `.pb`, `.pbtxt`), **Keras** (`.h5`, `.keras`), **Core ML** (`.mlmodel`), **Caffe2** (`predict_net.pb`, `predict_net.pbtxt`), **MXNet** (`.model`, `-symbol.json`) and **TensorFlow Lite** (`.tflite`). Netron has experimental support for **Caffe** (`.caffemodel`, `.prototxt`), **PyTorch** (`.pt`, `.pth`), **TorchScript** (`.pt`, `.pth`), **Torch** (`.t7`), **CNTK** (`.model`, `.cntk`), **PaddlePaddle** (`__model__`), **Darknet** (`.cfg`), **NCNN** (`.param`), **scikit-learn** (`.pkl`), **TensorFlow.js** (`model.json`, `.pb`) and **TensorFlow** (`.pb`, `.meta`, `.pbtxt`).
+Netron supports **ONNX** (`.onnx`, `.pb`, `.pbtxt`), **Keras** (`.h5`, `.keras`), **Core ML** (`.mlmodel`), **Caffe** (`.caffemodel`, `.prototxt`), **Caffe2** (`predict_net.pb`, `predict_net.pbtxt`), **MXNet** (`.model`, `-symbol.json`), **TorchScript** (`.pt`, `.pth`), **NCNN** (`.param`) and **TensorFlow Lite** (`.tflite`).
+
+Netron has experimental support for **PyTorch** (`.pt`, `.pth`), **Torch** (`.t7`), **CNTK** (`.model`, `.cntk`), **Deeplearning4j** (`.zip`, `configuration.json`), **PaddlePaddle** (`.zip`, `__model__`), **Darknet** (`.cfg`), **scikit-learn** (`.pkl`), **TensorFlow.js** (`model.json`, `.pb`) and **TensorFlow** (`.pb`, `.meta`, `.pbtxt`).
 
 <p align='center'><a href='https://www.lutzroeder.com/ai'><img src='media/screenshot.png' width='800'></a></p>
 

+ 2 - 1
setup.py

@@ -66,7 +66,7 @@ setuptools.setup(
     version=package_version(),
     description="Viewer for neural network, deep learning and machine learning models",
     long_description='Netron is a viewer for neural network, deep learning and machine learning models.\n\n' +
-                     'Netron supports **ONNX** (`.onnx`, `.pb`), **Keras** (`.h5`, `.keras`), **Core ML** (`.mlmodel`), **Caffe2** (`predict_net.pb`), **MXNet** (`.model`, `-symbol.json`), and **TensorFlow Lite** (`.tflite`). Netron has experimental support for **Caffe** (`.caffemodel`, `.prototxt`), **PyTorch** (`.pt`, `.pth`), **TorchScript** (`.pt`, `.pth`), **Torch** (`.t7`), **CNTK** (`.model`, `.cntk`), **PaddlePaddle** (`__model__`), **Darknet** (`.cfg`), **NCNN** (`.param`), **scikit-learn** (`.pkl`), **TensorFlow.js** (`model.json`, `.pb`) and **TensorFlow** (`.pb`, `.meta`, `.pbtxt`).',
+                     'Netron supports **ONNX** (`.onnx`, `.pb`), **Keras** (`.h5`, `.keras`), **Core ML** (`.mlmodel`), **Caffe** (`.caffemodel`, `.prototxt`), **Caffe2** (`predict_net.pb`), **MXNet** (`.model`, `-symbol.json`), **TorchScript** (`.pt`, `.pth`), NCNN (`.param`) and **TensorFlow Lite** (`.tflite`). Netron has experimental support for **PyTorch** (`.pt`, `.pth`), **Torch** (`.t7`), **CNTK** (`.model`, `.cntk`), Deeplearning4j (`.zip`, `configuration.json`), **PaddlePaddle** (`__model__`), **Darknet** (`.cfg`), **scikit-learn** (`.pkl`), **TensorFlow.js** (`model.json`, `.pb`) and **TensorFlow** (`.pb`, `.meta`, `.pbtxt`).',
     keywords=[
         'onnx', 'keras', 'tensorflow', 'coreml', 'mxnet', 'caffe', 'caffe2',
         'artificial intelligence', 'machine learning', 'deep learning', 'neural network',
@@ -93,6 +93,7 @@ setuptools.setup(
             'cntk.js', 'cntk-metadata.json', 'cntk-proto.js',
             'coreml.js', 'coreml-metadata.json', 'coreml-proto.js',
             'darknet.js', 'darknet-metadata.json',
+            'dl4j.js', 'dl4j-metadata.json',
             'keras.js', 'keras-metadata.json', 'hdf5.js',
             'mxnet.js', 'mxnet-metadata.json',
             'ncnn.js', 'ncnn-metadata.json',

+ 112 - 0
src/dl4j-metadata.json

@@ -0,0 +1,112 @@
+[
+  {
+    "name": "Dense",
+    "schema": {
+      "category": "Layer",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Convolution",
+    "schema": {
+      "category": "Layer",
+      "attributes": [
+        { "name": "dilation" },
+        { "name": "kernelSize" },
+        { "name": "padding" }
+      ]
+    }
+  },
+  {
+    "name": "SeparableConvolution2D",
+    "schema": {
+      "category": "Layer",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "BatchNormalization",
+    "schema": {
+      "category": "Normalization",
+      "attributes": [
+        { "name": "eps" },
+        { "name": "gamma" },
+        { "name": "decay" }
+      ]
+    }
+  },
+  {
+    "name": "Sigmoid",
+    "schema": {
+      "category": "Activation",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "LReLU",
+    "schema": {
+      "category": "Activation",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "ReLU",
+    "schema": {
+      "category": "Activation",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Softmax",
+    "schema": {
+      "category": "Activation",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Merge",
+    "schema": {
+      "category": "Tensor",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Upsampling2D",
+    "schema": {
+      "category": "Layer",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Dropout",
+    "schema": {
+      "category": "Dropout",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "GlobalPooling",
+    "schema": {
+      "category": "Pool",
+      "attributes": [
+      ]
+    }
+  },
+  {
+    "name": "Subsampling",
+    "schema": {
+      "category": "Layer",
+      "attributes": [
+      ]
+    }
+  }
+]

+ 603 - 0
src/dl4j.js

@@ -0,0 +1,603 @@
+/* jshint esversion: 6 */
+/* eslint "indent": [ "error", 4, { "SwitchCase": 1 } ] */
+
+// Experimental
+
+var dl4j = dl4j || {};
+var long = long || { Long: require('long') };
+
+dl4j.ModelFactory = class {
+
+    match(context) {
+        var identifier = context.identifier.toLowerCase();
+        if (identifier == 'configuration.json') {
+            var text = context.text;
+            if (text && (text.indexOf('"vertices"') !== -1 || text.indexOf('"confs"') !== -1)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    open(context, host) {
+        var identifier = context.identifier;
+        try {
+            var configuration = JSON.parse(context.text);
+            return dl4j.Metadata.open(host).then((metadata) => {
+                return context.request('coefficients.bin', null).then((coefficients) => {
+                    return this._openModel(context, host, metadata, configuration, coefficients);
+                }).catch(() => {
+                    return this._openModel(context, host, metadata, configuration, null);
+                });
+            });
+        }
+        catch (error) {
+            host.exception(error, false);
+            var message = error && error.message ? error.message : error.toString();
+            message = message.endsWith('.') ? message.substring(0, message.length - 1) : message;
+            return Promise.reject(new dl4j.Error(message + " in '" + identifier + "'."));
+        }
+    }
+
+    _openModel(context, host, metadata, configuration, coefficients) {
+        var identifier = context.identifier;
+        try {
+            return new dl4j.Model(metadata, configuration, coefficients);
+        }
+        catch (error) {
+            host.exception(error, false);
+            var message = error && error.message ? error.message : error.toString();
+            message = message.endsWith('.') ? message.substring(0, message.length - 1) : message;
+            throw new dl4j.Error(message + " in '" + identifier + "'.");
+        }
+    }
+}
+
+dl4j.Model = class {
+
+    constructor(metadata, configuration, coefficients) {
+        this._graphs = [];
+        this._graphs.push(new dl4j.Graph(metadata, configuration, coefficients))
+    }
+
+    get format() {
+        return 'Deeplearning4j';
+    }
+
+    get graphs() {
+        return this._graphs;
+    }
+}
+
+dl4j.Graph = class {
+
+    constructor(metadata, configuration, coefficients) {
+
+        this._inputs = [];
+        this._outputs =[];
+        this._nodes = [];
+
+        var reader = new dl4j.NDArrayReader(coefficients);
+        var dataType = reader.dataType;
+
+        if (configuration.networkInputs) {
+            for (var input of configuration.networkInputs) {
+                this._inputs.push(new dl4j.Parameter(input, true, [
+                    new dl4j.Argument(input, null, null)
+                ]));
+            }
+        }
+
+        if (configuration.networkOutputs) {
+            for (var output of configuration.networkOutputs) {
+                this._outputs.push(new dl4j.Parameter(output, true, [
+                    new dl4j.Argument(output, null, null)
+                ]));
+            }
+        }
+
+        var inputs = null;
+
+        // Computation Graph
+        if (configuration.vertices) {
+            for (var name in configuration.vertices) {
+
+                var vertex = dl4j.Node._object(configuration.vertices[name]);
+                inputs = configuration.vertexInputs[name];
+                var variables = [];
+                var layer = null;
+
+                switch (vertex.__type__) {
+                    case 'LayerVertex':
+                        layer = dl4j.Node._object(vertex.layerConf.layer);
+                        variables = vertex.layerConf.variables;
+                        break;
+                    case 'MergeVertex':
+                        layer = { __type__: 'Merge', layerName: name };
+                        break;
+                    case 'ElementWiseVertex':
+                        layer = { __type__: 'ElementWise', layerName: name, op: vertex.op };
+                        break;
+                    case 'PreprocessorVertex':
+                        layer = { __type__: 'Preprocessor', layerName: name };
+                        break;
+                    default:
+                        throw new dl4j.Error("Unsupported vertex class '" + vertex['@class'] + "'.");
+                }
+        
+                this._nodes.push(new dl4j.Node(metadata, layer, inputs, dataType, variables));
+            }
+        }
+
+        // Multi Layer Network
+        if (configuration.confs) {
+            inputs = [ 'input' ];
+            this._inputs.push(new dl4j.Parameter('input', true, [
+                new dl4j.Argument('input', null, null)
+            ]));
+            for (var conf of configuration.confs) {
+                layer = dl4j.Node._object(conf.layer);
+                this._nodes.push(new dl4j.Node(metadata, layer, inputs, dataType, conf.variables));
+                inputs = [ layer.layerName ];
+            }
+            this._outputs.push(new dl4j.Parameter('output', true, [
+                new dl4j.Argument(inputs[0], null, null)
+            ]));
+        }
+    }
+
+    get inputs() {
+        return this._inputs;
+    }
+
+    get outputs() {
+        return this._outputs;
+    }
+
+    get nodes() {
+        return this._nodes;
+    }
+}
+
+dl4j.Parameter = class {
+
+    constructor(name, visible, args) {
+        this._name = name;
+        this._visible = visible;
+        this._arguments = args;
+    }
+
+    get name() {
+        return this._name;
+    }
+
+    get visible() {
+        return this._visible;
+    }
+
+    get arguments() {
+        return this._arguments;
+    }
+};
+
+dl4j.Argument = class {
+
+    constructor(id, type, initializer) {
+        this._id = id;
+        this._type = type;
+        this._initializer = initializer;
+    }
+
+    get id() {
+        return this._id;
+    }
+
+    get type() {
+        if (this._initializer) {
+            return this._initializer.type;
+        }
+        return this._type;
+    }
+
+    get initializer() {
+        return this._initializer;
+    }
+};
+
+dl4j.Node = class {
+
+    constructor(metadata, layer, inputs, dataType, variables) {
+
+        this._metadata = metadata;
+        this._operator = layer.__type__;
+        this._name = layer.layerName || '';
+        this._inputs = [];
+        this._outputs = [];
+        this._attributes = [];
+
+        if (inputs && inputs.length > 0) {
+            var args = inputs.map((input) => new dl4j.Argument(input, null, null));
+            this._inputs.push(new dl4j.Parameter(args.length < 2 ? 'input' : 'inputs', true, args));
+        }
+
+        if (variables) {
+            for (var variable of variables) {
+                var tensor = null;
+                switch (this._operator) {
+                    case 'Convolution':
+                        switch (variable) {
+                            case 'W':
+                                tensor = new dl4j.Tensor(dataType, layer.kernelSize.concat([ layer.nin, layer.nout ]));
+                                break;
+                            case 'b':
+                                tensor = new dl4j.Tensor(dataType, [ layer.nout ]);
+                                break;
+                            default:
+                                throw new dl4j.Error("Unknown '" + this._operator + "' variable '" + variable + "'.");
+                        }
+                        break;
+                    case 'SeparableConvolution2D':
+                        switch (variable) {
+                            case 'W':
+                                tensor = new dl4j.Tensor(dataType, layer.kernelSize.concat([ layer.nin, layer.nout ]));
+                                break;
+                            case 'pW':
+                                tensor = new dl4j.Tensor(dataType, [ layer.nout ]);
+                                break;
+                            default:
+                                throw new dl4j.Error("Unknown '" + this._operator + "' variable '" + variable + "'.");
+                        }
+                        break;    
+                    case 'Dense':
+                        switch (variable) {
+                            case 'W':
+                                tensor = new dl4j.Tensor(dataType, [ layer.nout, layer.nin ]);
+                                break;
+                            case 'b':
+                                tensor = new dl4j.Tensor(dataType, [ layer.nout ]);
+                                break;
+                            default:
+                                throw new dl4j.Error("Unknown '" + this._operator + "' variable '" + variable + "'.");
+                        }
+                        break;
+                    case 'BatchNormalization':
+                        tensor = new dl4j.Tensor(dataType, [ layer.nin ]);
+                        break;
+                    default:
+                        throw new dl4j.Error("Unknown '" + this._operator + "' variable '" + variable + "'.");
+                }
+                this._inputs.push(new dl4j.Parameter(variable, true, [
+                    new dl4j.Argument(variable, null, tensor)
+                ]));
+            }
+        }
+
+        if (this._name) {
+            this._outputs.push(new dl4j.Parameter('output', true, [
+                new dl4j.Argument(this._name, null, null)
+            ]));
+        }
+
+        var attributes = layer;
+
+        if (layer.activationFn) {
+            var activation = dl4j.Node._object(layer.activationFn);
+            if (activation.__type__ !== 'ActivationIdentity' && activation.__type__ !== 'Identity') {
+                if (activation.__type__.startsWith('Activation')) {
+                    activation.__type__ = activation.__type__.substring('Activation'.length);
+                }
+                if (this._operator == 'Activation') {
+                    this._operator = activation.__type__;
+                    attributes = activation;
+                }
+                else {
+                    this._chain = this._chain || []; 
+                    this._chain.push(new dl4j.Node(metadata, activation, [], null, null));
+                }
+            }
+        }
+
+        for (var key in attributes) {
+            switch (key) {
+                case '__type__':
+                case 'constraints':
+                case 'layerName':
+                case 'activationFn':
+                case 'idropout':
+                case 'hasBias':
+                    continue;
+            }
+            this._attributes.push(new dl4j.Attribute(metadata, this._operator, key, attributes[key]));
+        }
+
+        if (layer.idropout) {
+            var dropout = dl4j.Node._object(layer.idropout);
+            if (dropout.p !== 1.0) {
+                throw new dl4j.Error("Layer 'idropout' not implemented.");
+            }
+        }
+    }
+
+    get operator() {
+        return this._operator;
+    }
+
+    get name() {
+        return this._name;
+    }
+
+    get category() {
+        var schema = this._metadata.getSchema(this._operator);
+        return (schema && schema.category) ? schema.category : '';
+    }
+
+    get documentation() {
+        return '';
+    }
+
+    get inputs() {
+        return this._inputs;
+    }
+
+    get outputs() {
+        return this._outputs;
+    }
+
+    get attributes() {
+        return this._attributes;
+    }
+
+    get chain() {
+        return this._chain;
+    }
+
+    static _object(value) {
+        var result = {};
+        if (value['@class']) {
+            result = value;
+            var type = value['@class'].split('.').pop();
+            if (type.endsWith('Layer')) {
+                type = type.substring(0, type.length - 5);
+            }
+            delete value['@class'];
+            result.__type__ = type;
+        }
+        else {
+            var key = Object.keys(value)[0];
+            result = value[key];
+            if (key.length > 0) {
+                key = key[0].toUpperCase() + key.substring(1);
+            }
+            result.__type__ = key;
+        }
+        return result;
+    }
+}
+
+dl4j.Attribute = class {
+
+    constructor(metadata, operator, name, value) {
+        this._name = name;
+        this._value = value;
+        this._visible = false;
+        var schema = metadata.getAttributeSchema(operator, name);
+        if (schema) {
+            if (schema.visible) {
+                this._visible = true;
+            } 
+        }
+    }
+
+    get name() {
+        return this._name;
+    }
+
+    get type() {
+        return this._type;
+    }
+
+    get value() {
+        return this._value;
+    }
+
+    get visible() {
+        return this._visible;
+    }
+}
+
+dl4j.Tensor = class {
+
+    constructor(dataType, shape) {
+        this._type = new dl4j.TensorType(dataType, new dl4j.TensorShape(shape));
+    }
+
+    get type() {
+        return this._type;
+    }
+
+    get state() {
+        return 'Not implemented.'
+    }
+}
+
+dl4j.TensorType = class {
+
+    constructor(dataType, shape) {
+        this._dataType = dataType;
+        this._shape = shape;
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    get shape() {
+        return this._shape;
+    }
+
+    toString() {
+        return (this.dataType || '?') + this._shape.toString();
+    }
+};
+
+dl4j.TensorShape = class {
+
+    constructor(dimensions) {
+        this._dimensions = dimensions;
+    }
+
+    get dimensions() {
+        return this._dimensions;
+    }
+
+    toString() {
+        if (this._dimensions) {
+            if (this._dimensions.length == 0) {
+                return '';
+            }
+            return '[' + this._dimensions.map((dimension) => dimension.toString()).join(',') + ']';
+        }
+        return '';
+    }
+};
+
+dl4j.Metadata = class {
+
+    static open(host) {
+        dl4j.Metadata.textDecoder = dl4j.Metadata.textDecoder || new TextDecoder('utf-8');
+        if (dl4j.Metadata._metadata) {
+            return Promise.resolve(dl4j.Metadata._metadata);
+        }
+        return host.request(null, 'dl4j-metadata.json', 'utf-8').then((data) => {
+            dl4j.Metadata._metadata = new dl4j.Metadata(data);
+            return dl4j.Metadata._metadata;
+        }).catch(() => {
+            dl4j.Metadata._metadata = new dl4j.Metadata(null);
+            return dl4j.Metadata._metadata;
+        });
+    }
+
+    constructor(data) {
+        this._map = {};
+        this._attributeCache = {};
+        if (data) {
+            if (data) {
+                var items = JSON.parse(data);
+                if (items) {
+                    for (var item of items) {
+                        if (item.name && item.schema) {
+                            this._map[item.name] = item.schema;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    getSchema(operator) {
+        return this._map[operator];
+    }
+
+    getAttributeSchema(operator, name) {
+        var map = this._attributeCache[operator];
+        if (!map) {
+            map = {};
+            var schema = this.getSchema(operator);
+            if (schema && schema.attributes && schema.attributes.length > 0) {
+                for (var attribute of schema.attributes) {
+                    map[attribute.name] = attribute;
+                }
+            }
+            this._attributeCache[operator] = map;
+        }
+        return map[name] || null;
+    }
+};
+
+dl4j.NDArrayReader = class {
+
+    constructor(buffer) {
+        var reader = new dl4j.BinaryReader(buffer);
+        /* var shape = */ dl4j.NDArrayReader._header(reader);
+        var data = dl4j.NDArrayReader._header(reader);
+        this._dataType = data.type;
+    }
+
+    get dataType() {
+        return this._dataType;
+    }
+
+    static _header(reader) {
+        var header = {};
+        header.alloc = reader.string();
+        header.length = 0;
+        switch (header.alloc) {
+            case 'DIRECT':
+            case 'HEAP':
+            case 'JAVACPP':
+                header.length = reader.int32();
+                break;
+            case 'LONG_SHAPE':
+            case 'MIXED_DATA_TYPES':
+                header.length = reader.int64();
+                break;
+        }
+        header.type = reader.string();
+        switch (header.type) {
+            case 'INT':
+                header.type = 'int32';
+                header.itemsize = 4;
+                break;
+            case 'FLOAT':
+                header.type = 'float32';
+                header.itemsize = 4;
+                break;
+        }
+        header.data = reader.bytes(header.itemsize * header.length);
+        return header;
+    }
+}
+
+dl4j.BinaryReader = class {
+
+    constructor(buffer) {
+        this._buffer = buffer;
+        this._position = 0;
+    }
+
+    bytes(size) {
+        var data = this._buffer.subarray(this._position, this._position + size);
+        this._position += size;
+        return data;
+    }
+
+    string() {
+        var size = this._buffer[this._position++] << 8 | this._buffer[this._position++];
+        var buffer = this.bytes(size);
+        return new TextDecoder('ascii').decode(buffer);
+    }
+
+    int32() {
+        return this._buffer[this._position++] << 24 | 
+            this._buffer[this._position++] << 16 |
+            this._buffer[this._position++] << 8 |
+            this._buffer[this._position++];
+    }
+
+    int64() {
+        var hi = this.int32();
+        var lo = this.int32();
+        return new long.Long(hi, lo, true).toNumber();
+    }
+}
+
+dl4j.Error = class extends Error {
+    constructor(message) {
+        super(message);
+        this.name = 'Error loading Deeplearning4j model.';
+    }
+};
+
+if (typeof module !== 'undefined' && typeof module.exports === 'object') {
+    module.exports.ModelFactory = dl4j.ModelFactory;
+}

+ 2 - 1
src/view.js

@@ -1077,7 +1077,7 @@ class ArchiveContext {
             return Promise.reject(new Error('File not found.'));
         }
         var data = entry.data;
-        if (data != null) {
+        if (encoding != null) {
             data = new TextDecoder(encoding).decode(data);
         }
         return Promise.resolve(data);
@@ -1121,6 +1121,7 @@ view.ModelFactoryService = class {
         this.register('./darknet', [ '.cfg' ]);
         this.register('./paddle', [ '.paddle', '__model__' ]);
         this.register('./ncnn', [ '.param', '.bin', '.cfg.ncnn', '.weights.ncnn' ]);
+        this.register('./dl4j', [ 'configuration.json' ]);
     }
 
     register(id, extensions) {

+ 70 - 0
test/models.json

@@ -1878,6 +1878,76 @@
     "format": "Darknet",
     "link":   "https://pjreddie.com/darknet/yolo"
   },
+  {
+    "type":   "dl4j",
+    "target": "darknet19_dl4j_inference.v2.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/darknet19_dl4j_inference.v2.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "lenet_dl4j_mnist_inference.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/lenet_dl4j_mnist_inference.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "nasnetmobile_dl4j_inference.v1.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/nasnetmobile_dl4j_inference.v1.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "resnet50_dl4j_inference.v3.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/resnet50_dl4j_inference.v3.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "squeezenet_dl4j_inference.v2.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/squeezenet_dl4j_inference.v2.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "tiny-yolo-voc_dl4j_inference.v2.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/tiny-yolo-voc_dl4j_inference.v2.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "unet_dl4j_segment_inference.v1.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/unet_dl4j_segment_inference.v1.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "vgg19_dl4j_inference.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/vgg19_dl4j_inference.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "xception_dl4j_inference.v2.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/xception_dl4j_inference.v2.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
+  {
+    "type":   "dl4j",
+    "target": "yolo2_dl4j_inference.v3.zip",
+    "source": "https://deeplearning4jblob.blob.core.windows.net/models/yolo2_dl4j_inference.v3.zip",
+    "format": "Deeplearning4j",
+    "link":   "https://github.com/eclipse/deeplearning4j"
+  },
   {
     "type":   "keras",
     "target": "babi_rnn.h5",

+ 44 - 0
tools/dl4j

@@ -0,0 +1,44 @@
+#!/bin/bash
+
+set -e
+
+root=$(cd $(dirname ${0})/..; pwd)
+node_modules=${root}/node_modules
+src=${root}/src
+third_party=${root}/third_party
+tools=${root}/tools
+
+identifier=dl4j
+
+bold() {
+    echo "$(tty -s && tput bold)$1$(tty -s && tput sgr0)" 
+}
+
+git_sync() {
+    mkdir -p "${third_party}"
+    if [ -d "${third_party}/${1}" ]; then
+        git -C "${third_party}/${1}" fetch --quiet -p
+        git -C "${third_party}/${1}" reset --quiet --hard origin/master
+    else
+        git -C "${third_party}" clone --quiet --recursive ${2} ${1}
+    fi
+    git -C "${third_party}" submodule update --quiet --init
+}
+
+clean() {
+    bold "deeplearning4j clean"
+    rm -rf ${third_party}/${identifier}
+}
+
+sync() {
+    bold "deeplearning4j sync"
+    git_sync dl4j [email protected]:eclipse/deeplearning4j.git
+}
+
+while [ "$#" != 0 ]; do
+    command="$1" && shift
+    case "${command}" in
+        "clean") clean;;
+        "sync") sync;;
+    esac
+done