| <!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"> |
| |
| <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> |
| |
| |
| <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"> |
| |
| </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> |
| |
| |
| <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"> |
| |
| </div> |
| </div> |
| |
| |
| <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> |
| |
| |
| <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() { |
| |
| const config = { |
| hiddenLayers: 3, |
| neuronsPerLayer: 6, |
| learningRate: 0.2, |
| iterations: 20000, |
| errorThresh: 0.005 |
| }; |
| |
| |
| 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 } |
| ]; |
| |
| |
| 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'); |
| |
| |
| let net = new brain.NeuralNetwork(); |
| let errorChart; |
| |
| |
| initUI(); |
| drawNetwork(); |
| initChart(); |
| renderTeamCards(); |
| |
| |
| hiddenLayersInput.addEventListener('input', updateConfig); |
| neuronsPerLayerInput.addEventListener('input', updateConfig); |
| learningRateInput.addEventListener('input', updateConfig); |
| trainBtn.addEventListener('click', trainNetwork); |
| resetBtn.addEventListener('click', resetNetwork); |
| |
| |
| [eraInput, baInput, hrInput, errorsInput].forEach(input => { |
| input.addEventListener('input', () => { |
| |
| if (net.trainOpts) { |
| predictLossProbability(); |
| } |
| }); |
| }); |
| |
| |
| 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; |
| |
| |
| const trainingData = generateTrainingData(); |
| |
| |
| 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 |
| }; |
| |
| |
| setTimeout(() => { |
| net.train(trainingData, networkConfig); |
| loadingOverlay.classList.add('hidden'); |
| trainBtn.disabled = false; |
| drawNetwork(); |
| |
| |
| updateAllTeamPredictions(); |
| |
| |
| predictLossProbability(); |
| }, 100); |
| } |
| |
| function generateTrainingData() { |
| |
| |
| |
| |
| const data = []; |
| const seasons = parseInt(seasonRange.value); |
| const weight = statWeight.value; |
| |
| |
| for (let i = 0; i < seasons * 100; i++) { |
| |
| 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)); |
| |
| |
| let lossProbability; |
| |
| if (weight === 'offense') { |
| |
| 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') { |
| |
| 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 { |
| |
| 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)) |
| ); |
| } |
| |
| |
| lossProbability += randomInRange(-0.1, 0.1); |
| lossProbability = Math.max(0, Math.min(1, lossProbability)); |
| |
| |
| const outcome = lossProbability > 0.5 ? 1 : 0; |
| |
| |
| 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; |
| |
| |
| 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); |
| }); |
| |
| |
| 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); |
| |
| |
| 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}%`; |
| |
| |
| 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() { |
| |
| diagram.innerHTML = ''; |
| |
| const width = diagram.clientWidth; |
| const height = diagram.clientHeight; |
| const padding = 50; |
| |
| |
| 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); |
| |
| |
| 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; |
| |
| |
| 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); |
| } |
| } |
| } |
| |
| |
| 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; |
| |
| |
| const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle'); |
| circle.setAttribute('cx', x); |
| circle.setAttribute('cy', y); |
| circle.setAttribute('r', 18); |
| |
| |
| if (l === 0) { |
| circle.setAttribute('fill', '#10B981'); |
| } else if (l === layerCount - 1) { |
| circle.setAttribute('fill', '#EF4444'); |
| } else { |
| circle.setAttribute('fill', '#3B82F6'); |
| } |
| |
| circle.setAttribute('class', 'neuron'); |
| diagram.appendChild(circle); |
| |
| |
| 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'); |
| |
| |
| 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); |
| } |
| |
| |
| 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(); |
| } |
| |
| |
| 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> |