|
|
|
|
|
|
|
|
|
|
|
|
|
(function() { |
|
|
|
const layerCounters = { |
|
'input': 0, |
|
'hidden': 0, |
|
'output': 0, |
|
'conv': 0, |
|
'pool': 0, |
|
'linear': 0 |
|
}; |
|
|
|
|
|
const nodeConfigTemplates = { |
|
'input': { |
|
units: 784, |
|
shape: [28, 28, 1], |
|
batchSize: 32, |
|
description: 'Input layer for raw data', |
|
parameters: 0, |
|
inputShape: null, |
|
outputShape: [784] |
|
}, |
|
'hidden': { |
|
units: 128, |
|
activation: 'relu', |
|
useBias: true, |
|
kernelInitializer: 'glorotUniform', |
|
biasInitializer: 'zeros', |
|
dropoutRate: 0.2, |
|
description: 'Dense hidden layer with ReLU activation', |
|
inputShape: null, |
|
outputShape: null |
|
}, |
|
'output': { |
|
units: 10, |
|
activation: 'softmax', |
|
useBias: true, |
|
kernelInitializer: 'glorotUniform', |
|
biasInitializer: 'zeros', |
|
description: 'Output layer with Softmax activation for classification', |
|
inputShape: null, |
|
outputShape: [10] |
|
}, |
|
'conv': { |
|
filters: 32, |
|
kernelSize: [3, 3], |
|
strides: [1, 1], |
|
padding: 'valid', |
|
activation: 'relu', |
|
useBias: true, |
|
kernelInitializer: 'glorotUniform', |
|
biasInitializer: 'zeros', |
|
description: 'Convolutional layer for feature extraction', |
|
inputShape: null, |
|
outputShape: null |
|
}, |
|
'pool': { |
|
poolSize: [2, 2], |
|
strides: [2, 2], |
|
padding: 'valid', |
|
description: 'Max pooling layer for spatial downsampling', |
|
inputShape: null, |
|
outputShape: null |
|
}, |
|
'linear': { |
|
inputFeatures: 1, |
|
outputFeatures: 1, |
|
useBias: true, |
|
activation: 'linear', |
|
learningRate: 0.01, |
|
optimizer: 'sgd', |
|
lossFunction: 'mse', |
|
biasInitializer: 'zeros', |
|
kernelInitializer: 'glorotUniform', |
|
description: 'Linear regression layer for numerical prediction', |
|
inputShape: [1], |
|
outputShape: [1] |
|
} |
|
}; |
|
|
|
|
|
const sampleData = { |
|
'mnist': { |
|
name: 'MNIST Handwritten Digits', |
|
inputShape: [28, 28, 1], |
|
numClasses: 10, |
|
trainSamples: 60000, |
|
testSamples: 10000, |
|
description: 'Dataset of handwritten digits for classification' |
|
}, |
|
'cifar10': { |
|
name: 'CIFAR-10', |
|
inputShape: [32, 32, 3], |
|
numClasses: 10, |
|
trainSamples: 50000, |
|
testSamples: 10000, |
|
description: 'Dataset of common objects like airplanes, cars, birds, etc.' |
|
}, |
|
'fashion': { |
|
name: 'Fashion MNIST', |
|
inputShape: [28, 28, 1], |
|
numClasses: 10, |
|
trainSamples: 60000, |
|
testSamples: 10000, |
|
description: 'Dataset of fashion items like shirts, shoes, bags, etc.' |
|
} |
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
function getNextLayerId(layerType) { |
|
layerCounters[layerType]++; |
|
return `${layerType}-${layerCounters[layerType]}`; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
function resetLayerCounter() { |
|
for (let key in layerCounters) { |
|
layerCounters[key] = 0; |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function createNodeConfig(layerType, customConfig = {}) { |
|
const baseConfig = { ...nodeConfigTemplates[layerType] }; |
|
|
|
|
|
const config = { ...baseConfig, ...customConfig }; |
|
|
|
|
|
if (config.parameters === undefined) { |
|
config.parameters = calculateParameters(layerType, config); |
|
} |
|
|
|
return config; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateParameters(layerType, config, prevLayerConfig = null) { |
|
let parameters = 0; |
|
|
|
switch(layerType) { |
|
case 'input': |
|
parameters = 0; |
|
break; |
|
|
|
case 'hidden': |
|
if (prevLayerConfig) { |
|
|
|
let inputUnits; |
|
if (prevLayerConfig.outputShape && Array.isArray(prevLayerConfig.outputShape)) { |
|
inputUnits = prevLayerConfig.outputShape.reduce((a, b) => a * b, 1); |
|
} else if (prevLayerConfig.units) { |
|
inputUnits = prevLayerConfig.units; |
|
} else if (prevLayerConfig.shape) { |
|
inputUnits = prevLayerConfig.shape.reduce((a, b) => a * b, 1); |
|
} else { |
|
inputUnits = 784; |
|
} |
|
|
|
|
|
parameters = inputUnits * config.units; |
|
|
|
|
|
if (config.useBias) { |
|
parameters += config.units; |
|
} |
|
} |
|
break; |
|
|
|
case 'output': |
|
if (prevLayerConfig) { |
|
|
|
let inputUnits; |
|
if (prevLayerConfig.outputShape && Array.isArray(prevLayerConfig.outputShape)) { |
|
inputUnits = prevLayerConfig.outputShape.reduce((a, b) => a * b, 1); |
|
} else if (prevLayerConfig.units) { |
|
inputUnits = prevLayerConfig.units; |
|
} else { |
|
inputUnits = 128; |
|
} |
|
|
|
|
|
parameters = inputUnits * config.units; |
|
|
|
|
|
if (config.useBias) { |
|
parameters += config.units; |
|
} |
|
} |
|
break; |
|
|
|
case 'conv': |
|
if (prevLayerConfig) { |
|
|
|
let inputChannels; |
|
if (prevLayerConfig.outputShape && prevLayerConfig.outputShape.length > 2) { |
|
inputChannels = prevLayerConfig.outputShape[2]; |
|
} else if (prevLayerConfig.shape && prevLayerConfig.shape.length > 2) { |
|
inputChannels = prevLayerConfig.shape[2]; |
|
} else if (prevLayerConfig.filters) { |
|
inputChannels = prevLayerConfig.filters; |
|
} else { |
|
inputChannels = 1; |
|
} |
|
|
|
|
|
const kernelSize = Array.isArray(config.kernelSize) ? |
|
config.kernelSize[0] * config.kernelSize[1] : |
|
config.kernelSize * config.kernelSize; |
|
|
|
parameters = kernelSize * inputChannels * config.filters; |
|
|
|
|
|
if (config.useBias) { |
|
parameters += config.filters; |
|
} |
|
|
|
|
|
if (prevLayerConfig.shape || prevLayerConfig.outputShape) { |
|
const inputShape = prevLayerConfig.outputShape || prevLayerConfig.shape; |
|
const padding = config.padding === 'same' ? Math.floor(config.kernelSize[0] / 2) : 0; |
|
const outputHeight = Math.floor((inputShape[0] - config.kernelSize[0] + 2 * padding) / config.strides[0]) + 1; |
|
const outputWidth = Math.floor((inputShape[1] - config.kernelSize[1] + 2 * padding) / config.strides[1]) + 1; |
|
|
|
config.outputShape = [outputHeight, outputWidth, config.filters]; |
|
} |
|
} |
|
break; |
|
|
|
case 'pool': |
|
parameters = 0; |
|
|
|
|
|
if (prevLayerConfig && (prevLayerConfig.shape || prevLayerConfig.outputShape)) { |
|
const inputShape = prevLayerConfig.outputShape || prevLayerConfig.shape; |
|
const padding = config.padding === 'same' ? Math.floor(config.poolSize[0] / 2) : 0; |
|
const outputHeight = Math.floor((inputShape[0] - config.poolSize[0] + 2 * padding) / config.strides[0]) + 1; |
|
const outputWidth = Math.floor((inputShape[1] - config.poolSize[1] + 2 * padding) / config.strides[1]) + 1; |
|
|
|
config.outputShape = [outputHeight, outputWidth, inputShape[2]]; |
|
} |
|
break; |
|
|
|
default: |
|
parameters = 0; |
|
} |
|
|
|
return parameters; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateFLOPs(layerType, config, inputDims) { |
|
let flops = 0; |
|
|
|
switch(layerType) { |
|
case 'input': |
|
flops = 0; |
|
break; |
|
|
|
case 'hidden': |
|
|
|
flops = 2 * inputDims.reduce((a, b) => a * b, 1) * config.units; |
|
break; |
|
|
|
case 'output': |
|
|
|
flops = 2 * inputDims.reduce((a, b) => a * b, 1) * config.units; |
|
break; |
|
|
|
case 'conv': |
|
|
|
const outputHeight = Math.floor((inputDims[0] - config.kernelSize[0] + 2 * |
|
(config.padding === 'same' ? config.kernelSize[0] / 2 : 0)) / |
|
config.strides[0] + 1); |
|
|
|
const outputWidth = Math.floor((inputDims[1] - config.kernelSize[1] + 2 * |
|
(config.padding === 'same' ? config.kernelSize[1] / 2 : 0)) / |
|
config.strides[1] + 1); |
|
|
|
|
|
const flopsPerPoint = 2 * config.kernelSize[0] * config.kernelSize[1] * inputDims[2]; |
|
|
|
|
|
flops = outputHeight * outputWidth * flopsPerPoint * config.filters; |
|
break; |
|
|
|
case 'pool': |
|
|
|
const poolOutputHeight = Math.floor((inputDims[0] - config.poolSize[0]) / |
|
config.strides[0] + 1); |
|
|
|
const poolOutputWidth = Math.floor((inputDims[1] - config.poolSize[1]) / |
|
config.strides[1] + 1); |
|
|
|
|
|
flops = poolOutputHeight * poolOutputWidth * inputDims[2] * |
|
config.poolSize[0] * config.poolSize[1]; |
|
break; |
|
|
|
default: |
|
flops = 0; |
|
} |
|
|
|
return flops; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function calculateMemoryUsage(layerType, config, batchSize = 32) { |
|
|
|
const bytesPerParam = 4; |
|
let outputShape = []; |
|
let parameters = 0; |
|
let activationMemory = 0; |
|
|
|
switch(layerType) { |
|
case 'input': |
|
outputShape = config.shape || [28, 28, 1]; |
|
parameters = 0; |
|
break; |
|
|
|
case 'hidden': |
|
outputShape = [config.units]; |
|
parameters = config.parameters || 0; |
|
break; |
|
|
|
case 'output': |
|
outputShape = [config.units]; |
|
parameters = config.parameters || 0; |
|
break; |
|
|
|
case 'conv': |
|
|
|
const inputShape = config.inputShape || [28, 28, 1]; |
|
const outputHeight = Math.floor((inputShape[0] - config.kernelSize[0] + 2 * |
|
(config.padding === 'same' ? config.kernelSize[0] / 2 : 0)) / |
|
config.strides[0] + 1); |
|
|
|
const outputWidth = Math.floor((inputShape[1] - config.kernelSize[1] + 2 * |
|
(config.padding === 'same' ? config.kernelSize[1] / 2 : 0)) / |
|
config.strides[1] + 1); |
|
|
|
outputShape = [outputHeight, outputWidth, config.filters]; |
|
parameters = config.parameters || 0; |
|
break; |
|
|
|
case 'pool': |
|
const poolInputShape = config.inputShape || [28, 28, 32]; |
|
const poolOutputHeight = Math.floor((poolInputShape[0] - config.poolSize[0]) / |
|
config.strides[0] + 1); |
|
|
|
const poolOutputWidth = Math.floor((poolInputShape[1] - config.poolSize[1]) / |
|
config.strides[1] + 1); |
|
|
|
outputShape = [poolOutputHeight, poolOutputWidth, poolInputShape[2]]; |
|
parameters = 0; |
|
break; |
|
|
|
default: |
|
outputShape = [0]; |
|
parameters = 0; |
|
} |
|
|
|
|
|
activationMemory = batchSize * outputShape.reduce((a, b) => a * b, 1) * bytesPerParam; |
|
|
|
|
|
const paramMemory = parameters * bytesPerParam; |
|
|
|
return { |
|
parameters: parameters, |
|
paramMemory: paramMemory, |
|
activationMemory: activationMemory, |
|
totalMemory: paramMemory + activationMemory, |
|
outputShape: outputShape |
|
}; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function generateLayerDescription(layerType, config) { |
|
let description = ''; |
|
|
|
switch(layerType) { |
|
case 'input': |
|
description = `Input Layer: Shape=${config.shape.join('×')}`; |
|
break; |
|
|
|
case 'hidden': |
|
description = `Dense Layer: ${config.units} units, ${config.activation} activation`; |
|
if (config.dropoutRate > 0) { |
|
description += `, dropout ${config.dropoutRate}`; |
|
} |
|
break; |
|
|
|
case 'output': |
|
description = `Output Layer: ${config.units} units, ${config.activation} activation`; |
|
break; |
|
|
|
case 'conv': |
|
description = `Conv2D: ${config.filters} filters, ${config.kernelSize.join('×')} kernel, ${config.activation} activation`; |
|
break; |
|
|
|
case 'pool': |
|
description = `MaxPooling2D: ${config.poolSize.join('×')} pool size`; |
|
break; |
|
|
|
default: |
|
description = 'Unknown layer type'; |
|
} |
|
|
|
return description; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function validateNetwork(layers, connections) { |
|
const errors = []; |
|
|
|
|
|
const inputLayers = layers.filter(layer => layer.type === 'input'); |
|
if (inputLayers.length === 0) { |
|
errors.push('Network must have at least one input layer'); |
|
} else if (inputLayers.length > 1) { |
|
errors.push('Network can have only one input layer'); |
|
} |
|
|
|
|
|
const outputLayers = layers.filter(layer => layer.type === 'output'); |
|
if (outputLayers.length === 0) { |
|
errors.push('Network must have at least one output layer'); |
|
} |
|
|
|
|
|
const connectedNodes = new Set(); |
|
connections.forEach(conn => { |
|
connectedNodes.add(conn.source); |
|
connectedNodes.add(conn.target); |
|
}); |
|
|
|
const isolatedNodes = layers.filter(layer => !connectedNodes.has(layer.id)); |
|
if (isolatedNodes.length > 0) { |
|
isolatedNodes.forEach(node => { |
|
if (node.type !== 'input' && node.type !== 'output') { |
|
errors.push(`Layer "${node.name}" (${node.id}) is isolated`); |
|
} |
|
}); |
|
} |
|
|
|
|
|
inputLayers.forEach(layer => { |
|
const incomingConnections = connections.filter(conn => conn.target === layer.id); |
|
if (incomingConnections.length > 0) { |
|
errors.push(`Input layer "${layer.name}" cannot have incoming connections`); |
|
} |
|
}); |
|
|
|
|
|
outputLayers.forEach(layer => { |
|
const outgoingConnections = connections.filter(conn => conn.source === layer.id); |
|
if (outgoingConnections.length > 0) { |
|
errors.push(`Output layer "${layer.name}" cannot have outgoing connections`); |
|
} |
|
}); |
|
|
|
return { |
|
valid: errors.length === 0, |
|
errors: errors |
|
}; |
|
} |
|
|
|
|
|
window.neuralNetwork = { |
|
getNextLayerId, |
|
resetLayerCounter, |
|
createNodeConfig, |
|
calculateParameters, |
|
calculateFLOPs, |
|
calculateMemoryUsage, |
|
generateLayerDescription, |
|
validateNetwork, |
|
nodeConfigTemplates, |
|
sampleData |
|
}; |
|
})(); |