375 lines
19 KiB
JavaScript
375 lines
19 KiB
JavaScript
/*
|
|
* File: TensorFlowModelsManager.js
|
|
* TensorFlow Library Model types currently supported by SuperAlgos:
|
|
* 1.) Sequential Model
|
|
*
|
|
* History:
|
|
* 7/6/21: Start
|
|
* 8/24/21: Pushing as beta
|
|
*
|
|
* Contents:
|
|
* newTensorFlowModelsManager.thisObject,
|
|
* Model Manager Initialization ()=>{},
|
|
* sequentialModelSetup(),
|
|
* compileModel(),
|
|
* trainModel(),
|
|
* trainSequentialModel(),
|
|
* fitArgsConstructor()
|
|
*
|
|
*/
|
|
|
|
|
|
exports.newTensorFlowModelsManager = function (processIndex, learningSystem) {
|
|
const MODULE_NAME = 'Tensorflow Models Manager';
|
|
|
|
/* ModelsManager Variables/Objects/Dependencies Sections: */
|
|
const _tfjsCore = require("@tensorflow/tfjs-core");
|
|
var _tensorFlowAPI;
|
|
var _learningSystem;
|
|
var _tensorFlowModel;
|
|
let isTrained;
|
|
let modelType;
|
|
|
|
|
|
let thisObject = {
|
|
sequentialModelSetup: sequentialModelSetup,
|
|
compileModel: compileModel,
|
|
trainModel: trainModel,
|
|
get api() {return _tensorFlowAPI; },
|
|
get model() { return _tensorFlowModel; },
|
|
set model(value) {
|
|
_tensorFlowModel = value;
|
|
},
|
|
get isTrained() { return isTrained; },
|
|
};
|
|
|
|
// Model Manager Initialization:
|
|
( () => {
|
|
if (
|
|
learningSystem.machineLearningLibrary.typeOfLearning !== undefined &&
|
|
learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel !== undefined &&
|
|
learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model !== undefined &&
|
|
learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.api !== undefined
|
|
) {
|
|
_learningSystem = learningSystem; // Record locally
|
|
isTrained = false;
|
|
|
|
switch (learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.api.type) {
|
|
case 'Layers API': {
|
|
_tensorFlowAPI = require("@tensorflow/tfjs-layers")
|
|
if (learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.api.layersModel === undefined) { return }
|
|
|
|
switch (learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.api.layersModel.type) {
|
|
case 'Sequential Model': {
|
|
modelType = 'Sequential Model';
|
|
sequentialModelSetup();
|
|
break
|
|
}
|
|
case 'Functional Model': {
|
|
modelType = 'FunctionalModel';
|
|
// To be implemented some day...
|
|
break
|
|
}
|
|
}
|
|
break
|
|
}
|
|
case 'Core API': {
|
|
_tensorFlowAPI = require("@tensorflow/tfjs-core")
|
|
// To be implemented some day...
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
compileModel();
|
|
})();
|
|
|
|
return thisObject;
|
|
|
|
|
|
/* Model specific setup section: */
|
|
function sequentialModelSetup() {
|
|
let layersModel = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.api.layersModel;
|
|
|
|
/* Load the Model: */
|
|
_tensorFlowModel = _tensorFlowAPI.sequential();
|
|
|
|
/* Construct the layers and add to the model: */
|
|
for (let i = 0; i < layersModel.layers.length; i++) {
|
|
let layerNode = layersModel.layers[i];
|
|
let layerFunction; // The layer function.
|
|
let layerArgsObject = {}; // layerFunction's arguments.
|
|
let layerObject; // The completed layer.
|
|
|
|
if (layerNode.typeOfLayer === undefined) { continue }
|
|
if (layerNode.typeOfLayer.layer === undefined) { continue }
|
|
|
|
/* Assign tensorFlowAPI layer type to the layer function: */
|
|
switch (layerNode.typeOfLayer.layer.type) {
|
|
case 'Dense Layer': {
|
|
layerFunction = _tensorFlowAPI.layers.dense
|
|
break
|
|
}
|
|
case ' ': {
|
|
break
|
|
}
|
|
case ' ': {
|
|
break
|
|
}
|
|
default: {
|
|
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
|
|
'[WARN] Layer of type ' + layerNode.typeOfLayer.layer.type + ' not implemented yet.')
|
|
continue
|
|
}
|
|
}
|
|
|
|
/* Construct layerArgsObject for this layerFunction via collecting the children of the layerNode: */
|
|
if (layerNode.typeOfLayer.layer.dimensionalityUnits !== undefined) {
|
|
layerArgsObject.units = layerNode.typeOfLayer.layer.dimensionalityUnits.config.value
|
|
} else {
|
|
layerArgsObject.units = 1
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.activationFunction !== undefined) {
|
|
layerArgsObject.activation = layerNode.typeOfLayer.layer.activationFunction.config.value
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.bias !== undefined) {
|
|
layerArgsObject.useBias = true
|
|
} else {
|
|
layerArgsObject.useBias = false
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.kernel !== undefined) {
|
|
if (layerNode.typeOfLayer.layer.kernel.kernelInitializer !== undefined) {
|
|
layerArgsObject.kernelInitializer = layerNode.typeOfLayer.layer.kernel.kernelInitializer.config.value
|
|
}
|
|
if (layerNode.typeOfLayer.layer.kernel.kernelConstraint !== undefined) {
|
|
layerArgsObject.kernelConstraint = layerNode.typeOfLayer.layer.kernel.kernelConstraint.config.value
|
|
}
|
|
if (layerNode.typeOfLayer.layer.kernel.kernelRegularizer !== undefined) {
|
|
layerArgsObject.kernelRegularizer = layerNode.typeOfLayer.layer.kernel.kernelRegularizer.config.value
|
|
}
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.bias !== undefined) {
|
|
if (layerNode.typeOfLayer.layer.bias.biasInitializer !== undefined) {
|
|
layerArgsObject.biasInitializer = layerNode.typeOfLayer.layer.bias.biasInitializer.config.value
|
|
}
|
|
if (layerNode.typeOfLayer.layer.bias.biasConstraint !== undefined) {
|
|
layerArgsObject.biasConstraint = layerNode.typeOfLayer.layer.bias.biasConstraint.config.value
|
|
}
|
|
if (layerNode.typeOfLayer.layer.bias.biasRegularizer !== undefined) {
|
|
layerArgsObject.biasRegularizer = layerNode.typeOfLayer.layer.bias.biasRegularizer.config.value
|
|
}
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.batchSize !== undefined) {
|
|
layerArgsObject.batchSize = layerNode.typeOfLayer.layer.batchSize.config.value
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.dtype !== undefined) {
|
|
layerArgsObject.dtype = layerNode.typeOfLayer.layer.dtype.config.value
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.trainable !== undefined) {
|
|
layerArgsObject.trainable = layerNode.typeOfLayer.layer.trainable.config.value
|
|
}
|
|
|
|
if (layerNode.typeOfLayer.layer.weights !== undefined) {
|
|
if (layerNode.typeOfLayer.layer.weights.tensor !== undefined) {
|
|
layerArgsObject.weights = layerNode.typeOfLayer.layer.weights.tensor.config.value
|
|
}
|
|
}
|
|
layerArgsObject.name = layerNode.typeOfLayer.layer.config.codeName;
|
|
|
|
/* Check if this is the input layer: */
|
|
if (layersModel.inputLayer.referenceParent.id === layerNode.id) {
|
|
/*
|
|
* If there is a explicitly defined Input Shape, then we will take it,
|
|
* otherwise, we will count the amount of features declared.
|
|
*/
|
|
if (layersModel.inputLayer.inputShape !== undefined) {
|
|
if (layerArgsObject.inputShape = layersModel.inputLayer.inputShape !== undefined) {
|
|
if (layerArgsObject.inputShape = layersModel.inputLayer.inputShape.config.value !== undefined) {
|
|
layerArgsObject.inputShape = layersModel.inputLayer.inputShape.config.value
|
|
}
|
|
}
|
|
}
|
|
if (layerArgsObject.inputShape === undefined) {
|
|
layerArgsObject.inputShape = [];
|
|
layerArgsObject.inputShape.push(layersModel.inputLayer.inputFeatures.dataFeatures.length);
|
|
}
|
|
}
|
|
|
|
/* Call the layerFunction and add layer to the model: */
|
|
layerObject = layerFunction(layerArgsObject);
|
|
_tensorFlowModel.add(layerObject);
|
|
}
|
|
}
|
|
|
|
|
|
/* Compile Models Section: */
|
|
function compileModel() {
|
|
/* optimizerBuilder() : builds and returns either a string or an tf.train.Optimizer:
|
|
* TF Docs: optimizer (string|tf.train.Optimizer) An instance of tf.train.Optimizer or a string name for an Optimizer.
|
|
*/
|
|
let optimizerBuilder = () => {
|
|
let optimizerType = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.type;
|
|
let learningRate, momentum, useNesterov, initAccVal, rho, decay, epsilon, beta1, beta2, centered;
|
|
switch(optimizerType) {
|
|
case 'Optimizer by Name': // For specifying string name as optimizer.
|
|
// This ensures acceptable type entered:
|
|
return _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.value;
|
|
// Following Cases for building instance of tf.train.Optimizer:
|
|
case 'SGD Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
return _tfjsCore.train.sgd(learningRate);
|
|
case 'Momentum Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
momentum = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.momentum;
|
|
useNesterov = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.useNesterov;
|
|
return _tfjsCore.train.momentum(learningRate, momentum, useNesterov);
|
|
case 'Adagrad Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
initAccVal = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.initialAccumulatorValue;
|
|
return _tfjsCore.train.adagrad(learningRate, initAccVal);
|
|
case 'Adadelta Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
rho = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.rho;
|
|
epsilon = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.epsilon;
|
|
return _tfjsCore.train.adadelta(learningRate, rho, epsilon);
|
|
case 'Adam Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
beta1 = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.beta1;
|
|
beta2 = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.beta2;
|
|
epsilon = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.epsilon;
|
|
return _tfjsCore.train.adam(learningRate, beta1, beta2, epsilon);
|
|
case 'Rmsprop Instance':
|
|
learningRate = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.learningRate;
|
|
decay = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.decay;
|
|
momentum = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.momentum;
|
|
epsilon = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.epsilon;
|
|
centered = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.optimizer.optimizerType.config.centered;
|
|
return _tfjsCore.train.rmsprop(learningRate, decay, momentum, epsilon, centered);
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
let compileMetrics;
|
|
if (_learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.metrics !== undefined &&
|
|
_learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.metrics.config.value !== undefined) {
|
|
compileMetrics = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.metrics.config.value;
|
|
}
|
|
|
|
// Collect, build and process optimizer type:
|
|
let optimizerBuild = optimizerBuilder();
|
|
let compileArgs = {
|
|
optimizer: optimizerBuild,
|
|
/* Loss Types: see: @tensorflow -> tf-layers.node.js:
|
|
* categoricalCrossentropy, sparseCategoricalCrossentropy, binaryCrossentropy, meanSquaredError ...
|
|
*/
|
|
loss: _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.compile.lossFunction.config.value,
|
|
metrics: compileMetrics
|
|
}
|
|
|
|
_tensorFlowModel.compile(compileArgs);
|
|
}
|
|
|
|
/* Fit Model Section: */
|
|
async function trainModel(mlDataObj) {
|
|
switch(modelType) {
|
|
case 'Sequential Model':
|
|
_tensorFlowModel.summary();
|
|
await trainSequentialModel(mlDataObj);
|
|
break;
|
|
case 'Functional Model':
|
|
// To be implemented some day...
|
|
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
|
|
'[WARN] Model of type ' + modelType + ' not yet implemented.');
|
|
throw new Error('Model Type: >' + modelType + '< not yet implemented');
|
|
default: {
|
|
TS.projects.foundations.globals.loggerVariables.VARIABLES_BY_PROCESS_INDEX_MAP.get(processIndex).BOT_MAIN_LOOP_LOGGER_MODULE_OBJECT.write(MODULE_NAME,
|
|
'[WARN] Model of type ' + modelType + ' not yet implemented.');
|
|
throw new Error('Model Type: >' + modelType + '< not yet implemented');
|
|
}
|
|
}
|
|
}
|
|
|
|
async function trainSequentialModel(mlDataObj) {
|
|
// Should we print state of mlDataObj:
|
|
let dataReporting = (learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.dataReporting !== undefined &&
|
|
learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.dataReporting.config.printDataReports === true) ? true : false;
|
|
if (dataReporting) {
|
|
mlDataObj.print();
|
|
}
|
|
|
|
let xTensor = mlDataObj.featuresTensor();
|
|
let yTensor = mlDataObj.labelsTensor();
|
|
let modelFitDatasetArgs = {};
|
|
|
|
// Should we collect any fitArgs:
|
|
if (_learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.fitDataset !== undefined) {
|
|
modelFitDatasetArgs = fitArgsConstructor();
|
|
if (dataReporting) {
|
|
let fitKeys = Object.keys(modelFitDatasetArgs);
|
|
let fitVals = Object.values(modelFitDatasetArgs);
|
|
console.log("__________________________________________________\nModel Fitting Arguments:\n==================================================");
|
|
fitKeys.forEach((e, i) => console.log(i + ": \"" + e + "\"=>[" + fitVals[i] + "]"));
|
|
}
|
|
}
|
|
|
|
console.log("__________________________________________________\nTraining " +
|
|
_learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.name +
|
|
":\n==================================================");
|
|
|
|
const history = await _tensorFlowModel.fit(xTensor, yTensor, modelFitDatasetArgs);
|
|
isTrained = true;
|
|
|
|
if (dataReporting) {
|
|
console.log("\n__________________________________________________\nModel Fitting Report:\n==================================================");
|
|
console.log("First Epoch #1:\n\tacc=>" + history.history.acc[0] + "\n\tloss=>" + history.history.loss[0]);
|
|
let totalEpochs = history.epoch.length;
|
|
console.log("Final Epoch #" + totalEpochs + ":\n\tacc=>" + history.history.acc[totalEpochs - 1] + "\n\tloss=>" + history.history.loss[totalEpochs - 1] + "\n__________________________________________________");
|
|
}
|
|
}
|
|
|
|
|
|
/* Construct modelFitDatasetArgs by collecting available arguments: */
|
|
function fitArgsConstructor() {
|
|
let modelFitDatasetArgs = {};
|
|
let fitDatasetNode = _learningSystem.machineLearningLibrary.typeOfLearning.typeOfModel.model.fitDataset;
|
|
// verbose- Verbosity level:
|
|
if (fitDatasetNode.verbose !== undefined) {
|
|
if (fitDatasetNode.verbose.config.value !== undefined) {
|
|
modelFitDatasetArgs.verbose = fitDatasetNode.verbose.config.value
|
|
}
|
|
}
|
|
// epochs- Integer number of times to iterate over the training data arrays:
|
|
if (fitDatasetNode.epochs !== undefined) {
|
|
if (fitDatasetNode.epochs.config.value !== undefined) {
|
|
modelFitDatasetArgs.epochs = fitDatasetNode.epochs.config.value
|
|
}
|
|
}
|
|
// batchSize- Number of samples per gradient update. If unspecified, it will default to 32 per tf.org:
|
|
if (fitDatasetNode.datasetArgs !== undefined) {
|
|
if (fitDatasetNode.datasetArgs.batchesPerGradientUpdate !== undefined &&
|
|
fitDatasetNode.datasetArgs.batchesPerGradientUpdate.config.value !== undefined) {
|
|
modelFitDatasetArgs.batchSize = fitDatasetNode.datasetArgs.batchesPerGradientUpdate.config.value;
|
|
}
|
|
if (fitDatasetNode.datasetArgs.shuffle !== undefined &&
|
|
fitDatasetNode.datasetArgs.shuffle.config.booleanValue !== undefined) {
|
|
modelFitDatasetArgs.shuffle = fitDatasetNode.datasetArgs.shuffle.config.booleanValue;
|
|
}
|
|
// Future Add validation split:
|
|
}
|
|
|
|
// This callback hard coded for now:
|
|
modelFitDatasetArgs.callbacks = {
|
|
onEpochEnd: (epoch, logs) => console.log("callbacks:\tEpoch:" + epoch + " | Loss=>" + logs.loss)
|
|
}
|
|
|
|
return modelFitDatasetArgs;
|
|
}
|
|
} |