bpe / index.html
MarcRyan's picture
Add 3 files
ef3b96b verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Baseball Prediction Neural Network</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://unpkg.com/brain.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.neuron {
transition: all 0.3s ease;
}
.neuron:hover {
transform: scale(1.1);
box-shadow: 0 0 15px rgba(59, 130, 246, 0.6);
}
.connection {
stroke-dasharray: 1000;
stroke-dashoffset: 1000;
animation: dash 2s linear forwards;
}
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
.pulse {
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(220, 38, 38, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(220, 38, 38, 0);
}
}
.team-card {
transition: all 0.3s ease;
}
.team-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
}
.progress-ring__circle {
transition: stroke-dashoffset 0.5s;
transform: rotate(-90deg);
transform-origin: 50% 50%;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-12 text-center">
<h1 class="text-4xl font-bold text-blue-600 mb-2">
<i class="fas fa-baseball-ball mr-3"></i>Baseball Prediction Engine
</h1>
<p class="text-gray-600 max-w-3xl mx-auto">
Neural network-powered analysis to predict likely losing teams based on historical performance metrics.
Adjust parameters and see how the network learns from baseball statistics.
</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Control Panel -->
<div class="bg-white rounded-xl shadow-lg p-6 h-fit">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-sliders-h mr-2 text-blue-500"></i>Network Configuration
</h2>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Hidden Layers</label>
<input type="range" id="hiddenLayers" min="1" max="5" value="3" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Neurons per Layer</label>
<input type="range" id="neuronsPerLayer" min="2" max="12" value="6" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>2</span>
<span>4</span>
<span>6</span>
<span>8</span>
<span>10</span>
<span>12</span>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Learning Rate</label>
<input type="range" id="learningRate" min="0.01" max="0.5" step="0.01" value="0.2" class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-xs text-gray-500 mt-1">
<span>0.01</span>
<span>0.25</span>
<span>0.5</span>
</div>
</div>
<div class="pt-2">
<button id="trainBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-4 rounded-lg transition duration-200 flex items-center justify-center">
<i class="fas fa-brain mr-2"></i>
Train Network
</button>
</div>
</div>
<div class="mt-6 pt-4 border-t border-gray-200">
<h3 class="text-sm font-medium text-gray-700 mb-2 flex items-center">
<i class="fas fa-database mr-2 text-blue-500"></i>Training Data Parameters
</h3>
<div class="grid grid-cols-2 gap-3">
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">Seasons</label>
<select id="seasonRange" class="w-full text-sm border rounded px-2 py-1">
<option value="5">Last 5 seasons</option>
<option value="10" selected>Last 10 seasons</option>
<option value="15">Last 15 seasons</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">Stat Weight</label>
<select id="statWeight" class="w-full text-sm border rounded px-2 py-1">
<option value="balanced">Balanced</option>
<option value="offense">Offense-heavy</option>
<option value="defense">Defense-heavy</option>
</select>
</div>
</div>
</div>
</div>
<!-- Network Visualization -->
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-lg p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800 flex items-center">
<i class="fas fa-project-diagram mr-2 text-blue-500"></i>Network Architecture
</h2>
<div class="flex space-x-2">
<button id="resetBtn" class="text-sm bg-gray-200 hover:bg-gray-300 text-gray-800 py-1 px-3 rounded flex items-center">
<i class="fas fa-redo mr-1 text-xs"></i> Reset
</button>
</div>
</div>
<div class="relative h-96 w-full bg-gray-50 rounded-lg overflow-hidden">
<svg id="networkDiagram" class="w-full h-full">
<!-- Network will be rendered here -->
</svg>
<div id="loadingOverlay" class="absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden">
<div class="bg-white p-4 rounded-lg flex items-center shadow-lg">
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-600" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-gray-800">Training network with baseball data...</span>
</div>
</div>
</div>
<div class="mt-6 grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-sm font-medium text-gray-700 mb-2 flex items-center">
<i class="fas fa-chart-line mr-2 text-blue-500"></i>Team Performance Input
</h3>
<div class="grid grid-cols-2 gap-2">
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">ERA</label>
<input type="number" id="eraInput" min="2" max="6" step="0.1" value="4.5" class="w-full px-2 py-1 border rounded text-sm">
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">BA</label>
<input type="number" id="baInput" min="0.2" max="0.35" step="0.01" value="0.25" class="w-full px-2 py-1 border rounded text-sm">
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">HR</label>
<input type="number" id="hrInput" min="100" max="300" step="1" value="200" class="w-full px-2 py-1 border rounded text-sm">
</div>
<div>
<label class="block text-xs font-medium text-gray-500 mb-1">Errors</label>
<input type="number" id="errorsInput" min="50" max="150" step="1" value="100" class="w-full px-2 py-1 border rounded text-sm">
</div>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<h3 class="text-sm font-medium text-gray-700 mb-2 flex items-center">
<i class="fas fa-percentage mr-2 text-blue-500"></i>Loss Probability
</h3>
<div class="flex items-center">
<div class="relative w-16 h-16 mr-4">
<svg class="w-full h-full" viewBox="0 0 36 36">
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#E5E7EB"
stroke-width="3"
/>
<path
id="progressRing"
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#EF4444"
stroke-width="3"
stroke-dasharray="100, 100"
/>
</svg>
<div id="lossPercentage" class="absolute inset-0 flex items-center justify-center text-lg font-bold text-red-600">0%</div>
</div>
<div class="flex-1">
<div class="text-xs text-gray-500 mb-1">Prediction Confidence</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div id="confidenceBar" class="bg-blue-600 h-2 rounded-full" style="width: 0%"></div>
</div>
<div id="predictionText" class="text-sm mt-2 font-medium text-gray-700">Enter team stats to predict</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Team Prediction Cards -->
<div class="mt-8">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-table mr-2 text-blue-500"></i>Team Loss Predictions
</h2>
<div id="teamPredictions" class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
<!-- Team cards will be dynamically inserted here -->
</div>
</div>
<!-- Training Progress -->
<div class="mt-8 bg-white rounded-xl shadow-lg p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-chart-bar mr-2 text-blue-500"></i>Training Progress
</h2>
<canvas id="errorChart" class="w-full h-64"></canvas>
</div>
<!-- Baseball Statistics Explanation -->
<div class="mt-8 bg-white rounded-xl shadow-lg p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4 flex items-center">
<i class="fas fa-info-circle mr-2 text-blue-500"></i>Key Baseball Metrics
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div class="bg-blue-50 p-4 rounded-lg">
<h3 class="font-medium text-blue-800 mb-1">ERA (Earned Run Average)</h3>
<p class="text-sm text-blue-600">Average earned runs allowed by a pitcher per 9 innings. Higher = worse defense.</p>
</div>
<div class="bg-green-50 p-4 rounded-lg">
<h3 class="font-medium text-green-800 mb-1">BA (Batting Average)</h3>
<p class="text-sm text-green-600">Hits divided by at bats. Higher = better offensive performance.</p>
</div>
<div class="bg-yellow-50 p-4 rounded-lg">
<h3 class="font-medium text-yellow-800 mb-1">HR (Home Runs)</h3>
<p class="text-sm text-yellow-600">Total home runs hit. More = better offensive power.</p>
</div>
<div class="bg-red-50 p-4 rounded-lg">
<h3 class="font-medium text-red-800 mb-1">Errors</h3>
<p class="text-sm text-red-600">Defensive mistakes leading to opponent advancement. More = worse defense.</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Configuration
const config = {
hiddenLayers: 3,
neuronsPerLayer: 6,
learningRate: 0.2,
iterations: 20000,
errorThresh: 0.005
};
// Sample baseball team data (in a real app, this would come from an API)
const baseballTeams = [
{ name: "Yankees", logo: "⚾", era: 3.8, ba: 0.258, hr: 245, errors: 78 },
{ name: "Dodgers", logo: "⚾", era: 3.2, ba: 0.272, hr: 263, errors: 65 },
{ name: "Astros", logo: "⚾", era: 3.5, ba: 0.267, hr: 251, errors: 72 },
{ name: "Red Sox", logo: "⚾", era: 4.1, ba: 0.261, hr: 238, errors: 85 },
{ name: "Giants", logo: "⚾", era: 3.9, ba: 0.249, hr: 222, errors: 82 },
{ name: "Cubs", logo: "⚾", era: 4.3, ba: 0.243, hr: 215, errors: 91 },
{ name: "Mets", logo: "⚾", era: 4.0, ba: 0.255, hr: 231, errors: 88 },
{ name: "Cardinals", logo: "⚾", era: 3.7, ba: 0.263, hr: 241, errors: 76 }
];
// DOM Elements
const diagram = document.getElementById('networkDiagram');
const hiddenLayersInput = document.getElementById('hiddenLayers');
const neuronsPerLayerInput = document.getElementById('neuronsPerLayer');
const learningRateInput = document.getElementById('learningRate');
const trainBtn = document.getElementById('trainBtn');
const resetBtn = document.getElementById('resetBtn');
const eraInput = document.getElementById('eraInput');
const baInput = document.getElementById('baInput');
const hrInput = document.getElementById('hrInput');
const errorsInput = document.getElementById('errorsInput');
const lossPercentage = document.getElementById('lossPercentage');
const confidenceBar = document.getElementById('confidenceBar');
const predictionText = document.getElementById('predictionText');
const progressRing = document.getElementById('progressRing');
const loadingOverlay = document.getElementById('loadingOverlay');
const teamPredictions = document.getElementById('teamPredictions');
const seasonRange = document.getElementById('seasonRange');
const statWeight = document.getElementById('statWeight');
// Initialize network
let net = new brain.NeuralNetwork();
let errorChart;
// Initialize UI
initUI();
drawNetwork();
initChart();
renderTeamCards();
// Event Listeners
hiddenLayersInput.addEventListener('input', updateConfig);
neuronsPerLayerInput.addEventListener('input', updateConfig);
learningRateInput.addEventListener('input', updateConfig);
trainBtn.addEventListener('click', trainNetwork);
resetBtn.addEventListener('click', resetNetwork);
// Add input event listeners for all stat inputs
[eraInput, baInput, hrInput, errorsInput].forEach(input => {
input.addEventListener('input', () => {
// Only predict if network is trained
if (net.trainOpts) {
predictLossProbability();
}
});
});
// Functions
function initUI() {
hiddenLayersInput.value = config.hiddenLayers;
neuronsPerLayerInput.value = config.neuronsPerLayer;
learningRateInput.value = config.learningRate;
}
function updateConfig() {
config.hiddenLayers = parseInt(hiddenLayersInput.value);
config.neuronsPerLayer = parseInt(neuronsPerLayerInput.value);
config.learningRate = parseFloat(learningRateInput.value);
drawNetwork();
}
function resetNetwork() {
net = new brain.NeuralNetwork();
drawNetwork();
resetChart();
lossPercentage.textContent = '0%';
progressRing.setAttribute('stroke-dasharray', '0, 100');
confidenceBar.style.width = '0%';
predictionText.textContent = 'Enter team stats to predict';
}
function trainNetwork() {
loadingOverlay.classList.remove('hidden');
trainBtn.disabled = true;
// Generate synthetic training data based on baseball stats
const trainingData = generateTrainingData();
// Configure network
const networkConfig = {
hiddenLayers: Array(config.hiddenLayers).fill(config.neuronsPerLayer),
learningRate: config.learningRate,
iterations: config.iterations,
errorThresh: config.errorThresh,
log: true,
logPeriod: 1000,
callback: function(info) {
updateChart(info.iterations, info.error);
},
callbackPeriod: 1000
};
// Train in a timeout to allow UI to update
setTimeout(() => {
net.train(trainingData, networkConfig);
loadingOverlay.classList.add('hidden');
trainBtn.disabled = false;
drawNetwork();
// After training, update predictions for all teams
updateAllTeamPredictions();
// Also predict for the current input values
predictLossProbability();
}, 100);
}
function generateTrainingData() {
// In a real application, this would be actual historical baseball data
// For this demo, we generate synthetic data that follows general trends:
// Higher ERA, lower BA, fewer HR, more errors = more likely to lose
const data = [];
const seasons = parseInt(seasonRange.value);
const weight = statWeight.value;
// Generate data for multiple seasons
for (let i = 0; i < seasons * 100; i++) {
// Random stats within realistic ranges
const era = randomInRange(2.5, 5.5);
const ba = randomInRange(0.22, 0.32);
const hr = Math.floor(randomInRange(100, 300));
const errors = Math.floor(randomInRange(50, 150));
// Calculate loss probability based on stats
let lossProbability;
if (weight === 'offense') {
// More weight on offensive stats (BA and HR)
lossProbability = (
(0.3 * normalize(era, 2.5, 5.5)) +
(0.4 * (1 - normalize(ba, 0.22, 0.32))) +
(0.2 * (1 - normalize(hr, 100, 300))) +
(0.1 * normalize(errors, 50, 150))
);
} else if (weight === 'defense') {
// More weight on defensive stats (ERA and Errors)
lossProbability = (
(0.5 * normalize(era, 2.5, 5.5)) +
(0.2 * (1 - normalize(ba, 0.22, 0.32))) +
(0.1 * (1 - normalize(hr, 100, 300))) +
(0.2 * normalize(errors, 50, 150))
);
} else {
// Balanced approach
lossProbability = (
(0.4 * normalize(era, 2.5, 5.5)) +
(0.3 * (1 - normalize(ba, 0.22, 0.32))) +
(0.15 * (1 - normalize(hr, 100, 300))) +
(0.15 * normalize(errors, 50, 150))
);
}
// Add some randomness
lossProbability += randomInRange(-0.1, 0.1);
lossProbability = Math.max(0, Math.min(1, lossProbability));
// Determine if this would be a loss (1) or win (0)
const outcome = lossProbability > 0.5 ? 1 : 0;
// Add to training data
data.push({
input: [
normalize(era, 2, 6),
normalize(ba, 0.2, 0.35),
normalize(hr, 100, 300),
normalize(errors, 50, 150)
],
output: [outcome]
});
}
return data;
}
function predictLossProbability() {
const era = parseFloat(eraInput.value);
const ba = parseFloat(baInput.value);
const hr = parseFloat(hrInput.value);
const errors = parseFloat(errorsInput.value);
const input = [
normalize(era, 2, 6),
normalize(ba, 0.2, 0.35),
normalize(hr, 100, 300),
normalize(errors, 50, 150)
];
const output = net.run(input);
const lossProb = output[0];
const lossPercent = Math.round(lossProb * 100);
const confidence = Math.abs(lossProb - 0.5) * 2; // How far from 50%
// Update UI
lossPercentage.textContent = `${lossPercent}%`;
progressRing.setAttribute('stroke-dasharray', `${lossPercent}, ${100 - lossPercent}`);
confidenceBar.style.width = `${confidence * 100}%`;
if (lossProb > 0.7) {
predictionText.innerHTML = `<span class="text-red-600">High loss probability</span>`;
progressRing.setAttribute('stroke', '#EF4444');
} else if (lossProb > 0.55) {
predictionText.innerHTML = `<span class="text-orange-500">Moderate loss probability</span>`;
progressRing.setAttribute('stroke', '#F97316');
} else if (lossProb > 0.45) {
predictionText.textContent = "Neutral prediction";
progressRing.setAttribute('stroke', '#6B7280');
} else if (lossProb > 0.3) {
predictionText.innerHTML = `<span class="text-green-500">Moderate win probability</span>`;
progressRing.setAttribute('stroke', '#10B981');
} else {
predictionText.innerHTML = `<span class="text-green-600">High win probability</span>`;
progressRing.setAttribute('stroke', '#059669');
}
}
function renderTeamCards() {
teamPredictions.innerHTML = '';
baseballTeams.forEach(team => {
const card = document.createElement('div');
card.className = 'bg-white rounded-lg shadow-md p-4 team-card';
card.innerHTML = `
<div class="flex items-center mb-3">
<div class="text-2xl mr-3">${team.logo}</div>
<h3 class="font-semibold text-gray-800">${team.name}</h3>
</div>
<div class="grid grid-cols-2 gap-2 text-sm mb-3">
<div><span class="text-gray-500">ERA:</span> ${team.era}</div>
<div><span class="text-gray-500">BA:</span> ${team.ba.toFixed(3)}</div>
<div><span class="text-gray-500">HR:</span> ${team.hr}</div>
<div><span class="text-gray-500">Errors:</span> ${team.errors}</div>
</div>
<div class="flex items-center">
<div class="relative w-10 h-10 mr-2">
<svg class="w-full h-full" viewBox="0 0 36 36">
<path
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#E5E7EB"
stroke-width="2"
/>
<path
class="team-progress"
d="M18 2.0845
a 15.9155 15.9155 0 0 1 0 31.831
a 15.9155 15.9155 0 0 1 0 -31.831"
fill="none"
stroke="#EF4444"
stroke-width="2"
stroke-dasharray="0, 100"
/>
</svg>
<div class="team-percentage absolute inset-0 flex items-center justify-center text-xs font-bold text-red-600">-</div>
</div>
<button class="text-xs bg-blue-100 hover:bg-blue-200 text-blue-800 py-1 px-2 rounded flex items-center ml-auto predict-btn" data-team='${JSON.stringify(team)}'>
<i class="fas fa-calculator mr-1 text-xs"></i> Predict
</button>
</div>
`;
teamPredictions.appendChild(card);
});
// Add event listeners to predict buttons
document.querySelectorAll('.predict-btn').forEach(btn => {
btn.addEventListener('click', function() {
const team = JSON.parse(this.getAttribute('data-team'));
eraInput.value = team.era;
baInput.value = team.ba;
hrInput.value = team.hr;
errorsInput.value = team.errors;
if (net.trainOpts) {
predictLossProbability();
} else {
predictionText.textContent = "Train the network first";
}
});
});
}
function updateAllTeamPredictions() {
baseballTeams.forEach((team, index) => {
const input = [
normalize(team.era, 2, 6),
normalize(team.ba, 0.2, 0.35),
normalize(team.hr, 100, 300),
normalize(team.errors, 50, 150)
];
const output = net.run(input);
const lossPercent = Math.round(output[0] * 100);
// Update the corresponding team card
const card = teamPredictions.children[index];
const progress = card.querySelector('.team-progress');
const percentage = card.querySelector('.team-percentage');
progress.setAttribute('stroke-dasharray', `${lossPercent}, ${100 - lossPercent}`);
percentage.textContent = `${lossPercent}%`;
// Color based on probability
if (lossPercent > 70) {
progress.setAttribute('stroke', '#EF4444');
percentage.classList.add('text-red-600');
percentage.classList.remove('text-orange-500', 'text-gray-500', 'text-green-500', 'text-green-600');
} else if (lossPercent > 55) {
progress.setAttribute('stroke', '#F97316');
percentage.classList.add('text-orange-500');
percentage.classList.remove('text-red-600', 'text-gray-500', 'text-green-500', 'text-green-600');
} else if (lossPercent > 45) {
progress.setAttribute('stroke', '#6B7280');
percentage.classList.add('text-gray-500');
percentage.classList.remove('text-red-600', 'text-orange-500', 'text-green-500', 'text-green-600');
} else if (lossPercent > 30) {
progress.setAttribute('stroke', '#10B981');
percentage.classList.add('text-green-500');
percentage.classList.remove('text-red-600', 'text-orange-500', 'text-gray-500', 'text-green-600');
} else {
progress.setAttribute('stroke', '#059669');
percentage.classList.add('text-green-600');
percentage.classList.remove('text-red-600', 'text-orange-500', 'text-gray-500', 'text-green-500');
}
});
}
function drawNetwork() {
// Clear diagram
diagram.innerHTML = '';
const width = diagram.clientWidth;
const height = diagram.clientHeight;
const padding = 50;
// Calculate layer positions
const layers = [
{ neurons: 4, label: 'Input', stats: ['ERA', 'BA', 'HR', 'Errors'] },
...Array(config.hiddenLayers).fill().map((_, i) => ({
neurons: config.neuronsPerLayer,
label: `Hidden ${i + 1}`
})),
{ neurons: 1, label: 'Output', stats: ['Loss %'] }
];
const layerCount = layers.length;
const layerWidth = (width - 2 * padding) / (layerCount - 1);
// Draw connections first (so they appear behind neurons)
for (let l = 0; l < layerCount - 1; l++) {
const currentLayer = layers[l];
const nextLayer = layers[l + 1];
for (let n1 = 0; n1 < currentLayer.neurons; n1++) {
for (let n2 = 0; n2 < nextLayer.neurons; n2++) {
const x1 = padding + l * layerWidth;
const y1 = padding + (height - 2 * padding) * (n1 + 0.5) / currentLayer.neurons;
const x2 = padding + (l + 1) * layerWidth;
const y2 = padding + (height - 2 * padding) * (n2 + 0.5) / nextLayer.neurons;
// Create connection
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
line.setAttribute('x1', x1);
line.setAttribute('y1', y1);
line.setAttribute('x2', x2);
line.setAttribute('y2', y2);
line.setAttribute('stroke', '#9CA3AF');
line.setAttribute('stroke-width', '1');
line.setAttribute('class', 'connection');
diagram.appendChild(line);
}
}
}
// Draw neurons
for (let l = 0; l < layerCount; l++) {
const layer = layers[l];
const x = padding + l * layerWidth;
for (let n = 0; n < layer.neurons; n++) {
const y = padding + (height - 2 * padding) * (n + 0.5) / layer.neurons;
// Create neuron circle
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
circle.setAttribute('cx', x);
circle.setAttribute('cy', y);
circle.setAttribute('r', 18);
// Color input/output layers differently
if (l === 0) {
circle.setAttribute('fill', '#10B981'); // Green for input
} else if (l === layerCount - 1) {
circle.setAttribute('fill', '#EF4444'); // Red for output
} else {
circle.setAttribute('fill', '#3B82F6'); // Blue for hidden
}
circle.setAttribute('class', 'neuron');
diagram.appendChild(circle);
// Create neuron text
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
text.setAttribute('x', x);
text.setAttribute('y', y + 5);
text.setAttribute('text-anchor', 'middle');
text.setAttribute('fill', 'white');
text.setAttribute('font-size', '10');
text.setAttribute('font-weight', 'bold');
// Show stat names for input layer
if (l === 0 && layer.stats) {
text.textContent = layer.stats[n].substring(0, 3);
} else if (l === layerCount - 1 && layer.stats) {
text.textContent = layer.stats[n].substring(0, 3);
} else {
text.textContent = `${layer.label[0]}${n + 1}`;
}
diagram.appendChild(text);
}
// Add layer label
const label = document.createElementNS('http://www.w3.org/2000/svg', 'text');
label.setAttribute('x', x);
label.setAttribute('y', height - 20);
label.setAttribute('text-anchor', 'middle');
label.setAttribute('fill', '#4B5563');
label.setAttribute('font-size', '12');
label.textContent = layer.label;
diagram.appendChild(label);
}
}
function initChart() {
const ctx = document.getElementById('errorChart').getContext('2d');
errorChart = new Chart(ctx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: 'Training Error',
data: [],
borderColor: '#3B82F6',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Neural Network Learning Progress',
font: {
size: 16
}
},
tooltip: {
callbacks: {
label: function(context) {
return `Error: ${context.parsed.y.toFixed(5)}`;
}
}
}
},
scales: {
y: {
beginAtZero: true,
title: {
display: true,
text: 'Error Rate'
}
},
x: {
title: {
display: true,
text: 'Training Iterations'
}
}
}
}
});
}
function updateChart(iteration, error) {
errorChart.data.labels.push(iteration);
errorChart.data.datasets[0].data.push(error);
errorChart.update();
}
function resetChart() {
errorChart.data.labels = [];
errorChart.data.datasets[0].data = [];
errorChart.update();
}
// Helper functions
function randomInRange(min, max) {
return Math.random() * (max - min) + min;
}
function normalize(value, min, max) {
return (value - min) / (max - min);
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=MarcRyan/bpe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>