Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plugin import keras #120

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9708c77
Add new Plugin From the webgme cli
umesh-timalsina Oct 21, 2019
5bfd953
Modify .gitignore, add LF/CRLF fix to .gitattributes
umesh-timalsina Oct 21, 2019
01c312a
Add Plugin Implmentation, flatten JSON file from utils/json-model-par…
umesh-timalsina Oct 21, 2019
90a403f
Remove return from main
umesh-timalsina Oct 21, 2019
8979f69
Refactor ImportKeras util files
umesh-timalsina Oct 22, 2019
da00cfb
Finalize Test
umesh-timalsina Oct 22, 2019
c27bcef
Move helper files to utils/ in ImportKeras dir
brollb Oct 22, 2019
6fcd15a
Remove count methods from plugin utils
umesh-timalsina Oct 22, 2019
112abbe
Add Input/Output Connection Test
umesh-timalsina Oct 22, 2019
2c697a7
Finalize connection test
umesh-timalsina Oct 22, 2019
2c93550
Fix code-climate issues for ImportKeras.js, ImportKeras.spec.js
umesh-timalsina Oct 22, 2019
acfc268
Fix code-climate issues for ImportKeras.js, ImportKeras.spec.js
umesh-timalsina Oct 22, 2019
0caeac1
Fix missing semicolon
umesh-timalsina Oct 22, 2019
8684e64
Merge Conflict
umesh-timalsina Oct 22, 2019
1b8143c
Checkout gitignore from master, remove gitattribute file
umesh-timalsina Oct 23, 2019
c56b62c
Convert boolean type of python string (Adress #120#issuecomment-54545…
umesh-timalsina Oct 23, 2019
0b1921c
Fix typo in sourcemembers, remove plugin cli comments
umesh-timalsina Oct 23, 2019
a4d8a71
Merge branch 'master' into plugin-import-keras
umesh-timalsina Nov 5, 2019
d36c3b0
Merge remote-tracking branch 'origin/master' into plugin-import-keras
umesh-timalsina Nov 6, 2019
0f5a34b
WIP- #130: Initial Implementation Complete ImportKeras, try to run Ge…
umesh-timalsina Nov 7, 2019
5c35223
WIP- #131: Add Correct Layer Pointers, refactor string layer addition…
umesh-timalsina Nov 7, 2019
6c257ce
Merge remote-tracking branch 'origin/master' into plugin-import-keras
umesh-timalsina Nov 7, 2019
1cd436a
WIP- #131: Complete Integration Test for JSON Imports(Only works for …
umesh-timalsina Nov 7, 2019
aba4e1e
WIP- #130: Complete Integration Test for JSON Imports(Only works for …
umesh-timalsina Nov 7, 2019
57e71dc
WIP- #130: Typo and CodeClimate Fix
umesh-timalsina Nov 7, 2019
aeb519a
WIP- #130: Add Failing tests for GenerateKeras
umesh-timalsina Nov 13, 2019
6b1a1d5
WIP Add helper method for getting ordering inputs/outputs
brollb Nov 14, 2019
8a66175
WIP Change src/dstPort to be a single port. Added question
brollb Nov 14, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* text eol=lf
umesh-timalsina marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,9 @@ node_modules
tmp/
blob-local-storage/
notes/

#IntelliJ
.idea

#vscode
.vscode
299 changes: 299 additions & 0 deletions src/plugins/ImportKeras/ImportKeras.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
/*globals define*/
/*eslint-env node, browser*/

/**
* Generated by PluginGenerator 2.20.5 from webgme on Tue Sep 10 2019 15:28:36 GMT-0500 (Central Daylight Time).
umesh-timalsina marked this conversation as resolved.
Show resolved Hide resolved
* A plugin that inherits from the PluginBase. To see source code documentation about available
* properties and methods visit %host%/docs/source/PluginBase.html.
*/

define([
'plugin/PluginConfig',
'text!./metadata.json',
'plugin/PluginBase',
'./utils/JSONModelMaps',
'./utils/json-model-parser',
], function (
PluginConfig,
pluginMetadata,
PluginBase,
ModelMaps,
JSONLayerParser,
) {
'use strict';

pluginMetadata = JSON.parse(pluginMetadata);


/**
* Initializes a new instance of ImportKeras.
* @class
* @augments {PluginBase}
* @classdesc This class represents the plugin ImportKeras.
* @constructor
*/
var ImportKeras = function () {
// Call base class' constructor.
PluginBase.call(this);
this.pluginMetadata = pluginMetadata;
};

/**
* Metadata associated with the plugin. Contains id, name, version, description, icon, configStructure etc.
* This is also available at the instance at this.pluginMetadata.
* @type {object}
*/
ImportKeras.metadata = pluginMetadata;

// Prototypical inheritance from PluginBase.
ImportKeras.prototype = Object.create(PluginBase.prototype);
ImportKeras.prototype.constructor = ImportKeras;

/**
* Main function for the plugin to execute. This will perform the execution.
* Notes:
* - Always log with the provided logger.[error,warning,info,debug].
* - Do NOT put any user interaction logic UI, etc. inside this method.
* - callback always has to be called even if error happened.
*
* @param {function(Error|null, plugin.PluginResult)} callback - the result callback
*/
ImportKeras.prototype.main = async function(callback){
let srcJsonHash = this.getCurrentConfig().srcModel;
if (!srcJsonHash) {
callback(new Error('Keras Json Not Provided'), this.result);
return;
}
try {
this.archName = this.getCurrentConfig().archName;
umesh-timalsina marked this conversation as resolved.
Show resolved Hide resolved
let metadata = await this.blobClient.getMetadata(srcJsonHash);
this.archName = this.archName ? this.archName : metadata.name.replace('.json', '');
let modelJson = await this.blobClient.getObjectAsJSON(srcJsonHash);
this.modelInfo = JSONLayerParser.flatten(modelJson).config;
this.addNewArchitecture();
this.addLayers();
await this.addConnections();
await this.save('Completed Import Model');
this.result.setSuccess(true);
callback(null, this.result);
} catch (err) {
this.logger.debug(`Something Went Wrong, Error Message: ${err}`);
callback(err, this.result);
}
};

ImportKeras.prototype.addNewArchitecture = function () {
// Add Architecture
this.importedArch = this.core.createNode({
umesh-timalsina marked this conversation as resolved.
Show resolved Hide resolved
parent: this.activeNode,
base: this.META.Architecture
});
const uniqueName = this.archName;
this.core.setAttribute(this.importedArch, 'name', uniqueName);

this.logger.debug(`Added ${uniqueName} as a new architecture.`);
};

// This should add layers. constraints, initializers, regularizers as well as activations to the layer.
ImportKeras.prototype.addLayers = function () {
let layers = this.modelInfo.layers;
let layerToCreate = null;
this.layerInfo = {};
layers.forEach((layer) => {
layerToCreate = this._getMetaTypeForClass(layer.class_name);
let layerNode = this.core.createNode({
parent: this.importedArch,
base: this.META[layerToCreate]
});
this.logger.debug(`Added ${layerToCreate}\
to ${this.core.getAttribute(this.importedArch, 'name')}`);

// Add all attributes, from the model JSON, as well as from the layers schema
this._addLayerAttributes(layerNode, layer);
this._addConfigurableNodes(layerNode, layer);
});
};

// All the attributes, which do not require a separate node to be created
// 1. First find the validAttributeNames for the layer
// 2. If the name is in layer, set it.
// 3. If the name is in layer.config, set it.
// 4. Finally, check the layers schema for remaining attributes.
ImportKeras.prototype._addLayerAttributes = function (layerNode, attrObj) {
let config = attrObj.config;
let validAttributeNamesForThisLayer = this.core.getValidAttributeNames(layerNode);
let configKeys = Object.keys(config);
let remainingKeys = Object.keys(attrObj)
.filter(value => value !== 'config');

validAttributeNamesForThisLayer.forEach((attribute) => {
if (remainingKeys.indexOf(this._jsonConfigToNodeAttr(attribute)) > -1) {
this.core.setAttribute(layerNode, attribute, this._toPythonIterable(attrObj[this._jsonConfigToNodeAttr(attribute)]));
this.logger.debug(`Set ${attribute} for ${this.core.getGuid(layerNode)}` +
` to ${this.core.getAttribute(layerNode, attribute)}`);
} else if (configKeys.indexOf(this._jsonConfigToNodeAttr(attribute)) > -1) {
this.core.setAttribute(layerNode, attribute, this._toPythonIterable(config[this._jsonConfigToNodeAttr(attribute)]));
this.logger.debug(`Set ${attribute} for ${this.core.getGuid(layerNode)}` +
` to ${this.core.getAttribute(layerNode, attribute)}`);

}
});
let layerName = this.core.getAttribute(layerNode, 'name');
this.layerInfo[layerName] = layerNode;

};

ImportKeras.prototype._addConfigurableNodes = function (layerNode, layerConfig) {
let allPointerNames = this.core.getValidPointerNames(layerNode);
let config = layerConfig.config;
this.logger.debug(`Layer ${this.core.getAttribute(layerNode, 'name')}` +
` has following configurable attributes ${allPointerNames.join(', ')}`);
allPointerNames.filter(pointer => !!config[pointer])
.forEach((pointer) => {
if (typeof config[pointer] == 'string') {
this._addStringPropertiesNode(layerNode, config, pointer);
} else {
this._addPluggableNodes(layerNode, config, pointer);
}
});
};

ImportKeras.prototype._addStringPropertiesNode = function(layerNode, config, pointer) {
let configurableNode = this.core.createNode({
parent: layerNode,
base: this.META[config[pointer]]
});
// This will set the necessary pointers.
// Of things like activations and so on...
this.core.setPointer(layerNode, pointer, configurableNode);
this.logger.debug(`Added ${this.core.getAttribute(configurableNode, 'name')}`
+ ` as ${pointer} to the layer `
+ `${this.core.getAttribute(layerNode, 'name')}`);
};

ImportKeras.prototype._addPluggableNodes = function (layerNode, config, pointer){
let pluggableNode = this.core.createNode({
parent: layerNode,
base: this.META[config[pointer].class_name]
});
this.logger.debug(`Added ${this.core.getAttribute(pluggableNode, 'name')} as` +
` ${pointer} to the layer ${this.core.getAttribute(layerNode, 'name')}`);
let validArgumentsForThisNode = this.core.getValidAttributeNames(pluggableNode);
let configForAddedNode = config[pointer].config;
if (validArgumentsForThisNode && configForAddedNode) {
validArgumentsForThisNode.forEach((arg) => {
if (configForAddedNode[arg])
this.core.setAttribute(pluggableNode, arg,
this._toPythonIterable(configForAddedNode[arg]));
});
}
};

// This method is used to convert javascript arrays to a
// tuple/ list(Python) in string Representation. Needed for
// Code generation.
ImportKeras.prototype._toPythonIterable = function (obj) {
if (obj == null) {
return 'None';
}
if (obj instanceof Array) {
return '[' + obj.map((val) => {
return this._toPythonIterable(val);
}).join(', ') + ']';
} else {
return obj;
}
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, upon testing it a bit further, I found a couple bugs around parsing and setting attributes. Essentially, I imported the redshiftModel.json file and then generated code to ensure that it was valid keras and encountered the following error:

NameError: name 'false' not defined

I think I know the cause of the problem you alluded to, booleans start with capital case in python which I had not considered here. Refactoring this to the following should do the job.

// This method is used to convert javascript arrays/booleans to a
    // list(python)/boolean in string Representation. Needed for
    // Code generation.
    ImportKeras.prototype._toPythonType = function (obj) {
        if (obj == null) {
            return 'None';
        }
        if (obj instanceof Array) {
            return '[' + obj.map((val) => {
                return this._toPythonType(val);
            }).join(', ') + ']';
        } else if (typeof obj === 'boolean') {
            return obj ? 'True' : 'False';
        } else {
            return obj;
        }
    };

Copy link
Contributor Author

@umesh-timalsina umesh-timalsina Oct 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On a minor side note, it might make sense to test GenerateKeras on this plugin as a test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. I think it is also expecting tuples ((1,2,3)) rather than arrays which also caused an error after fixing the booleans. However, this is something that might be better to fix in GenerateKeras (https://github.com/deepforge-dev/deepforge-keras/blob/master/src/plugins/GenerateKeras/GenerateKeras.js#L416)


// This method is used to convert various classes from the
// keras JSON to deepforge meta Nodes
ImportKeras.prototype._getMetaTypeForClass = function (kerasClass) {
let classMap = ModelMaps.CLASS_MAP;
if (Object.keys(classMap).indexOf(kerasClass) > -1) {
return classMap[kerasClass];
} else {
return kerasClass;
}
};

// Change the model converts some JSON
// layer attributes names (from keras) to the correct
// attribute name for deepforge-keras nodes
ImportKeras.prototype._jsonConfigToNodeAttr = function (orgName) {
let argMap = ModelMaps.ARGUMENTS_MAP;
if (Object.keys(argMap).indexOf(orgName) > -1) {
return argMap[orgName];
} else {
return orgName;
}
};

/**********************The functions below Add Connections between the Layers**************/
ImportKeras.prototype.addConnections = async function () {
// this._findNodeByName();
let layers = this.modelInfo.layers;
let layerInputConnections = {};
let layerOutputConnections = {};
let connections = null;
layers.forEach((layer) => {
layerInputConnections[layer.name] = [];
layerOutputConnections[layer.name] = [];
});

layers.forEach((layer) => {
if (layer.inbound_nodes.length > 0) {
connections = layer.inbound_nodes;
connections.forEach((connection) => {

if (this._layerNameExists(connection)) {
layerInputConnections[layer.name].push(connection);
layerOutputConnections[connection].push(layer.name);
}
});

}
});

await this._updateConnections(layerInputConnections);
};


ImportKeras.prototype._layerNameExists = function (layerName) {
let allLayerNames = this.modelInfo.layers.map((layer) => {
return layer.name;
});

return allLayerNames.indexOf(layerName) > -1;
};

ImportKeras.prototype._updateConnections = function (inputs) {
let allLayerNames = Object.keys(inputs);
return Promise.all(allLayerNames.map((layerName) => {
let dstLayer = this.layerInfo[layerName];
let srcs = inputs[layerName];
return Promise.all(srcs.map((src, index) => {
return this._connectLayers(this.layerInfo[src], dstLayer, index);
}));
}));
};

ImportKeras.prototype._connectLayers = async function (srcLayer, dstLayer, index) {

let srcPort = await this.core.loadMembers(srcLayer, 'outputs');
let dstPort = await this.core.loadMembers(dstLayer, 'inputs');
if (dstPort && srcPort) {
this.core.addMember(dstPort[0], 'source', srcPort[0]);
this.core.setMemberRegistry(dstPort[0],
'source',
this.core.getPath(srcPort[0]),
'position', {x: 100, y: 100});
this.core.setMemberAttribute(dstPort[0], 'source',
this.core.getPath(srcPort[0]),
'index', index);
this.logger.debug(`Connected ${this.core.getAttribute(srcLayer, 'name')} ` +
`with ${this.core.getAttribute(dstLayer, 'name')} as input ${index}`);
}
};

return ImportKeras;
});
29 changes: 29 additions & 0 deletions src/plugins/ImportKeras/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"id": "ImportKeras",
"name": "ImportKeras",
"version": "0.1.0",
"description": "",
"icon": {
"class": "glyphicon glyphicon-download-alt",
"src": ""
},
"disableServerSideExecution": false,
"disableBrowserSideExecution": false,
"dependencies": [],
"writeAccessRequired": false,
"configStructure": [{
"name": "srcModel",
"displayName": "Keras Model JSON File",
"description": "The Keras model JSON to import.",
"valueType": "asset",
"readOnly": false
},
{
"name": "archName",
"displayName": "Model Name",
"description": "Name of the imported model",
"valueType": "string",
"readOnly": false
}
]
}
26 changes: 26 additions & 0 deletions src/plugins/ImportKeras/utils/JSONModelMaps.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*globals define */
define([], function() {
const ModelMaps = {};

ModelMaps.CLASS_MAP = {
InputLayer: 'Input'
};

ModelMaps.ARGUMENTS_MAP = {
batch_shape: 'batch_input_shape'
};

ModelMaps.ModelTypes = {
sequential : 'Sequential',
functional : 'Model'
};

ModelMaps.AbstractLayerTypeMapping = {
Activation: 'activation',
ActivityRegularization: 'activity_regularizer'
};


return ModelMaps;

});
Loading