// Data const data = { linearRegression: [ { experience: 1, salary: 39.764 }, { experience: 2, salary: 48.900 }, { experience: 3, salary: 56.978 }, { experience: 4, salary: 68.290 }, { experience: 5, salary: 77.867 }, { experience: 6, salary: 85.022 } ], logistic: [ { height: 150, label: 0, prob: 0.2 }, { height: 160, label: 0, prob: 0.35 }, { height: 170, label: 0, prob: 0.5 }, { height: 180, label: 1, prob: 0.65 }, { height: 190, label: 1, prob: 0.8 }, { height: 200, label: 1, prob: 0.9 } ], svm: [ { label: 'A', x1: 2, x2: 7, class: 1 }, { label: 'B', x1: 3, x2: 8, class: 1 }, { label: 'C', x1: 4, x2: 7, class: 1 }, { label: 'D', x1: 6, x2: 2, class: -1 }, { label: 'E', x1: 7, x2: 3, class: -1 }, { label: 'F', x1: 8, x2: 2, class: -1 } ], knn: [ { x: 1, y: 2, class: 'orange' }, { x: 0.9, y: 1.7, class: 'orange' }, { x: 1.5, y: 2.5, class: 'orange' }, { x: 4, y: 5, class: 'yellow' }, { x: 4.2, y: 4.8, class: 'yellow' }, { x: 3.8, y: 5.2, class: 'yellow' } ], roc: [ { id: 'A', true_label: 1, score: 0.95 }, { id: 'B', true_label: 0, score: 0.70 }, { id: 'C', true_label: 1, score: 0.60 }, { id: 'D', true_label: 0, score: 0.40 }, { id: 'E', true_label: 1, score: 0.20 } ] }; // State let state = { slope: 7.5, intercept: 32, learningRate: 0.1, gdIterations: [], testPoint: { x: 2, y: 1 }, svm: { w1: 1, w2: 1, b: -10, C: 1, kernel: 'linear', kernelParam: 1, training: { w: [0, 0], b: 0, step: 0, learningRate: 0.01, isTraining: false } } }; // Initialize collapsible sections function initSections() { const sections = document.querySelectorAll('.section'); sections.forEach(section => { const header = section.querySelector('.section-header'); const toggle = section.querySelector('.section-toggle'); const body = section.querySelector('.section-body'); // Start with first section expanded if (section.id === 'intro') { body.classList.add('expanded'); toggle.classList.remove('collapsed'); } else { toggle.classList.add('collapsed'); } header.addEventListener('click', () => { const isExpanded = body.classList.contains('expanded'); if (isExpanded) { body.classList.remove('expanded'); toggle.classList.add('collapsed'); } else { body.classList.add('expanded'); toggle.classList.remove('collapsed'); // Initialize visualizations when section opens if (section.id === 'linear-regression') initLinearRegression(); if (section.id === 'gradient-descent') initGradientDescent(); if (section.id === 'logistic-regression') initLogistic(); if (section.id === 'svm') initSVM(); if (section.id === 'knn') initKNN(); if (section.id === 'model-evaluation') initModelEvaluation(); if (section.id === 'regularization') initRegularization(); if (section.id === 'bias-variance') initBiasVariance(); if (section.id === 'cross-validation') initCrossValidation(); if (section.id === 'preprocessing') initPreprocessing(); if (section.id === 'loss-functions') initLossFunctions(); } }); }); } // Smooth scroll for TOC links function initTOCLinks() { const links = document.querySelectorAll('.toc-link'); links.forEach(link => { link.addEventListener('click', (e) => { e.preventDefault(); const targetId = link.getAttribute('href').substring(1); const target = document.getElementById(targetId); if (target) { // Remove active from all links links.forEach(l => l.classList.remove('active')); link.classList.add('active'); // Scroll to target target.scrollIntoView({ behavior: 'smooth', block: 'start' }); // Expand the section const toggle = target.querySelector('.section-toggle'); const body = target.querySelector('.section-body'); body.classList.add('expanded'); toggle.classList.remove('collapsed'); } }); }); // Update active link on scroll let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { window.requestAnimationFrame(() => { updateActiveLink(); ticking = false; }); ticking = true; } }); } function updateActiveLink() { const sections = document.querySelectorAll('.section'); const scrollPos = window.scrollY + 100; sections.forEach(section => { const top = section.offsetTop; const height = section.offsetHeight; const id = section.getAttribute('id'); if (scrollPos >= top && scrollPos < top + height) { document.querySelectorAll('.toc-link').forEach(link => { link.classList.remove('active'); if (link.getAttribute('href') === '#' + id) { link.classList.add('active'); } }); } }); } // Linear Regression Visualization function initLinearRegression() { const canvas = document.getElementById('lr-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const slopeSlider = document.getElementById('slope-slider'); const interceptSlider = document.getElementById('intercept-slider'); const slopeVal = document.getElementById('slope-val'); const interceptVal = document.getElementById('intercept-val'); if (slopeSlider) { slopeSlider.addEventListener('input', (e) => { state.slope = parseFloat(e.target.value); slopeVal.textContent = state.slope.toFixed(1); drawLinearRegression(); }); } if (interceptSlider) { interceptSlider.addEventListener('input', (e) => { state.intercept = parseFloat(e.target.value); interceptVal.textContent = state.intercept.toFixed(1); drawLinearRegression(); }); } drawLinearRegression(); } function drawLinearRegression() { const canvas = document.getElementById('lr-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 0, xMax = 7; const yMin = 0, yMax = 100; // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); // Grid ctx.strokeStyle = 'rgba(42, 53, 68, 0.3)'; ctx.lineWidth = 1; for (let i = 0; i <= 5; i++) { const x = padding + (chartWidth / 5) * i; ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); const y = height - padding - (chartHeight / 5) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Draw data points ctx.fillStyle = '#6aa9ff'; data.linearRegression.forEach(point => { const x = scaleX(point.experience); const y = scaleY(point.salary); ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); }); // Draw regression line ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.beginPath(); const y1 = state.slope * xMin + state.intercept; const y2 = state.slope * xMax + state.intercept; ctx.moveTo(scaleX(xMin), scaleY(y1)); ctx.lineTo(scaleX(xMax), scaleY(y2)); ctx.stroke(); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Experience (years)', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Salary ($k)', 0, 0); ctx.restore(); // Calculate MSE let mse = 0; data.linearRegression.forEach(point => { const predicted = state.slope * point.experience + state.intercept; const error = point.salary - predicted; mse += error * error; }); mse /= data.linearRegression.length; // Display MSE ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(`MSE: ${mse.toFixed(2)}`, width - padding, padding + 20); } // Gradient Descent Visualization function initGradientDescent() { const canvas = document.getElementById('gd-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const runBtn = document.getElementById('run-gd'); const resetBtn = document.getElementById('reset-gd'); const lrSlider = document.getElementById('lr-slider'); const lrVal = document.getElementById('lr-val'); if (lrSlider) { lrSlider.addEventListener('input', (e) => { state.learningRate = parseFloat(e.target.value); lrVal.textContent = state.learningRate.toFixed(2); }); } if (runBtn) { runBtn.addEventListener('click', runGradientDescent); } if (resetBtn) { resetBtn.addEventListener('click', () => { state.gdIterations = []; drawGradientDescent(); }); } drawGradientDescent(); } function runGradientDescent() { state.gdIterations = []; let m = 0, c = 20; // Start with poor values const alpha = state.learningRate; const iterations = 50; for (let i = 0; i < iterations; i++) { let dm = 0, dc = 0; const n = data.linearRegression.length; // Calculate gradients data.linearRegression.forEach(point => { const predicted = m * point.experience + c; const error = predicted - point.salary; dm += (2 / n) * error * point.experience; dc += (2 / n) * error; }); // Update parameters m -= alpha * dm; c -= alpha * dc; // Calculate loss let loss = 0; data.linearRegression.forEach(point => { const predicted = m * point.experience + c; const error = point.salary - predicted; loss += error * error; }); loss /= n; state.gdIterations.push({ m, c, loss }); } animateGradientDescent(); } function animateGradientDescent() { let step = 0; const interval = setInterval(() => { if (step >= state.gdIterations.length) { clearInterval(interval); return; } const iteration = state.gdIterations[step]; state.slope = iteration.m; state.intercept = iteration.c; // Update linear regression chart drawLinearRegression(); drawGradientDescent(step); step++; }, 50); } function drawGradientDescent(currentStep = -1) { const canvas = document.getElementById('gd-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); if (state.gdIterations.length === 0) { ctx.fillStyle = '#a9b4c2'; ctx.font = '16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Click "Run Gradient Descent" to see the algorithm in action', width / 2, height / 2); return; } const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const maxLoss = Math.max(...state.gdIterations.map(i => i.loss)); const minLoss = Math.min(...state.gdIterations.map(i => i.loss)); // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); const scaleX = (i) => padding + (i / (state.gdIterations.length - 1)) * chartWidth; const scaleY = (loss) => height - padding - ((loss - minLoss) / (maxLoss - minLoss)) * chartHeight; // Draw loss curve ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; ctx.beginPath(); state.gdIterations.forEach((iter, i) => { const x = scaleX(i); const y = scaleY(iter.loss); if (i === 0) { ctx.moveTo(x, y); } else { ctx.lineTo(x, y); } }); ctx.stroke(); // Highlight current step if (currentStep >= 0 && currentStep < state.gdIterations.length) { const iter = state.gdIterations[currentStep]; const x = scaleX(currentStep); const y = scaleY(iter.loss); ctx.fillStyle = '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); // Display current values ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Step: ${currentStep + 1}`, padding + 10, padding + 20); ctx.fillText(`Loss: ${iter.loss.toFixed(2)}`, padding + 10, padding + 40); } // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Iterations', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Loss (MSE)', 0, 0); ctx.restore(); } // Initialize everything when DOM is ready function init() { initSections(); initTOCLinks(); // Initialize first section visualizations setTimeout(() => { initLinearRegression(); }, 100); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // SVM Visualizations function initSVM() { initSVMBasic(); initSVMMargin(); initSVMCParameter(); initSVMTraining(); initSVMKernel(); } function initSVMBasic() { const canvas = document.getElementById('svm-basic-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const w1Slider = document.getElementById('svm-w1-slider'); const w2Slider = document.getElementById('svm-w2-slider'); const bSlider = document.getElementById('svm-b-slider'); if (w1Slider) { w1Slider.addEventListener('input', (e) => { state.svm.w1 = parseFloat(e.target.value); document.getElementById('svm-w1-val').textContent = state.svm.w1.toFixed(1); drawSVMBasic(); }); } if (w2Slider) { w2Slider.addEventListener('input', (e) => { state.svm.w2 = parseFloat(e.target.value); document.getElementById('svm-w2-val').textContent = state.svm.w2.toFixed(1); drawSVMBasic(); }); } if (bSlider) { bSlider.addEventListener('input', (e) => { state.svm.b = parseFloat(e.target.value); document.getElementById('svm-b-val').textContent = state.svm.b.toFixed(1); drawSVMBasic(); }); } drawSVMBasic(); } function drawSVMBasic() { const canvas = document.getElementById('svm-basic-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Draw grid ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)'; ctx.lineWidth = 1; for (let i = 0; i <= 10; i++) { const x = scaleX(i); const y = scaleY(i); ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); // Draw decision boundary const w1 = state.svm.w1; const w2 = state.svm.w2; const b = state.svm.b; if (Math.abs(w2) > 0.01) { ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); const x1 = xMin; const y1 = -(w1 * x1 + b) / w2; const x2 = xMax; const y2 = -(w1 * x2 + b) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } // Draw data points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); ctx.strokeStyle = '#1a2332'; ctx.lineWidth = 2; ctx.stroke(); // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 15); // Score ctx.font = '11px monospace'; ctx.fillStyle = '#a9b4c2'; ctx.fillText(score.toFixed(2), x, y + 20); }); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '13px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('X₁', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('X₂', 0, 0); ctx.restore(); // Equation ctx.fillStyle = '#7ef0d4'; ctx.font = '14px monospace'; ctx.textAlign = 'left'; ctx.fillText(`w·x + b = ${w1.toFixed(1)}x₁ + ${w2.toFixed(1)}x₂ + ${b.toFixed(1)}`, padding + 10, padding + 25); } function initSVMMargin() { const canvas = document.getElementById('svm-margin-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawSVMMargin(); } function drawSVMMargin() { const canvas = document.getElementById('svm-margin-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Use good values for visualization const w1 = 0.5, w2 = -1, b = 5.5; // Draw margin lines if (Math.abs(w2) > 0.01) { // Positive margin line ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); let x1 = xMin, y1 = -(w1 * x1 + b - 1) / w2; let x2 = xMax, y2 = -(w1 * x2 + b - 1) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); // Negative margin line ctx.beginPath(); y1 = -(w1 * x1 + b + 1) / w2; y2 = -(w1 * x2 + b + 1) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); // Decision boundary ctx.setLineDash([]); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); y1 = -(w1 * x1 + b) / w2; y2 = -(w1 * x2 + b) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } // Draw data points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; const isSupport = Math.abs(Math.abs(score) - 1) < 0.5; ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); // Highlight support vectors if (isSupport) { ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, 14, 0, 2 * Math.PI); ctx.stroke(); } ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 20); }); // Show margin width const wNorm = Math.sqrt(w1 * w1 + w2 * w2); const marginWidth = 2 / wNorm; ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Margin Width: ${marginWidth.toFixed(2)}`, padding + 10, padding + 25); ctx.fillText('Support vectors highlighted with cyan ring', padding + 10, padding + 50); } function initSVMCParameter() { const canvas = document.getElementById('svm-c-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const cSlider = document.getElementById('svm-c-slider'); if (cSlider) { cSlider.addEventListener('input', (e) => { const val = parseFloat(e.target.value); state.svm.C = Math.pow(10, val); document.getElementById('svm-c-val').textContent = state.svm.C.toFixed(state.svm.C < 10 ? 1 : 0); drawSVMCParameter(); }); } drawSVMCParameter(); } function drawSVMCParameter() { const canvas = document.getElementById('svm-c-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Adjust margin based on C const C = state.svm.C; const marginFactor = Math.min(1, 10 / C); const w1 = 0.5 * marginFactor, w2 = -1 * marginFactor, b = 5.5; // Calculate violations let violations = 0; data.svm.forEach(point => { const score = w1 * point.x1 + w2 * point.x2 + b; if (point.class * score < 1) violations++; }); // Draw margin lines if (Math.abs(w2) > 0.01) { ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); let x1 = xMin, y1 = -(w1 * x1 + b - 1) / w2; let x2 = xMax, y2 = -(w1 * x2 + b - 1) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); ctx.beginPath(); y1 = -(w1 * x1 + b + 1) / w2; y2 = -(w1 * x2 + b + 1) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); ctx.setLineDash([]); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); y1 = -(w1 * x1 + b) / w2; y2 = -(w1 * x2 + b) / w2; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } // Draw points data.svm.forEach(point => { const x = scaleX(point.x1); const y = scaleY(point.x2); const score = w1 * point.x1 + w2 * point.x2 + b; const violates = point.class * score < 1; ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); if (violates) { ctx.strokeStyle = '#ff4444'; ctx.lineWidth = 3; ctx.stroke(); } }); // Update info const wNorm = Math.sqrt(w1 * w1 + w2 * w2); const marginWidth = 2 / wNorm; document.getElementById('margin-width').textContent = marginWidth.toFixed(2); document.getElementById('violations-count').textContent = violations; } function initSVMTraining() { const canvas = document.getElementById('svm-train-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const trainBtn = document.getElementById('svm-train-btn'); const stepBtn = document.getElementById('svm-step-btn'); const resetBtn = document.getElementById('svm-reset-btn'); if (trainBtn) { trainBtn.addEventListener('click', () => { state.svm.training.step = 0; state.svm.training.w = [0, 0]; state.svm.training.b = 0; state.svm.training.isTraining = true; autoTrain(); }); } if (stepBtn) { stepBtn.addEventListener('click', () => { if (state.svm.training.step < data.svm.length) { trainStep(); } }); } if (resetBtn) { resetBtn.addEventListener('click', () => { state.svm.training.step = 0; state.svm.training.w = [0, 0]; state.svm.training.b = 0; state.svm.training.isTraining = false; updateTrainingInfo(); drawSVMTraining(); }); } drawSVMTraining(); } function trainStep() { if (state.svm.training.step >= data.svm.length) return; const point = data.svm[state.svm.training.step]; const w = state.svm.training.w; const b = state.svm.training.b; const lr = state.svm.training.learningRate; const C = 1; const score = w[0] * point.x1 + w[1] * point.x2 + b; const violation = point.class * score < 1; if (violation) { w[0] = w[0] - lr * (w[0] - C * point.class * point.x1); w[1] = w[1] - lr * (w[1] - C * point.class * point.x2); state.svm.training.b = b + lr * C * point.class; } else { w[0] = w[0] - lr * w[0]; w[1] = w[1] - lr * w[1]; } state.svm.training.step++; updateTrainingInfo(point, violation); drawSVMTraining(); } function autoTrain() { if (!state.svm.training.isTraining) return; if (state.svm.training.step < data.svm.length) { trainStep(); setTimeout(autoTrain, 800); } else { state.svm.training.isTraining = false; } } function updateTrainingInfo(point = null, violation = null) { document.getElementById('train-step').textContent = state.svm.training.step; document.getElementById('train-point').textContent = point ? `${point.label} (${point.x1}, ${point.x2})` : '-'; document.getElementById('train-w').textContent = `${state.svm.training.w[0].toFixed(2)}, ${state.svm.training.w[1].toFixed(2)}`; document.getElementById('train-b').textContent = state.svm.training.b.toFixed(2); document.getElementById('train-violation').textContent = violation === null ? '-' : (violation ? 'YES ❌' : 'NO ✓'); } function drawSVMTraining() { const canvas = document.getElementById('svm-train-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; const w = state.svm.training.w; const b = state.svm.training.b; // Draw boundary if weights are non-zero if (Math.abs(w[1]) > 0.01) { ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); const x1 = xMin, y1 = -(w[0] * x1 + b) / w[1]; const x2 = xMax, y2 = -(w[0] * x2 + b) / w[1]; ctx.moveTo(scaleX(x1), scaleY(y1)); ctx.lineTo(scaleX(x2), scaleY(y2)); ctx.stroke(); } // Draw points data.svm.forEach((point, i) => { const x = scaleX(point.x1); const y = scaleY(point.x2); const processed = i < state.svm.training.step; const current = i === state.svm.training.step - 1; ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.globalAlpha = processed ? 1 : 0.3; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); if (current) { ctx.globalAlpha = 1; ctx.strokeStyle = '#ffff00'; ctx.lineWidth = 3; ctx.beginPath(); ctx.arc(x, y, 14, 0, 2 * Math.PI); ctx.stroke(); } ctx.globalAlpha = 1; ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.label, x, y - 15); }); } function initSVMKernel() { const canvas = document.getElementById('svm-kernel-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const kernelRadios = document.querySelectorAll('input[name="kernel"]'); kernelRadios.forEach(radio => { radio.addEventListener('change', (e) => { state.svm.kernel = e.target.value; const paramGroup = document.getElementById('kernel-param-group'); if (paramGroup) { paramGroup.style.display = state.svm.kernel === 'linear' ? 'none' : 'block'; } drawSVMKernel(); }); }); const paramSlider = document.getElementById('kernel-param-slider'); if (paramSlider) { paramSlider.addEventListener('input', (e) => { state.svm.kernelParam = parseFloat(e.target.value); document.getElementById('kernel-param-val').textContent = state.svm.kernelParam.toFixed(1); drawSVMKernel(); }); } drawSVMKernel(); } function drawSVMKernel() { const canvas = document.getElementById('svm-kernel-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 500; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; // Generate circular data const innerPoints = []; const outerPoints = []; for (let i = 0; i < 15; i++) { const angle = (i / 15) * 2 * Math.PI; innerPoints.push({ x: 5 + 1.5 * Math.cos(angle), y: 5 + 1.5 * Math.sin(angle), class: 1 }); } for (let i = 0; i < 20; i++) { const angle = (i / 20) * 2 * Math.PI; const r = 3.5 + Math.random() * 0.5; outerPoints.push({ x: 5 + r * Math.cos(angle), y: 5 + r * Math.sin(angle), class: -1 }); } const allPoints = [...innerPoints, ...outerPoints]; const xMin = 0, xMax = 10; const yMin = 0, yMax = 10; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Draw decision boundary based on kernel if (state.svm.kernel === 'linear') { // Linear can't separate circular data well ctx.strokeStyle = 'rgba(106, 169, 255, 0.5)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(scaleX(2), scaleY(2)); ctx.lineTo(scaleX(8), scaleY(8)); ctx.stroke(); } else if (state.svm.kernel === 'polynomial' || state.svm.kernel === 'rbf') { // Draw circular boundary ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); const radius = state.svm.kernel === 'polynomial' ? 2.5 : 2.3 + state.svm.kernelParam * 0.1; ctx.arc(scaleX(5), scaleY(5), radius * (chartWidth / 10), 0, 2 * Math.PI); ctx.stroke(); } // Draw points allPoints.forEach(point => { const x = scaleX(point.x); const y = scaleY(point.y); ctx.fillStyle = point.class === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 5, 0, 2 * Math.PI); ctx.fill(); }); // Draw kernel info ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; const kernelName = state.svm.kernel === 'linear' ? 'Linear Kernel' : state.svm.kernel === 'polynomial' ? 'Polynomial Kernel' : 'RBF Kernel'; ctx.fillText(kernelName, padding + 10, padding + 25); if (state.svm.kernel === 'linear') { ctx.font = '13px sans-serif'; ctx.fillStyle = '#ff8c6a'; ctx.fillText('❌ Linear kernel cannot separate circular data!', padding + 10, padding + 50); } else { ctx.font = '13px sans-serif'; ctx.fillStyle = '#7ef0d4'; ctx.fillText('✓ Non-linear kernel successfully separates the data', padding + 10, padding + 50); } } // Logistic Regression Visualizations function initLogistic() { initSigmoid(); initLogisticClassification(); } function initSigmoid() { const canvas = document.getElementById('sigmoid-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawSigmoid(); } function drawSigmoid() { const canvas = document.getElementById('sigmoid-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 350; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const zMin = -10, zMax = 10; const scaleX = (z) => padding + ((z - zMin) / (zMax - zMin)) * chartWidth; const scaleY = (sig) => height - padding - sig * chartHeight; // Draw grid ctx.strokeStyle = 'rgba(42, 53, 68, 0.5)'; ctx.lineWidth = 1; for (let i = 0; i <= 10; i++) { const x = padding + (chartWidth / 10) * i; ctx.beginPath(); ctx.moveTo(x, padding); ctx.lineTo(x, height - padding); ctx.stroke(); const y = padding + (chartHeight / 10) * i; ctx.beginPath(); ctx.moveTo(padding, y); ctx.lineTo(width - padding, y); ctx.stroke(); } // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, padding); ctx.lineTo(padding, height - padding); ctx.lineTo(width - padding, height - padding); ctx.stroke(); // Draw threshold line at 0.5 ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 1; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(padding, scaleY(0.5)); ctx.lineTo(width - padding, scaleY(0.5)); ctx.stroke(); ctx.setLineDash([]); // Draw sigmoid curve ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 3; ctx.beginPath(); for (let z = zMin; z <= zMax; z += 0.1) { const sig = 1 / (1 + Math.exp(-z)); const x = scaleX(z); const y = scaleY(sig); if (z === zMin) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('z (input)', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('σ(z) probability', 0, 0); ctx.restore(); // Annotations ctx.fillStyle = '#7ef0d4'; ctx.textAlign = 'left'; ctx.fillText('σ(z) = 1/(1+e⁻ᶻ)', padding + 10, padding + 25); ctx.fillStyle = '#ff8c6a'; ctx.fillText('Threshold = 0.5', padding + 10, scaleY(0.5) - 10); } function initLogisticClassification() { const canvas = document.getElementById('logistic-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawLogisticClassification(); } function drawLogisticClassification() { const canvas = document.getElementById('logistic-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const hMin = 140, hMax = 210; const scaleX = (h) => padding + ((h - hMin) / (hMax - hMin)) * chartWidth; const scaleY = (p) => height - padding - p * chartHeight; // Draw sigmoid curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); for (let h = hMin; h <= hMax; h += 1) { const z = (h - 170) / 10; // Simple linear transformation const p = 1 / (1 + Math.exp(-z)); const x = scaleX(h); const y = scaleY(p); if (h === hMin) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.stroke(); // Draw threshold line ctx.strokeStyle = '#ff8c6a'; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(padding, scaleY(0.5)); ctx.lineTo(width - padding, scaleY(0.5)); ctx.stroke(); ctx.setLineDash([]); // Draw data points data.logistic.forEach(point => { const x = scaleX(point.height); const y = scaleY(point.prob); ctx.fillStyle = point.label === 1 ? '#7ef0d4' : '#ff8c6a'; ctx.beginPath(); ctx.arc(x, y, 6, 0, 2 * Math.PI); ctx.fill(); // Label ctx.fillStyle = '#e8eef6'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(point.height, x, height - padding + 20); }); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Height (cm)', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('P(Tall)', 0, 0); ctx.restore(); } // KNN Visualization let knnState = { testPoint: { x: 2.5, y: 2.5 }, k: 3, distanceMetric: 'euclidean', dragging: false }; function initKNN() { const canvas = document.getElementById('knn-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const kSlider = document.getElementById('knn-k-slider'); if (kSlider) { kSlider.addEventListener('input', (e) => { knnState.k = parseInt(e.target.value); document.getElementById('knn-k-val').textContent = knnState.k; drawKNN(); }); } const distanceRadios = document.querySelectorAll('input[name="knn-distance"]'); distanceRadios.forEach(radio => { radio.addEventListener('change', (e) => { knnState.distanceMetric = e.target.value; drawKNN(); }); }); canvas.addEventListener('mousedown', startDragKNN); canvas.addEventListener('mousemove', dragKNN); canvas.addEventListener('mouseup', stopDragKNN); drawKNN(); } function startDragKNN(e) { const canvas = document.getElementById('knn-canvas'); const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; const padding = 60; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; const tx = padding + (knnState.testPoint.x / 6) * chartWidth; const ty = canvas.height - padding - (knnState.testPoint.y / 6) * chartHeight; if (Math.abs(mx - tx) < 15 && Math.abs(my - ty) < 15) { knnState.dragging = true; } } function dragKNN(e) { if (!knnState.dragging) return; const canvas = document.getElementById('knn-canvas'); const rect = canvas.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; const padding = 60; const chartWidth = canvas.width - 2 * padding; const chartHeight = canvas.height - 2 * padding; knnState.testPoint.x = Math.max(0, Math.min(6, ((mx - padding) / chartWidth) * 6)); knnState.testPoint.y = Math.max(0, Math.min(6, ((canvas.height - padding - my) / chartHeight) * 6)); drawKNN(); } function stopDragKNN() { knnState.dragging = false; } function drawKNN() { const canvas = document.getElementById('knn-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const scaleX = (x) => padding + (x / 6) * chartWidth; const scaleY = (y) => height - padding - (y / 6) * chartHeight; // Calculate distances const distances = data.knn.map(point => { let d; if (knnState.distanceMetric === 'euclidean') { d = Math.sqrt(Math.pow(point.x - knnState.testPoint.x, 2) + Math.pow(point.y - knnState.testPoint.y, 2)); } else { d = Math.abs(point.x - knnState.testPoint.x) + Math.abs(point.y - knnState.testPoint.y); } return { ...point, distance: d }; }); distances.sort((a, b) => a.distance - b.distance); const kNearest = distances.slice(0, knnState.k); // Count votes const votes = {}; kNearest.forEach(p => { votes[p.class] = (votes[p.class] || 0) + 1; }); const prediction = Object.keys(votes).reduce((a, b) => votes[a] > votes[b] ? a : b); // Draw lines to K nearest kNearest.forEach(point => { ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(scaleX(knnState.testPoint.x), scaleY(knnState.testPoint.y)); ctx.lineTo(scaleX(point.x), scaleY(point.y)); ctx.stroke(); }); // Draw training points distances.forEach(point => { const x = scaleX(point.x); const y = scaleY(point.y); const isNearest = kNearest.includes(point); ctx.fillStyle = point.class === 'orange' ? '#ff8c6a' : '#ffeb3b'; ctx.globalAlpha = isNearest ? 1 : 0.5; ctx.beginPath(); ctx.arc(x, y, 8, 0, 2 * Math.PI); ctx.fill(); if (isNearest) { ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; ctx.globalAlpha = 1; ctx.beginPath(); ctx.arc(x, y, 12, 0, 2 * Math.PI); ctx.stroke(); } ctx.globalAlpha = 1; }); // Draw test point const tx = scaleX(knnState.testPoint.x); const ty = scaleY(knnState.testPoint.y); ctx.fillStyle = prediction === 'orange' ? '#ff8c6a' : '#ffeb3b'; ctx.beginPath(); ctx.arc(tx, ty, 12, 0, 2 * Math.PI); ctx.fill(); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.stroke(); // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`K=${knnState.k} | Prediction: ${prediction}`, padding + 10, padding + 25); ctx.fillText(`Votes: Orange=${votes.orange || 0}, Yellow=${votes.yellow || 0}`, padding + 10, padding + 50); } // Model Evaluation function initModelEvaluation() { initConfusionMatrix(); initROC(); initR2(); } function initConfusionMatrix() { const canvas = document.getElementById('confusion-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawConfusionMatrix(); } function drawConfusionMatrix() { const canvas = document.getElementById('confusion-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 300; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const size = Math.min(width, height) - 100; const cellSize = size / 2; const startX = (width - size) / 2; const startY = 50; const cm = { tp: 600, fp: 100, fn: 300, tn: 900 }; // Draw cells const cells = [ { x: startX, y: startY, val: cm.tp, label: 'TP', color: '#7ef0d4' }, { x: startX + cellSize, y: startY, val: cm.fn, label: 'FN', color: '#ff8c6a' }, { x: startX, y: startY + cellSize, val: cm.fp, label: 'FP', color: '#ff8c6a' }, { x: startX + cellSize, y: startY + cellSize, val: cm.tn, label: 'TN', color: '#7ef0d4' } ]; cells.forEach(cell => { ctx.fillStyle = cell.color + '22'; ctx.fillRect(cell.x, cell.y, cellSize, cellSize); ctx.strokeStyle = cell.color; ctx.lineWidth = 2; ctx.strokeRect(cell.x, cell.y, cellSize, cellSize); ctx.fillStyle = cell.color; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(cell.label, cell.x + cellSize / 2, cell.y + cellSize / 2 - 10); ctx.font = 'bold 32px sans-serif'; ctx.fillText(cell.val, cell.x + cellSize / 2, cell.y + cellSize / 2 + 25); }); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Predicted Positive', startX + cellSize / 2, startY - 15); ctx.fillText('Predicted Negative', startX + cellSize * 1.5, startY - 15); ctx.save(); ctx.translate(startX - 30, startY + cellSize / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Actual Positive', 0, 0); ctx.restore(); ctx.save(); ctx.translate(startX - 30, startY + cellSize * 1.5); ctx.rotate(-Math.PI / 2); ctx.fillText('Actual Negative', 0, 0); ctx.restore(); } let rocState = { threshold: 0.5 }; function initROC() { const canvas = document.getElementById('roc-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const slider = document.getElementById('roc-threshold-slider'); if (slider) { slider.addEventListener('input', (e) => { rocState.threshold = parseFloat(e.target.value); document.getElementById('roc-threshold-val').textContent = rocState.threshold.toFixed(1); drawROC(); }); } drawROC(); } function drawROC() { const canvas = document.getElementById('roc-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 450; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartSize = Math.min(width - 2 * padding, height - 2 * padding); const chartX = (width - chartSize) / 2; const chartY = (height - chartSize) / 2; // Calculate ROC points const rocPoints = []; for (let t = 0; t <= 1; t += 0.1) { let tp = 0, fp = 0, tn = 0, fn = 0; data.roc.forEach(e => { const pred = e.score >= t ? 1 : 0; if (e.true_label === 1 && pred === 1) tp++; else if (e.true_label === 0 && pred === 1) fp++; else if (e.true_label === 1 && pred === 0) fn++; else tn++; }); const tpr = tp / (tp + fn) || 0; const fpr = fp / (fp + tn) || 0; rocPoints.push({ t, tpr, fpr }); } // Current threshold point let tp = 0, fp = 0, tn = 0, fn = 0; data.roc.forEach(e => { const pred = e.score >= rocState.threshold ? 1 : 0; if (e.true_label === 1 && pred === 1) tp++; else if (e.true_label === 0 && pred === 1) fp++; else if (e.true_label === 1 && pred === 0) fn++; else tn++; }); const tpr = tp / (tp + fn) || 0; const fpr = fp / (fp + tn) || 0; // Draw diagonal (random) ctx.strokeStyle = 'rgba(255, 140, 106, 0.5)'; ctx.lineWidth = 2; ctx.setLineDash([5, 5]); ctx.beginPath(); ctx.moveTo(chartX, chartY + chartSize); ctx.lineTo(chartX + chartSize, chartY); ctx.stroke(); ctx.setLineDash([]); // Draw ROC curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); rocPoints.forEach((p, i) => { const x = chartX + p.fpr * chartSize; const y = chartY + chartSize - p.tpr * chartSize; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); // Draw current point const cx = chartX + fpr * chartSize; const cy = chartY + chartSize - tpr * chartSize; ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(cx, cy, 8, 0, 2 * Math.PI); ctx.fill(); // Draw axes ctx.strokeStyle = '#2a3544'; ctx.lineWidth = 2; ctx.beginPath(); ctx.rect(chartX, chartY, chartSize, chartSize); ctx.stroke(); // Labels ctx.fillStyle = '#a9b4c2'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('FPR (False Positive Rate)', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('TPR (True Positive Rate)', 0, 0); ctx.restore(); // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`TPR: ${tpr.toFixed(2)} | FPR: ${fpr.toFixed(2)}`, chartX + 10, chartY + 25); ctx.fillText(`TP=${tp} FP=${fp} TN=${tn} FN=${fn}`, chartX + 10, chartY + 50); } function initR2() { const canvas = document.getElementById('r2-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawR2(); } function drawR2() { const canvas = document.getElementById('r2-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 350; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); // Dummy R² data const r2data = [ { x: 150, y: 50, pred: 52 }, { x: 160, y: 60, pred: 61 }, { x: 170, y: 70, pred: 69 }, { x: 180, y: 80, pred: 78 }, { x: 190, y: 90, pred: 87 } ]; const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const xMin = 140, xMax = 200, yMin = 40, yMax = 100; const scaleX = (x) => padding + ((x - xMin) / (xMax - xMin)) * chartWidth; const scaleY = (y) => height - padding - ((y - yMin) / (yMax - yMin)) * chartHeight; // Mean const mean = r2data.reduce((sum, p) => sum + p.y, 0) / r2data.length; // Draw mean line ctx.strokeStyle = '#ff8c6a'; ctx.setLineDash([5, 5]); ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(padding, scaleY(mean)); ctx.lineTo(width - padding, scaleY(mean)); ctx.stroke(); ctx.setLineDash([]); // Draw regression line ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(scaleX(xMin), scaleY(40)); ctx.lineTo(scaleX(xMax), scaleY(95)); ctx.stroke(); // Draw points r2data.forEach(p => { // Residual line ctx.strokeStyle = 'rgba(126, 240, 212, 0.3)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(scaleX(p.x), scaleY(p.y)); ctx.lineTo(scaleX(p.x), scaleY(p.pred)); ctx.stroke(); // Actual point ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(scaleX(p.x), scaleY(p.y), 6, 0, 2 * Math.PI); ctx.fill(); }); // Calculate R² let ssRes = 0, ssTot = 0; r2data.forEach(p => { ssRes += Math.pow(p.y - p.pred, 2); ssTot += Math.pow(p.y - mean, 2); }); const r2 = 1 - (ssRes / ssTot); // Info ctx.fillStyle = '#7ef0d4'; ctx.font = '16px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`R² = ${r2.toFixed(3)}`, padding + 10, padding + 25); ctx.fillText(`Model explains ${(r2 * 100).toFixed(1)}% of variance`, padding + 10, padding + 50); } // Regularization let regState = { lambda: 0.1 }; function initRegularization() { const canvas = document.getElementById('regularization-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; const slider = document.getElementById('reg-lambda-slider'); if (slider) { slider.addEventListener('input', (e) => { regState.lambda = parseFloat(e.target.value); document.getElementById('reg-lambda-val').textContent = regState.lambda.toFixed(1); drawRegularization(); }); } drawRegularization(); } function drawRegularization() { const canvas = document.getElementById('regularization-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const features = ['x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10']; const vanilla = [100, 200, 300, 50, 150, 250, 80, 120, 90, 180]; // Simulate L1 and L2 effects const l1 = vanilla.map(v => Math.abs(v) > 50 / regState.lambda ? v * (1 - regState.lambda * 0.5) : 0); const l2 = vanilla.map(v => v / (1 + regState.lambda)); const barWidth = chartWidth / (features.length * 3.5); const maxVal = Math.max(...vanilla); features.forEach((f, i) => { const x = padding + (i * chartWidth / features.length); // Vanilla const h1 = (vanilla[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#a9b4c2'; ctx.fillRect(x, height - padding - h1, barWidth, h1); // L1 const h2 = (l1[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#ff8c6a'; ctx.fillRect(x + barWidth * 1.2, height - padding - h2, barWidth, h2); // L2 const h3 = (l2[i] / maxVal) * chartHeight * 0.8; ctx.fillStyle = '#6aa9ff'; ctx.fillRect(x + barWidth * 2.4, height - padding - h3, barWidth, h3); // Feature label ctx.fillStyle = '#a9b4c2'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(f, x + barWidth * 1.5, height - padding + 20); }); // Legend const legendY = padding + 20; ctx.fillStyle = '#a9b4c2'; ctx.fillRect(padding + 10, legendY, 15, 15); ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Vanilla', padding + 30, legendY + 12); ctx.fillStyle = '#ff8c6a'; ctx.fillRect(padding + 100, legendY, 15, 15); ctx.fillStyle = '#e8eef6'; ctx.fillText('L1 (Lasso)', padding + 120, legendY + 12); ctx.fillStyle = '#6aa9ff'; ctx.fillRect(padding + 210, legendY, 15, 15); ctx.fillStyle = '#e8eef6'; ctx.fillText('L2 (Ridge)', padding + 230, legendY + 12); } // Bias-Variance function initBiasVariance() { const canvas = document.getElementById('bias-variance-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawBiasVariance(); const canvas2 = document.getElementById('complexity-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; drawComplexityCurve(); } } function drawBiasVariance() { const canvas = document.getElementById('bias-variance-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const sectionWidth = width / 3; const padding = 40; const chartHeight = height - 2 * padding; // Generate curved data const trueData = []; for (let x = 0; x <= 10; x += 0.5) { trueData.push({ x, y: 50 + 30 * Math.sin(x / 2) }); } // Draw three scenarios const scenarios = [ { title: 'High Bias\n(Underfit)', color: '#ff8c6a', degree: 1 }, { title: 'Good Fit', color: '#7ef0d4', degree: 2 }, { title: 'High Variance\n(Overfit)', color: '#ff8c6a', degree: 8 } ]; scenarios.forEach((scenario, idx) => { const offsetX = idx * sectionWidth; const scaleX = (x) => offsetX + padding + (x / 10) * (sectionWidth - 2 * padding); const scaleY = (y) => padding + chartHeight - ((y - 20) / 80) * chartHeight; // Draw true curve ctx.strokeStyle = 'rgba(106, 169, 255, 0.3)'; ctx.lineWidth = 2; ctx.beginPath(); trueData.forEach((p, i) => { if (i === 0) ctx.moveTo(scaleX(p.x), scaleY(p.y)); else ctx.lineTo(scaleX(p.x), scaleY(p.y)); }); ctx.stroke(); // Draw model fit ctx.strokeStyle = scenario.color; ctx.lineWidth = 3; ctx.beginPath(); if (scenario.degree === 1) { // Straight line ctx.moveTo(scaleX(0), scaleY(50)); ctx.lineTo(scaleX(10), scaleY(65)); } else if (scenario.degree === 2) { // Good fit trueData.forEach((p, i) => { const noise = (Math.random() - 0.5) * 3; if (i === 0) ctx.moveTo(scaleX(p.x), scaleY(p.y + noise)); else ctx.lineTo(scaleX(p.x), scaleY(p.y + noise)); }); } else { // Wiggly overfit for (let x = 0; x <= 10; x += 0.2) { const y = 50 + 30 * Math.sin(x / 2) + 15 * Math.sin(x * 2); if (x === 0) ctx.moveTo(scaleX(x), scaleY(y)); else ctx.lineTo(scaleX(x), scaleY(y)); } } ctx.stroke(); // Title ctx.fillStyle = scenario.color; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; const lines = scenario.title.split('\n'); lines.forEach((line, i) => { ctx.fillText(line, offsetX + sectionWidth / 2, 20 + i * 18); }); }); } function drawComplexityCurve() { const canvas = document.getElementById('complexity-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 350; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const scaleX = (x) => padding + (x / 10) * chartWidth; const scaleY = (y) => padding + chartHeight - (y / 100) * chartHeight; // Draw curves ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.beginPath(); for (let x = 0; x <= 10; x += 0.1) { const trainError = 80 * Math.exp(-x / 2) + 5; if (x === 0) ctx.moveTo(scaleX(x), scaleY(trainError)); else ctx.lineTo(scaleX(x), scaleY(trainError)); } ctx.stroke(); ctx.strokeStyle = '#6aa9ff'; ctx.beginPath(); for (let x = 0; x <= 10; x += 0.1) { const testError = 80 * Math.exp(-x / 2) + 5 + 15 * (x / 10) ** 2; if (x === 0) ctx.moveTo(scaleX(x), scaleY(testError)); else ctx.lineTo(scaleX(x), scaleY(testError)); } ctx.stroke(); // Sweet spot ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.arc(scaleX(5), scaleY(18), 8, 0, 2 * Math.PI); ctx.fill(); // Legend ctx.fillStyle = '#ff8c6a'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Training Error', padding + 10, padding + 20); ctx.fillStyle = '#6aa9ff'; ctx.fillText('Test Error', padding + 10, padding + 40); ctx.fillStyle = '#7ef0d4'; ctx.fillText('● Sweet Spot', padding + 10, padding + 60); // Labels ctx.fillStyle = '#a9b4c2'; ctx.textAlign = 'center'; ctx.fillText('Model Complexity →', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Error', 0, 0); ctx.restore(); } // Cross-Validation function initCrossValidation() { const canvas = document.getElementById('cv-canvas'); if (!canvas || canvas.dataset.initialized) return; canvas.dataset.initialized = 'true'; drawCrossValidation(); } function drawCrossValidation() { const canvas = document.getElementById('cv-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const blockSize = 50; const gap = 10; const numBlocks = 12; const k = 3; const blocksPerFold = numBlocks / k; const startX = (width - (numBlocks * blockSize + (numBlocks - 1) * gap)) / 2; const folds = [0.96, 0.84, 0.90]; for (let fold = 0; fold < k; fold++) { const offsetY = 80 + fold * 120; // Fold label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(`Fold ${fold + 1}:`, startX - 20, offsetY + blockSize / 2 + 5); // Draw blocks for (let i = 0; i < numBlocks; i++) { const x = startX + i * (blockSize + gap); const isFold = i >= fold * blocksPerFold && i < (fold + 1) * blocksPerFold; ctx.fillStyle = isFold ? '#6aa9ff' : '#7ef0d4'; ctx.fillRect(x, offsetY, blockSize, blockSize); // Label ctx.fillStyle = '#1a2332'; ctx.font = 'bold 12px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(String.fromCharCode(65 + i), x + blockSize / 2, offsetY + blockSize / 2 + 5); } // Accuracy ctx.fillStyle = '#7ef0d4'; ctx.font = '14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(`Acc: ${folds[fold].toFixed(2)}`, startX + numBlocks * (blockSize + gap) + 20, offsetY + blockSize / 2 + 5); } // Legend ctx.fillStyle = '#6aa9ff'; ctx.fillRect(startX, 30, 30, 20); ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('Test Set', startX + 40, 45); ctx.fillStyle = '#7ef0d4'; ctx.fillRect(startX + 120, 30, 30, 20); ctx.fillText('Training Set', startX + 160, 45); // Final result const mean = folds.reduce((a, b) => a + b) / folds.length; const std = Math.sqrt(folds.reduce((sum, x) => sum + Math.pow(x - mean, 2), 0) / folds.length); ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(`Final Score: ${mean.toFixed(2)} ± ${std.toFixed(3)}`, width / 2, height - 20); } // Preprocessing function initPreprocessing() { const canvas = document.getElementById('scaling-canvas'); if (canvas && !canvas.dataset.initialized) { canvas.dataset.initialized = 'true'; drawScaling(); } const canvas2 = document.getElementById('pipeline-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; drawPipeline(); } } function drawScaling() { const canvas = document.getElementById('scaling-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 350; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const before = [10, 20, 30, 40, 50]; const standard = [-1.26, -0.63, 0, 0.63, 1.26]; const minmax = [0, 0.25, 0.5, 0.75, 1.0]; const sectionWidth = width / 3; const padding = 40; const barWidth = 30; const datasets = [ { data: before, title: 'Original', maxVal: 60 }, { data: standard, title: 'StandardScaler', maxVal: 2 }, { data: minmax, title: 'MinMaxScaler', maxVal: 1.2 } ]; datasets.forEach((dataset, idx) => { const offsetX = idx * sectionWidth; const centerX = offsetX + sectionWidth / 2; // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(dataset.title, centerX, 30); // Draw bars dataset.data.forEach((val, i) => { const barHeight = Math.abs(val) / dataset.maxVal * 200; const x = centerX - barWidth / 2; const y = val >= 0 ? 200 - barHeight : 200; ctx.fillStyle = '#6aa9ff'; ctx.fillRect(x, y, barWidth, barHeight); // Value label ctx.fillStyle = '#a9b4c2'; ctx.font = '10px sans-serif'; ctx.textAlign = 'center'; ctx.fillText(val.toFixed(2), centerX, val >= 0 ? y - 5 : y + barHeight + 15); centerX += 35; }); }); } function drawPipeline() { const canvas = document.getElementById('pipeline-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 300; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const steps = ['Raw Data', 'Handle Missing', 'Encode Categories', 'Scale Features', 'Train Model']; const stepWidth = (width - 100) / steps.length; const y = height / 2; steps.forEach((step, i) => { const x = 50 + i * stepWidth; // Box ctx.fillStyle = '#2a3544'; ctx.fillRect(x, y - 30, stepWidth - 40, 60); ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 2; ctx.strokeRect(x, y - 30, stepWidth - 40, 60); // Text ctx.fillStyle = '#e8eef6'; ctx.font = '12px sans-serif'; ctx.textAlign = 'center'; const words = step.split(' '); words.forEach((word, j) => { ctx.fillText(word, x + (stepWidth - 40) / 2, y + j * 15 - 5); }); // Arrow if (i < steps.length - 1) { ctx.strokeStyle = '#7ef0d4'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(x + stepWidth - 40, y); ctx.lineTo(x + stepWidth - 10, y); ctx.stroke(); // Arrowhead ctx.fillStyle = '#7ef0d4'; ctx.beginPath(); ctx.moveTo(x + stepWidth - 10, y); ctx.lineTo(x + stepWidth - 20, y - 5); ctx.lineTo(x + stepWidth - 20, y + 5); ctx.fill(); } }); } // Loss Functions function initLossFunctions() { const canvas = document.getElementById('loss-comparison-canvas'); if (canvas && !canvas.dataset.initialized) { canvas.dataset.initialized = 'true'; drawLossComparison(); } const canvas2 = document.getElementById('loss-curves-canvas'); if (canvas2 && !canvas2.dataset.initialized) { canvas2.dataset.initialized = 'true'; drawLossCurves(); } } function drawLossComparison() { const canvas = document.getElementById('loss-comparison-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 400; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const actual = [10, 20, 30, 40, 50]; const predicted = [12, 19, 32, 38, 51]; // Calculate losses let mse = 0, mae = 0; actual.forEach((a, i) => { const error = a - predicted[i]; mse += error * error; mae += Math.abs(error); }); mse /= actual.length; mae /= actual.length; const rmse = Math.sqrt(mse); // Display const padding = 60; const barHeight = 60; const startY = 100; const maxWidth = width - 2 * padding; const losses = [ { name: 'MSE', value: mse, color: '#ff8c6a' }, { name: 'MAE', value: mae, color: '#6aa9ff' }, { name: 'RMSE', value: rmse, color: '#7ef0d4' } ]; const maxLoss = Math.max(...losses.map(l => l.value)); losses.forEach((loss, i) => { const y = startY + i * (barHeight + 30); const barWidth = (loss.value / maxLoss) * maxWidth; // Bar ctx.fillStyle = loss.color; ctx.fillRect(padding, y, barWidth, barHeight); // Label ctx.fillStyle = '#e8eef6'; ctx.font = 'bold 14px sans-serif'; ctx.textAlign = 'left'; ctx.fillText(loss.name, padding + 10, y + barHeight / 2 + 5); // Value ctx.font = '16px sans-serif'; ctx.textAlign = 'right'; ctx.fillText(loss.value.toFixed(2), padding + barWidth - 10, y + barHeight / 2 + 5); }); // Title ctx.fillStyle = '#7ef0d4'; ctx.font = 'bold 16px sans-serif'; ctx.textAlign = 'center'; ctx.fillText('Regression Loss Comparison', width / 2, 50); } function drawLossCurves() { const canvas = document.getElementById('loss-curves-canvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const width = canvas.width = canvas.offsetWidth; const height = canvas.height = 350; ctx.clearRect(0, 0, width, height); ctx.fillStyle = '#1a2332'; ctx.fillRect(0, 0, width, height); const padding = 60; const chartWidth = width - 2 * padding; const chartHeight = height - 2 * padding; const scaleX = (x) => padding + (x / 10) * chartWidth; const scaleY = (y) => height - padding - (y / 100) * chartHeight; // Draw MSE curve ctx.strokeStyle = '#ff8c6a'; ctx.lineWidth = 3; ctx.beginPath(); for (let x = -10; x <= 10; x += 0.2) { const y = x * x; if (x === -10) ctx.moveTo(scaleX(x + 10), scaleY(y)); else ctx.lineTo(scaleX(x + 10), scaleY(y)); } ctx.stroke(); // Draw MAE curve ctx.strokeStyle = '#6aa9ff'; ctx.lineWidth = 3; ctx.beginPath(); for (let x = -10; x <= 10; x += 0.2) { const y = Math.abs(x) * 10; if (x === -10) ctx.moveTo(scaleX(x + 10), scaleY(y)); else ctx.lineTo(scaleX(x + 10), scaleY(y)); } ctx.stroke(); // Legend ctx.fillStyle = '#ff8c6a'; ctx.font = '12px sans-serif'; ctx.textAlign = 'left'; ctx.fillText('MSE (quadratic penalty)', padding + 10, padding + 20); ctx.fillStyle = '#6aa9ff'; ctx.fillText('MAE (linear penalty)', padding + 10, padding + 40); // Labels ctx.fillStyle = '#a9b4c2'; ctx.textAlign = 'center'; ctx.fillText('Error', width / 2, height - 20); ctx.save(); ctx.translate(20, height / 2); ctx.rotate(-Math.PI / 2); ctx.fillText('Loss', 0, 0); ctx.restore(); } // Handle window resize let resizeTimer; window.addEventListener('resize', () => { clearTimeout(resizeTimer); resizeTimer = setTimeout(() => { drawLinearRegression(); drawGradientDescent(); drawSigmoid(); drawLogisticClassification(); drawKNN(); drawConfusionMatrix(); drawROC(); drawR2(); drawRegularization(); drawBiasVariance(); drawComplexityCurve(); drawCrossValidation(); drawScaling(); drawPipeline(); drawLossComparison(); drawLossCurves(); drawSVMBasic(); drawSVMMargin(); drawSVMCParameter(); drawSVMTraining(); drawSVMKernel(); }, 250); });