Download templates/Neural-Networks-for-Classification-three.html from deedrop1140/machinelearningalgorithms: direct link, hf CLI and curl.
- Browser
- Download file 45 kB
-
https://huggingface.co/spaces/deedrop1140/machinelearningalgorithms/resolve/main/templates/Neural-Networks-for-Classification-three.html
- Command line
-
hf download hf://spaces/deedrop1140/machinelearningalgorithms/templates/Neural-Networks-for-Classification-three.html
-
curl -L -o Neural-Networks-for-Classification-three.html https://huggingface.co/spaces/deedrop1140/machinelearningalgorithms/resolve/main/templates/Neural-Networks-for-Classification-three.html
45 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neural Network Playground</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <!-- Lucide Icons --> | |
| <script src="https://unpkg.com/lucide@latest"></script> | |
| <style> | |
| :root { | |
| --background: 222 47% 6%; | |
| --foreground: 210 40% 96%; | |
| --card: 222 47% 8%; | |
| --primary: 199 89% 48%; | |
| --primary-foreground: 222 47% 6%; | |
| --secondary: 280 65% 55%; | |
| --secondary-foreground: 210 40% 98%; | |
| --muted: 217 33% 15%; | |
| --muted-foreground: 215 20% 55%; | |
| --accent: 142 71% 45%; | |
| --destructive: 0 84% 60%; | |
| --destructive-foreground: 210 40% 98%; | |
| --border: 217 33% 20%; | |
| /* Custom neural network colors */ | |
| --node-input: 199 89% 48%; | |
| --node-hidden: 280 65% 55%; | |
| --node-positive: 142 71% 45%; /* Class A (Green) */ | |
| --node-negative: 350 89% 60%; /* Class B (Red) */ | |
| --radius: 0.75rem; | |
| } | |
| body { | |
| background-color: hsl(var(--background)); | |
| color: hsl(var(--foreground)); | |
| font-family: system-ui, -apple-system, sans-serif; | |
| } | |
| .glass-panel { | |
| background-color: hsla(var(--card), 0.6); | |
| backdrop-filter: blur(16px); | |
| border: 1px solid hsla(var(--border), 0.5); | |
| border-radius: var(--radius); | |
| box-shadow: 0 0 30px hsl(199 89% 48% / 0.1); | |
| } | |
| .gradient-text { | |
| background: linear-gradient(135deg, hsl(199 89% 48%) 0%, hsl(280 65% 55%) 100%); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| /* Animation Utilities */ | |
| @keyframes flow { | |
| 0% { stroke-dashoffset: 20; } | |
| 100% { stroke-dashoffset: 0; } | |
| } | |
| .animate-flow { | |
| animation: flow 1s linear infinite; | |
| } | |
| @keyframes pulse-glow { | |
| 0%, 100% { opacity: 1; filter: brightness(1); } | |
| 50% { opacity: 0.7; filter: brightness(1.2); } | |
| } | |
| .animate-node-pulse { | |
| animation: pulse-glow 2s ease-in-out infinite; | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { width: 8px; } | |
| ::-webkit-scrollbar-track { background: hsl(var(--background)); } | |
| ::-webkit-scrollbar-thumb { background: hsl(var(--muted)); border-radius: 4px; } | |
| ::-webkit-scrollbar-thumb:hover { background: hsl(var(--muted-foreground)); } | |
| input[type=range] { | |
| -webkit-appearance: none; | |
| background: transparent; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| height: 16px; | |
| width: 16px; | |
| border-radius: 50%; | |
| background: hsl(var(--primary)); | |
| cursor: pointer; | |
| margin-top: -6px; | |
| } | |
| input[type=range]::-webkit-slider-runnable-track { | |
| width: 100%; | |
| height: 4px; | |
| cursor: pointer; | |
| background: hsl(var(--muted)); | |
| border-radius: 2px; | |
| } | |
| .btn { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 0.5rem; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| transition-colors: 0.15s; | |
| cursor: pointer; | |
| } | |
| .btn:disabled { | |
| opacity: 0.5; | |
| pointer-events: none; | |
| } | |
| .btn-glass { background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); color: white; } | |
| .btn-glass:hover { background: rgba(255,255,255,0.1); } | |
| .btn-accent { background: hsl(var(--accent)); color: hsl(var(--accent-foreground)); } | |
| .btn-destructive { background: hsl(var(--destructive)); color: hsl(var(--destructive-foreground)); } | |
| .btn-glow { | |
| background: hsl(var(--primary)); | |
| color: white; | |
| box-shadow: 0 0 15px hsl(var(--primary)/0.5); | |
| } | |
| .btn-glow:hover { box-shadow: 0 0 25px hsl(var(--primary)/0.6); } | |
| .tab-btn { | |
| flex: 1; | |
| padding: 0.375rem; | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| border-radius: 0.375rem; | |
| transition: all 0.2s; | |
| color: hsl(var(--muted-foreground)); | |
| } | |
| .tab-btn.active { | |
| background-color: hsl(var(--card)); | |
| color: hsl(var(--primary)); | |
| box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen p-4 selection:bg-[hsl(var(--primary))] selection:text-white"> | |
| <!-- Background Ambience --> | |
| <div class="fixed inset-0 pointer-events-none overflow-hidden -z-10"> | |
| <div class="absolute top-0 left-1/4 w-96 h-96 bg-[hsl(var(--primary)/0.1)] rounded-full blur-[100px]"></div> | |
| <div class="absolute bottom-0 right-1/4 w-96 h-96 bg-[hsl(var(--secondary)/0.1)] rounded-full blur-[100px]"></div> | |
| </div> | |
| <!-- Header --> | |
| <header class="relative z-10 border-b border-white/10 bg-[hsl(var(--background)/0.8)] backdrop-blur-md sticky top-0 mb-8 rounded-xl"> | |
| <div class="max-w-7xl mx-auto px-4 py-4 flex items-center justify-between"> | |
| <div class="flex items-center gap-3"> | |
| <div class="p-2 rounded-xl bg-[hsl(var(--primary)/0.2)] animate-pulse"> | |
| <i data-lucide="brain" class="h-6 w-6 text-[hsl(var(--primary))]"></i> | |
| </div> | |
| <div> | |
| <h1 class="text-xl font-bold gradient-text">Neural Network Playground</h1> | |
| <div class="absolute left-1/2 -translate-x-1/2 flex items-center"> <audio id="clickSound" src="https://www.soundjay.com/buttons/sounds/button-3.mp3"></audio> <a href="/neural-network-classification" onclick="playSound(); return false;" class="inline-flex items-center justify-center text-center leading-none bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-6 rounded-xl text-sm transition-all duration-150 shadow-[0_4px_0_rgb(29,78,216)] active:shadow-none active:translate-y-[4px] uppercase tracking-wider"> Back to Core </a> </div> | |
| <p class="text-xs text-[hsl(var(--muted-foreground))]">Interactive Classification Visualizer</p> | |
| <p class="text-xxl p-3 text-[hsl(var(--muted-foreground))]">After training you cant train agian and cant change output so if you want add a custom data in predefine data so add before training</p> | |
| </div> | |
| </div> | |
| <div class="hidden md:flex items-center gap-4 text-sm text-[hsl(var(--muted-foreground))]"> | |
| <div class="flex items-center gap-2 bg-white/5 px-3 py-1.5 rounded-full"> | |
| <i data-lucide="layers" class="h-4 w-4"></i> | |
| <span id="header-neurons-count">4 hidden neurons</span> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Main Content --> | |
| <main class="relative z-10 max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| <!-- Left Sidebar: Controls --> | |
| <div class="lg:col-span-3 space-y-6"> | |
| <!-- Control Panel --> | |
| <div class="glass-panel p-5 space-y-5"> | |
| <div> | |
| <h3 class="text-sm font-medium text-[hsl(var(--muted-foreground))] mb-3">Data Class</h3> | |
| <div class="flex gap-2"> | |
| <button onclick="setClass(1)" id="btn-class-a" class="btn btn-accent h-9 px-3 flex-1 text-sm"> | |
| <div class="w-3 h-3 rounded-full bg-[hsl(var(--node-positive))] mr-2"></div> Class A | |
| </button> | |
| <button onclick="setClass(0)" id="btn-class-b" class="btn btn-glass h-9 px-3 flex-1 text-sm"> | |
| <div class="w-3 h-3 rounded-full bg-[hsl(var(--node-negative))] mr-2"></div> Class B | |
| </button> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-sm font-medium text-[hsl(var(--muted-foreground))] mb-3"> | |
| Hidden Neurons: <span id="neurons-display" class="text-[hsl(var(--primary))]">4</span> | |
| </h3> | |
| <div class="flex items-center gap-3"> | |
| <button onclick="changeNeurons(-1)" class="btn btn-glass h-10 w-10 p-0"><i data-lucide="minus" class="h-4 w-4"></i></button> | |
| <input type="range" min="1" max="8" value="4" class="flex-1" id="neurons-slider" oninput="changeNeuronsFromSlider(this.value)"> | |
| <button onclick="changeNeurons(1)" class="btn btn-glass h-10 w-10 p-0"><i data-lucide="plus" class="h-4 w-4"></i></button> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-sm font-medium text-[hsl(var(--muted-foreground))] mb-3"> | |
| Learning Rate: <span id="lr-display" class="text-[hsl(var(--secondary))]">0.50</span> | |
| </h3> | |
| <input type="range" min="1" max="100" value="50" class="w-full" id="lr-slider" oninput="changeLR(this.value)"> | |
| </div> | |
| <div class="flex gap-2"> | |
| <button id="btn-train" onclick="toggleTraining()" class="btn btn-glow flex-1 h-10 px-4"> | |
| <i data-lucide="play" class="h-4 w-4 mr-2"></i> Train Network | |
| </button> | |
| <button onclick="resetApp()" class="btn btn-glass h-10 w-10 p-0"> | |
| <i data-lucide="rotate-ccw" class="h-4 w-4"></i> | |
| </button> | |
| </div> | |
| <div id="accuracy-panel" class="text-center py-3 rounded-lg bg-white/5 hidden"> | |
| <span class="text-sm text-[hsl(var(--muted-foreground))]">Accuracy: </span> | |
| <span id="accuracy-display" class="text-lg font-bold">0.0%</span> | |
| </div> | |
| </div> | |
| <!-- Presets --> | |
| <div class="glass-panel p-4 space-y-3"> | |
| <h3 class="text-sm font-medium text-[hsl(var(--muted-foreground))]">Presets</h3> | |
| <div class="grid grid-cols-2 gap-2"> | |
| <button onclick="loadPreset('Linear')" class="btn btn-glass flex flex-col h-auto py-3"> | |
| <i data-lucide="waves" class="h-4 w-4 mb-1"></i> <span class="text-xs">Linear</span> | |
| </button> | |
| <button onclick="loadPreset('XOR')" class="btn btn-glass flex flex-col h-auto py-3"> | |
| <i data-lucide="target" class="h-4 w-4 mb-1"></i> <span class="text-xs">XOR</span> | |
| </button> | |
| <button onclick="loadPreset('Circle')" class="btn btn-glass flex flex-col h-auto py-3"> | |
| <i data-lucide="circle" class="h-4 w-4 mb-1"></i> <span class="text-xs">Circle</span> | |
| </button> | |
| <button onclick="loadPreset('Spiral')" class="btn btn-glass flex flex-col h-auto py-3"> | |
| <i data-lucide="sparkles" class="h-4 w-4 mb-1"></i> <span class="text-xs">Spiral</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Logs --> | |
| <div class="glass-panel p-4 space-y-3"> | |
| <div class="flex justify-between items-center text-sm font-medium text-[hsl(var(--muted-foreground))]"> | |
| <span class="flex items-center gap-2"><i data-lucide="clock" class="h-4 w-4"></i> Training Log</span> | |
| <span id="epoch-display" class="text-[hsl(var(--primary))] animate-pulse hidden">Epoch 0</span> | |
| </div> | |
| <div class="w-full bg-white/10 rounded-full h-1.5 overflow-hidden"> | |
| <div id="progress-bar" class="h-full bg-gradient-to-r from-[hsl(var(--primary))] to-[hsl(var(--secondary))]" style="width: 0%"></div> | |
| </div> | |
| <div id="logs-container" class="space-y-1 max-h-32 overflow-y-auto"> | |
| <!-- Logs go here --> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Center: Visualizations --> | |
| <div class="lg:col-span-6 space-y-6"> | |
| <!-- Network Vis --> | |
| <div class="glass-panel p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-lg font-semibold flex items-center gap-2"> | |
| <i data-lucide="brain" class="h-5 w-5 text-[hsl(var(--primary))]"></i> Network Architecture | |
| </h2> | |
| </div> | |
| <div class="flex justify-center" id="network-container"> | |
| <!-- SVG Network goes here --> | |
| </div> | |
| </div> | |
| <!-- Data Canvas --> | |
| <div class="glass-panel p-6"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-lg font-semibold">Data & Decision Boundary</h2> | |
| <span class="text-xs px-2 py-1 bg-white/10 rounded text-[hsl(var(--primary))] font-mono">Points: <span id="points-count">0</span></span> | |
| </div> | |
| <div class="flex justify-center relative"> | |
| <div class="relative"> | |
| <canvas id="main-canvas" width="300" height="300" class="rounded-lg border border-white/10 cursor-crosshair shadow-2xl bg-black"></canvas> | |
| <div class="absolute -bottom-6 left-0 right-0 text-center text-xs text-muted-foreground text-[hsl(var(--muted-foreground))]">X Coordinate</div> | |
| <div class="absolute -left-6 top-1/2 -translate-y-1/2 -rotate-90 text-xs text-muted-foreground text-[hsl(var(--muted-foreground))]">Y Coordinate</div> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex flex-wrap justify-center gap-4 text-xs text-[hsl(var(--muted-foreground))]"> | |
| <div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-[hsl(var(--node-positive))]"></div> Class A</div> | |
| <div class="flex items-center gap-2"><div class="w-3 h-3 rounded-full bg-[hsl(var(--node-negative))]"></div> Class B</div> | |
| <div class="flex items-center gap-2"><div class="w-3 h-3 bg-[hsl(var(--node-positive))/0.3]"></div> Prediction A</div> | |
| <div class="flex items-center gap-2"><div class="w-3 h-3 bg-[hsl(var(--node-negative))/0.3]"></div> Prediction B</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Sidebar: Explainers --> | |
| <div class="lg:col-span-3 space-y-6"> | |
| <div class="w-full"> | |
| <div class="flex bg-white/5 p-1 rounded-lg mb-4"> | |
| <button onclick="switchTab('howItWorks')" id="tab-howItWorks" class="tab-btn active"><i data-lucide="lightbulb" class="h-3 w-3 mr-1 inline"></i> How It Works</button> | |
| <button onclick="switchTab('learn')" id="tab-learn" class="tab-btn"><i data-lucide="sparkles" class="h-3 w-3 mr-1 inline"></i> Learn</button> | |
| </div> | |
| <div id="content-howItWorks" class="glass-panel p-5 space-y-4"> | |
| <h3 class="text-lg font-semibold gradient-text">Live Prediction (Hover)</h3> | |
| <div class="space-y-4 text-sm"> | |
| <div> | |
| <div class="flex items-center gap-2 mb-2 font-medium text-[hsl(var(--node-input))]"> | |
| <span class="w-5 h-5 rounded-full bg-[hsl(var(--node-input))/0.2] flex items-center justify-center text-xs">1</span> Input | |
| </div> | |
| <div class="bg-white/5 p-3 rounded-lg border border-white/10 font-mono text-xs"> | |
| X: <span id="val-x">0.00</span><br> | |
| Y: <span id="val-y">0.00</span> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex items-center gap-2 mb-2 font-medium text-[hsl(var(--node-hidden))]"> | |
| <span class="w-5 h-5 rounded-full bg-[hsl(var(--node-hidden))/0.2] flex items-center justify-center text-xs">2</span> Hidden Layer | |
| </div> | |
| <div id="val-hidden" class="bg-white/5 p-3 rounded-lg border border-white/10 font-mono text-xs grid grid-cols-4 gap-1"> | |
| <!-- Hidden values --> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="flex items-center gap-2 mb-2 font-medium text-[hsl(var(--accent))]"> | |
| <span class="w-5 h-5 rounded-full bg-[hsl(var(--accent))/0.2] flex items-center justify-center text-xs">3</span> Output | |
| </div> | |
| <div class="bg-white/5 p-3 rounded-lg border border-white/10"> | |
| <div class="flex justify-between items-center"> | |
| <span class="text-xs text-gray-400">Raw: <span id="val-raw">0.0000</span></span> | |
| <span id="val-class" class="font-bold text-gray-500">-</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="content-learn" class="glass-panel p-5 space-y-4 hidden"> | |
| <div class="flex items-center gap-3"> | |
| <div class="p-2 rounded-lg bg-[hsl(var(--primary))/0.2]"><i data-lucide="brain" class="h-5 w-5 text-[hsl(var(--primary))]"></i></div> | |
| <h3 class="font-semibold gradient-text">Training Process</h3> | |
| </div> | |
| <p class="text-sm text-gray-300 leading-relaxed"> | |
| The network learns by "Backpropagation". It compares its guess to the real label, finds the error, and adjusts the weights backwards from output to input. | |
| </p> | |
| <div class="p-3 rounded-lg bg-white/5 text-xs text-gray-400 border border-white/10"> | |
| 💡 <strong>Tip:</strong> If the network gets stuck, try increasing neurons or clicking "Reset" to randomize weights. | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <script> | |
| // --- Neural Network Logic --- | |
| class SimpleNeuralNetwork { | |
| constructor(inputSize, hiddenSize, outputSize, learningRate) { | |
| this.inputSize = inputSize; | |
| this.hiddenSize = hiddenSize; | |
| this.outputSize = outputSize; | |
| this.learningRate = learningRate; | |
| // Xavier initialization | |
| const scale1 = Math.sqrt(2 / (this.inputSize + this.hiddenSize)); | |
| this.w1 = Array(this.hiddenSize).fill(0).map(() => | |
| Array(this.inputSize).fill(0).map(() => (Math.random() * 2 - 1) * scale1) | |
| ); | |
| this.b1 = Array(this.hiddenSize).fill(0); | |
| const scale2 = Math.sqrt(2 / (this.hiddenSize + this.outputSize)); | |
| this.w2 = Array(this.outputSize).fill(0).map(() => | |
| Array(this.hiddenSize).fill(0).map(() => (Math.random() * 2 - 1) * scale2) | |
| ); | |
| this.b2 = Array(this.outputSize).fill(0); | |
| } | |
| sigmoid(x) { return 1 / (1 + Math.exp(-x)); } | |
| sigmoidDeriv(y) { return y * (1 - y); } | |
| forward(inputs) { | |
| const hActivations = this.w1.map((weights, i) => | |
| this.sigmoid(weights.reduce((acc, w, j) => acc + w * inputs[j], 0) + this.b1[i]) | |
| ); | |
| const outputs = this.w2.map((weights, i) => | |
| this.sigmoid(weights.reduce((acc, w, j) => acc + w * hActivations[j], 0) + this.b2[i]) | |
| ); | |
| return { activations: [[...inputs], hActivations, outputs], output: outputs[0] }; | |
| } | |
| predict(x, y) { return this.forward([x, y]).output; } | |
| train(data, batchSize) { | |
| for(let k = 0; k < batchSize * 5; k++) { | |
| const point = data[Math.floor(Math.random() * data.length)]; | |
| const inputs = [point.x, point.y]; | |
| const target = [point.label]; | |
| const { activations } = this.forward(inputs); | |
| const hActivations = activations[1]; | |
| const outputs = activations[2]; | |
| const outputErrors = outputs.map((o, i) => target[i] - o); | |
| const outputGradients = outputs.map((o, i) => outputErrors[i] * this.sigmoidDeriv(o)); | |
| const hiddenErrors = this.w1.map((_, i) => | |
| this.w2.reduce((acc, weights, j) => acc + weights[i] * outputGradients[j], 0) | |
| ); | |
| const hiddenGradients = hActivations.map((h, i) => hiddenErrors[i] * this.sigmoidDeriv(h)); | |
| for(let i=0; i<this.outputSize; i++) { | |
| for(let j=0; j<this.hiddenSize; j++) { | |
| this.w2[i][j] += this.learningRate * outputGradients[i] * hActivations[j]; | |
| } | |
| this.b2[i] += this.learningRate * outputGradients[i]; | |
| } | |
| for(let i=0; i<this.hiddenSize; i++) { | |
| for(let j=0; j<this.inputSize; j++) { | |
| this.w1[i][j] += this.learningRate * hiddenGradients[i] * inputs[j]; | |
| } | |
| this.b1[i] += this.learningRate * hiddenGradients[i]; | |
| } | |
| } | |
| } | |
| getWeights() { return [this.w1, this.w2]; } | |
| } | |
| // --- Application State --- | |
| let state = { | |
| dataPoints: [], | |
| currentClass: 1, | |
| hiddenNeurons: 4, | |
| learningRate: 0.5, | |
| isTraining: false, | |
| network: null, | |
| epoch: 0, | |
| accuracy: 0, | |
| activations: null, | |
| predictions: [], // Store heatmap data here | |
| logs: [], | |
| lastProbe: { x: 0, y: 0 } // Track last cursor position for live updates | |
| }; | |
| // --- Helper: Generate Grid Predictions --- | |
| function generatePredictions() { | |
| const gridSize = 30; | |
| const grid = []; | |
| for (let i = 0; i < gridSize; i++) { | |
| const row = []; | |
| for (let j = 0; j < gridSize; j++) { | |
| const x = (j / gridSize) * 2 - 1; | |
| const y = 1 - (i / gridSize) * 2; | |
| row.push(state.network.predict(x, y)); | |
| } | |
| grid.push(row); | |
| } | |
| return grid; | |
| } | |
| // --- Initialization --- | |
| function init() { | |
| lucide.createIcons(); | |
| state.network = new SimpleNeuralNetwork(2, state.hiddenNeurons, 1, state.learningRate); | |
| state.predictions = generatePredictions(); // Initial heatmap | |
| setupCanvas(); | |
| renderUI(); | |
| // Set initial dummy activations | |
| state.activations = [[0,0], Array(state.hiddenNeurons).fill(0), [0]]; | |
| updateExplainers(); | |
| } | |
| // --- UI Updates --- | |
| function setClass(c) { | |
| state.currentClass = c; | |
| const btnA = document.getElementById('btn-class-a'); | |
| const btnB = document.getElementById('btn-class-b'); | |
| if(c === 1) { | |
| btnA.classList.remove('btn-glass'); btnA.classList.add('btn-accent'); | |
| btnB.classList.add('btn-glass'); btnB.classList.remove('btn-destructive'); | |
| } else { | |
| btnA.classList.add('btn-glass'); btnA.classList.remove('btn-accent'); | |
| btnB.classList.remove('btn-glass'); btnB.classList.add('btn-destructive'); | |
| } | |
| } | |
| function changeNeurons(delta) { | |
| const newVal = Math.max(1, Math.min(8, state.hiddenNeurons + delta)); | |
| state.hiddenNeurons = newVal; | |
| document.getElementById('neurons-slider').value = newVal; | |
| updateNeuronsUI(); | |
| } | |
| function changeNeuronsFromSlider(val) { | |
| state.hiddenNeurons = parseInt(val); | |
| updateNeuronsUI(); | |
| } | |
| function updateNeuronsUI() { | |
| document.getElementById('neurons-display').innerText = state.hiddenNeurons; | |
| document.getElementById('header-neurons-count').innerText = state.hiddenNeurons + " hidden neurons"; | |
| resetApp(); // Rebuild network on architecture change | |
| } | |
| function changeLR(val) { | |
| state.learningRate = val / 100; | |
| document.getElementById('lr-display').innerText = state.learningRate.toFixed(2); | |
| if(state.network) state.network.learningRate = state.learningRate; | |
| } | |
| function resetApp() { | |
| state.dataPoints = []; | |
| state.isTraining = false; | |
| state.epoch = 0; | |
| state.accuracy = 0; | |
| state.logs = []; | |
| state.network = new SimpleNeuralNetwork(2, state.hiddenNeurons, 1, state.learningRate); | |
| state.predictions = generatePredictions(); // Generate initial random boundary | |
| state.lastProbe = { x: 0, y: 0 }; | |
| document.getElementById('points-count').innerText = "0"; | |
| document.getElementById('accuracy-panel').classList.add('hidden'); | |
| document.getElementById('epoch-display').classList.add('hidden'); | |
| document.getElementById('progress-bar').style.width = '0%'; | |
| document.getElementById('logs-container').innerHTML = ''; | |
| const btnTrain = document.getElementById('btn-train'); | |
| btnTrain.innerHTML = '<i data-lucide="play" class="h-4 w-4 mr-2"></i> Train Network'; | |
| lucide.createIcons(); | |
| renderCanvas(); | |
| renderNetwork(); | |
| // Recalc activations for default probe | |
| state.activations = state.network.forward([0, 0]).activations; | |
| updateExplainers(); | |
| } | |
| function switchTab(tab) { | |
| document.getElementById('tab-howItWorks').classList.remove('active'); | |
| document.getElementById('tab-learn').classList.remove('active'); | |
| document.getElementById('content-howItWorks').classList.add('hidden'); | |
| document.getElementById('content-learn').classList.add('hidden'); | |
| document.getElementById('tab-' + tab).classList.add('active'); | |
| document.getElementById('content-' + tab).classList.remove('hidden'); | |
| } | |
| // --- Canvas Logic --- | |
| const canvas = document.getElementById('main-canvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const canvasSize = 300; | |
| const gridSize = 30; | |
| function setupCanvas() { | |
| canvas.addEventListener('mousedown', handleCanvasClick); | |
| canvas.addEventListener('mousemove', handleCanvasHover); | |
| canvas.addEventListener('mouseleave', () => { | |
| renderCanvas(); // clear hover | |
| }); | |
| renderCanvas(); | |
| } | |
| function handleCanvasClick(e) { | |
| const rect = canvas.getBoundingClientRect(); | |
| const scaleX = canvas.width / rect.width; | |
| const scaleY = canvas.height / rect.height; | |
| const clickX = (e.clientX - rect.left) * scaleX; | |
| const clickY = (e.clientY - rect.top) * scaleY; | |
| const x = (clickX / (canvasSize / 2)) - 1; | |
| const y = 1 - (clickY / (canvasSize / 2)); | |
| const point = { | |
| x: Math.max(-1, Math.min(1, x)), | |
| y: Math.max(-1, Math.min(1, y)), | |
| label: state.currentClass | |
| }; | |
| state.dataPoints.push(point); | |
| state.lastProbe = { x, y }; // Update probe | |
| document.getElementById('points-count').innerText = state.dataPoints.length; | |
| // Forward pass for viz | |
| state.activations = state.network.forward([point.x, point.y]).activations; | |
| updateExplainers(); | |
| renderCanvas(); | |
| renderNetwork(); | |
| } | |
| function handleCanvasHover(e) { | |
| const rect = canvas.getBoundingClientRect(); | |
| const scaleX = canvas.width / rect.width; | |
| const scaleY = canvas.height / rect.height; | |
| const clickX = (e.clientX - rect.left) * scaleX; | |
| const clickY = (e.clientY - rect.top) * scaleY; | |
| renderCanvas(); | |
| // Draw hover cursor | |
| ctx.beginPath(); | |
| ctx.arc(clickX, clickY, 8, 0, Math.PI * 2); | |
| ctx.strokeStyle = state.currentClass === 1 ? 'hsl(142, 71%, 45%)' : 'hsl(350, 89%, 60%)'; | |
| ctx.setLineDash([4, 4]); | |
| ctx.stroke(); | |
| ctx.setLineDash([]); | |
| // LIVE UPDATE: Calculate network output for current mouse position | |
| const x = (clickX / (canvasSize / 2)) - 1; | |
| const y = 1 - (clickY / (canvasSize / 2)); | |
| state.lastProbe = { x, y }; // Update probe tracker | |
| if (state.network) { | |
| state.activations = state.network.forward([x, y]).activations; | |
| updateExplainers(); // Update the "Output" panel text | |
| renderNetwork(); // Update the node visualizations/colors | |
| } | |
| } | |
| function renderCanvas() { | |
| // Background | |
| ctx.fillStyle = 'hsl(222, 47%, 8%)'; | |
| ctx.fillRect(0, 0, canvasSize, canvasSize); | |
| // Heatmap - Draw if predictions exist | |
| if (state.predictions && state.predictions.length > 0) { | |
| const cellSize = canvasSize / gridSize; | |
| for (let i = 0; i < gridSize; i++) { | |
| for (let j = 0; j < gridSize; j++) { | |
| const pred = state.predictions[i][j]; | |
| const hue = pred > 0.5 ? 142 : 350; | |
| const lightness = 20 + Math.abs(pred - 0.5) * 40; | |
| const alpha = 0.3 + Math.abs(pred - 0.5) * 0.4; | |
| ctx.fillStyle = `hsla(${hue}, 70%, ${lightness}%, ${alpha})`; | |
| ctx.fillRect(j * cellSize, i * cellSize, cellSize, cellSize); | |
| } | |
| } | |
| } | |
| // Grid | |
| ctx.strokeStyle = 'hsla(217, 33%, 40%, 0.2)'; | |
| ctx.lineWidth = 1; | |
| for (let i = 0; i <= canvasSize; i += 30) { | |
| ctx.beginPath(); ctx.moveTo(i, 0); ctx.lineTo(i, canvasSize); ctx.stroke(); | |
| ctx.beginPath(); ctx.moveTo(0, i); ctx.lineTo(canvasSize, i); ctx.stroke(); | |
| } | |
| // Axes | |
| ctx.strokeStyle = 'hsla(217, 33%, 50%, 0.5)'; | |
| ctx.lineWidth = 2; | |
| ctx.beginPath(); ctx.moveTo(canvasSize / 2, 0); ctx.lineTo(canvasSize / 2, canvasSize); ctx.stroke(); | |
| ctx.beginPath(); ctx.moveTo(0, canvasSize / 2); ctx.lineTo(canvasSize, canvasSize / 2); ctx.stroke(); | |
| // Points | |
| state.dataPoints.forEach(point => { | |
| const drawX = (point.x + 1) * (canvasSize / 2); | |
| const drawY = (1 - point.y) * (canvasSize / 2); | |
| ctx.beginPath(); | |
| ctx.arc(drawX, drawY, 6, 0, Math.PI * 2); | |
| ctx.fillStyle = point.label === 1 ? 'hsl(142, 71%, 45%)' : 'hsl(350, 89%, 60%)'; | |
| ctx.fill(); | |
| ctx.strokeStyle = 'white'; | |
| ctx.lineWidth = 2; | |
| ctx.stroke(); | |
| }); | |
| } | |
| // --- Network Visualizer (SVG) --- | |
| function renderNetwork() { | |
| const container = document.getElementById('network-container'); | |
| const width = 500; | |
| const height = 300; | |
| const layers = [2, state.hiddenNeurons, 1]; | |
| const layerSpacing = (width - 100) / (layers.length - 1); | |
| let svgHtml = `<svg width="${width}" height="${height}" style="overflow: visible;">`; | |
| // Calculate positions | |
| const nodePositions = []; | |
| layers.forEach((count, layerIdx) => { | |
| const x = 50 + layerIdx * layerSpacing; | |
| const maxNodes = Math.max(...layers); | |
| const vSpacing = (height - 100) / (maxNodes + 1); | |
| const offset = ((maxNodes - count) * vSpacing) / 2; | |
| for(let i=0; i<count; i++) { | |
| nodePositions.push({ | |
| x: x, | |
| y: 50 + offset + (i+1) * vSpacing, | |
| layer: layerIdx, | |
| index: i | |
| }); | |
| } | |
| }); | |
| // Draw Connections | |
| const weights = state.network.getWeights(); | |
| let fromIndex = 0; | |
| for(let l=0; l<layers.length-1; l++) { | |
| const fromCount = layers[l]; | |
| const toCount = layers[l+1]; | |
| const toStartIndex = fromIndex + fromCount; | |
| for(let i=0; i<fromCount; i++) { | |
| for(let j=0; j<toCount; j++) { | |
| const w = weights[l][j][i]; | |
| const fromNode = nodePositions[fromIndex + i]; | |
| const toNode = nodePositions[toStartIndex + j]; | |
| const opacity = Math.min(0.8, 0.1 + Math.abs(w) * 0.3); | |
| const color = w > 0 ? 'hsl(142, 71%, 45%)' : 'hsl(350, 89%, 60%)'; | |
| const dash = state.isTraining ? 'stroke-dasharray="4 4" class="animate-flow"' : ''; | |
| svgHtml += `<line x1="${fromNode.x}" y1="${fromNode.y}" x2="${toNode.x}" y2="${toNode.y}" stroke="${color}" stroke-width="${1 + Math.abs(w)}" stroke-opacity="${opacity}" ${dash} />`; | |
| } | |
| } | |
| fromIndex += fromCount; | |
| } | |
| // Draw Nodes | |
| nodePositions.forEach(node => { | |
| let activation = 0; | |
| if(state.activations) { | |
| activation = state.activations[node.layer][node.index]; | |
| } | |
| let color = 'hsl(280, 65%, 55%)'; // hidden | |
| if(node.layer === 0) color = 'hsl(199, 89%, 48%)'; // input | |
| if(node.layer === layers.length - 1) { | |
| color = activation > 0.5 ? 'hsl(142, 71%, 45%)' : 'hsl(350, 89%, 60%)'; | |
| } | |
| const r = 10 + (activation * 5); | |
| const pulseClass = state.isTraining ? 'class="animate-node-pulse"' : ''; | |
| svgHtml += `<circle cx="${node.x}" cy="${node.y}" r="${r+4}" fill="none" stroke="${color}" stroke-opacity="0.3" ${pulseClass} />`; | |
| svgHtml += `<circle cx="${node.x}" cy="${node.y}" r="${r}" fill="${color}" />`; | |
| svgHtml += `<text x="${node.x}" y="${node.y - r - 5}" text-anchor="middle" fill="white" font-size="9">${activation.toFixed(2)}</text>`; | |
| }); | |
| svgHtml += `</svg>`; | |
| container.innerHTML = svgHtml; | |
| } | |
| // --- Explainers --- | |
| function updateExplainers() { | |
| if(!state.activations) return; | |
| // Input | |
| document.getElementById('val-x').innerText = state.activations[0][0].toFixed(2); | |
| document.getElementById('val-y').innerText = state.activations[0][1].toFixed(2); | |
| // Hidden | |
| const hiddenContainer = document.getElementById('val-hidden'); | |
| let hiddenHtml = ''; | |
| state.activations[1].forEach(v => { | |
| const cls = v > 0.5 ? 'bg-white/10 text-white' : 'text-gray-500'; | |
| hiddenHtml += `<div class="text-center p-1 rounded ${cls}">${v.toFixed(1)}</div>`; | |
| }); | |
| hiddenContainer.innerHTML = hiddenHtml; | |
| // Output | |
| const outVal = state.activations[2][0]; | |
| document.getElementById('val-raw').innerText = outVal.toFixed(4); | |
| const classEl = document.getElementById('val-class'); | |
| // Direct style application | |
| if(outVal > 0.5) { | |
| classEl.innerText = "Class A"; | |
| classEl.style.color = "hsl(142, 71%, 45%)"; // Green | |
| } else { | |
| classEl.innerText = "Class B"; | |
| classEl.style.color = "hsl(350, 89%, 60%)"; // Red | |
| } | |
| } | |
| // --- Training Loop --- | |
| function toggleTraining() { | |
| if(state.dataPoints.length < 2) { | |
| alert("Please add at least 2 data points first!"); | |
| return; | |
| } | |
| state.isTraining = !state.isTraining; | |
| const btn = document.getElementById('btn-train'); | |
| if(state.isTraining) { | |
| btn.innerHTML = '<i data-lucide="zap" class="h-4 w-4 animate-pulse mr-2"></i> Stop'; | |
| document.getElementById('epoch-display').classList.remove('hidden'); | |
| document.getElementById('accuracy-panel').classList.remove('hidden'); | |
| trainStep(); | |
| } else { | |
| btn.innerHTML = '<i data-lucide="play" class="h-4 w-4 mr-2"></i> Train Network'; | |
| } | |
| lucide.createIcons(); | |
| } | |
| function trainStep() { | |
| if(!state.isTraining) return; | |
| if(state.epoch >= 100) { | |
| toggleTraining(); | |
| return; | |
| } | |
| state.network.train(state.dataPoints, 10); | |
| state.epoch++; | |
| // Recalculate predictions for the current cursor position ("lastProbe") | |
| // This ensures the "Live Prediction" text updates instantly as the network learns | |
| state.activations = state.network.forward([state.lastProbe.x, state.lastProbe.y]).activations; | |
| updateExplainers(); | |
| renderNetwork(); // Update the nodes/weights visual | |
| if(state.epoch % 5 === 0) { | |
| // Update predictions every 5 epochs | |
| state.predictions = generatePredictions(); | |
| // Calc accuracy | |
| let correct = 0; | |
| state.dataPoints.forEach(p => { | |
| if ((state.network.predict(p.x, p.y) > 0.5 ? 1 : 0) === p.label) correct++; | |
| }); | |
| state.accuracy = correct / state.dataPoints.length; | |
| // Update UI | |
| document.getElementById('epoch-display').innerText = "Epoch " + state.epoch; | |
| document.getElementById('accuracy-display').innerText = (state.accuracy * 100).toFixed(1) + "%"; | |
| document.getElementById('accuracy-display').className = "text-lg font-bold " + (state.accuracy > 0.8 ? 'text-[hsl(var(--accent))]' : state.accuracy > 0.5 ? 'text-[hsl(var(--secondary))]' : 'text-[hsl(var(--destructive))]'); | |
| document.getElementById('progress-bar').style.width = state.epoch + "%"; | |
| // Add log | |
| const logItem = `<div class="flex justify-between text-xs py-1 border-b border-white/5 last:border-0"> | |
| <span class="text-[hsl(var(--muted-foreground))]">Epoch ${state.epoch}</span> | |
| <span class="${state.accuracy > 0.8 ? 'text-[hsl(var(--accent))]' : 'text-white'}">${(state.accuracy * 100).toFixed(1)}%</span> | |
| </div>`; | |
| document.getElementById('logs-container').insertAdjacentHTML('afterbegin', logItem); | |
| renderCanvas(); | |
| } | |
| requestAnimationFrame(trainStep); | |
| } | |
| // --- Presets --- | |
| function loadPreset(type) { | |
| let points = []; | |
| if (type === 'XOR') { | |
| for(let i=0; i<20; i++) { | |
| points.push({ x: -0.5 + Math.random()*0.3, y: 0.5 + Math.random()*0.3, label: 1 }); | |
| points.push({ x: 0.5 + Math.random()*0.3, y: -0.5 - Math.random()*0.3, label: 1 }); | |
| points.push({ x: 0.5 + Math.random()*0.3, y: 0.5 + Math.random()*0.3, label: 0 }); | |
| points.push({ x: -0.5 + Math.random()*0.3, y: -0.5 - Math.random()*0.3, label: 0 }); | |
| } | |
| } else if (type === 'Circle') { | |
| for(let i=0; i<40; i++) { | |
| const angle = Math.random() * Math.PI * 2; | |
| const r1 = Math.random() * 0.4; | |
| points.push({ x: Math.cos(angle)*r1, y: Math.sin(angle)*r1, label: 1 }); | |
| const r2 = 0.6 + Math.random() * 0.3; | |
| points.push({ x: Math.cos(angle)*r2, y: Math.sin(angle)*r2, label: 0 }); | |
| } | |
| } else if (type === 'Linear') { | |
| for(let i=0; i<30; i++) { | |
| points.push({ x: -0.4 - Math.random()*0.4, y: Math.random()*1.6 - 0.8, label: 1 }); | |
| points.push({ x: 0.4 + Math.random()*0.4, y: Math.random()*1.6 - 0.8, label: 0 }); | |
| } | |
| } else if (type === 'Spiral') { | |
| for (let i = 0; i < 60; i++) { | |
| const r = i / 60; | |
| const t = 1.75 * i / 60 * 2 * Math.PI; | |
| points.push({ x: r * Math.sin(t), y: r * Math.cos(t), label: 1 }); | |
| points.push({ x: r * Math.sin(t + Math.PI), y: r * Math.cos(t + Math.PI), label: 0 }); | |
| } | |
| } | |
| // Reset but keep new points and generate initial map | |
| state.dataPoints = points; | |
| state.isTraining = false; | |
| state.epoch = 0; | |
| state.accuracy = 0; | |
| state.logs = []; | |
| state.network = new SimpleNeuralNetwork(2, state.hiddenNeurons, 1, state.learningRate); | |
| state.predictions = generatePredictions(); // Generate immediate prediction map | |
| state.lastProbe = { x: 0, y: 0 }; | |
| document.getElementById('points-count').innerText = points.length; | |
| document.getElementById('accuracy-panel').classList.add('hidden'); | |
| document.getElementById('epoch-display').classList.add('hidden'); | |
| document.getElementById('progress-bar').style.width = '0%'; | |
| document.getElementById('logs-container').innerHTML = ''; | |
| const btnTrain = document.getElementById('btn-train'); | |
| btnTrain.innerHTML = '<i data-lucide="play" class="h-4 w-4 mr-2"></i> Train Network'; | |
| lucide.createIcons(); | |
| renderCanvas(); | |
| renderNetwork(); | |
| if(points.length) { | |
| // Set probe to first point to avoid 0,0 default | |
| state.lastProbe = { x: points[0].x, y: points[0].y }; | |
| state.activations = state.network.forward([points[0].x, points[0].y]).activations; | |
| updateExplainers(); | |
| } | |
| } | |
| function renderUI() { | |
| renderNetwork(); | |
| } | |
| // Start | |
| window.onload = init; | |
| </script> | |
| </body> | |
| </html> |