nrn-ml
Version:
Core ML library for the NRN ecosystem
1,064 lines (1,054 loc) • 37 kB
JavaScript
// lib/math/policy-mapping.ts
var probabilisticSampling = (modelOutput) => {
const selectedActionsArray = modelOutput.map((currentActionProbabilities) => {
const randomNumber = Math.random();
let selectedAction = 0;
let counter = 0;
while (selectedAction < currentActionProbabilities.length - 1) {
counter += currentActionProbabilities[selectedAction];
if (randomNumber <= counter) {
break;
} else {
selectedAction++;
}
}
return selectedAction;
});
return selectedActionsArray;
};
var argmax = (array) => {
return array.map((x, i) => [x, i]).reduce((r, a) => a[0] > r[0] ? a : r)[1];
};
var argmaxPolicy = (modelOutput) => {
const selectedActionsArray = modelOutput.map((currentModelOutputs) => {
return argmax(currentModelOutputs);
});
return selectedActionsArray;
};
var epsilonGreedy = (modelOutput) => {
const epsilon = 0.1;
const selectedActionsArray = modelOutput.map((currentModelOutputs) => {
if (Math.random() < epsilon) {
return Math.min(
Math.floor(Math.random() * currentModelOutputs.length),
currentModelOutputs.length - 1
);
} else {
return argmax(currentModelOutputs);
}
});
return selectedActionsArray;
};
var policyMapping = {
probabilisticSampling,
epsilonGreedy,
argmaxPolicy
};
// lib/inference-models/model-core.ts
var ModelCore = class {
outputGroups;
getActionHeadOutput(actionOutput, actionMetadata, outputName, row = 0) {
let action;
const actionType = actionMetadata[outputName].actionType;
const policyMethod = actionMetadata[outputName].policyMapping;
if (actionType === "discrete") {
action = policyMapping[policyMethod](actionOutput);
} else {
action = actionOutput;
}
const actionObj = {};
actionMetadata[outputName].order.forEach((actionName, idx) => {
actionObj[actionName] = actionType === "continuous" ? action[row][idx] : action[row] === idx;
});
return actionObj;
}
};
var model_core_default = ModelCore;
// lib/utils/model-validation.ts
var isArray1D = (x) => {
const isArray = Array.isArray(x);
const is1D = isArray && !Array.isArray(x[0]);
return isArray && is1D;
};
var isArray2D = (x) => {
const isArray = Array.isArray(x);
const is2D = isArray && Array.isArray(x[0]) && !Array.isArray(x[0][0]);
return isArray && is2D;
};
var checkSingleHeadActionParam = (param, key) => {
if (param !== void 0 && typeof param !== "string" && (!isArray1D(param) || !(isArray1D(param) && param.length === 1 && typeof param[0] === "string"))) {
return `'${key}' is improperly defined`;
}
return "";
};
var allowedActionParams = {
actionTypes: ["discrete", "continuous"],
actionPolicies: ["argmaxPolicy", "probabilisticSampling"],
actionActivations: ["softmax", "linear", "tanh", "sigmoid"]
};
var validateActionNames = (modelData) => {
const config = modelData.config;
const { actionNames } = config;
if (config.multiheadBool) {
if (!isArray1D(actionNames)) {
return "'actionNames' for multi-head models must be a 1D array";
} else if (actionNames?.length < 2) {
return "'multiheadBool' is true, but multiple action head names were not detected";
}
} else {
const msg = checkSingleHeadActionParam(actionNames, "actionNames");
if (msg !== "") return msg;
if (actionNames === void 0) {
config.actionNames = ["Actions"];
} else if (typeof actionNames === "string") {
config.actionNames = [actionNames];
}
}
return "";
};
var validateActionOrder = (modelData) => {
const config = modelData.config;
const { actionOrder } = config;
if (config.multiheadBool) {
const numActionNames = config.actionNames?.length;
if (!isArray2D(actionOrder)) {
return "'actionOrder' for multi-head models must be a 2D array";
} else if (actionOrder.length !== numActionNames) {
return `Mistmatch between 'actionOrder' length and 'actionNames': ${actionOrder.length} vs ${numActionNames}`;
}
} else {
if (!isArray1D(actionOrder) && !isArray2D(actionOrder)) {
return "'actionOrder' for single-head models should be an array of actions";
} else if (isArray2D(actionOrder) && actionOrder.length > 1) {
return "'actionOrder' for single-head is a 2D array with too many rows";
}
}
return "";
};
var validateSupplementaryActionData = (modelConfig, key) => {
const actionData = modelConfig[key];
if (modelConfig.multiheadBool) {
if (actionData !== void 0) {
if (!isArray1D(actionData)) {
return `'${key}' for multi-head models must be a 1D array`;
} else if (actionData.length !== modelConfig.actionNames.length) {
return `Mistmatch between ${key} length and 'actionNames': ${actionData.length} vs ${modelConfig.actionNames.length}`;
} else {
const illegalParams = actionData.filter((x) => !allowedActionParams[key].includes(x));
if (illegalParams.length > 0) {
return `'${illegalParams[0]}' is not an allowed value for ${key}`;
}
}
}
} else {
const msg = checkSingleHeadActionParam(actionData, key);
if (msg) return msg;
if (typeof actionData === "string") {
if (!allowedActionParams[key].includes(actionData)) {
return `'${actionData}' is not an allowed value for ${key}`;
} else {
modelConfig[key] = [modelConfig[key]];
}
} else if (isArray1D(actionData) && !allowedActionParams[key].includes(actionData[0])) {
return `'${actionData[0]}' is not an allowed value for ${key}`;
}
}
return "";
};
var createActionMetadata = (referenceConfig, includeActivation = false) => {
const actionHeads = [];
const actionMetadata = {};
for (let i = 0; i < referenceConfig.actionNames.length; i++) {
const name = referenceConfig.actionNames[i];
actionHeads.push(name);
actionMetadata[name] = {
actionType: referenceConfig.actionTypes?.[i] ?? "discrete",
policyMapping: referenceConfig.actionPolicies?.[i] ?? "argmaxPolicy",
order: referenceConfig.actionOrder[i]
};
if (includeActivation) {
actionMetadata[name].activationName = referenceConfig.actionActivations?.[i] ?? "softmax";
}
}
return [actionHeads, actionMetadata];
};
var validateFormattedActionHeads = (modelConfig) => {
let msg = "";
if (!isArray1D(modelConfig.actionHeads)) {
msg = "'actionHeads' must be a 1D array";
} else if (modelConfig.actionHeads.length !== Object.keys(modelConfig.actionMetadata).length) {
msg = `Mistmatch between 'actionHeads' length and 'actionMetadata': ${modelConfig.actionHeads.length} vs ${Object.keys(modelConfig.actionMetadata).length}`;
} else {
modelConfig.actionHeads.forEach((actionGroup) => {
if (modelConfig.actionMetadata[actionGroup] === void 0) {
msg = `'${actionGroup}' is not found in 'actionMetadata'`;
} else if (!isArray1D(modelConfig.actionMetadata[actionGroup].order)) {
msg = `'order' must be a 1D array in 'actionMetadata' for '${actionGroup}'`;
}
});
}
return msg;
};
var checkValidActionHeads = (modelData) => {
let msg = "";
const formattedConfig = modelData.config;
const rawConfig = modelData.config;
if (formattedConfig.actionMetadata !== void 0 && formattedConfig.actionHeads !== void 0) {
msg = validateFormattedActionHeads(formattedConfig);
if (msg !== "") return { success: false, msg };
} else {
if (rawConfig.actionOrder === void 0) {
return { success: false, msg: "Model data config must contain 'actionOrder'" };
}
msg = validateActionNames(modelData);
if (msg !== "") return { success: false, msg };
msg = validateActionOrder(modelData);
if (msg !== "") return { success: false, msg };
msg = validateSupplementaryActionData(rawConfig, "actionTypes");
if (msg !== "") return { success: false, msg };
msg = validateSupplementaryActionData(rawConfig, "actionActivations");
if (msg !== "") return { success: false, msg };
msg = validateSupplementaryActionData(rawConfig, "actionPolicies");
if (msg !== "") return { success: false, msg };
if (!rawConfig.multiheadBool) {
if (isArray1D(rawConfig.actionOrder)) {
rawConfig.actionOrder = [rawConfig.actionOrder];
}
}
}
return { success: true, msg };
};
var isSimpleModelValid = (modelData) => {
if (modelData.config === void 0) {
return { success: false, msg: "Model data must contain 'config'" };
}
let msg = "";
let success = false;
const config = modelData.config;
const actionHeadRes = checkValidActionHeads(modelData);
const newModel = modelData.frequencies === void 0;
if (actionHeadRes.msg === "") {
if (![void 0, "empty", "random"].includes(config.initializationMethod)) {
msg = "initializationMethod should be either: 'empty' or 'random'";
} else if (newModel && config.inputDim === void 0 || !newModel && config.numDiscreteStates === void 0) {
msg = "Number of discrete states not provided";
} else {
success = true;
}
return { success, msg };
} else {
return actionHeadRes;
}
};
var getExpectedNeuralNetParams = (modelConfig) => {
var inDim;
var outDim = modelConfig.nFeatures;
const nLayers = modelConfig.neurons.length;
const expectedParams = {};
for (var l = 0; l < nLayers; l++) {
if (l === 0) inDim = modelConfig.nFeatures ?? modelConfig.inputDim;
else inDim = modelConfig.neurons[l - 1];
outDim = modelConfig.neurons[l];
expectedParams[`biases-${l}-${l + 1}`] = { inDim: 1, outDim };
expectedParams[`weights-${l}-${l + 1}`] = { inDim, outDim };
}
Object.keys(modelConfig.actionMetadata).forEach((actionGroup) => {
const nActions = modelConfig.actionMetadata[actionGroup].order.length;
const keyAppend = `${nLayers}-${actionGroup}`;
expectedParams[`biases-${keyAppend}`] = { inDim: 1, outDim: nActions };
expectedParams[`weights-${keyAppend}`] = { inDim: outDim, outDim: nActions };
});
return expectedParams;
};
var validateNeuralNetParameters = (modelConfig, parameters) => {
let msg = "";
const expectedParams = getExpectedNeuralNetParams(modelConfig);
const paramKeys = Object.keys(parameters);
const expectedKeys = Object.keys(expectedParams);
if (expectedKeys.length !== paramKeys.length) {
msg = `Parameter keys mismatch in 'parameters' vs expectation: ${paramKeys.length} vs ${expectedKeys.length}`;
} else {
for (let i = 0; i < paramKeys.length; i++) {
if (expectedParams[paramKeys[i]] === void 0) {
msg = `'${paramKeys[i]}' is an invalid parameter key`;
break;
} else if (!isArray2D(parameters[paramKeys[i]])) {
msg = `'${paramKeys[i]}' is not structured as a matrix`;
} else {
const paramShape = [parameters[paramKeys[i]].length, parameters[paramKeys[i]][0].length];
const expectedShape = [
expectedParams[paramKeys[i]].inDim,
expectedParams[paramKeys[i]].outDim
];
if (paramShape[0] !== expectedShape[0] || paramShape[1] !== expectedShape[1]) {
msg = `'${paramKeys[i]}' shape does not match expectation - ${paramShape} vs ${expectedShape}`;
}
}
}
}
return { success: msg === "", msg };
};
var isNeuralNetModelValid = (modelData) => {
if (modelData.config === void 0) {
return { success: false, msg: "Model data must contain 'config'" };
}
const actionHeadRes = checkValidActionHeads(modelData);
return actionHeadRes;
};
// lib/inference-models/tabular-model.ts
var TabularModel = class _TabularModel extends model_core_default {
config;
frequencies;
constructor(modelData) {
super();
const modelValidity = isSimpleModelValid(modelData);
if (!modelValidity.success) {
throw Error(modelValidity.msg);
}
const createNewModel = modelData.frequencies === void 0;
if (createNewModel) {
let actionHeads;
let actionMetadata;
if (modelData.config.actionHeads === void 0 || modelData.config.actionMetadata === void 0) {
const actionMetadataResult = createActionMetadata(modelData.config);
actionHeads = actionMetadataResult[0];
actionMetadata = actionMetadataResult[1];
} else {
actionHeads = modelData.config.actionHeads;
actionMetadata = modelData.config.actionMetadata;
}
this.config = {
initializationMethod: "empty",
numDiscreteStates: modelData.config.inputDim,
actionHeads,
actionMetadata
};
this.frequencies = this.initializeFrequencies(this.config.initializationMethod);
} else {
this.config = { ...modelData.config };
this.frequencies = { ...modelData.frequencies };
}
this.outputGroups = Object.keys(this.config.actionMetadata);
if (this.config.numDiscreteStates !== this.frequencies.length) {
throw Error(
`Number of discrete states (${this.config.numDiscreteStates}) does not match length of frequency array (${this.frequencies.length})`
);
}
}
createEmptyCell() {
const emptyCell = {};
Object.keys(this.config.actionMetadata).forEach((actionGroup) => {
const size = this.config.actionMetadata[actionGroup].order.length;
emptyCell[actionGroup] = new Array(size).fill(0);
});
return emptyCell;
}
createRandomProbability(size) {
return _TabularModel.convertFrequencyToProbability(
[...Array(size).keys()].map((_) => Math.random())
);
}
createRandomCell() {
const randomCell = {};
Object.keys(this.config.actionMetadata).forEach((actionGroup) => {
const size = this.config.actionMetadata[actionGroup].order.length;
randomCell[actionGroup] = this.createRandomProbability(size);
});
return randomCell;
}
initializeFrequencies(initializationMethod = "empty") {
return [...Array(this.config.numDiscreteStates).keys()].map((_) => {
if (initializationMethod === "empty") {
return this.createEmptyCell();
} else if (initializationMethod === "random") {
return this.createRandomCell();
}
});
}
static convertFrequencyToProbability(output) {
const denom = output.reduce((total, current) => total + current, 0);
if (denom !== 0) {
return output.map((x) => x / denom);
}
return new Array(output.length).fill(1 / output.length);
}
getProbabilities(cell) {
const cellFrequencies = this.frequencies[cell];
if (cellFrequencies === void 0) {
throw Error("Cell out of range");
}
const rawProbabilities = {};
this.outputGroups.forEach((actionGroup) => {
const prob = _TabularModel.convertFrequencyToProbability(cellFrequencies[actionGroup]);
rawProbabilities[actionGroup] = [prob];
});
return rawProbabilities;
}
selectActionOneHead(probabilities, outputName, row = 0) {
return this.getActionHeadOutput(
probabilities[outputName],
this.config.actionMetadata,
outputName,
row
);
}
selectAction(cell, row = 0) {
var actions = {};
const probabilities = this.getProbabilities(cell);
this.outputGroups.forEach((actionGroup) => {
actions = { ...actions, ...this.selectActionOneHead(probabilities, actionGroup, row) };
});
return actions;
}
};
var tabular_model_default = TabularModel;
// lib/math/rand.ts
function box_muller(mu, sigma) {
var x, y, r, v = 0;
do {
x = 2 * Math.random() - 1;
y = 2 * Math.random() - 1;
r = x * x + y * y;
} while (r >= 1);
var co = Math.sqrt(-2 * Math.log(r) / r);
v = x * co;
return v * sigma + mu;
}
// lib/math/matrix-math.ts
var createMatrix = (rows, cols, fill = 0) => {
return new Array(rows).fill(fill).map(() => {
return new Array(cols).fill(fill);
});
};
var createMatrixFromRef = (referenceMatrix, fill = 0) => {
return createMatrix(referenceMatrix.length, referenceMatrix[0].length, fill);
};
var matrixMultiplication = (A, B) => {
if (A[0].length !== B.length) {
throw new Error(
`Matrix shapes of (${A.length},${A[0].length}) and (${B.length},${B[0].length}) are incompatible`
);
}
var result = createMatrix(A.length, B[0].length);
return result.map((row, i) => {
return row.map((val, j) => {
return A[i].reduce((sum, elm, k) => sum + elm * B[k][j], 0);
});
});
};
var matrixElementwiseOperation = (A, B, operation) => {
if (typeof A === "number" && isArray2D(B)) {
A = createMatrix(B.length, B[0].length, A);
} else if (typeof B === "number" && isArray2D(A)) {
B = createMatrix(A.length, A[0].length, B);
}
if (!isArray2D(A) || !isArray2D(B)) {
throw Error("Failed to broadcast A or B to a matrix");
}
const matrixA = A;
const matrixB = B;
if (matrixA.length !== matrixB.length || matrixA[0].length !== matrixB[0].length) {
const shapes = {
"A": `(${matrixA.length},${matrixA[0].length})`,
"B": `(${matrixB.length},${matrixB[0].length})`
};
throw new Error(
`Matrix shapes of ${shapes["A"]} and ${shapes["B"]} are incompatible`
);
}
var result = createMatrixFromRef(matrixA);
for (var i = 0; i < matrixA.length; i++) {
for (var j = 0; j < matrixA[0].length; j++) {
if (operation === "Addition") {
result[i][j] = matrixA[i][j] + matrixB[i][j];
} else if (operation === "Subtraction") {
result[i][j] = matrixA[i][j] - matrixB[i][j];
} else if (operation === "Multiplication") {
result[i][j] = matrixA[i][j] * matrixB[i][j];
} else if (operation === "Division") {
result[i][j] = matrixA[i][j] / matrixB[i][j];
}
}
}
return result;
};
var linear = (x, derivativeBool) => {
if (derivativeBool) {
const linearMatrix = [];
x.forEach((currentInstance) => {
linearMatrix.push(
currentInstance.map((x_i) => {
return x_i;
})
);
});
return linearMatrix;
}
return x;
};
var sigmoidScalar = (x, derivativeBool) => {
if (derivativeBool) {
const sig = sigmoidScalar(x, false);
return sig * (1 - sig);
}
return 1 / (1 + Math.exp(-x));
};
var sigmoid = (x, derivativeBool) => {
var sigmoidMatrix = [];
x.forEach((currentInstance) => {
sigmoidMatrix.push(
currentInstance.map((x_i) => {
return sigmoidScalar(x_i, derivativeBool);
})
);
});
return sigmoidMatrix;
};
var tanhScalar = (x, derivativeBool) => {
if (derivativeBool) {
return 1 - tanhScalar(x, false) ** 2;
}
return (Math.exp(x) - Math.exp(-x)) / (Math.exp(x) + Math.exp(-x));
};
var tanh = (x, derivativeBool) => {
var tanhMatrix = [];
x.forEach((currentInstance) => {
tanhMatrix.push(
currentInstance.map((x_i) => {
return tanhScalar(x_i, derivativeBool);
})
);
});
return tanhMatrix;
};
var relu = (x, derivativeBool) => {
var reluMatrix = [];
x.forEach((currentInstance) => {
if (derivativeBool) {
reluMatrix.push(
currentInstance.map((x_i) => {
return x_i > 0 ? 1 : 0;
})
);
} else {
reluMatrix.push(
currentInstance.map((x_i) => {
return x_i > 0 ? x_i : 0;
})
);
}
});
return reluMatrix;
};
var elu = (x, derivativeBool) => {
var eluMatrix = [];
const alpha = 1;
x.forEach((currentInstance) => {
if (derivativeBool) {
eluMatrix.push(
currentInstance.map((x_i) => {
return x_i > 0 ? 1 : alpha * (Math.exp(x_i) - 1) + alpha;
})
);
} else {
eluMatrix.push(
currentInstance.map((x_i) => {
return x_i > 0 ? x_i : alpha * (Math.exp(x_i) - 1);
})
);
}
});
return eluMatrix;
};
var softmax = (x, derivativeBool) => {
var xMax;
var denom;
var stabalizedCurrentInstance;
var softmaxMatrix = [];
x.forEach((currentInstance) => {
if (derivativeBool) {
} else {
xMax = currentInstance.reduce((acc, val) => Math.max(acc, val), currentInstance[0]);
stabalizedCurrentInstance = currentInstance.map((x_i) => {
return x_i - xMax;
});
denom = stabalizedCurrentInstance.reduce((total, current) => total + Math.exp(current), 0);
softmaxMatrix.push(
stabalizedCurrentInstance.map((x_i) => {
return Math.exp(x_i) / denom;
})
);
}
});
return softmaxMatrix;
};
// lib/utils/create-model-multihead.ts
var defaultDecimalPrecision = 6;
var roundNumber = (x, decimalPrecision) => {
const factor = 10 ** decimalPrecision;
return Math.round(x * factor) / factor;
};
var generateRandomMatrix = (inDim, outDim, precision) => {
const layerStd = Math.sqrt(2 / (inDim + outDim));
var rawRandomNumber;
const randomMatrix = [];
precision = precision !== void 0 ? precision : defaultDecimalPrecision;
for (var i = 0; i < inDim; i++) {
randomMatrix.push([]);
for (var j = 0; j < outDim; j++) {
rawRandomNumber = box_muller(0, layerStd);
randomMatrix[i].push(roundNumber(rawRandomNumber, precision));
}
}
return randomMatrix;
};
var createModelParameters = (modelConfig) => {
try {
const parameters = {};
var inDim;
var outDim = modelConfig.nFeatures;
const nLayers = modelConfig.neurons.length;
const precision = modelConfig.decimalPrecision;
for (var l = 0; l < nLayers; l++) {
if (l === 0) {
inDim = modelConfig.nFeatures;
} else {
inDim = modelConfig.neurons[l - 1];
}
outDim = modelConfig.neurons[l];
parameters[`biases-${l}-${l + 1}`] = generateRandomMatrix(1, outDim, precision);
parameters[`weights-${l}-${l + 1}`] = generateRandomMatrix(inDim, outDim, precision);
}
Object.keys(modelConfig.actionMetadata).forEach((actionGroup) => {
const nActions = modelConfig.actionMetadata[actionGroup].order.length;
const keyAppend = `${nLayers}-${actionGroup}`;
parameters[`biases-${keyAppend}`] = generateRandomMatrix(1, nActions, precision);
parameters[`weights-${keyAppend}`] = generateRandomMatrix(outDim, nActions, precision);
});
return parameters;
} catch {
throw Error("Model was not configured correctly");
}
};
// lib/inference-models/multihead-neural-net.ts
var activationMapping = {
relu,
elu,
sigmoid,
tanh,
softmax,
linear
};
var defaultActivations = {
hidden: relu,
output: softmax
};
var NeuralNetworkMultihead = class extends model_core_default {
config;
parameters;
outputActivations;
activationFunction;
constructor(modelData) {
super();
const modelValidity = isNeuralNetModelValid(modelData);
if (!modelValidity.success) {
throw Error(modelValidity.msg);
}
if (modelData.parameters === void 0) {
let actionHeads;
let actionMetadata;
if (modelData.config.actionHeads === void 0 || modelData.config.actionMetadata === void 0) {
const actionMetadataResult = createActionMetadata(modelData.config, true);
actionHeads = actionMetadataResult[0];
actionMetadata = actionMetadataResult[1];
} else {
actionHeads = modelData.config.actionHeads;
actionMetadata = modelData.config.actionMetadata;
}
this.config = {
actionHeads,
actionMetadata,
neurons: modelData.config.neurons ?? [8, 6],
movingAverageType: "Simple",
activationFunctionName: modelData.config.activationFunctionName ?? "elu",
nFeatures: modelData.config.inputDim,
decimalPrecision: 6
};
this.parameters = createModelParameters(this.config);
} else {
this.config = { ...modelData.config };
if (this.config.actionMetadata === void 0) {
const actionMetadataResult = createActionMetadata(modelData.config, true);
this.config.actionHeads = actionMetadataResult[0];
this.config.actionMetadata = actionMetadataResult[1];
}
const paramtersValid = validateNeuralNetParameters(this.config, modelData.parameters);
if (!paramtersValid.success) {
throw Error(paramtersValid.msg);
}
this.parameters = { ...modelData.parameters };
}
this.activationFunction = activationMapping[this.config.activationFunctionName];
if (this.activationFunction === void 0) {
this.activationFunction = defaultActivations["hidden"];
}
this.outputGroups = Object.keys(this.config.actionMetadata);
this.outputActivations = {};
this.outputGroups.forEach((actionGroup) => {
const currentActionData = this.config.actionMetadata[actionGroup];
this.outputActivations[actionGroup] = activationMapping[currentActionData.activationName];
if (this.outputActivations[actionGroup] === void 0) {
this.outputActivations[actionGroup] = defaultActivations["output"];
}
});
}
sliceInputs(inputs) {
if (inputs.length > 0 && inputs[0].length > this.config.nFeatures) {
return inputs.map((x) => x.slice(0, this.config.nFeatures));
}
return inputs;
}
nextLayer(currentLayer, keyAppend) {
const biasesKey = `biases-${keyAppend}`;
const weightsKey = `weights-${keyAppend}`;
return matrixElementwiseOperation(
matrixMultiplication(currentLayer, this.parameters[weightsKey]),
Array(currentLayer.length).fill(this.parameters[biasesKey][0]),
"Addition"
);
}
forwardProp(inputs) {
var cache = {};
var currentLayer = this.sliceInputs(inputs);
for (var layer = 0; layer < this.config.neurons.length; layer++) {
currentLayer = this.nextLayer(currentLayer, `${layer}-${layer + 1}`);
cache["z" + layer] = currentLayer;
currentLayer = this.activationFunction(currentLayer, false);
cache["a" + layer] = currentLayer;
}
Object.keys(this.outputActivations).forEach((actionGroup) => {
const outputRaw = this.nextLayer(currentLayer, `${this.config.neurons.length}-${actionGroup}`);
cache["z" + actionGroup] = outputRaw;
const output = this.outputActivations[actionGroup](outputRaw);
cache[actionGroup] = output;
});
return cache;
}
getProbabilities(inputs) {
const cache = this.forwardProp(inputs);
const probabilities = {};
this.outputGroups.forEach((actionGroup) => {
probabilities[actionGroup] = cache[actionGroup];
});
return probabilities;
}
selectActionOneHead(cache, outputName, row = 0) {
return this.getActionHeadOutput(cache[outputName], this.config.actionMetadata, outputName, row);
}
selectAction(inputs, row = 0) {
var actions = {};
const cache = this.forwardProp(inputs);
Object.keys(this.outputActivations).forEach((actionGroup) => {
actions = { ...actions, ...this.selectActionOneHead(cache, actionGroup, row) };
});
return actions;
}
selectMultipleActions(inputs) {
if (inputs.length <= 1) {
throw Error("State matrix must have at least 2 rows to select multiple actions");
}
const actions = [];
const cache = this.forwardProp(inputs);
const actionGroups = Object.keys(this.outputActivations);
for (let row = 0; row < inputs.length; row++) {
var currentActions = {};
actionGroups.forEach((actionGroup) => {
currentActions = { ...currentActions, ...this.selectActionOneHead(cache, actionGroup, row) };
});
actions.push(currentActions);
}
return actions;
}
};
var multihead_neural_net_default = NeuralNetworkMultihead;
// lib/utils/data-validation.ts
var DataValidation = class {
validationParams;
constructor() {
this.validationParams = {};
}
addValidationParams(modelConfig, modelType) {
if (modelType === "simple" || modelType === "neural-network") {
this.validationParams = { modelType, modelConfig };
} else {
throw Error("Invalid model type - must be 'simple' or 'neural-network'");
}
}
checkArraySizeMatch(array, targetSize, mismatchPrepend) {
if (array.length !== targetSize) {
throw Error(
`${mismatchPrepend} mismatch compared to model config: ${array.length} vs ${targetSize}`
);
}
}
checkFeatureSize(state1D, nFeatures) {
this.checkArraySizeMatch(state1D, nFeatures, "State size");
}
checkActionSize(action1D, nActions) {
this.checkArraySizeMatch(action1D, nActions, "Action array length");
}
checkValidNumber(action) {
let numberCount = 0;
action.forEach((a) => {
if (typeof a === "number") {
numberCount += 1;
}
});
if (numberCount !== action.length) {
throw Error("All continuous action values must be numbers");
}
}
checkOneHot(action) {
const counts = { zero: 0, one: 0 };
action.forEach((a) => {
if (a === 0) counts.zero += 1;
else if (a === 1) counts.one += 1;
});
const validOneHot = counts.zero === action.length - 1 && counts.one === 1;
if (!validOneHot) {
throw Error("Action is not structured as a one-hot encoded vector");
}
}
validateState(state) {
if (this.validationParams.modelType === "simple") {
if (!Number.isInteger(state)) {
throw Error("State for 'simple' model must be an integer");
}
} else if (this.validationParams.modelType === "neural-network") {
const modelConfig = this.validationParams.modelConfig;
if (isArray1D(state)) {
this.checkFeatureSize(state, modelConfig.nFeatures);
return [state];
} else if (isArray2D(state)) {
this.checkFeatureSize(state[0], modelConfig.nFeatures);
} else {
throw Error("State for 'neural-network' must be 1D or 2D array");
}
}
return state;
}
validateFrameInterval(interval) {
if (typeof interval !== "number" || !Number.isInteger(interval)) {
throw Error("Frame interval must be an integer");
} else if (interval <= 0) {
throw Error("Frame interval must be greater than 0");
}
}
validateCustomActionIntervals(customIntervals) {
const modelConfig = this.validationParams.modelConfig;
if (!modelConfig) {
throw Error("Validation parameters must be set up to use custom action intervals");
}
const formattedCustomIntervals = {};
Object.keys(customIntervals).forEach((intervalAction) => {
let validAction = false;
modelConfig.actionHeads.forEach((actionGroup) => {
const { order } = modelConfig.actionMetadata[actionGroup];
if (order.includes(intervalAction)) {
validAction = true;
if (formattedCustomIntervals[actionGroup] === void 0) {
formattedCustomIntervals[actionGroup] = {};
}
formattedCustomIntervals[actionGroup][order.indexOf(intervalAction)] = customIntervals[intervalAction];
}
});
if (!validAction) {
throw Error(`'${intervalAction}' is not a valid action`);
} else {
this.validateFrameInterval(customIntervals[intervalAction]);
}
});
return formattedCustomIntervals;
}
validateAction(action) {
const modelConfig = this.validationParams.modelConfig;
if (!modelConfig) return;
if (isArray1D(action)) {
const { order, actionType } = modelConfig.actionMetadata[modelConfig.actionHeads[0]];
if (modelConfig.multiheadBool) {
throw Error("Models specified with multiple heads, but a 1D array of actions is provided");
}
const action1D = action;
this.checkActionSize(action1D, order?.length);
if (actionType === "continuous") {
this.checkValidNumber(action1D);
} else {
this.checkOneHot(action1D);
}
return { [modelConfig.actionHeads[0]]: [action1D] };
} else if (isArray2D(action)) {
if (modelConfig.actionHeads.length === action.length) {
const formattedAction = {};
modelConfig.actionHeads.forEach((actionGroup, row) => {
this.checkActionSize(action[row], modelConfig.actionMetadata[actionGroup]?.order?.length);
if (modelConfig.actionMetadata[actionGroup]?.actionType === "continuous") {
this.checkValidNumber(action[row]);
} else {
this.checkOneHot(action[row]);
}
formattedAction[actionGroup] = [action[row]];
});
return formattedAction;
} else {
throw Error(
`Mismatch between number of heads and number of rows in action matrix: ${modelConfig.actionHeads.length} vs ${action.length}`
);
}
} else {
try {
const formattedAction = {};
modelConfig.actionHeads.forEach((actionGroup) => {
const a = action[actionGroup];
const arrayBools = { "1D": isArray1D(a), "2D": isArray2D(a) };
if (arrayBools["1D"] || arrayBools["2D"]) {
this.checkActionSize(
arrayBools["1D"] ? a : a[0],
modelConfig.actionMetadata[actionGroup].order.length
);
formattedAction[actionGroup] = arrayBools["1D"] ? [a] : a;
} else {
throw Error();
}
});
return formattedAction;
} catch {
throw Error("Invalid configuration for action data");
}
}
}
validateInstance(dataInstance) {
if (dataInstance.state !== void 0 && dataInstance.action !== void 0) {
if (this.validationParams.modelType) {
dataInstance.state = this.validateState(dataInstance.state);
dataInstance.action = this.validateAction(dataInstance.action);
}
return dataInstance;
} else {
throw Error("Invalid data instance - it should consist of a 'state' and 'action' object");
}
}
};
var data_validation_default = DataValidation;
// lib/utils/data-collection.ts
var DataCollector = class extends data_validation_default {
_currentFrame;
_prevAction;
frameInterval;
rewardThreshold;
customIntervalActions;
usingCustomIntervals;
trainingData;
constructor(frameInterval = 10) {
super();
this.frameInterval = frameInterval;
this.rewardThreshold = void 0;
this.customIntervalActions = {};
this.usingCustomIntervals = false;
this.reset();
}
_incrementFrame() {
this._currentFrame += 1;
}
setFrameInterval(interval) {
this.validateFrameInterval(interval);
this.frameInterval = interval;
}
setRewardThreshold(threshold) {
if (typeof threshold !== "number") {
throw Error("Reward threshold must be a number");
} else if (threshold <= 0) {
throw Error("Reward threshold must be a positive number");
}
this.rewardThreshold = threshold;
}
setCustomIntervalActions(customIntervals = {}) {
this.customIntervalActions = this.validateCustomActionIntervals(customIntervals);
this.usingCustomIntervals = true;
}
checkFrame(action) {
if (this.usingCustomIntervals) {
let customInterval = Infinity;
Object.keys(action).forEach((actionGroup) => {
for (let i = 0; i < action[actionGroup][0].length; i++) {
const tempInterval = this.customIntervalActions[actionGroup]?.[i];
if (tempInterval !== void 0 && action[actionGroup][0][i] && tempInterval < customInterval) {
customInterval = tempInterval;
}
}
});
if (customInterval !== Infinity) {
return this._currentFrame >= customInterval - 1;
}
}
return this._currentFrame >= this.frameInterval - 1;
}
dotProduct(v1, v2) {
return v1.reduce((total, x, idx) => x * v2[idx] + total, 0);
}
checkAction(action) {
if (!this._prevAction) {
return true;
} else {
let actionChanged = false;
Object.keys(action).forEach((actionGroup) => {
for (let i = 0; i < action[actionGroup][0].length; i++) {
if (action[actionGroup][0][i] !== this._prevAction[actionGroup][0][i]) {
actionChanged = true;
break;
}
}
});
return actionChanged;
}
}
checkReward(reward) {
if (reward !== void 0 && this.rewardThreshold !== void 0) {
return Math.abs(reward) > this.rewardThreshold;
}
return false;
}
checkEligibility(dataInstance, adjustFrameBool = false) {
const { action, reward } = dataInstance;
if (this.checkFrame(action) || this.checkAction(action) || this.checkReward(reward)) {
if (adjustFrameBool) {
this._currentFrame = 0;
}
return true;
} else {
if (adjustFrameBool) {
this._incrementFrame();
}
return false;
}
}
collect(dataInstance) {
const validDataInstance = this.validateInstance(dataInstance);
const eligible = this.checkEligibility(validDataInstance, true);
if (eligible) {
this.trainingData.push(validDataInstance);
this._prevAction = validDataInstance.action;
}
return eligible;
}
reset() {
this._currentFrame = this.frameInterval;
this.trainingData = [];
this._prevAction = null;
}
};
var data_collection_default = DataCollector;
export {
data_collection_default as DataCollector,
multihead_neural_net_default as NeuralNetworkMultihead,
tabular_model_default as TabularModel
};