Autoencoder Visualizer
Explore compression and reconstruction through neural networks!
What is an Autoencoder?
An autoencoder is a neural network that learns to compress data into a smaller representation
(encoding) and then reconstruct it back to the original form (decoding). It's like learning
to zip and unzip files, but the network discovers the best compression strategy automatically!
Key concepts:
- Encoder: Compresses input into a compact latent representation
- Bottleneck/Latent Space: The compressed middle layer with reduced dimensions
- Decoder: Reconstructs the original input from the compressed representation
- Reconstruction Loss: Measures how well the output matches the input
- Dimensionality Reduction: Compressing data while preserving key features
- Unsupervised Learning: Learns patterns without labeled data
Instructions: Select sample patterns below to see encoding and reconstruction.
Adjust the bottleneck size to experiment with different compression levels. Watch how the latent
space organizes similar patterns together!
Network Architecture (Hourglass Shape)
Input vs Reconstruction
Latent Space (2D Projection)
archCtx.beginPath();
archCtx.moveTo(x, y1);
archCtx.lineTo(nextX, y2);
archCtx.stroke();
}
}
}
// Draw labels
archCtx.fillStyle = '#2d3748';
archCtx.font = 'bold 12px Arial';
archCtx.textAlign = 'center';
if (i === 0) {
archCtx.fillText('Input', x, centerY + 90);
archCtx.fillText('64', x, centerY + 105);
} else if (i === 4) {
archCtx.fillText('Bottleneck', x, centerY + 90);
archCtx.fillText(String(bottleneckSize), x, centerY + 105);
} else if (i === layers.length - 1) {
archCtx.fillText('Output', x, centerY + 90);
archCtx.fillText('64', x, centerY + 105);
}
});
// Draw labels
archCtx.fillStyle = '#2d3748';
archCtx.font = 'bold 14px Arial';
archCtx.textAlign = 'center';
archCtx.fillText('ENCODER', startX + 200, 30);
archCtx.fillText('DECODER', startX + 600, 30);
// Draw arrows
drawArrow(archCtx, startX + 200, 45, startX + 200, 60, '#4CAF50');
drawArrow(archCtx, startX + 600, 45, startX + 600, 60, '#8BC34A');
}
function drawArchitectureWithData(latentCode) {
drawArchitecture();
// Highlight the data flow
const centerY = architectureCanvas.height / 2;
const startX = 50;
// Draw latent code values
if (latentCode && latentCode.length > 0) {
const x = startX + 4 * 100;
archCtx.fillStyle = 'rgba(244, 67, 54, 0.2)';
archCtx.fillRect(x - 30, centerY - 80, 60, 160);
archCtx.fillStyle = '#2d3748';
archCtx.font = '10px Arial';
archCtx.textAlign = 'center';
const displayCount = Math.min(8, latentCode.length);
for (let i = 0; i < displayCount; i++) { const y=centerY - 60 + i * 15; archCtx.fillText(latentCode[i].toFixed(2),
x, y); } } } function drawComparison(input, reconstructed) { compCtx.clearRect(0, 0, comparisonCanvas.width,
comparisonCanvas.height); const cellSize=20; const gridWidth=GRID_SIZE * cellSize; const spacing=40; // Draw
input compCtx.fillStyle='#2d3748' ; compCtx.font='bold 14px Arial' ; compCtx.textAlign='center' ;
compCtx.fillText('Input', gridWidth / 2, 20); for (let i=0; i < GRID_SIZE; i++) { for (let j=0; j < GRID_SIZE;
j++) { const value=Math.floor(input[i][j] * 255); compCtx.fillStyle=`rgb(${value}, ${value}, ${value})`;
compCtx.fillRect(j * cellSize, 30 + i * cellSize, cellSize, cellSize); compCtx.strokeStyle='#ddd' ;
compCtx.lineWidth=1; compCtx.strokeRect(j * cellSize, 30 + i * cellSize, cellSize, cellSize); } } // Draw
reconstructed const offsetX=gridWidth + spacing; compCtx.fillText('Reconstructed', offsetX + gridWidth / 2, 20);
for (let i=0; i < GRID_SIZE; i++) { for (let j=0; j < GRID_SIZE; j++) { const
value=Math.floor(reconstructed[i][j] * 255); compCtx.fillStyle=`rgb(${value}, ${value}, ${value})`;
compCtx.fillRect(offsetX + j * cellSize, 30 + i * cellSize, cellSize, cellSize); compCtx.strokeStyle='#ddd' ;
compCtx.lineWidth=1; compCtx.strokeRect(offsetX + j * cellSize, 30 + i * cellSize, cellSize, cellSize); } } }
function drawLatentSpace() { latentCtx.clearRect(0, 0, latentCanvas.width, latentCanvas.height); const
centerX=latentCanvas.width / 2; const centerY=latentCanvas.height / 2; const scale=60; // Draw axes
latentCtx.strokeStyle='#ccc' ; latentCtx.lineWidth=2; latentCtx.beginPath(); latentCtx.moveTo(0, centerY);
latentCtx.lineTo(latentCanvas.width, centerY); latentCtx.moveTo(centerX, 0); latentCtx.lineTo(centerX,
latentCanvas.height); latentCtx.stroke(); // Draw labels latentCtx.fillStyle='#666' ;
latentCtx.font='12px Arial' ; latentCtx.textAlign='center' ; latentCtx.fillText('Latent Dimension 1', centerX,
latentCanvas.height - 10); latentCtx.save(); latentCtx.translate(15, centerY); latentCtx.rotate(-Math.PI / 2);
latentCtx.fillText('Latent Dimension 2', 0, 0); latentCtx.restore(); // Draw points latentPoints.forEach(point=>
{
const x = centerX + point.x * scale;
const y = centerY - point.y * scale;
latentCtx.fillStyle = categoryColors[point.category];
latentCtx.beginPath();
latentCtx.arc(x, y, 8, 0, Math.PI * 2);
latentCtx.fill();
latentCtx.strokeStyle = '#2d3748';
latentCtx.lineWidth = 2;
latentCtx.stroke();
// Add label
latentCtx.fillStyle = '#2d3748';
latentCtx.font = 'bold 10px Arial';
latentCtx.textAlign = 'center';
latentCtx.fillText(point.category[0].toUpperCase() + point.index, x, y - 12);
});
// Draw legend
let legendY = 20;
const legendX = 20;
latentCtx.font = 'bold 12px Arial';
latentCtx.textAlign = 'left';
latentCtx.fillStyle = '#2d3748';
latentCtx.fillText('Categories:', legendX, legendY);
legendY += 20;
Object.keys(categoryColors).forEach(category => {
latentCtx.fillStyle = categoryColors[category];
latentCtx.beginPath();
latentCtx.arc(legendX + 8, legendY - 4, 6, 0, Math.PI * 2);
latentCtx.fill();
latentCtx.fillStyle = '#2d3748';
latentCtx.font = '11px Arial';
latentCtx.fillText(category, legendX + 20, legendY);
legendY += 18;
});
}
function drawArrow(ctx, fromX, fromY, toX, toY, color) {
const headLength = 10;
const angle = Math.atan2(toY - fromY, toX - fromX);
ctx.strokeStyle = color;
ctx.fillStyle = color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLength * Math.cos(angle - Math.PI / 6),
toY - headLength * Math.sin(angle - Math.PI / 6));
ctx.lineTo(toX - headLength * Math.cos(angle + Math.PI / 6),
toY - headLength * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.fill();
}
function reset() {
currentSample = null;
currentCategory = null;
currentIndex = null;
latentPoints = [];
isTraining = false;
compCtx.clearRect(0, 0, comparisonCanvas.width, comparisonCanvas.height);
latentCtx.clearRect(0, 0, latentCanvas.width, latentCanvas.height);
document.getElementById('statusIndicator').textContent = 'Ready - Select a sample';
document.getElementById('statusIndicator').style.background = '#e3f2fd';
document.getElementById('statusIndicator').style.color = '#1565c0';
document.getElementById('lossValue').textContent = '0.00';
drawArchitecture();
}
// Initialize
drawArchitecture();
selectSample('digit', 0);