space / index.html
harshil09's picture
Add 3 files
f8de770 verified
<!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>
// AdaBoost implementation
class AdaBoost {
constructor() {
this.classifiers = [];
this.betas = [];
}
// Decision stump (weak classifier)
createStump(X, y, weights) {
let bestErr = Infinity;
let bestStump = {};
let bestPred = null;
// Try all features
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);
// Try 3 possible thresholds between min and max
for (let threshold of [minVal + (maxVal-minVal)/4,
minVal + (maxVal-minVal)/2,
minVal + 3*(maxVal-minVal)/4]) {
// Try both inequality directions
for (let direction of [-1, 1]) {
let err = 0;
const pred = X.map(x =>
direction * x[feature] < direction * threshold ? 1 : -1
);
// Calculate weighted error
for (let i = 0; i < y.length; i++) {
if (pred[i] !== y[i]) {
err += weights[i];
}
}
// Keep track of best stump
if (err < bestErr) {
bestErr = err;
bestStump = { feature, threshold, direction };
bestPred = pred;
}
}
}
}
return { stump: bestStump, err: bestErr, pred: bestPred };
}
// Train AdaBoost with decision stumps
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++) {
// Create and train a new stump
const { stump, err, pred } = this.createStump(X, y, weights);
// Calculate beta (classifier weight)
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]);
// Update sample weights
for (let i = 0; i < n; i++) {
weights[i] *= Math.exp(-beta * y[i] * pred[i]);
}
// Normalize weights
const sumWeights = weights.reduce((a, b) => a + b, 0);
weights = weights.map(w => w / sumWeights);
// Calculate training error
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 };
}
// Make predictions using all classifiers
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;
}
}
// Load MNIST data
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();
}
// Prepare data for binary classification (0 vs 1)
function prepareData(data, trainSamplesPerClass, pcaComponents) {
// Filter only 0s and 1s
const zeros = data.filter(d => d.label === 0);
const ones = data.filter(d => d.label === 1);
// Shuffle and select samples
shuffleArray(zeros);
shuffleArray(ones);
const trainSize = Math.min(trainSamplesPerClass, zeros.length, ones.length);
const testZeros = zeros.slice(trainSize);
const testOnes = ones.slice(trainSize);
// Create train/test sets
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));
// Split train into train/validation (80/20)
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);
// Apply PCA
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
};
}
// Utility function to shuffle array
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]];
}
}
// Calculate accuracy
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;
}
// Main function to run training
async function runTraining() {
const trainButton = document.getElementById('trainButton');
const buttonText = document.getElementById('buttonText');
const loadingSpinner = document.getElementById('loadingSpinner');
const resultsSection = document.getElementById('resultsSection');
// Show loading state
trainButton.disabled = true;
buttonText.textContent = 'Loading Data...';
loadingSpinner.classList.remove('hidden');
try {
// Get parameters
const numRounds = parseInt(document.getElementById('numRounds').value);
const trainSamples = parseInt(document.getElementById('trainSamples').value);
const pcaComponents = parseInt(document.getElementById('pcaComponents').value);
// Load and prepare data
const mnistData = await loadMNIST();
const { X_train, y_train, X_val, y_val, X_test, y_test } =
prepareData(mnistData, trainSamples, pcaComponents);
buttonText.textContent = 'Training...';
// Train AdaBoost
const adaboost = new AdaBoost();
const { trainErrors, betasHistory } = adaboost.fit(X_train, y_train, numRounds);
// Make predictions
const trainPred = adaboost.predict(X_train);
const valPred = adaboost.predict(X_val);
const testPred = adaboost.predict(X_test);
// Calculate accuracies
const trainAcc = calculateAccuracy(y_train, trainPred);
const valAcc = calculateAccuracy(y_val, valPred);
const testAcc = calculateAccuracy(y_test, testPred);
// Update UI with results
document.getElementById('trainAccuracy').textContent = `${(trainAcc * 100).toFixed(1)}%`;
document.getElementById('valAccuracy').textContent = `${(valAcc * 100).toFixed(1)}%`;
document.getElementById('testAccuracy').textContent = `${(testAcc * 100).toFixed(1)}%`;
// Create charts
createCharts(trainErrors, betasHistory, numRounds);
// Show results
resultsSection.classList.remove('hidden');
} catch (error) {
console.error('Error during training:', error);
alert('An error occurred during training. Please check console for details.');
} finally {
// Reset button state
trainButton.disabled = false;
buttonText.textContent = 'Train AdaBoost Model';
loadingSpinner.classList.add('hidden');
}
}
// Create visualization charts
function createCharts(trainErrors, betasHistory, numRounds) {
const rounds = Array.from({length: numRounds}, (_, i) => i + 1);
// Training error chart
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'
}
}
}
}
});
// Loss chart (using training error as proxy)
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), // Placeholder
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'
}
}
}
}
});
// Classifier weights chart
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'
}
}
}
}
});
}
// Initialize
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>