|
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
console.log('Neural Network Playground Initialized'); |
|
|
|
|
|
const canvas = document.getElementById('network-canvas'); |
|
const tooltip = document.createElement('div'); |
|
tooltip.className = 'canvas-tooltip'; |
|
tooltip.innerHTML = ` |
|
<div class="tooltip-header"></div> |
|
<div class="tooltip-content"></div> |
|
`; |
|
document.body.appendChild(tooltip); |
|
|
|
|
|
initializeDragAndDrop(); |
|
|
|
|
|
let networkConfig = { |
|
learningRate: 0.01, |
|
activation: 'relu', |
|
batchSize: 32, |
|
epochs: 10 |
|
}; |
|
|
|
|
|
setupUIControls(); |
|
|
|
|
|
setupLayerEditor(); |
|
|
|
|
|
document.addEventListener('networkUpdated', handleNetworkUpdate); |
|
|
|
|
|
document.addEventListener('openLayerEditor', handleOpenLayerEditor); |
|
|
|
|
|
function setupUIControls() { |
|
|
|
const learningRateSlider = document.getElementById('learning-rate'); |
|
const learningRateValue = document.getElementById('learning-rate-value'); |
|
|
|
if (learningRateSlider && learningRateValue) { |
|
learningRateSlider.value = networkConfig.learningRate; |
|
learningRateValue.textContent = networkConfig.learningRate.toFixed(3); |
|
|
|
learningRateSlider.addEventListener('input', (e) => { |
|
networkConfig.learningRate = parseFloat(e.target.value); |
|
learningRateValue.textContent = networkConfig.learningRate.toFixed(3); |
|
}); |
|
} |
|
|
|
|
|
const activationSelect = document.getElementById('activation'); |
|
if (activationSelect) { |
|
activationSelect.value = networkConfig.activation; |
|
|
|
activationSelect.addEventListener('change', (e) => { |
|
networkConfig.activation = e.target.value; |
|
updateActivationFunctionGraph(networkConfig.activation); |
|
}); |
|
} |
|
|
|
|
|
updateActivationFunctionGraph(networkConfig.activation); |
|
|
|
|
|
const sampleItems = document.querySelectorAll('.sample-item'); |
|
sampleItems.forEach(item => { |
|
item.addEventListener('click', () => { |
|
const sampleId = item.getAttribute('data-sample'); |
|
handleSampleSelection(sampleId); |
|
}); |
|
}); |
|
|
|
|
|
const runButton = document.getElementById('run-network'); |
|
if (runButton) { |
|
runButton.addEventListener('click', runNetwork); |
|
} |
|
|
|
const clearButton = document.getElementById('clear-canvas'); |
|
if (clearButton) { |
|
clearButton.addEventListener('click', clearCanvas); |
|
} |
|
|
|
|
|
setupModals(); |
|
} |
|
|
|
|
|
function setupModals() { |
|
const aboutModal = document.getElementById('about-modal'); |
|
const aboutLink = document.getElementById('about-link'); |
|
|
|
if (aboutLink && aboutModal) { |
|
aboutLink.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
openModal(aboutModal); |
|
}); |
|
|
|
const closeButtons = aboutModal.querySelectorAll('.close-modal'); |
|
closeButtons.forEach(btn => { |
|
btn.addEventListener('click', () => { |
|
closeModal(aboutModal); |
|
}); |
|
}); |
|
|
|
|
|
aboutModal.addEventListener('click', (e) => { |
|
if (e.target === aboutModal) { |
|
closeModal(aboutModal); |
|
} |
|
}); |
|
} |
|
} |
|
|
|
|
|
function setupLayerEditor() { |
|
const layerEditorModal = document.getElementById('layer-editor-modal'); |
|
|
|
if (layerEditorModal) { |
|
const closeButtons = layerEditorModal.querySelectorAll('.close-modal'); |
|
closeButtons.forEach(btn => { |
|
btn.addEventListener('click', () => { |
|
closeModal(layerEditorModal); |
|
}); |
|
}); |
|
|
|
|
|
layerEditorModal.addEventListener('click', (e) => { |
|
if (e.target === layerEditorModal) { |
|
closeModal(layerEditorModal); |
|
} |
|
}); |
|
|
|
|
|
const saveButton = layerEditorModal.querySelector('.save-layer-btn'); |
|
if (saveButton) { |
|
saveButton.addEventListener('click', saveLayerConfig); |
|
} |
|
} |
|
} |
|
|
|
|
|
function openModal(modal) { |
|
if (modal) { |
|
modal.style.display = 'flex'; |
|
} |
|
} |
|
|
|
|
|
function closeModal(modal) { |
|
if (modal) { |
|
modal.style.display = 'none'; |
|
} |
|
} |
|
|
|
|
|
function handleNetworkUpdate(e) { |
|
const networkLayers = e.detail; |
|
console.log('Network updated:', networkLayers); |
|
|
|
|
|
updatePropertiesPanel(networkLayers); |
|
} |
|
|
|
|
|
function updatePropertiesPanel(networkLayers) { |
|
const propertiesPanel = document.querySelector('.props-panel'); |
|
if (!propertiesPanel) return; |
|
|
|
|
|
const propsContent = propertiesPanel.querySelector('.props-content'); |
|
if (!propsContent) return; |
|
|
|
|
|
const layerCount = networkLayers.layers.length; |
|
const connectionCount = networkLayers.connections.length; |
|
|
|
let layerTypeCounts = {}; |
|
networkLayers.layers.forEach(layer => { |
|
layerTypeCounts[layer.type] = (layerTypeCounts[layer.type] || 0) + 1; |
|
}); |
|
|
|
|
|
const validationResult = window.neuralNetwork.validateNetwork( |
|
networkLayers.layers, |
|
networkLayers.connections |
|
); |
|
|
|
|
|
let networkArchitectureHTML = ` |
|
<div class="props-section"> |
|
<div class="props-heading"> |
|
<i class="icon">🔍</i> Network Architecture |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Total Layers</div> |
|
<div class="props-value">${layerCount}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Connections</div> |
|
<div class="props-value">${connectionCount}</div> |
|
</div> |
|
`; |
|
|
|
|
|
Object.entries(layerTypeCounts).forEach(([type, count]) => { |
|
networkArchitectureHTML += ` |
|
<div class="props-row"> |
|
<div class="props-key">${type.charAt(0).toUpperCase() + type.slice(1)} Layers</div> |
|
<div class="props-value">${count}</div> |
|
</div> |
|
`; |
|
}); |
|
|
|
|
|
networkArchitectureHTML += ` |
|
<div class="props-row"> |
|
<div class="props-key">Validity</div> |
|
<div class="props-value" style="color: ${validationResult.valid ? 'var(--secondary-color)' : 'var(--warning-color)'}"> |
|
${validationResult.valid ? 'Valid' : 'Invalid'} |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
if (!validationResult.valid && validationResult.errors.length > 0) { |
|
networkArchitectureHTML += ` |
|
<div class="props-row"> |
|
<div class="props-key">Errors</div> |
|
<div class="props-value" style="color: var(--warning-color)"> |
|
${validationResult.errors.join('<br>')} |
|
</div> |
|
</div> |
|
`; |
|
} |
|
|
|
networkArchitectureHTML += `</div>`; |
|
|
|
|
|
let totalParameters = 0; |
|
let totalFlops = 0; |
|
let totalMemory = 0; |
|
|
|
if (layerCount > 0) { |
|
|
|
const modelStatsHTML = ` |
|
<div class="props-section"> |
|
<div class="props-heading"> |
|
<i class="icon">📊</i> Model Statistics |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Parameters</div> |
|
<div class="props-value">${formatNumber(totalParameters)}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">FLOPs</div> |
|
<div class="props-value">${formatNumber(totalFlops)}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Memory</div> |
|
<div class="props-value">${formatMemorySize(totalMemory)}</div> |
|
</div> |
|
</div> |
|
`; |
|
|
|
|
|
propsContent.innerHTML = networkArchitectureHTML + modelStatsHTML; |
|
} else { |
|
|
|
propsContent.innerHTML = networkArchitectureHTML; |
|
} |
|
} |
|
|
|
|
|
function formatNumber(num) { |
|
if (num === 0) return '0'; |
|
if (!num) return 'N/A'; |
|
|
|
if (num >= 1e9) return (num / 1e9).toFixed(2) + 'B'; |
|
if (num >= 1e6) return (num / 1e6).toFixed(2) + 'M'; |
|
if (num >= 1e3) return (num / 1e3).toFixed(2) + 'K'; |
|
return num.toString(); |
|
} |
|
|
|
|
|
function formatMemorySize(bytes) { |
|
if (bytes === 0) return '0 Bytes'; |
|
if (!bytes) return 'N/A'; |
|
|
|
const k = 1024; |
|
const sizes = ['Bytes', 'KB', 'MB', 'GB']; |
|
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; |
|
} |
|
|
|
|
|
function handleOpenLayerEditor(e) { |
|
const node = e.detail.node; |
|
const nodeType = node.getAttribute('data-type'); |
|
const layerId = node.getAttribute('data-id'); |
|
|
|
|
|
const layerConfig = node.layerConfig || window.neuralNetwork.createNodeConfig(nodeType); |
|
|
|
|
|
const modalTitle = document.querySelector('.layer-editor-modal .modal-title'); |
|
if (modalTitle) { |
|
modalTitle.textContent = `Edit ${nodeType.charAt(0).toUpperCase() + nodeType.slice(1)} Layer`; |
|
} |
|
|
|
|
|
const layerForm = document.querySelector('.layer-form'); |
|
if (!layerForm) return; |
|
|
|
|
|
layerForm.innerHTML = ''; |
|
|
|
|
|
switch (nodeType) { |
|
case 'input': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Input Dimensions:</label> |
|
<div class="form-row"> |
|
<input type="number" id="input-height" min="1" value="${layerConfig.shape[0]}" placeholder="Height"> |
|
<input type="number" id="input-width" min="1" value="${layerConfig.shape[1]}" placeholder="Width"> |
|
<input type="number" id="input-channels" min="1" value="${layerConfig.shape[2]}" placeholder="Channels"> |
|
</div> |
|
<small>Input shape: [${layerConfig.shape.join(' × ')}]</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Batch Size:</label> |
|
<input type="number" id="batch-size" min="1" value="${layerConfig.batchSize}" placeholder="Batch Size"> |
|
</div> |
|
`; |
|
break; |
|
|
|
case 'hidden': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Units:</label> |
|
<input type="number" id="hidden-units" min="1" value="${layerConfig.units}" placeholder="Number of units"> |
|
<small>Output shape: [${layerConfig.units}]</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Activation Function:</label> |
|
<select id="hidden-activation"> |
|
<option value="relu" ${layerConfig.activation === 'relu' ? 'selected' : ''}>ReLU</option> |
|
<option value="sigmoid" ${layerConfig.activation === 'sigmoid' ? 'selected' : ''}>Sigmoid</option> |
|
<option value="tanh" ${layerConfig.activation === 'tanh' ? 'selected' : ''}>Tanh</option> |
|
<option value="leaky_relu" ${layerConfig.activation === 'leaky_relu' ? 'selected' : ''}>Leaky ReLU</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label>Dropout Rate:</label> |
|
<input type="range" id="dropout-rate" min="0" max="0.9" step="0.1" value="${layerConfig.dropoutRate}"> |
|
<span id="dropout-value">${layerConfig.dropoutRate}</span> |
|
</div> |
|
<div class="form-group"> |
|
<label>Use Bias:</label> |
|
<input type="checkbox" id="use-bias" ${layerConfig.useBias ? 'checked' : ''}> |
|
</div> |
|
`; |
|
|
|
|
|
setTimeout(() => { |
|
const dropoutSlider = document.getElementById('dropout-rate'); |
|
const dropoutValue = document.getElementById('dropout-value'); |
|
if (dropoutSlider && dropoutValue) { |
|
dropoutSlider.addEventListener('input', (e) => { |
|
dropoutValue.textContent = e.target.value; |
|
}); |
|
} |
|
}, 100); |
|
break; |
|
|
|
case 'output': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Units:</label> |
|
<input type="number" id="output-units" min="1" value="${layerConfig.units}" placeholder="Number of output units"> |
|
<small>Output shape: [${layerConfig.units}]</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Activation Function:</label> |
|
<select id="output-activation"> |
|
<option value="softmax" ${layerConfig.activation === 'softmax' ? 'selected' : ''}>Softmax (Classification)</option> |
|
<option value="sigmoid" ${layerConfig.activation === 'sigmoid' ? 'selected' : ''}>Sigmoid (Binary Classification)</option> |
|
<option value="linear" ${layerConfig.activation === 'linear' ? 'selected' : ''}>Linear (Regression)</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label>Use Bias:</label> |
|
<input type="checkbox" id="output-use-bias" ${layerConfig.useBias ? 'checked' : ''}> |
|
</div> |
|
`; |
|
break; |
|
|
|
case 'conv': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Filters:</label> |
|
<input type="number" id="conv-filters" min="1" value="${layerConfig.filters}" placeholder="Number of filters"> |
|
<small>Output channels</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Kernel Size:</label> |
|
<div class="form-row"> |
|
<input type="number" id="kernel-size-h" min="1" max="7" value="${layerConfig.kernelSize[0]}" placeholder="Height"> |
|
<input type="number" id="kernel-size-w" min="1" max="7" value="${layerConfig.kernelSize[1]}" placeholder="Width"> |
|
</div> |
|
<small>Filter dimensions: ${layerConfig.kernelSize.join(' × ')}</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Strides:</label> |
|
<div class="form-row"> |
|
<input type="number" id="stride-h" min="1" max="4" value="${layerConfig.strides[0]}" placeholder="Height"> |
|
<input type="number" id="stride-w" min="1" max="4" value="${layerConfig.strides[1]}" placeholder="Width"> |
|
</div> |
|
</div> |
|
<div class="form-group"> |
|
<label>Padding:</label> |
|
<select id="padding-type"> |
|
<option value="valid" ${layerConfig.padding === 'valid' ? 'selected' : ''}>Valid (No Padding)</option> |
|
<option value="same" ${layerConfig.padding === 'same' ? 'selected' : ''}>Same (Preserve Dimensions)</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label>Activation Function:</label> |
|
<select id="conv-activation"> |
|
<option value="relu" ${layerConfig.activation === 'relu' ? 'selected' : ''}>ReLU</option> |
|
<option value="sigmoid" ${layerConfig.activation === 'sigmoid' ? 'selected' : ''}>Sigmoid</option> |
|
<option value="tanh" ${layerConfig.activation === 'tanh' ? 'selected' : ''}>Tanh</option> |
|
<option value="leaky_relu" ${layerConfig.activation === 'leaky_relu' ? 'selected' : ''}>Leaky ReLU</option> |
|
</select> |
|
</div> |
|
`; |
|
break; |
|
|
|
case 'pool': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Pool Size:</label> |
|
<div class="form-row"> |
|
<input type="number" id="pool-size-h" min="1" max="4" value="${layerConfig.poolSize[0]}" placeholder="Height"> |
|
<input type="number" id="pool-size-w" min="1" max="4" value="${layerConfig.poolSize[1]}" placeholder="Width"> |
|
</div> |
|
</div> |
|
<div class="form-group"> |
|
<label>Strides:</label> |
|
<div class="form-row"> |
|
<input type="number" id="pool-stride-h" min="1" max="4" value="${layerConfig.strides[0]}" placeholder="Height"> |
|
<input type="number" id="pool-stride-w" min="1" max="4" value="${layerConfig.strides[1]}" placeholder="Width"> |
|
</div> |
|
</div> |
|
<div class="form-group"> |
|
<label>Padding:</label> |
|
<select id="pool-padding"> |
|
<option value="valid" ${layerConfig.padding === 'valid' ? 'selected' : ''}>Valid (No Padding)</option> |
|
<option value="same" ${layerConfig.padding === 'same' ? 'selected' : ''}>Same (Preserve Dimensions)</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label>Pool Type:</label> |
|
<select id="pool-type"> |
|
<option value="max" selected>Max Pooling</option> |
|
<option value="avg">Average Pooling</option> |
|
</select> |
|
</div> |
|
`; |
|
break; |
|
|
|
case 'linear': |
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Input Features:</label> |
|
<input type="number" id="input-features" min="1" value="${layerConfig.inputFeatures}" placeholder="Number of input features"> |
|
<small>Input shape: [${layerConfig.inputFeatures}]</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Output Features:</label> |
|
<input type="number" id="output-features" min="1" value="${layerConfig.outputFeatures}" placeholder="Number of output features"> |
|
<small>Output shape: [${layerConfig.outputFeatures}]</small> |
|
</div> |
|
<div class="form-group"> |
|
<label>Use Bias:</label> |
|
<input type="checkbox" id="linear-use-bias" ${layerConfig.useBias ? 'checked' : ''}> |
|
</div> |
|
<div class="form-group"> |
|
<label>Learning Rate:</label> |
|
<input type="range" id="learning-rate-slider" min="0.001" max="0.1" step="0.001" value="${layerConfig.learningRate}"> |
|
<span id="learning-rate-value">${layerConfig.learningRate}</span> |
|
</div> |
|
<div class="form-group"> |
|
<label>Loss Function:</label> |
|
<select id="loss-function"> |
|
<option value="mse" ${layerConfig.lossFunction === 'mse' ? 'selected' : ''}>Mean Squared Error</option> |
|
<option value="mae" ${layerConfig.lossFunction === 'mae' ? 'selected' : ''}>Mean Absolute Error</option> |
|
<option value="huber" ${layerConfig.lossFunction === 'huber' ? 'selected' : ''}>Huber Loss</option> |
|
</select> |
|
</div> |
|
<div class="form-group"> |
|
<label>Optimizer:</label> |
|
<select id="optimizer"> |
|
<option value="sgd" ${layerConfig.optimizer === 'sgd' ? 'selected' : ''}>Stochastic Gradient Descent</option> |
|
<option value="adam" ${layerConfig.optimizer === 'adam' ? 'selected' : ''}>Adam</option> |
|
<option value="rmsprop" ${layerConfig.optimizer === 'rmsprop' ? 'selected' : ''}>RMSprop</option> |
|
</select> |
|
</div> |
|
`; |
|
|
|
|
|
setTimeout(() => { |
|
const learningRateSlider = document.getElementById('learning-rate-slider'); |
|
const learningRateValue = document.getElementById('learning-rate-value'); |
|
if (learningRateSlider && learningRateValue) { |
|
learningRateSlider.addEventListener('input', (e) => { |
|
learningRateValue.textContent = parseFloat(e.target.value).toFixed(3); |
|
}); |
|
} |
|
}, 100); |
|
break; |
|
|
|
default: |
|
layerForm.innerHTML = '<p>No editable properties for this layer type.</p>'; |
|
} |
|
|
|
|
|
if (nodeType !== 'input') { |
|
const parameterCount = window.neuralNetwork.calculateParameters(nodeType, layerConfig); |
|
if (parameterCount) { |
|
layerForm.innerHTML += ` |
|
<div class="form-group"> |
|
<label>Parameter Summary:</label> |
|
<div class="parameters-summary"> |
|
<p>Total parameters: <strong>${formatNumber(parameterCount)}</strong></p> |
|
<p>Memory usage (32-bit): ~${formatMemorySize(parameterCount * 4)}</p> |
|
</div> |
|
</div> |
|
`; |
|
} |
|
} |
|
|
|
|
|
layerForm.innerHTML += ` |
|
<div class="form-buttons"> |
|
<button type="button" id="save-layer-config" class="btn-primary">Save Changes</button> |
|
<button type="button" id="cancel-layer-edit" class="btn-secondary">Cancel</button> |
|
</div> |
|
`; |
|
|
|
|
|
const modal = document.getElementById('layer-editor-modal'); |
|
if (modal) { |
|
openModal(modal); |
|
|
|
|
|
const saveButton = document.getElementById('save-layer-config'); |
|
if (saveButton) { |
|
saveButton.addEventListener('click', () => { |
|
saveLayerConfig(node, nodeType, layerId); |
|
closeModal(modal); |
|
}); |
|
} |
|
|
|
const cancelButton = document.getElementById('cancel-layer-edit'); |
|
if (cancelButton) { |
|
cancelButton.addEventListener('click', () => { |
|
closeModal(modal); |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
function saveLayerConfig(node, nodeType, layerId) { |
|
|
|
const form = document.querySelector('.layer-form'); |
|
if (!form) return; |
|
|
|
const values = {}; |
|
const inputs = form.querySelectorAll('input, select'); |
|
inputs.forEach(input => { |
|
values[input.id] = input.value; |
|
}); |
|
|
|
|
|
node.layerConfig = { |
|
type: nodeType, |
|
shape: [ |
|
parseInt(values['input-height']), |
|
parseInt(values['input-width']), |
|
parseInt(values['input-channels']) |
|
], |
|
batchSize: parseInt(values['batch-size']), |
|
units: parseInt(values['hidden-units']), |
|
activation: values['hidden-activation'], |
|
dropoutRate: parseFloat(values['dropout-rate']), |
|
useBias: values['use-bias'] === 'true', |
|
learningRate: parseFloat(values['learning-rate-slider']), |
|
lossFunction: values['loss-function'], |
|
optimizer: values['optimizer'], |
|
inputFeatures: parseInt(values['input-features']), |
|
outputFeatures: parseInt(values['output-features']) |
|
}; |
|
|
|
|
|
const nodeTitle = node.querySelector('.node-title'); |
|
if (nodeTitle) { |
|
nodeTitle.textContent = nodeType.charAt(0).toUpperCase() + nodeType.slice(1); |
|
} |
|
|
|
|
|
node.setAttribute('data-name', nodeType.charAt(0).toUpperCase() + nodeType.slice(1)); |
|
|
|
|
|
let dimensions = ''; |
|
switch (nodeType) { |
|
case 'input': |
|
dimensions = values['input-height'] + ' × ' + values['input-width'] + ' × ' + values['input-channels']; |
|
break; |
|
|
|
case 'hidden': |
|
case 'output': |
|
dimensions = values['hidden-units']; |
|
break; |
|
|
|
case 'conv': |
|
dimensions = values['conv-filters'] + ' × ' + values['kernel-size-h'] + ' × ' + values['kernel-size-w']; |
|
break; |
|
|
|
case 'pool': |
|
dimensions = values['pool-size-h'] + ' × ' + values['pool-size-w']; |
|
break; |
|
|
|
case 'linear': |
|
dimensions = values['input-features'] + ' → ' + values['output-features']; |
|
break; |
|
} |
|
|
|
|
|
const nodeDimensions = node.querySelector('.node-dimensions'); |
|
if (nodeDimensions) { |
|
nodeDimensions.textContent = dimensions; |
|
} |
|
|
|
|
|
node.setAttribute('data-dimensions', dimensions); |
|
|
|
|
|
const networkLayers = window.dragDrop.getNetworkArchitecture(); |
|
const layerIndex = networkLayers.layers.findIndex(layer => layer.id === layerId); |
|
|
|
if (layerIndex !== -1) { |
|
networkLayers.layers[layerIndex].name = nodeType.charAt(0).toUpperCase() + nodeType.slice(1); |
|
networkLayers.layers[layerIndex].dimensions = dimensions; |
|
} |
|
|
|
|
|
const event = new CustomEvent('networkUpdated', { detail: networkLayers }); |
|
document.dispatchEvent(event); |
|
} |
|
|
|
|
|
function handleSampleSelection(sampleId) { |
|
|
|
document.querySelectorAll('.sample-item').forEach(item => { |
|
item.classList.remove('active'); |
|
if (item.getAttribute('data-sample') === sampleId) { |
|
item.classList.add('active'); |
|
} |
|
}); |
|
|
|
|
|
const sampleData = window.neuralNetwork.sampleData[sampleId]; |
|
if (!sampleData) return; |
|
|
|
console.log(`Selected sample: ${sampleData.name}`); |
|
|
|
|
|
const propertiesPanel = document.querySelector('.props-panel'); |
|
if (!propertiesPanel) return; |
|
|
|
const propsContent = propertiesPanel.querySelector('.props-content'); |
|
if (!propsContent) return; |
|
|
|
propsContent.innerHTML = ` |
|
<div class="props-section"> |
|
<div class="props-heading"> |
|
<i class="icon">📊</i> ${sampleData.name} |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Input Shape</div> |
|
<div class="props-value">${sampleData.inputShape.join(' × ')}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Classes</div> |
|
<div class="props-value">${sampleData.numClasses}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Training Samples</div> |
|
<div class="props-value">${sampleData.trainSamples.toLocaleString()}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Test Samples</div> |
|
<div class="props-value">${sampleData.testSamples.toLocaleString()}</div> |
|
</div> |
|
<div class="props-row"> |
|
<div class="props-key">Description</div> |
|
<div class="props-value">${sampleData.description}</div> |
|
</div> |
|
</div> |
|
|
|
<div class="props-section"> |
|
<p class="hint-text">Click "Run Network" to train on this dataset</p> |
|
</div> |
|
`; |
|
} |
|
|
|
|
|
function runNetwork() { |
|
console.log('Running neural network simulation with config:', networkConfig); |
|
|
|
|
|
const networkLayers = window.dragDrop.getNetworkArchitecture(); |
|
|
|
|
|
if (networkLayers.layers.length === 0) { |
|
alert('Please add some nodes to the network first!'); |
|
return; |
|
} |
|
|
|
|
|
const validationResult = window.neuralNetwork.validateNetwork( |
|
networkLayers.layers, |
|
networkLayers.connections |
|
); |
|
|
|
if (!validationResult.valid) { |
|
alert('Network is not valid: ' + validationResult.errors.join('\n')); |
|
return; |
|
} |
|
|
|
|
|
document.querySelectorAll('.canvas-node').forEach(node => { |
|
node.classList.add('highlight-pulse'); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.connection').forEach((connection, index) => { |
|
setTimeout(() => { |
|
connection.style.background = 'linear-gradient(90deg, var(--primary-color), var(--accent-color))'; |
|
|
|
|
|
setTimeout(() => { |
|
connection.style.background = ''; |
|
}, 800); |
|
}, 300 * index); |
|
}); |
|
|
|
|
|
simulateTraining(); |
|
|
|
|
|
setTimeout(() => { |
|
document.querySelectorAll('.canvas-node').forEach(node => { |
|
node.classList.remove('highlight-pulse'); |
|
}); |
|
}, 3000); |
|
} |
|
|
|
|
|
function simulateTraining() { |
|
const progressBar = document.querySelector('.progress-bar'); |
|
const lossValue = document.getElementById('loss-value'); |
|
const accuracyValue = document.getElementById('accuracy-value'); |
|
|
|
if (!progressBar || !lossValue || !accuracyValue) return; |
|
|
|
|
|
progressBar.style.width = '0%'; |
|
lossValue.textContent = '2.3021'; |
|
accuracyValue.textContent = '0.12'; |
|
|
|
|
|
let progress = 0; |
|
let loss = 2.3021; |
|
let accuracy = 0.12; |
|
|
|
const interval = setInterval(() => { |
|
progress += 10; |
|
loss *= 0.85; |
|
accuracy = Math.min(0.99, accuracy * 1.2); |
|
|
|
progressBar.style.width = `${progress}%`; |
|
lossValue.textContent = loss.toFixed(4); |
|
accuracyValue.textContent = accuracy.toFixed(2); |
|
|
|
if (progress >= 100) { |
|
clearInterval(interval); |
|
} |
|
}, 300); |
|
} |
|
|
|
|
|
function clearCanvas() { |
|
if (window.dragDrop && typeof window.dragDrop.clearAllNodes === 'function') { |
|
window.dragDrop.clearAllNodes(); |
|
} |
|
|
|
|
|
const progressBar = document.querySelector('.progress-bar'); |
|
const lossValue = document.getElementById('loss-value'); |
|
const accuracyValue = document.getElementById('accuracy-value'); |
|
|
|
if (progressBar) progressBar.style.width = '0%'; |
|
if (lossValue) lossValue.textContent = '-'; |
|
if (accuracyValue) accuracyValue.textContent = '-'; |
|
} |
|
|
|
|
|
function updateActivationFunctionGraph(activationType) { |
|
const activationGraph = document.querySelector('.activation-function'); |
|
if (!activationGraph) return; |
|
|
|
|
|
let canvas = activationGraph.querySelector('canvas'); |
|
if (!canvas) { |
|
canvas = document.createElement('canvas'); |
|
canvas.width = 200; |
|
canvas.height = 100; |
|
activationGraph.appendChild(canvas); |
|
} |
|
|
|
const ctx = canvas.getContext('2d'); |
|
|
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.fillStyle = '#f8f9fa'; |
|
ctx.fillRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
ctx.strokeStyle = '#ccc'; |
|
ctx.lineWidth = 1; |
|
ctx.beginPath(); |
|
ctx.moveTo(0, canvas.height / 2); |
|
ctx.lineTo(canvas.width, canvas.height / 2); |
|
ctx.moveTo(canvas.width / 2, 0); |
|
ctx.lineTo(canvas.width / 2, canvas.height); |
|
ctx.stroke(); |
|
|
|
|
|
ctx.strokeStyle = 'var(--primary-color)'; |
|
ctx.lineWidth = 2; |
|
ctx.beginPath(); |
|
|
|
switch(activationType) { |
|
case 'relu': |
|
ctx.moveTo(0, canvas.height / 2); |
|
ctx.lineTo(canvas.width / 2, canvas.height / 2); |
|
ctx.lineTo(canvas.width, 0); |
|
break; |
|
|
|
case 'sigmoid': |
|
for (let x = 0; x < canvas.width; x++) { |
|
const normalizedX = (x / canvas.width - 0.5) * 10; |
|
const sigmoidY = 1 / (1 + Math.exp(-normalizedX)); |
|
const y = canvas.height - sigmoidY * canvas.height; |
|
if (x === 0) ctx.moveTo(x, y); |
|
else ctx.lineTo(x, y); |
|
} |
|
break; |
|
|
|
case 'tanh': |
|
for (let x = 0; x < canvas.width; x++) { |
|
const normalizedX = (x / canvas.width - 0.5) * 6; |
|
const tanhY = Math.tanh(normalizedX); |
|
const y = canvas.height / 2 - tanhY * canvas.height / 2; |
|
if (x === 0) ctx.moveTo(x, y); |
|
else ctx.lineTo(x, y); |
|
} |
|
break; |
|
|
|
case 'softmax': |
|
|
|
ctx.moveTo(0, canvas.height * 0.8); |
|
ctx.bezierCurveTo( |
|
canvas.width * 0.3, canvas.height * 0.7, |
|
canvas.width * 0.6, canvas.height * 0.3, |
|
canvas.width, canvas.height * 0.2 |
|
); |
|
break; |
|
|
|
default: |
|
ctx.moveTo(0, canvas.height * 0.8); |
|
ctx.lineTo(canvas.width, canvas.height * 0.2); |
|
} |
|
|
|
ctx.stroke(); |
|
|
|
|
|
ctx.fillStyle = 'var(--text-color)'; |
|
ctx.font = '12px Arial'; |
|
ctx.textAlign = 'center'; |
|
ctx.fillText(activationType, canvas.width / 2, canvas.height - 10); |
|
} |
|
|
|
|
|
canvas.addEventListener('mouseover', (e) => { |
|
const node = e.target.closest('.canvas-node'); |
|
if (node) { |
|
const rect = node.getBoundingClientRect(); |
|
const nodeType = node.getAttribute('data-type'); |
|
const nodeName = node.getAttribute('data-name'); |
|
const dimensions = node.getAttribute('data-dimensions'); |
|
|
|
|
|
tooltip.style.display = 'block'; |
|
tooltip.style.left = `${rect.right + 10}px`; |
|
tooltip.style.top = `${rect.top}px`; |
|
|
|
const tooltipHeader = tooltip.querySelector('.tooltip-header'); |
|
const tooltipContent = tooltip.querySelector('.tooltip-content'); |
|
|
|
if (tooltipHeader && tooltipContent) { |
|
tooltipHeader.textContent = nodeName; |
|
|
|
let content = ''; |
|
content += `<div class="tooltip-row"> |
|
<div class="tooltip-label">Type:</div> |
|
<div class="tooltip-value">${nodeType.charAt(0).toUpperCase() + nodeType.slice(1)}</div> |
|
</div>`; |
|
|
|
content += `<div class="tooltip-row"> |
|
<div class="tooltip-label">Dimensions:</div> |
|
<div class="tooltip-value">${dimensions}</div> |
|
</div>`; |
|
|
|
|
|
const configTemplate = window.neuralNetwork.nodeConfigTemplates[nodeType]; |
|
|
|
if (configTemplate) { |
|
if (configTemplate.activation) { |
|
content += `<div class="tooltip-row"> |
|
<div class="tooltip-label">Activation:</div> |
|
<div class="tooltip-value">${configTemplate.activation}</div> |
|
</div>`; |
|
} |
|
|
|
if (configTemplate.description) { |
|
content += `<div class="tooltip-row"> |
|
<div class="tooltip-label">Description:</div> |
|
<div class="tooltip-value">${configTemplate.description}</div> |
|
</div>`; |
|
} |
|
} |
|
|
|
tooltipContent.innerHTML = content; |
|
} |
|
} |
|
}); |
|
|
|
canvas.addEventListener('mouseout', (e) => { |
|
const node = e.target.closest('.canvas-node'); |
|
if (node) { |
|
tooltip.style.display = 'none'; |
|
} |
|
}); |
|
|
|
|
|
canvas.addEventListener('mousemove', (e) => { |
|
const node = e.target.closest('.canvas-node'); |
|
if (node && node.classList.contains('dragging')) { |
|
const rect = node.getBoundingClientRect(); |
|
tooltip.style.left = `${rect.right + 10}px`; |
|
tooltip.style.top = `${rect.top}px`; |
|
} |
|
}); |
|
}); |