start .
me .
HTML Document File : som_interactive.html
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Self-Organizing Maps (SOM) - Esempio Interattivo</title>
<style>
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
min-height: 100vh;
}
.container {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 20px;
padding: 30px;
box-shadow: 0 8px 32px 0 rgba(31, 38, 135, 0.37);
}
h1 {
text-align: center;
margin-bottom: 30px;
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
}
.explanation {
background: rgba(255, 255, 255, 0.15);
padding: 20px;
border-radius: 15px;
margin-bottom: 30px;
border-left: 4px solid #64ffda;
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.control-group {
background: rgba(255, 255, 255, 0.1);
padding: 15px;
border-radius: 10px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input[type="range"] {
width: 100%;
margin-bottom: 10px;
}
button {
background: linear-gradient(45deg, #64ffda, #00bcd4);
border: none;
color: white;
padding: 12px 20px;
border-radius: 25px;
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
width: 100%;
margin: 5px 0;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.3);
}
button:disabled {
background: #666;
cursor: not-allowed;
transform: none;
}
.visualization {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 30px;
}
.canvas-container {
background: rgba(255, 255, 255, 0.9);
border-radius: 15px;
padding: 20px;
text-align: center;
}
.canvas-container h3 {
color: #333;
margin-top: 0;
}
canvas {
border: 2px solid #333;
border-radius: 10px;
cursor: crosshair;
}
.stats {
background: rgba(255, 255, 255, 0.15);
padding: 20px;
border-radius: 15px;
margin-top: 20px;
}
.info-panel {
background: rgba(255, 255, 255, 0.1);
padding: 20px;
border-radius: 15px;
margin-top: 20px;
}
@media (max-width: 768px) {
.visualization {
grid-template-columns: 1fr;
}
.controls {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<h1>🧠 Mappe Auto Organizzanti (SOM)</h1>
<div class="explanation">
<h3>💡 Cosa sono le SOM?</h3>
<p>Le Self-Organizing Maps sono reti neurali che imparano a organizzare dati complessi in una mappa bidimensionale ordinata. Ogni neurone nella griglia ha un "peso" che rappresenta un punto nello spazio dei dati.</p>
<p><strong>Come funzionano:</strong> Quando presenti un dato, il neurone più simile (vincitore) si aggiorna per assomigliare di più al dato, e anche i neuroni vicini si aggiustano gradualmente.</p>
</div>
<div class="controls">
<div class="control-group">
<label>Dimensione Griglia: <span id="gridSizeValue">10x10</span></label>
<input type="range" id="gridSize" min="5" max="20" value="10">
</div>
<div class="control-group">
<label>Tasso di Apprendimento: <span id="learningRateValue">0.1</span></label>
<input type="range" id="learningRate" min="0.01" max="0.5" step="0.01" value="0.1">
</div>
<div class="control-group">
<label>Raggio Vicinato: <span id="neighborhoodValue">3</span></label>
<input type="range" id="neighborhood" min="1" max="8" value="3">
</div>
<div class="control-group">
<button id="resetBtn">🔄 Reset Rete</button>
<button id="trainBtn">🎯 Addestra (100 step)</button>
<button id="autoTrainBtn">⚡ Auto-Addestramento</button>
<button id="clearDataBtn">🗑️ Cancella Dati</button>
</div>
</div>
<div class="visualization">
<div class="canvas-container">
<h3>📊 Spazio dei Dati (Clicca per aggiungere punti)</h3>
<canvas id="dataCanvas" width="300" height="300"></canvas>
<p style="color: #666; font-size: 12px;">Clicca per aggiungere punti dati colorati</p>
</div>
<div class="canvas-container">
<h3>🗺️ Mappa SOM (Neuroni)</h3>
<canvas id="somCanvas" width="300" height="300"></canvas>
<p style="color: #666; font-size: 12px;">I colori rappresentano i pesi dei neuroni</p>
</div>
</div>
<div class="stats">
<h3>📈 Statistiche</h3>
<div id="statsContent">
<p>Iterazioni di addestramento: <span id="iterations">0</span></p>
<p>Punti dati: <span id="dataCount">0</span></p>
<p>Errore quantizzazione: <span id="quantError">N/A</span></p>
</div>
</div>
<div class="info-panel">
<h3>🎓 Come usare questa demo:</h3>
<ol>
<li><strong>Aggiungi dati:</strong> Clicca nel riquadro sinistro per creare punti colorati</li>
<li><strong>Osserva la rete:</strong> I neuroni (riquadro destro) iniziano con colori casuali</li>
<li><strong>Addestra:</strong> Usa i pulsanti per far imparare la rete</li>
<li><strong>Risultato:</strong> I neuroni si organizzeranno per rappresentare i tuoi dati!</li>
</ol>
<h4>🔍 Cosa osservare:</h4>
<ul>
<li>I neuroni vicini sviluppano colori simili (proprietà topologica)</li>
<li>La mappa preserva le relazioni di vicinanza dei dati originali</li>
<li>Aree dense di dati avranno più neuroni dedicati</li>
</ul>
</div>
</div>
<script>
class SOM {
constructor(width, height, inputDim) {
this.width = width;
this.height = height;
this.inputDim = inputDim;
this.neurons = [];
this.learningRate = 0.1;
this.neighborhoodRadius = 3;
this.iteration = 0;
this.initializeNeurons();
}
initializeNeurons() {
this.neurons = [];
for (let i = 0; i < this.height; i++) {
this.neurons[i] = [];
for (let j = 0; j < this.width; j++) {
this.neurons[i][j] = {
weights: [
Math.random() * 255, // R
Math.random() * 255, // G
Math.random() * 255 // B
],
x: j,
y: i
};
}
}
}
findBMU(input) {
let minDistance = Infinity;
let bmu = { x: 0, y: 0 };
for (let i = 0; i < this.height; i++) {
for (let j = 0; j < this.width; j++) {
const distance = this.euclideanDistance(input, this.neurons[i][j].weights);
if (distance < minDistance) {
minDistance = distance;
bmu = { x: j, y: i };
}
}
}
return { bmu, distance: minDistance };
}
euclideanDistance(a, b) {
return Math.sqrt(a.reduce((sum, val, i) => sum + Math.pow(val - b[i], 2), 0));
}
getNeighborhoodInfluence(distance) {
return Math.exp(-(distance * distance) / (2 * this.neighborhoodRadius * this.neighborhoodRadius));
}
updateWeights(input, bmu) {
for (let i = 0; i < this.height; i++) {
for (let j = 0; j < this.width; j++) {
const neuronDistance = Math.sqrt(Math.pow(j - bmu.x, 2) + Math.pow(i - bmu.y, 2));
if (neuronDistance <= this.neighborhoodRadius) {
const influence = this.getNeighborhoodInfluence(neuronDistance);
const learningInfluence = this.learningRate * influence;
for (let k = 0; k < this.inputDim; k++) {
this.neurons[i][j].weights[k] += learningInfluence * (input[k] - this.neurons[i][j].weights[k]);
}
}
}
}
}
train(dataPoints) {
if (dataPoints.length === 0) return 0;
let totalError = 0;
for (const point of dataPoints) {
const { bmu, distance } = this.findBMU(point);
this.updateWeights(point, bmu);
totalError += distance;
}
this.iteration++;
return totalError / dataPoints.length;
}
}
class SOMVisualizer {
constructor() {
this.som = new SOM(10, 10, 3);
this.dataPoints = [];
this.isAutoTraining = false;
this.autoTrainInterval = null;
this.initializeCanvases();
this.setupEventListeners();
this.updateDisplay();
}
initializeCanvases() {
this.dataCanvas = document.getElementById('dataCanvas');
this.dataCtx = this.dataCanvas.getContext('2d');
this.somCanvas = document.getElementById('somCanvas');
this.somCtx = this.somCanvas.getContext('2d');
// Disegna sfondo griglia per i dati
this.drawDataBackground();
}
setupEventListeners() {
// Controlli
document.getElementById('gridSize').addEventListener('input', (e) => {
const size = parseInt(e.target.value);
document.getElementById('gridSizeValue').textContent = `${size}x${size}`;
this.som = new SOM(size, size, 3);
this.updateDisplay();
});
document.getElementById('learningRate').addEventListener('input', (e) => {
this.som.learningRate = parseFloat(e.target.value);
document.getElementById('learningRateValue').textContent = e.target.value;
});
document.getElementById('neighborhood').addEventListener('input', (e) => {
this.som.neighborhoodRadius = parseInt(e.target.value);
document.getElementById('neighborhoodValue').textContent = e.target.value;
});
// Pulsanti
document.getElementById('resetBtn').addEventListener('click', () => {
this.som.initializeNeurons();
this.som.iteration = 0;
this.updateDisplay();
});
document.getElementById('trainBtn').addEventListener('click', () => {
this.trainSteps(100);
});
document.getElementById('autoTrainBtn').addEventListener('click', () => {
this.toggleAutoTraining();
});
document.getElementById('clearDataBtn').addEventListener('click', () => {
this.dataPoints = [];
this.updateDisplay();
});
// Click su canvas dati
this.dataCanvas.addEventListener('click', (e) => {
this.addDataPoint(e);
});
}
drawDataBackground() {
this.dataCtx.fillStyle = '#f0f0f0';
this.dataCtx.fillRect(0, 0, this.dataCanvas.width, this.dataCanvas.height);
// Griglia
this.dataCtx.strokeStyle = '#ddd';
this.dataCtx.lineWidth = 1;
for (let i = 0; i <= 10; i++) {
const x = (i / 10) * this.dataCanvas.width;
const y = (i / 10) * this.dataCanvas.height;
this.dataCtx.beginPath();
this.dataCtx.moveTo(x, 0);
this.dataCtx.lineTo(x, this.dataCanvas.height);
this.dataCtx.stroke();
this.dataCtx.beginPath();
this.dataCtx.moveTo(0, y);
this.dataCtx.lineTo(this.dataCanvas.width, y);
this.dataCtx.stroke();
}
}
addDataPoint(event) {
const rect = this.dataCanvas.getBoundingClientRect();
const x = ((event.clientX - rect.left) / this.dataCanvas.width) * 255;
const y = ((event.clientY - rect.top) / this.dataCanvas.height) * 255;
// Crea un colore basato sulla posizione
const r = x;
const g = y;
const b = 128 + Math.sin(x * 0.02) * 127;
this.dataPoints.push([r, g, b]);
this.updateDisplay();
}
trainSteps(steps) {
let totalError = 0;
for (let i = 0; i < steps; i++) {
const error = this.som.train(this.dataPoints);
totalError += error;
}
this.updateDisplay();
}
toggleAutoTraining() {
const btn = document.getElementById('autoTrainBtn');
if (this.isAutoTraining) {
clearInterval(this.autoTrainInterval);
this.isAutoTraining = false;
btn.textContent = '⚡ Auto-Addestramento';
btn.style.background = 'linear-gradient(45deg, #64ffda, #00bcd4)';
} else {
this.isAutoTraining = true;
btn.textContent = '⏹️ Stop Auto';
btn.style.background = 'linear-gradient(45deg, #ff6b6b, #ff8e53)';
this.autoTrainInterval = setInterval(() => {
this.trainSteps(5);
}, 100);
}
}
updateDisplay() {
this.drawDataPoints();
this.drawSOM();
this.updateStats();
}
drawDataPoints() {
this.drawDataBackground();
this.dataPoints.forEach(point => {
const x = (point[0] / 255) * this.dataCanvas.width;
const y = (point[1] / 255) * this.dataCanvas.height;
this.dataCtx.fillStyle = `rgb(${Math.round(point[0])}, ${Math.round(point[1])}, ${Math.round(point[2])})`;
this.dataCtx.beginPath();
this.dataCtx.arc(x, y, 6, 0, 2 * Math.PI);
this.dataCtx.fill();
this.dataCtx.strokeStyle = '#333';
this.dataCtx.lineWidth = 1;
this.dataCtx.stroke();
});
}
drawSOM() {
const cellWidth = this.somCanvas.width / this.som.width;
const cellHeight = this.somCanvas.height / this.som.height;
for (let i = 0; i < this.som.height; i++) {
for (let j = 0; j < this.som.width; j++) {
const weights = this.som.neurons[i][j].weights;
const color = `rgb(${Math.round(weights[0])}, ${Math.round(weights[1])}, ${Math.round(weights[2])})`;
this.somCtx.fillStyle = color;
this.somCtx.fillRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
this.somCtx.strokeStyle = '#333';
this.somCtx.lineWidth = 1;
this.somCtx.strokeRect(j * cellWidth, i * cellHeight, cellWidth, cellHeight);
}
}
}
updateStats() {
document.getElementById('iterations').textContent = this.som.iteration;
document.getElementById('dataCount').textContent = this.dataPoints.length;
if (this.dataPoints.length > 0) {
let totalError = 0;
this.dataPoints.forEach(point => {
const { distance } = this.som.findBMU(point);
totalError += distance;
});
document.getElementById('quantError').textContent = (totalError / this.dataPoints.length).toFixed(2);
} else {
document.getElementById('quantError').textContent = 'N/A';
}
}
}
// Inizializza l'applicazione
window.addEventListener('load', () => {
new SOMVisualizer();
});
</script>
</body>
</html>