neurex
Version:
A trainable neural network in NodeJS. Designed for ease of implementation and ANN modelling
228 lines (188 loc) • 9.06 kB
JavaScript
/**
*
This interpreter is dedicated for Neurex
*/
const activation = require('../gpu/kernels/activations');
const detect = require('../gpu/detectGPU');
const Layers = require('../layers');
const zlib = require('zlib');
const fs = require('fs');
const path = require('path');
/**
* This class allows you to run inference predictions on your applications. You can load your trained model and run predictions
*
* @class
*/
class Interpreter {
constructor () {
this.weights = []; // weights for calculating the dot product of each nueron
this.biases = []; // biases for calculating the dot product of each nueron
this.num_layers = 0; // the number layers in a network (hidden and the output layer)
this.layers = [];
this.input_size = 0; // the size of the input layer, basically the number of input neurons.
this.output_size = 0;
this.onGPU = true;
}
/**
* @method loadSavedModel()
* @param {*} model - the trained model
The loadSavedModel() method allows you to load the trained model. The model is typically in .nrx file format which contains the learned parameters of your trained model
*/
loadSavedModel(model) {
try {
if (!model) {
throw new Error("No model provided");
}
const dir = path.dirname(require.main.filename);
const model_file = path.join(dir, `${model}`);
// Check extension
if (path.extname(model_file) !== '.nrx') {
throw new Error("Invalid file type. Only .nrx model files are supported.");
}
// Read file
const rawBuffer = fs.readFileSync(model_file);
// Validate magic header
const header = rawBuffer.slice(0, 4).toString('utf-8');
if (header !== 'NRX2') {
throw new Error("Invalid file format. Not a valid NRX model.");
}
// Check version
const version = rawBuffer[4];
if (version !== 0x02) {
throw new Error(`Unsupported NRX version: ${version}`);
}
// Decompress and parse
const compressedData = rawBuffer.slice(5);
const jsonString = zlib.inflateSync(compressedData).toString('utf-8');
const modelData = JSON.parse(jsonString);
// Assign properties
this.task = modelData.task;
this.loss_function = modelData.loss_function;
this.epoch_count = modelData.epoch;
this.batch_size = modelData.batch_size;
this.optimizer = modelData.optimizer;
this.learning_rate = modelData.learning_rate;
this.input_size = modelData.input_size;
this.output_size = modelData.output_size;
this.num_layers = modelData.num_layers;
this.weights = modelData.weights;
this.biases = modelData.biases;
const layerBuilder = new Layers();
this.layers = modelData.layers.map(layerData => {
let newLayer;
if (layerData.layer_name === "connected_layer") {
// Recreate the connected layer with the correct activation and size
newLayer = layerBuilder.connectedLayer(layerData.activation_function_name, layerData.layer_size);
} else if (layerData.layer_name === "input_layer") {
// Recreate the input layer. Note: The input layer doesn't have methods, so this is just for consistency
newLayer = layerBuilder.inputShape({ features: layerData.layer_size });
} else if (layerData.layer_name === "flatten_layer") {
newLayer = layerBuilder.flatten();
} else {
throw new Error(`[ERROR] Unknown layer type '${layerData.layer_name}' found in model.`);
}
return newLayer;
});
console.log(`[SUCCESS]------- Model ${model} successfully loaded`);
} catch (error) {
console.log(error.message);
}
}
/**
*
@method modelSummary()
Shows the model architecture
*/
modelSummary() {
console.log("_______________________________________________________________");
console.log(" Model Summary ");
console.log("_______________________________________________________________");
console.log(`Input size: ${this.input_size}`);
console.log(`Number of layers: ${this.num_layers}`);
console.log("---------------------------------------------------------------");
console.log("Layer (type) Output Shape Activation ");
console.log("===============================================================");
// This will be the shape for the next layer. It starts with the input size.
let currentOutputShape = this.input_size;
this.layers.forEach((layer, i) => {
const layerName = layer.layer_name === "connected_layer" ? "Connected Layer" : layer.layer_name;
const activationName = layer.activation_function ? layer.activation_function.name : 'None';
const outputShape = layer.layer_size;
console.log(
`Connected Layer (None, ${outputShape}) ${activationName.padEnd(10, ' ')}`
);
// Update the output shape for the next iteration
currentOutputShape = outputShape;
});
const total_weights = this.weights.flat(Infinity).length
const total_biases = this.biases.flat(Infinity).length
console.log("===============================================================");
console.log("Total layers: " + this.num_layers);
console.log("Total Learnable parameters:",parseInt(total_weights+total_biases));
console.log("===============================================================");
}
/**
*
@method predict()
@param {Array} input - input data
@returns Array of predictions
@throws Error when there's shape mismatch and no input data
produces predictions based on the input data
*/
predict(input) {
try {
if (!input) {
throw new Error("\n[ERROR]-------No inputs")
}
if (input[0].length != this.input_size) {
throw new Error(`\n[ERROR]-------Shape Mismatch | Input shape length: ${input[0].length} | Expecting ${this.input_size}`);
}
const {gpu, backend, isGPUAvailable, isSoftwareGPU} = detect();
if (!isGPUAvailable || isSoftwareGPU) {
console.log(`[INFO]------- Falling back to CPU mode (no GPU acceleration)`);
this.onGPU = false;
} else {
console.log(`[INFO]-------- Backend Detected: ${backend}. Using ${gpu}`);
this.onGPU = true;
}
let outputs = [];
for (let sample_index = 0; sample_index < input.length; sample_index++) {
/**
*
we loop through the entire loaded dataset
*/
const array_of_features = input[sample_index];
// perform feedforward, similar when training but only outputs predictions. No updating of weights and biases.
const {predictions} = this.#Feedforward(array_of_features);
outputs.push(predictions);
}
return outputs;
}
catch (err) {
console.error(err.message);
}
}
// forward propagation
#Feedforward(input) {
let current_input = input
let all_layer_outputs = [input];
let zs = [];
for (let layer_index = 0; layer_index < this.num_layers; layer_index++) {
const current_layer = this.layers[layer_index];
const layer_weights = this.weights[layer_index];
const layer_biases = this.biases[layer_index];
// Call the layer's specific feedforward method
const { outputs, z_values } = current_layer.feedforward(this.onGPU, current_input, layer_weights, layer_biases);
zs.push(z_values);
current_input = outputs;
all_layer_outputs.push(current_input);
}
// after all the layers gives off their outputs, return final array of current_input as the predictions
return {
predictions: current_input,
activations : all_layer_outputs,
zs: zs
};
}
}
module.exports = Interpreter;