Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Softmax Function Visualizer</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
.slider-container { | |
transition: all 0.3s ease; | |
} | |
.slider-container:hover { | |
transform: translateY(-3px); | |
} | |
.bar-container { | |
transition: all 0.5s ease; | |
} | |
.glow { | |
box-shadow: 0 0 15px rgba(59, 130, 246, 0.5); | |
} | |
.input-value { | |
min-width: 60px; | |
text-align: center; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 min-h-screen"> | |
<div class="container mx-auto px-4 py-8"> | |
<div class="max-w-4xl mx-auto"> | |
<!-- Header --> | |
<div class="text-center mb-8"> | |
<h1 class="text-4xl font-bold text-blue-600 mb-2">Softmax Function Visualizer</h1> | |
<p class="text-gray-600 text-lg"> | |
Interactive visualization of the softmax activation function | |
</p> | |
</div> | |
<!-- Explanation Card --> | |
<div class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
<div class="flex items-start"> | |
<div class="bg-blue-100 p-3 rounded-full mr-4"> | |
<i class="fas fa-info-circle text-blue-600 text-xl"></i> | |
</div> | |
<div> | |
<h2 class="text-xl font-semibold text-gray-800 mb-2">About Softmax</h2> | |
<p class="text-gray-600 mb-2"> | |
The softmax function converts a vector of real numbers into a probability distribution. | |
It's commonly used in machine learning for multi-class classification. | |
</p> | |
<p class="text-gray-600"> | |
Formula: <code class="bg-gray-100 px-2 py-1 rounded">σ(z)_i = e^{z_i} / Σ_j e^{z_j}</code> | |
</p> | |
</div> | |
</div> | |
</div> | |
<!-- Controls --> | |
<div class="bg-white rounded-xl shadow-md p-6 mb-8"> | |
<div class="flex justify-between items-center mb-4"> | |
<h2 class="text-xl font-semibold text-gray-800">Input Values</h2> | |
<div class="flex space-x-2"> | |
<button id="add-input" class="bg-green-500 hover:bg-green-600 text-white px-3 py-1 rounded-lg transition"> | |
<i class="fas fa-plus mr-1"></i> Add | |
</button> | |
<button id="reset" class="bg-gray-500 hover:bg-gray-600 text-white px-3 py-1 rounded-lg transition"> | |
<i class="fas fa-sync-alt mr-1"></i> Reset | |
</button> | |
</div> | |
</div> | |
<div id="sliders-container" class="space-y-4"> | |
<!-- Sliders will be added here dynamically --> | |
</div> | |
</div> | |
<!-- Results --> | |
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8"> | |
<!-- Input Values --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Input Values</h2> | |
<div id="input-values" class="space-y-2"> | |
<!-- Input values will be displayed here --> | |
</div> | |
</div> | |
<!-- Softmax Output --> | |
<div class="bg-white rounded-xl shadow-md p-6"> | |
<h2 class="text-xl font-semibold text-gray-800 mb-4">Softmax Output</h2> | |
<div id="output-values" class="space-y-2"> | |
<!-- Output values will be displayed here --> | |
</div> | |
</div> | |
</div> | |
<!-- Summary --> | |
<div class="bg-blue-50 rounded-xl shadow-md p-6"> | |
<div class="flex items-start"> | |
<div class="bg-blue-100 p-3 rounded-full mr-4"> | |
<i class="fas fa-lightbulb text-blue-600 text-xl"></i> | |
</div> | |
<div> | |
<h2 class="text-xl font-semibold text-gray-800 mb-2">Key Insights</h2> | |
<ul class="list-disc pl-5 text-gray-600 space-y-1"> | |
<li>Softmax converts input values to probabilities that sum to 1</li> | |
<li>Higher input values get exponentially larger probabilities</li> | |
<li>The function is sensitive to differences between values</li> | |
<li>Adding a constant to all inputs doesn't change the output</li> | |
</ul> | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', function() { | |
// Initialize with 3 inputs | |
let inputValues = [1, 2, 3]; | |
// DOM elements | |
const slidersContainer = document.getElementById('sliders-container'); | |
const inputValuesContainer = document.getElementById('input-values'); | |
const outputValuesContainer = document.getElementById('output-values'); | |
const addInputBtn = document.getElementById('add-input'); | |
const resetBtn = document.getElementById('reset'); | |
// Initialize the app | |
function init() { | |
renderSliders(); | |
updateVisualization(); | |
} | |
// Render sliders based on current input values | |
function renderSliders() { | |
slidersContainer.innerHTML = ''; | |
inputValuesContainer.innerHTML = ''; | |
inputValues.forEach((value, index) => { | |
// Create slider container | |
const sliderContainer = document.createElement('div'); | |
sliderContainer.className = 'slider-container bg-gray-50 p-4 rounded-lg'; | |
// Create label | |
const label = document.createElement('div'); | |
label.className = 'flex justify-between items-center mb-2'; | |
label.innerHTML = ` | |
<span class="font-medium text-gray-700">Input ${index + 1}</span> | |
<div class="flex items-center"> | |
<span class="input-value bg-blue-100 text-blue-800 font-mono px-2 py-1 rounded mr-2">${value.toFixed(2)}</span> | |
<button class="delete-btn text-red-500 hover:text-red-700 transition" data-index="${index}"> | |
<i class="fas fa-trash"></i> | |
</button> | |
</div> | |
`; | |
// Create slider | |
const slider = document.createElement('input'); | |
slider.type = 'range'; | |
slider.min = '-5'; | |
slider.max = '5'; | |
slider.step = '0.1'; | |
slider.value = value; | |
slider.className = 'w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer'; | |
slider.dataset.index = index; | |
// Add event listener | |
slider.addEventListener('input', function() { | |
const index = parseInt(this.dataset.index); | |
const value = parseFloat(this.value); | |
inputValues[index] = value; | |
// Update the displayed value | |
this.parentNode.querySelector('.input-value').textContent = value.toFixed(2); | |
updateVisualization(); | |
}); | |
// Add delete button event listener | |
const deleteBtn = label.querySelector('.delete-btn'); | |
deleteBtn.addEventListener('click', function() { | |
if (inputValues.length > 1) { | |
const index = parseInt(this.dataset.index); | |
inputValues.splice(index, 1); | |
renderSliders(); | |
updateVisualization(); | |
} else { | |
alert("You need at least one input!"); | |
} | |
}); | |
// Assemble slider container | |
sliderContainer.appendChild(label); | |
sliderContainer.appendChild(slider); | |
slidersContainer.appendChild(sliderContainer); | |
}); | |
} | |
// Calculate softmax function | |
function softmax(values) { | |
const max = Math.max(...values); | |
const exps = values.map(x => Math.exp(x - max)); // Numerical stability | |
const sumExps = exps.reduce((a, b) => a + b, 0); | |
return exps.map(exp => exp / sumExps); | |
} | |
// Update all visualizations | |
function updateVisualization() { | |
const softmaxOutput = softmax(inputValues); | |
// Update input values display | |
inputValuesContainer.innerHTML = inputValues.map((value, index) => ` | |
<div class="flex justify-between items-center py-2 px-3 rounded ${index % 2 === 0 ? 'bg-gray-50' : ''}"> | |
<span class="font-medium">Input ${index + 1}</span> | |
<span class="font-mono text-blue-600">${value.toFixed(4)}</span> | |
</div> | |
`).join(''); | |
// Update output values display | |
outputValuesContainer.innerHTML = softmaxOutput.map((value, index) => { | |
const percent = (value * 100).toFixed(2); | |
return ` | |
<div class="bar-container"> | |
<div class="flex justify-between items-center mb-1"> | |
<span class="font-medium">Output ${index + 1}</span> | |
<span class="font-mono text-green-600">${value.toFixed(4)} <span class="text-gray-500">(${percent}%)</span></span> | |
</div> | |
<div class="w-full bg-gray-200 rounded-full h-2.5"> | |
<div class="bg-gradient-to-r from-blue-400 to-blue-600 h-2.5 rounded-full" | |
style="width: ${percent}%"></div> | |
</div> | |
</div> | |
`; | |
}).join(''); | |
} | |
// Event listeners | |
addInputBtn.addEventListener('click', function() { | |
if (inputValues.length < 8) { | |
// Add a new input with value similar to the last one | |
const newValue = inputValues.length > 0 ? inputValues[inputValues.length - 1] : 0; | |
inputValues.push(newValue); | |
renderSliders(); | |
updateVisualization(); | |
} else { | |
alert("Maximum of 8 inputs reached!"); | |
} | |
}); | |
resetBtn.addEventListener('click', function() { | |
inputValues = [1, 2, 3]; | |
renderSliders(); | |
updateVisualization(); | |
}); | |
// Initialize the app | |
init(); | |
}); | |
</script> | |
</body> | |
</html> |