Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>AI Explainer: How Neural Networks Work</title> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
} | |
body { | |
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; | |
background: #0a0a0a; | |
color: #e0e0e0; | |
line-height: 1.6; | |
overflow-x: hidden; | |
} | |
.container { | |
max-width: 1200px; | |
margin: 0 auto; | |
padding: 20px; | |
} | |
header { | |
text-align: center; | |
padding: 40px 20px; | |
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); | |
margin-bottom: 40px; | |
border-radius: 20px; | |
} | |
h1 { | |
font-size: clamp(2rem, 5vw, 3rem); | |
margin-bottom: 10px; | |
background: linear-gradient(135deg, #fff 0%, #a8dadc 100%); | |
-webkit-background-clip: text; | |
-webkit-text-fill-color: transparent; | |
} | |
.mode-toggle { | |
display: flex; | |
justify-content: center; | |
gap: 20px; | |
margin: 30px 0; | |
flex-wrap: wrap; | |
} | |
.mode-btn { | |
padding: 12px 30px; | |
background: #2a5298; | |
color: white; | |
border: none; | |
border-radius: 50px; | |
cursor: pointer; | |
font-size: 16px; | |
transition: all 0.3s ease; | |
font-weight: 600; | |
} | |
.mode-btn.active { | |
background: #4CAF50; | |
transform: scale(1.05); | |
} | |
.mode-btn:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 5px 15px rgba(74, 144, 226, 0.3); | |
} | |
.section { | |
background: #1a1a1a; | |
padding: 30px; | |
margin-bottom: 30px; | |
border-radius: 20px; | |
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5); | |
} | |
.section h2 { | |
color: #4CAF50; | |
margin-bottom: 20px; | |
font-size: clamp(1.5rem, 4vw, 2rem); | |
} | |
.section h3 { | |
color: #81C784; | |
margin: 20px 0 10px 0; | |
font-size: clamp(1.2rem, 3vw, 1.5rem); | |
} | |
.math-content { | |
background: #0d0d0d; | |
padding: 20px; | |
border-radius: 10px; | |
overflow-x: auto; | |
margin: 15px 0; | |
border: 1px solid #333; | |
} | |
.learn-content { | |
background: #1e3c72; | |
padding: 20px; | |
border-radius: 10px; | |
margin: 15px 0; | |
line-height: 1.8; | |
} | |
#xor-demo { | |
background: #0d0d0d; | |
padding: 20px; | |
border-radius: 15px; | |
margin: 20px 0; | |
} | |
#network-canvas { | |
width: 100%; | |
max-width: 800px; | |
height: 400px; | |
background: #000; | |
border-radius: 10px; | |
margin: 20px auto; | |
display: block; | |
} | |
.controls { | |
display: flex; | |
gap: 15px; | |
justify-content: center; | |
flex-wrap: wrap; | |
margin: 20px 0; | |
} | |
.control-btn { | |
padding: 10px 25px; | |
background: #4CAF50; | |
color: white; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
font-size: 16px; | |
transition: all 0.3s ease; | |
} | |
.control-btn:hover { | |
background: #45a049; | |
transform: translateY(-2px); | |
} | |
.control-btn:disabled { | |
background: #666; | |
cursor: not-allowed; | |
} | |
.stats { | |
display: grid; | |
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); | |
gap: 15px; | |
margin: 20px 0; | |
} | |
.stat-box { | |
background: #1a1a1a; | |
padding: 15px; | |
border-radius: 10px; | |
text-align: center; | |
border: 1px solid #333; | |
} | |
.stat-label { | |
color: #888; | |
font-size: 14px; | |
} | |
.stat-value { | |
color: #4CAF50; | |
font-size: 24px; | |
font-weight: bold; | |
margin-top: 5px; | |
} | |
.loss-chart { | |
width: 100%; | |
height: 200px; | |
background: #000; | |
border-radius: 10px; | |
margin: 20px 0; | |
} | |
.formula { | |
font-family: 'Courier New', monospace; | |
color: #64B5F6; | |
padding: 10px; | |
background: rgba(0, 0, 0, 0.5); | |
border-radius: 5px; | |
overflow-x: auto; | |
white-space: nowrap; | |
margin: 10px 0; | |
} | |
.highlight { | |
background: #4CAF50; | |
color: #000; | |
padding: 2px 6px; | |
border-radius: 3px; | |
font-weight: bold; | |
} | |
@media (max-width: 768px) { | |
.container { | |
padding: 10px; | |
} | |
.section { | |
padding: 20px; | |
} | |
#network-canvas { | |
height: 300px; | |
} | |
.controls { | |
gap: 10px; | |
} | |
.control-btn { | |
padding: 8px 20px; | |
font-size: 14px; | |
} | |
} | |
.mode-content { | |
display: none; | |
} | |
.mode-content.active { | |
display: block; | |
} | |
.animated-number { | |
transition: all 0.3s ease; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.1); } | |
100% { transform: scale(1); } | |
} | |
.pulse { | |
animation: pulse 0.5s ease; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container"> | |
<header> | |
<h1>🧠 How AI Really Works</h1> | |
<p>An Interactive Journey Inside Neural Networks</p> | |
</header> | |
<div class="mode-toggle"> | |
<button class="mode-btn active" onclick="setMode('learn')">🎓 Learn Mode</button> | |
<button class="mode-btn" onclick="setMode('math')">🔢 Math Mode</button> | |
</div> | |
<div class="section"> | |
<h2>What is a Neural Network?</h2> | |
<div class="mode-content learn-mode active"> | |
<div class="learn-content"> | |
<p>Imagine your brain is made of billions of tiny decision-makers called neurons. Each neuron:</p> | |
<ul style="margin: 15px 0; padding-left: 30px;"> | |
<li>🎯 Takes in information (inputs)</li> | |
<li>🤔 Thinks about it (processing)</li> | |
<li>💡 Makes a decision (output)</li> | |
</ul> | |
<p>An AI neural network works the same way! It's like a simplified brain made of math. Let's see it in action!</p> | |
</div> | |
</div> | |
<div class="mode-content math-mode"> | |
<div class="math-content"> | |
<p>A neural network is a function approximator that transforms inputs through layers of neurons:</p> | |
<div class="formula"> | |
f(x) = σ(W₃ · σ(W₂ · σ(W₁ · x + b₁) + b₂) + b₃) | |
</div> | |
<p>Where:</p> | |
<ul style="margin: 15px 0; padding-left: 30px;"> | |
<li>x = input vector</li> | |
<li>Wᵢ = weight matrix for layer i</li> | |
<li>bᵢ = bias vector for layer i</li> | |
<li>σ = activation function (e.g., ReLU, sigmoid)</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
<div class="section"> | |
<h2>🎮 Live XOR Training Demo</h2> | |
<p>Watch an AI learn the XOR problem in real-time! XOR outputs 1 when inputs are different, 0 when same.</p> | |
<div id="xor-demo"> | |
<canvas id="network-canvas"></canvas> | |
<div class="controls"> | |
<button class="control-btn" onclick="startTraining()">▶️ Start Training</button> | |
<button class="control-btn" onclick="pauseTraining()">⏸️ Pause</button> | |
<button class="control-btn" onclick="resetNetwork()">🔄 Reset</button> | |
<button class="control-btn" onclick="stepTraining()">⏭️ Step</button> | |
</div> | |
<div class="stats"> | |
<div class="stat-box"> | |
<div class="stat-label">Epoch</div> | |
<div class="stat-value animated-number" id="epoch">0</div> | |
</div> | |
<div class="stat-box"> | |
<div class="stat-label">Loss</div> | |
<div class="stat-value animated-number" id="loss">1.000</div> | |
</div> | |
<div class="stat-box"> | |
<div class="stat-label">Accuracy</div> | |
<div class="stat-value animated-number" id="accuracy">0%</div> | |
</div> | |
<div class="stat-box"> | |
<div class="stat-label">Learning Rate</div> | |
<div class="stat-value" id="learning-rate">0.1</div> | |
</div> | |
</div> | |
<canvas id="loss-chart" class="loss-chart"></canvas> | |
</div> | |
</div> | |
<div class="section"> | |
<h2>How Does Learning Work?</h2> | |
<div class="mode-content learn-mode active"> | |
<h3>🎯 Forward Pass: Making Predictions</h3> | |
<div class="learn-content"> | |
<p>The network makes a prediction by passing data forward through each layer:</p> | |
<ol style="margin: 15px 0; padding-left: 30px;"> | |
<li><span class="highlight">Input</span>: Feed in the data (like 0,1 for XOR)</li> | |
<li><span class="highlight">Multiply & Add</span>: Each connection has a "strength" (weight)</li> | |
<li><span class="highlight">Activate</span>: Decide if the neuron should "fire"</li> | |
<li><span class="highlight">Output</span>: Get the final prediction</li> | |
</ol> | |
</div> | |
<h3>📉 Backward Pass: Learning from Mistakes</h3> | |
<div class="learn-content"> | |
<p>When the network is wrong, it learns by adjusting its connections:</p> | |
<ol style="margin: 15px 0; padding-left: 30px;"> | |
<li><span class="highlight">Calculate Error</span>: How wrong was the prediction?</li> | |
<li><span class="highlight">Blame Game</span>: Which connections caused the error?</li> | |
<li><span class="highlight">Adjust Weights</span>: Make connections stronger or weaker</li> | |
<li><span class="highlight">Repeat</span>: Try again with new weights!</li> | |
</ol> | |
</div> | |
</div> | |
<div class="mode-content math-mode"> | |
<h3>Forward Propagation</h3> | |
<div class="math-content"> | |
<p>For each layer l:</p> | |
<div class="formula"> | |
z[l] = W[l] · a[l-1] + b[l] | |
</div> | |
<div class="formula"> | |
a[l] = σ(z[l]) | |
</div> | |
<p>Where a[0] = x (input) and a[L] = ŷ (output)</p> | |
</div> | |
<h3>Backpropagation</h3> | |
<div class="math-content"> | |
<p>Loss function (Mean Squared Error):</p> | |
<div class="formula"> | |
L = ½ Σ(y - ŷ)² | |
</div> | |
<p>Gradient computation:</p> | |
<div class="formula"> | |
δ[L] = ∇ₐL ⊙ σ'(z[L]) | |
</div> | |
<div class="formula"> | |
δ[l] = (W[l+1]ᵀ · δ[l+1]) ⊙ σ'(z[l]) | |
</div> | |
<p>Weight update:</p> | |
<div class="formula"> | |
W[l] = W[l] - α · δ[l] · a[l-1]ᵀ | |
</div> | |
<div class="formula"> | |
b[l] = b[l] - α · δ[l] | |
</div> | |
</div> | |
</div> | |
</div> | |
<div class="section"> | |
<h2>Key Components Explained</h2> | |
<div class="mode-content learn-mode active"> | |
<h3>🔗 Weights & Biases</h3> | |
<div class="learn-content"> | |
<p><span class="highlight">Weights</span> are like volume knobs - they control how much each input matters.</p> | |
<p><span class="highlight">Biases</span> are like thresholds - they decide when a neuron should activate.</p> | |
</div> | |
<h3>⚡ Activation Functions</h3> | |
<div class="learn-content"> | |
<p>These decide if a neuron should "fire" or not:</p> | |
<ul style="margin: 15px 0; padding-left: 30px;"> | |
<li><span class="highlight">ReLU</span>: If positive, pass it on. If negative, block it!</li> | |
<li><span class="highlight">Sigmoid</span>: Squash everything between 0 and 1</li> | |
<li><span class="highlight">Tanh</span>: Squash everything between -1 and 1</li> | |
</ul> | |
</div> | |
<h3>🎯 Gradient Descent</h3> | |
<div class="learn-content"> | |
<p>Imagine you're blindfolded on a hill, trying to reach the bottom:</p> | |
<ol style="margin: 15px 0; padding-left: 30px;"> | |
<li>Feel the slope around you (calculate gradient)</li> | |
<li>Take a small step downhill (adjust weights)</li> | |
<li>Repeat until you reach the bottom (minimum loss)</li> | |
</ol> | |
</div> | |
</div> | |
<div class="mode-content math-mode"> | |
<h3>Activation Functions</h3> | |
<div class="math-content"> | |
<p><strong>ReLU:</strong></p> | |
<div class="formula"> | |
f(x) = max(0, x) | |
</div> | |
<div class="formula"> | |
f'(x) = {1 if x > 0, 0 if x ≤ 0} | |
</div> | |
<p><strong>Sigmoid:</strong></p> | |
<div class="formula"> | |
σ(x) = 1 / (1 + e⁻ˣ) | |
</div> | |
<div class="formula"> | |
σ'(x) = σ(x) · (1 - σ(x)) | |
</div> | |
<p><strong>Tanh:</strong></p> | |
<div class="formula"> | |
tanh(x) = (eˣ - e⁻ˣ) / (eˣ + e⁻ˣ) | |
</div> | |
<div class="formula"> | |
tanh'(x) = 1 - tanh²(x) | |
</div> | |
</div> | |
<h3>Gradient Descent Update Rule</h3> | |
<div class="math-content"> | |
<div class="formula"> | |
θₜ₊₁ = θₜ - α · ∇θ L(θₜ) | |
</div> | |
<p>Where:</p> | |
<ul style="margin: 15px 0; padding-left: 30px;"> | |
<li>θ = parameters (weights and biases)</li> | |
<li>α = learning rate</li> | |
<li>∇θ L = gradient of loss with respect to parameters</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
// Global variables | |
let mode = 'learn'; | |
let network = null; | |
let training = false; | |
let epoch = 0; | |
let lossHistory = []; | |
const canvas = document.getElementById('network-canvas'); | |
const ctx = canvas.getContext('2d'); | |
const lossCanvas = document.getElementById('loss-chart'); | |
const lossCtx = lossCanvas.getContext('2d'); | |
// Set canvas sizes | |
function resizeCanvases() { | |
canvas.width = canvas.offsetWidth; | |
canvas.height = canvas.offsetHeight; | |
lossCanvas.width = lossCanvas.offsetWidth; | |
lossCanvas.height = lossCanvas.offsetHeight; | |
} | |
resizeCanvases(); | |
window.addEventListener('resize', resizeCanvases); | |
// Mode switching | |
function setMode(newMode) { | |
mode = newMode; | |
document.querySelectorAll('.mode-btn').forEach(btn => { | |
btn.classList.toggle('active', btn.textContent.toLowerCase().includes(newMode)); | |
}); | |
document.querySelectorAll('.mode-content').forEach(content => { | |
content.classList.toggle('active', content.classList.contains(`${newMode}-mode`)); | |
}); | |
} | |
// Neural Network Class | |
class NeuralNetwork { | |
constructor() { | |
// Network architecture: 2-25-25-1 (roughly 100 parameters) | |
this.layers = [2, 25, 25, 1]; | |
this.weights = []; | |
this.biases = []; | |
this.activations = []; | |
this.zValues = []; | |
this.gradients = []; | |
this.learningRate = 0.1; | |
this.initializeNetwork(); | |
} | |
initializeNetwork() { | |
// Xavier initialization | |
for (let i = 1; i < this.layers.length; i++) { | |
const rows = this.layers[i]; | |
const cols = this.layers[i-1]; | |
const scale = Math.sqrt(2.0 / cols); | |
// Initialize weights | |
this.weights[i-1] = []; | |
for (let r = 0; r < rows; r++) { | |
this.weights[i-1][r] = []; | |
for (let c = 0; c < cols; c++) { | |
this.weights[i-1][r][c] = (Math.random() * 2 - 1) * scale; | |
} | |
} | |
// Initialize biases | |
this.biases[i-1] = new Array(rows).fill(0); | |
} | |
} | |
sigmoid(x) { | |
return 1 / (1 + Math.exp(-x)); | |
} | |
sigmoidDerivative(x) { | |
const s = this.sigmoid(x); | |
return s * (1 - s); | |
} | |
relu(x) { | |
return Math.max(0, x); | |
} | |
reluDerivative(x) { | |
return x > 0 ? 1 : 0; | |
} | |
forward(input) { | |
this.activations = [input]; | |
this.zValues = []; | |
for (let i = 0; i < this.weights.length; i++) { | |
const z = []; | |
const a = []; | |
for (let j = 0; j < this.weights[i].length; j++) { | |
let sum = this.biases[i][j]; | |
for (let k = 0; k < this.weights[i][j].length; k++) { | |
sum += this.weights[i][j][k] * this.activations[i][k]; | |
} | |
z.push(sum); | |
// Use ReLU for hidden layers, sigmoid for output | |
if (i < this.weights.length - 1) { | |
a.push(this.relu(sum)); | |
} else { | |
a.push(this.sigmoid(sum)); | |
} | |
} | |
this.zValues.push(z); | |
this.activations.push(a); | |
} | |
return this.activations[this.activations.length - 1][0]; | |
} | |
backward(input, target) { | |
const output = this.forward(input); | |
const error = output - target; | |
// Initialize gradients | |
this.gradients = []; | |
// Output layer gradients | |
let delta = [error * this.sigmoidDerivative(this.zValues[this.zValues.length - 1][0])]; | |
this.gradients.unshift(delta); | |
// Hidden layer gradients | |
for (let i = this.weights.length - 2; i >= 0; i--) { | |
const newDelta = []; | |
for (let j = 0; j < this.weights[i].length; j++) { | |
let sum = 0; | |
for (let k = 0; k < delta.length; k++) { | |
sum += this.weights[i+1][k][j] * delta[k]; | |
} | |
const activation = i > 0 ? | |
this.reluDerivative(this.zValues[i][j]) : | |
this.reluDerivative(this.zValues[i][j]); | |
newDelta.push(sum * activation); | |
} | |
delta = newDelta; | |
this.gradients.unshift(delta); | |
} | |
// Update weights and biases | |
for (let i = 0; i < this.weights.length; i++) { | |
for (let j = 0; j < this.weights[i].length; j++) { | |
for (let k = 0; k < this.weights[i][j].length; k++) { | |
this.weights[i][j][k] -= this.learningRate * this.gradients[i][j] * this.activations[i][k]; | |
} | |
this.biases[i][j] -= this.learningRate * this.gradients[i][j]; | |
} | |
} | |
return error * error; | |
} | |
train(inputs, targets) { | |
let totalLoss = 0; | |
for (let i = 0; i < inputs.length; i++) { | |
totalLoss += this.backward(inputs[i], targets[i]); | |
} | |
return totalLoss / inputs.length; | |
} | |
predict(input) { | |
return this.forward(input); | |
} | |
} | |
// XOR training data | |
const xorInputs = [[0, 0], [0, 1], [1, 0], [1, 1]]; | |
const xorTargets = [0, 1, 1, 0]; | |
// Initialize network | |
function resetNetwork() { | |
network = new NeuralNetwork(); | |
epoch = 0; | |
lossHistory = []; | |
training = false; | |
updateStats(); | |
drawNetwork(); | |
drawLossChart(); | |
} | |
// Training functions | |
function startTraining() { | |
training = true; | |
trainLoop(); | |
} | |
function pauseTraining() { | |
training = false; | |
} | |
function stepTraining() { | |
if (!network) resetNetwork(); | |
trainStep(); | |
} | |
function trainStep() { | |
const loss = network.train(xorInputs, xorTargets); | |
epoch++; | |
lossHistory.push(loss); | |
if (lossHistory.length > 100) lossHistory.shift(); | |
updateStats(); | |
drawNetwork(); | |
drawLossChart(); | |
} | |
function trainLoop() { | |
if (!training) return; | |
trainStep(); | |
if (epoch < 1000 && lossHistory[lossHistory.length - 1] > 0.001) { | |
requestAnimationFrame(trainLoop); | |
} else { | |
training = false; | |
} | |
} | |
// Update statistics | |
function updateStats() { | |
document.getElementById('epoch').textContent = epoch; | |
const loss = lossHistory.length > 0 ? lossHistory[lossHistory.length - 1] : 1; | |
document.getElementById('loss').textContent = loss.toFixed(4); | |
// Calculate accuracy | |
let correct = 0; | |
for (let i = 0; i < xorInputs.length; i++) { | |
const prediction = network ? network.predict(xorInputs[i]) : 0.5; | |
const rounded = Math.round(prediction); | |
if (rounded === xorTargets[i]) correct++; | |
} | |
const accuracy = (correct / xorInputs.length * 100).toFixed(0); | |
document.getElementById('accuracy').textContent = accuracy + '%'; | |
// Add pulse animation on high accuracy | |
if (accuracy >= 100) { | |
document.getElementById('accuracy').parentElement.classList.add('pulse'); | |
setTimeout(() => { | |
document.getElementById('accuracy').parentElement.classList.remove('pulse'); | |
}, 500); | |
} | |
} | |
// Visualization functions | |
function drawNetwork() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
if (!network) return; | |
const layerSpacing = canvas.width / (network.layers.length + 1); | |
const neurons = []; | |
// Calculate neuron positions | |
for (let i = 0; i < network.layers.length; i++) { | |
neurons[i] = []; | |
const layerSize = network.layers[i]; | |
const ySpacing = canvas.height / (layerSize + 1); | |
for (let j = 0; j < layerSize; j++) { | |
const x = layerSpacing * (i + 1); | |
const y = ySpacing * (j + 1); | |
neurons[i].push({ x, y }); | |
} | |
} | |
// Draw connections | |
for (let i = 0; i < network.weights.length; i++) { | |
for (let j = 0; j < network.weights[i].length; j++) { | |
for (let k = 0; k < network.weights[i][j].length; k++) { | |
const weight = network.weights[i][j][k]; | |
const opacity = Math.min(Math.abs(weight) / 2, 1); | |
ctx.beginPath(); | |
ctx.moveTo(neurons[i][k].x, neurons[i][k].y); | |
ctx.lineTo(neurons[i+1][j].x, neurons[i+1][j].y); | |
if (weight > 0) { | |
ctx.strokeStyle = `rgba(76, 175, 80, ${opacity})`; | |
} else { | |
ctx.strokeStyle = `rgba(244, 67, 54, ${opacity})`; | |
} | |
ctx.lineWidth = Math.abs(weight) * 2; | |
ctx.stroke(); | |
} | |
} | |
} | |
// Draw neurons | |
for (let i = 0; i < neurons.length; i++) { | |
for (let j = 0; j < neurons[i].length; j++) { | |
const neuron = neurons[i][j]; | |
// Get activation value | |
let activation = 0; | |
if (network.activations[i] && network.activations[i][j] !== undefined) { | |
activation = network.activations[i][j]; | |
} | |
const intensity = Math.min(activation * 255, 255); | |
ctx.beginPath(); | |
ctx.arc(neuron.x, neuron.y, 15, 0, Math.PI * 2); | |
ctx.fillStyle = `rgb(${intensity}, ${intensity}, ${255})`; | |
ctx.fill(); | |
ctx.strokeStyle = '#4CAF50'; | |
ctx.lineWidth = 2; | |
ctx.stroke(); | |
// Draw activation value for visible neurons | |
if (network.layers[i] <= 5 || i === 0 || i === network.layers.length - 1) { | |
ctx.fillStyle = '#fff'; | |
ctx.font = '10px Arial'; | |
ctx.textAlign = 'center'; | |
ctx.textBaseline = 'middle'; | |
ctx.fillText(activation.toFixed(2), neuron.x, neuron.y); | |
} | |
} | |
} | |
// Draw layer labels | |
ctx.fillStyle = '#888'; | |
ctx.font = '14px Arial'; | |
ctx.textAlign = 'center'; | |
const labels = ['Input', 'Hidden 1', 'Hidden 2', 'Output']; | |
for (let i = 0; i < network.layers.length; i++) { | |
const x = layerSpacing * (i + 1); | |
ctx.fillText(labels[i], x, 30); | |
ctx.fillText(`(${network.layers[i]} neurons)`, x, 45); | |
} | |
// Draw XOR truth table | |
ctx.fillStyle = '#4CAF50'; | |
ctx.font = '12px Arial'; | |
ctx.textAlign = 'left'; | |
ctx.fillText('XOR Truth Table:', 20, canvas.height - 80); | |
ctx.fillStyle = '#888'; | |
ctx.fillText('0 XOR 0 = 0', 20, canvas.height - 60); | |
ctx.fillText('0 XOR 1 = 1', 20, canvas.height - 45); | |
ctx.fillText('1 XOR 0 = 1', 20, canvas.height - 30); | |
ctx.fillText('1 XOR 1 = 0', 20, canvas.height - 15); | |
// Show current predictions | |
if (network) { | |
ctx.fillStyle = '#4CAF50'; | |
ctx.fillText('Network Output:', 150, canvas.height - 80); | |
ctx.fillStyle = '#888'; | |
for (let i = 0; i < xorInputs.length; i++) { | |
const prediction = network.predict(xorInputs[i]); | |
const text = `${xorInputs[i][0]} XOR ${xorInputs[i][1]} = ${prediction.toFixed(3)}`; | |
ctx.fillText(text, 150, canvas.height - 60 + i * 15); | |
} | |
} | |
} | |
function drawLossChart() { | |
lossCtx.clearRect(0, 0, lossCanvas.width, lossCanvas.height); | |
if (lossHistory.length < 2) return; | |
// Find min and max for scaling | |
const maxLoss = Math.max(...lossHistory, 0.5); | |
const minLoss = 0; | |
// Draw axes | |
lossCtx.strokeStyle = '#444'; | |
lossCtx.lineWidth = 1; | |
lossCtx.beginPath(); | |
lossCtx.moveTo(40, 10); | |
lossCtx.lineTo(40, lossCanvas.height - 30); | |
lossCtx.lineTo(lossCanvas.width - 10, lossCanvas.height - 30); | |
lossCtx.stroke(); | |
// Draw labels | |
lossCtx.fillStyle = '#888'; | |
lossCtx.font = '12px Arial'; | |
lossCtx.textAlign = 'right'; | |
lossCtx.fillText(maxLoss.toFixed(3), 35, 15); | |
lossCtx.fillText('0', 35, lossCanvas.height - 30); | |
lossCtx.textAlign = 'center'; | |
lossCtx.fillText('Loss over Time', lossCanvas.width / 2, lossCanvas.height - 10); | |
// Draw loss curve | |
lossCtx.strokeStyle = '#4CAF50'; | |
lossCtx.lineWidth = 2; | |
lossCtx.beginPath(); | |
const xStep = (lossCanvas.width - 50) / (lossHistory.length - 1); | |
const yScale = (lossCanvas.height - 50) / (maxLoss - minLoss); | |
for (let i = 0; i < lossHistory.length; i++) { | |
const x = 40 + i * xStep; | |
const y = lossCanvas.height - 30 - (lossHistory[i] - minLoss) * yScale; | |
if (i === 0) { | |
lossCtx.moveTo(x, y); | |
} else { | |
lossCtx.lineTo(x, y); | |
} | |
} | |
lossCtx.stroke(); | |
// Draw current loss point | |
if (lossHistory.length > 0) { | |
const lastX = 40 + (lossHistory.length - 1) * xStep; | |
const lastY = lossCanvas.height - 30 - (lossHistory[lossHistory.length - 1] - minLoss) * yScale; | |
lossCtx.beginPath(); | |
lossCtx.arc(lastX, lastY, 4, 0, Math.PI * 2); | |
lossCtx.fillStyle = '#4CAF50'; | |
lossCtx.fill(); | |
} | |
} | |
// Initialize | |
resetNetwork(); | |
</script> | |
</body> | |
</html> |