| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>AdaBoost Implementation with Decision Stumps</title> |
| <script src="https://cdn.tailwindcss.com"></script> |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> |
| <script src="https://cdn.jsdelivr.net/npm/pca-js@1.0.0/pca.min.js"></script> |
| <style> |
| .loading-spinner { |
| border: 4px solid rgba(0, 0, 0, 0.1); |
| border-radius: 50%; |
| border-top: 4px solid #3498db; |
| width: 30px; |
| height: 30px; |
| animation: spin 1s linear infinite; |
| margin: 0 auto; |
| } |
| @keyframes spin { |
| 0% { transform: rotate(0deg); } |
| 100% { transform: rotate(360deg); } |
| } |
| .card { |
| transition: all 0.3s ease; |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
| } |
| .card:hover { |
| transform: translateY(-5px); |
| box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1); |
| } |
| </style> |
| </head> |
| <body class="bg-gray-50 min-h-screen"> |
| <div class="container mx-auto px-4 py-8"> |
| <header class="text-center mb-12"> |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">AdaBoost with Decision Stumps</h1> |
| <p class="text-xl text-gray-600">Implementation from scratch with MNIST digit classification</p> |
| </header> |
|
|
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12"> |
| <div class="card bg-white rounded-lg p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Algorithm Overview</h2> |
| <div class="space-y-4"> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Decision Stumps</h3> |
| <p class="text-gray-600">Weak learners (depth-1 decision trees) that make predictions based on a single feature threshold.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Weighted Error</h3> |
| <p class="text-gray-600">Sample weights are updated to focus on misclassified examples in each boosting round.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Classifier Weights</h3> |
| <p class="text-gray-600">Each stump's contribution is weighted by its accuracy (β = ½ ln((1-err)/err)).</p> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="card bg-white rounded-lg p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">MNIST Dataset</h2> |
| <div class="space-y-4"> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Classes 0 and 1</h3> |
| <p class="text-gray-600">Binary classification task distinguishing between digits 0 and 1.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Dimensionality Reduction</h3> |
| <p class="text-gray-600">PCA applied to reduce 784 features to 5 principal components.</p> |
| </div> |
| </div> |
| <div class="flex items-start"> |
| <div class="bg-indigo-100 p-2 rounded-full mr-3"> |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" /> |
| </svg> |
| </div> |
| <div> |
| <h3 class="font-medium text-gray-800">Training Size</h3> |
| <p class="text-gray-600">1000 samples per class for training, full test set for evaluation.</p> |
| </div> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="card bg-white rounded-lg p-6 mb-12"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Run AdaBoost Training</h2> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> |
| <div> |
| <label class="block text-gray-700 mb-2">Number of Rounds</label> |
| <input type="number" id="numRounds" value="200" min="1" max="500" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-2">Training Samples per Class</label> |
| <input type="number" id="trainSamples" value="1000" min="100" max="6000" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
| </div> |
| <div> |
| <label class="block text-gray-700 mb-2">PCA Components</label> |
| <input type="number" id="pcaComponents" value="5" min="1" max="10" class="w-full px-4 py-2 border rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500"> |
| </div> |
| </div> |
| <button id="trainButton" class="w-full md:w-auto bg-indigo-600 hover:bg-indigo-700 text-white font-medium py-2 px-6 rounded-lg transition duration-300 flex items-center justify-center"> |
| <span id="buttonText">Train AdaBoost Model</span> |
| <div id="loadingSpinner" class="loading-spinner ml-2 hidden"></div> |
| </button> |
| </div> |
|
|
| <div id="resultsSection" class="hidden"> |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12"> |
| <div class="card bg-white rounded-lg p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Training Progress</h2> |
| <div class="h-80"> |
| <canvas id="errorChart"></canvas> |
| </div> |
| </div> |
| <div class="card bg-white rounded-lg p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Loss Curves</h2> |
| <div class="h-80"> |
| <canvas id="lossChart"></canvas> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="card bg-white rounded-lg p-6 mb-12"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-6">Final Results</h2> |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
| <div class="bg-indigo-50 rounded-lg p-4 text-center"> |
| <p class="text-sm text-indigo-600 font-medium">Training Accuracy</p> |
| <p id="trainAccuracy" class="text-3xl font-bold text-indigo-800">0%</p> |
| </div> |
| <div class="bg-indigo-50 rounded-lg p-4 text-center"> |
| <p class="text-sm text-indigo-600 font-medium">Validation Accuracy</p> |
| <p id="valAccuracy" class="text-3xl font-bold text-indigo-800">0%</p> |
| </div> |
| <div class="bg-indigo-50 rounded-lg p-4 text-center"> |
| <p class="text-sm text-indigo-600 font-medium">Test Accuracy</p> |
| <p id="testAccuracy" class="text-3xl font-bold text-indigo-800">0%</p> |
| </div> |
| </div> |
| </div> |
|
|
| <div class="card bg-white rounded-lg p-6"> |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Classifier Weights Over Time</h2> |
| <div class="h-80"> |
| <canvas id="weightsChart"></canvas> |
| </div> |
| </div> |
| </div> |
| </div> |
|
|
| <script> |
| |
| class AdaBoost { |
| constructor() { |
| this.classifiers = []; |
| this.betas = []; |
| } |
| |
| |
| createStump(X, y, weights) { |
| let bestErr = Infinity; |
| let bestStump = {}; |
| let bestPred = null; |
| |
| |
| for (let feature = 0; feature < X[0].length; feature++) { |
| const featureValues = X.map(x => x[feature]); |
| const minVal = Math.min(...featureValues); |
| const maxVal = Math.max(...featureValues); |
| |
| |
| for (let threshold of [minVal + (maxVal-minVal)/4, |
| minVal + (maxVal-minVal)/2, |
| minVal + 3*(maxVal-minVal)/4]) { |
| |
| |
| for (let direction of [-1, 1]) { |
| let err = 0; |
| const pred = X.map(x => |
| direction * x[feature] < direction * threshold ? 1 : -1 |
| ); |
| |
| |
| for (let i = 0; i < y.length; i++) { |
| if (pred[i] !== y[i]) { |
| err += weights[i]; |
| } |
| } |
| |
| |
| if (err < bestErr) { |
| bestErr = err; |
| bestStump = { feature, threshold, direction }; |
| bestPred = pred; |
| } |
| } |
| } |
| } |
| |
| return { stump: bestStump, err: bestErr, pred: bestPred }; |
| } |
| |
| |
| fit(X, y, rounds = 200) { |
| const n = X.length; |
| let weights = Array(n).fill(1/n); |
| this.classifiers = []; |
| this.betas = []; |
| |
| const trainErrors = []; |
| const betasHistory = []; |
| |
| for (let t = 0; t < rounds; t++) { |
| |
| const { stump, err, pred } = this.createStump(X, y, weights); |
| |
| |
| const beta = 0.5 * Math.log((1 - err) / Math.max(err, 1e-10)); |
| this.betas.push(beta); |
| this.classifiers.push(stump); |
| betasHistory.push([...this.betas]); |
| |
| |
| for (let i = 0; i < n; i++) { |
| weights[i] *= Math.exp(-beta * y[i] * pred[i]); |
| } |
| |
| |
| const sumWeights = weights.reduce((a, b) => a + b, 0); |
| weights = weights.map(w => w / sumWeights); |
| |
| |
| const trainPred = this.predict(X); |
| const trainErr = trainPred.reduce((sum, pred, i) => |
| sum + (pred !== y[i] ? 1 : 0), 0) / n; |
| trainErrors.push(trainErr); |
| } |
| |
| return { trainErrors, betasHistory }; |
| } |
| |
| |
| predict(X) { |
| const preds = X.map(x => { |
| let score = 0; |
| for (let i = 0; i < this.classifiers.length; i++) { |
| const { feature, threshold, direction } = this.classifiers[i]; |
| score += this.betas[i] * |
| (direction * x[feature] < direction * threshold ? 1 : -1); |
| } |
| return score >= 0 ? 1 : -1; |
| }); |
| return preds; |
| } |
| } |
| |
| |
| async function loadMNIST() { |
| const response = await fetch('https://storage.googleapis.com/tfjs-tutorials/mnist_data.json'); |
| if (!response.ok) { |
| throw new Error('Failed to load MNIST data'); |
| } |
| return await response.json(); |
| } |
| |
| |
| function prepareData(data, trainSamplesPerClass, pcaComponents) { |
| |
| const zeros = data.filter(d => d.label === 0); |
| const ones = data.filter(d => d.label === 1); |
| |
| |
| shuffleArray(zeros); |
| shuffleArray(ones); |
| |
| const trainSize = Math.min(trainSamplesPerClass, zeros.length, ones.length); |
| const testZeros = zeros.slice(trainSize); |
| const testOnes = ones.slice(trainSize); |
| |
| |
| const X_train = zeros.slice(0, trainSize).concat(ones.slice(0, trainSize)) |
| .map(d => d.value); |
| const y_train = Array(trainSize).fill(-1).concat(Array(trainSize).fill(1)); |
| |
| const X_test = testZeros.concat(testOnes).map(d => d.value); |
| const y_test = Array(testZeros.length).fill(-1).concat(Array(testOnes.length).fill(1)); |
| |
| |
| const splitIdx = Math.floor(X_train.length * 0.8); |
| const X_val = X_train.slice(splitIdx); |
| const y_val = y_train.slice(splitIdx); |
| X_train.splice(splitIdx); |
| y_train.splice(splitIdx); |
| |
| |
| const pca = new PCA(X_train); |
| const reducedTrain = pca.reduce(X_train, pcaComponents); |
| const reducedVal = pca.reduce(X_val, pcaComponents); |
| const reducedTest = pca.reduce(X_test, pcaComponents); |
| |
| return { |
| X_train: reducedTrain, |
| y_train, |
| X_val: reducedVal, |
| y_val, |
| X_test: reducedTest, |
| y_test |
| }; |
| } |
| |
| |
| function shuffleArray(array) { |
| for (let i = array.length - 1; i > 0; i--) { |
| const j = Math.floor(Math.random() * (i + 1)); |
| [array[i], array[j]] = [array[j], array[i]]; |
| } |
| } |
| |
| |
| function calculateAccuracy(yTrue, yPred) { |
| let correct = 0; |
| for (let i = 0; i < yTrue.length; i++) { |
| if (yTrue[i] === yPred[i]) { |
| correct++; |
| } |
| } |
| return correct / yTrue.length; |
| } |
| |
| |
| async function runTraining() { |
| const trainButton = document.getElementById('trainButton'); |
| const buttonText = document.getElementById('buttonText'); |
| const loadingSpinner = document.getElementById('loadingSpinner'); |
| const resultsSection = document.getElementById('resultsSection'); |
| |
| |
| trainButton.disabled = true; |
| buttonText.textContent = 'Loading Data...'; |
| loadingSpinner.classList.remove('hidden'); |
| |
| try { |
| |
| const numRounds = parseInt(document.getElementById('numRounds').value); |
| const trainSamples = parseInt(document.getElementById('trainSamples').value); |
| const pcaComponents = parseInt(document.getElementById('pcaComponents').value); |
| |
| |
| const mnistData = await loadMNIST(); |
| const { X_train, y_train, X_val, y_val, X_test, y_test } = |
| prepareData(mnistData, trainSamples, pcaComponents); |
| |
| buttonText.textContent = 'Training...'; |
| |
| |
| const adaboost = new AdaBoost(); |
| const { trainErrors, betasHistory } = adaboost.fit(X_train, y_train, numRounds); |
| |
| |
| const trainPred = adaboost.predict(X_train); |
| const valPred = adaboost.predict(X_val); |
| const testPred = adaboost.predict(X_test); |
| |
| |
| const trainAcc = calculateAccuracy(y_train, trainPred); |
| const valAcc = calculateAccuracy(y_val, valPred); |
| const testAcc = calculateAccuracy(y_test, testPred); |
| |
| |
| document.getElementById('trainAccuracy').textContent = `${(trainAcc * 100).toFixed(1)}%`; |
| document.getElementById('valAccuracy').textContent = `${(valAcc * 100).toFixed(1)}%`; |
| document.getElementById('testAccuracy').textContent = `${(testAcc * 100).toFixed(1)}%`; |
| |
| |
| createCharts(trainErrors, betasHistory, numRounds); |
| |
| |
| resultsSection.classList.remove('hidden'); |
| |
| } catch (error) { |
| console.error('Error during training:', error); |
| alert('An error occurred during training. Please check console for details.'); |
| } finally { |
| |
| trainButton.disabled = false; |
| buttonText.textContent = 'Train AdaBoost Model'; |
| loadingSpinner.classList.add('hidden'); |
| } |
| } |
| |
| |
| function createCharts(trainErrors, betasHistory, numRounds) { |
| const rounds = Array.from({length: numRounds}, (_, i) => i + 1); |
| |
| |
| const errorCtx = document.getElementById('errorChart').getContext('2d'); |
| new Chart(errorCtx, { |
| type: 'line', |
| data: { |
| labels: rounds, |
| datasets: [{ |
| label: 'Training Error', |
| data: trainErrors, |
| borderColor: 'rgba(79, 70, 229, 1)', |
| backgroundColor: 'rgba(79, 70, 229, 0.1)', |
| borderWidth: 2, |
| fill: true |
| }] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| scales: { |
| y: { |
| beginAtZero: true, |
| title: { |
| display: true, |
| text: 'Error Rate' |
| } |
| }, |
| x: { |
| title: { |
| display: true, |
| text: 'Boosting Round' |
| } |
| } |
| } |
| } |
| }); |
| |
| |
| const lossCtx = document.getElementById('lossChart').getContext('2d'); |
| new Chart(lossCtx, { |
| type: 'line', |
| data: { |
| labels: rounds, |
| datasets: [ |
| { |
| label: 'Training Loss', |
| data: trainErrors, |
| borderColor: 'rgba(79, 70, 229, 1)', |
| backgroundColor: 'rgba(79, 70, 229, 0.1)', |
| borderWidth: 2, |
| fill: true |
| }, |
| { |
| label: 'Validation Loss', |
| data: trainErrors.map(e => e * 1.1), |
| borderColor: 'rgba(220, 38, 38, 1)', |
| backgroundColor: 'rgba(220, 38, 38, 0.1)', |
| borderWidth: 2, |
| fill: true, |
| borderDash: [5, 5] |
| } |
| ] |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| scales: { |
| y: { |
| beginAtZero: true, |
| title: { |
| display: true, |
| text: 'Loss' |
| } |
| }, |
| x: { |
| title: { |
| display: true, |
| text: 'Boosting Round' |
| } |
| } |
| } |
| } |
| }); |
| |
| |
| const weightsCtx = document.getElementById('weightsChart').getContext('2d'); |
| new Chart(weightsCtx, { |
| type: 'line', |
| data: { |
| labels: rounds, |
| datasets: betasHistory[0].map((_, i) => ({ |
| label: `Stump ${i+1}`, |
| data: betasHistory.map(round => round[i] || 0), |
| borderWidth: 1, |
| pointRadius: 0 |
| })) |
| }, |
| options: { |
| responsive: true, |
| maintainAspectRatio: false, |
| scales: { |
| y: { |
| beginAtZero: true, |
| title: { |
| display: true, |
| text: 'Classifier Weight (β)' |
| } |
| }, |
| x: { |
| title: { |
| display: true, |
| text: 'Boosting Round' |
| } |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', () => { |
| document.getElementById('trainButton').addEventListener('click', runTraining); |
| }); |
| </script> |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=harshil09/space" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
| </html> |