Machine Learning Basico con JavaScript
Descubre los fundamentos del Machine Learning y crea tu primera red neuronal directamente en el navegador usando brain.js.
1. Que es Machine Learning?
Machine Learning (Aprendizaje Automatico) es una rama de la inteligencia artificial donde los programas aprenden de datos en lugar de seguir instrucciones explicitas.
En vez de escribir reglas manualmente (if / else), le das al programa muchos ejemplos y el descubre los patrones por si mismo.
Programacion Tradicional:
Datos + Reglas → Resultado
1// Reglas escritas a mano2if (tieneOrejas && tieneBigotes) {3 return 'gato';4} else if (tieneOrejas && ladra) {5 return 'perro';6}7// Hay que pensar TODAS las reglas...Machine Learning:
Datos + Resultados → Reglas (aprendidas)
1// El modelo aprende las reglas solo2const datos = [3 { input: fotoGato, output: 'gato' },4 { input: fotoPerro, output: 'perro' },5 // ... miles de ejemplos6];7modelo.entrenar(datos);8modelo.predecir(fotoNueva); // 'gato'Dato: En el ejercicio de Barquitos, la IA (LLM de Groq) ya fue entrenada con millones de textos. Nosotros solo le pedimos que razone — pero detras hay un modelo de ML enorme que aprendio a "pensar" a partir de datos.
2. Tipos de Machine Learning
Existen tres grandes familias de ML. Haz clic en cada tipo para ver como funciona:
Tipos de Machine Learning
Supervisado
El modelo aprende de datos etiquetados
No supervisado
El modelo encuentra patrones sin etiquetas
Por refuerzo
El modelo aprende por prueba y error (recompensas)
Supervisado
Clasificacion de imagenes, deteccion de spam, prediccion de precios. Necesita datos etiquetados.
No supervisado
Agrupacion de clientes, deteccion de anomalias, compresion de datos. No necesita etiquetas.
Por refuerzo
Juegos (AlphaGo, ajedrez), robots, coches autonomos. Aprende por prueba y error.
3. Redes neuronales: como funciona un cerebro artificial
Una red neuronal es un modelo inspirado en el cerebro humano. Tiene capas de neuronas conectadas entre si por pesos (numeros que se ajustan al entrenar).
Capa de entrada
Recibe los datos (features). Una neurona por cada dato de entrada.
Capas ocultas
Procesan la informacion. Aqui se aprenden los patrones complejos.
Capa de salida
Da el resultado final (la prediccion del modelo).
Observa como los datos fluyen desde la entrada hasta la salida, activando neuronas en cada capa:
Red Neuronal en Accion
Observa como los datos fluyen por las capas de la red neuronal
La neurona por dentro — la formula real
Cada neurona de la red hace exactamente dos operaciones:
1// Paso 1: suma ponderada de todos los inputs + bias2z = w1*x1 + w2*x2 + w3*x3 + ... + wn*xn + bias3 4// Paso 2: funcion de activacion (ejemplo: sigmoid)5output = 1 / (1 + Math.exp(-z))6 7// El output de esta neurona es el input de las neuronas8// de la siguiente capa. Asi se propaga hacia adelante.4. El algoritmo — Backpropagation
La idea central — aprender reduciendo el error
Una red neuronal aprende ajustando miles de numeros internos llamados pesos. Al principio son aleatorios. En cada iteracion de entrenamiento, la red hace una prediccion, mide cuanto se equivoco, y ajusta los pesos un poco en la direccion que reduce ese error. Repitiendo esto miles de veces, los pesos convergen a valores que hacen predicciones correctas.
El ciclo de aprendizaje — 4 pasos:
1. FORWARD PASS — calcular la prediccion
El input atraviesa la red capa por capa. En cada neurona se calcula la suma ponderada de sus entradas mas un bias, y se aplica la funcion de activacion. El resultado de la ultima capa es la prediccion.
2. LOSS — medir el error
Se compara la prediccion con el output correcto del ejemplo. La diferencia se eleva al cuadrado: Error = (predicho - correcto)². Cuanto mayor, peor predice la red.
3. BACKWARD PASS — propagar el error
El error se propaga hacia atras por la red usando la regla de la cadena (calculo diferencial). Esto calcula el 'gradiente': cuanto contribuyo cada peso al error. Es el paso que le da el nombre al algoritmo.
4. UPDATE — ajustar los pesos
Cada peso se actualiza: peso = peso - learningRate * gradiente. El learningRate controla que tan grande es ese paso. Se repite para todos los ejemplos, una y otra vez, hasta que el error sea suficientemente pequeno.
Visualizacion: observa como el error baja y la precision sube con cada epoca de entrenamiento:
Proceso de Entrenamiento
Observa como el modelo mejora en cada epoca (iteracion)
Glosario de conceptos
| Concepto | Que significa en la practica |
|---|---|
| peso (weight) | Un numero que amplifica o atenua cada conexion. Se ajusta en cada iteracion. |
| bias | Un valor extra que se suma antes de la activacion. Permite desplazar la salida. |
| epoch | Una pasada completa por todos los ejemplos del dataset. |
| loss / error | Que tan equivocada esta la prediccion. El objetivo es minimizarlo. |
| gradiente | La direccion en la que el error sube. Vamos en la direccion contraria. |
| learningRate | El tamano del paso al ajustar pesos. Demasiado grande: oscila. Pequeno: lento. |
| overfitting | La red memoriza los ejemplos en vez de aprender el patron general. |
| underfitting | La red no tiene suficiente capacidad para aprender el patron. |
5. brain.js — instalacion y primeros pasos
Brain.js es una libreria de JavaScript que implementa redes neuronales de forma sencilla y accesible. Su principal ventaja es que funciona completamente en el navegador — no necesitas ningun servidor, ni Python, ni GPU dedicada. Con anadir una sola linea de CDN ya puedes entrenar una red y usarla en tu web.
Para que sirve?
- Clasificacion: detectar si un texto es positivo/negativo, si una imagen tiene X caracteristica...
- Prediccion: dado un conjunto de entradas, predecir un valor de salida.
- Reconocimiento de patrones: aprender a reconocer secuencias o configuraciones repetidas.
- Juegos e IA: entrenar un agente para tomar decisiones en un juego (como los Barquitos).
Instalacion:
1<!-- En el <head> de tu index.html -->2<script3 src="https://cdn.jsdelivr.net/npm/brain.js@2/dist/brain.js">4</script>5<!-- Tu script despues -->6<script src="src/js/red.js"></script>1// Si usas un bundler como Vite o Webpack:2npm install brain.js3import brain from 'brain.js';
Una vez anadido el CDN, tienes disponible el objeto global brain en toda tu pagina. Puedes comprobarlo escribiendo brain en la consola del navegador.
6. Crear y configurar la red
La clase principal de Brain.js es NeuralNetwork. Se instancia con un objeto de configuracion:
1const red = new brain.NeuralNetwork({2 hiddenLayers: [16, 8], // capas ocultas: 2 capas, 16 y 8 neuronas3 activation: 'sigmoid', // funcion de activacion4 learningRate: 0.01, // velocidad de ajuste5 momentum: 0.1, // inercia del gradiente6 binaryThresh: 0.5, // umbral para redondear a 0/17});Capas ocultas — hiddenLayers
El parametro hiddenLayers define la arquitectura interna de la red. Es un array donde cada numero representa una capa oculta con ese numero de neuronas. Por ejemplo, [16, 8] crea dos capas: la primera con 16 neuronas y la segunda con 8.
| hiddenLayers | Cuando usarlo |
|---|---|
| [4] | Problemas muy simples (XOR, clasificacion binaria basica) |
| [16, 8] | Clasificacion general — buen punto de partida |
| [32, 16] | Inputs medianos (25-50 valores), como la ventana 5x5 |
| [64, 32, 16] | Inputs grandes o patrones complejos |
Regla: Mas neuronas = mas capacidad para aprender patrones complejos, pero tambien mas tiempo de entrenamiento y mayor riesgo de overfitting. Empieza siempre con una configuracion pequena y aumenta si el error no baja.
Funciones de activacion
La funcion de activacion determina que valor produce cada neurona a partir de la suma ponderada de sus entradas:
| Activacion | Rango | Cuando usarla |
|---|---|---|
| sigmoid | 0 → 1 | Clasificacion binaria. La mas comun para empezar. |
| tanh | -1 → 1 | Cuando los outputs pueden ser negativos. |
| relu | 0 → ∞ | Capas profundas, entrena mas rapido. |
| leaky-relu | ~0 → ∞ | Variante de relu que evita neuronas 'muertas'. |
Tipos de red disponibles en Brain.js
| Clase | Descripcion | Cuando usarla |
|---|---|---|
| NeuralNetwork | Red feedforward estandar | Clasificacion y regresion general |
| NeuralNetworkGPU | Igual pero ejecutada en WebGL | Datasets grandes, entrenamiento lento |
| RNNTimeStep | Red recurrente simple | Series temporales, secuencias cortas |
| LSTMTimeStep | LSTM — memoria a largo plazo | Secuencias largas con dependencias |
7. Datos de entrenamiento
Formato basico
Cada ejemplo de entrenamiento es un objeto con dos propiedades: input (lo que la red recibe) y output (lo que debe predecir). El conjunto completo es un array de estos objetos:
1const datos = [2 { input: [0, 0], output: [0] }, // 0 XOR 0 = 03 { input: [0, 1], output: [1] }, // 0 XOR 1 = 14 { input: [1, 0], output: [1] }, // 1 XOR 0 = 15 { input: [1, 1], output: [0] }, // 1 XOR 1 = 06];Formatos de input disponibles
| Tipo | Ejemplo | Notas |
|---|---|---|
| Array de numeros | [0.5, 0.8, 0.1] | El mas comun. Valores entre 0 y 1. |
| Objeto clave-valor | { "temp": 0.7, "luz": 0.3 } | Mas legible. Brain.js lo convierte internamente. |
| String | 'hola mundo' | Para RNN/LSTM. Vectoriza automaticamente. |
| Array de arrays | [[1,0],[0,1],[1,1]] | Para TimeStep — secuencias temporales. |
Normalizacion — la regla mas importante
Los inputs SIEMPRE deben estar entre 0 y 1! Las redes neuronales no saben interpretar numeros grandes. Si pasas un valor como 1500 (temperatura en Kelvin, precio en euros...) la red se comportara mal o no aprendera. Antes de entrenar, normaliza todos tus valores al rango [0, 1].
1// Formula de normalizacion min-max:2const normalizar = (valor, min, max) =>3 (valor - min) / (max - min);4 5// Ejemplo: temperatura entre -10°C y 40°C6const tempNorm = normalizar(22, -10, 40); // → 0.647 8// Si tienes un array de datos:9const valores = [15, 22, 8, 35, 3];10const minVal = Math.min(...valores); // 311const maxVal = Math.max(...valores); // 3512const normalizados = valores.map(v => normalizar(v, minVal, maxVal));Equilibrio de clases
Si tienes un problema de clasificacion binaria (si/no, barco/no barco) y hay muchos mas ejemplos de una clase que de otra, la red aprendera a predecir siempre la clase mas frecuente. Eso se llama class imbalance:
1// MAL: 84 ejemplos 'no barco' y solo 16 'barco'2// La red aprende a decir siempre 'no barco' → 84% de acierto sin aprender nada3 4// BIEN: equilibrar antes de entrenar5function equilibrar(positivos, negativos) {6 const n = Math.min(positivos.length, negativos.length);7 return [8 ...positivos.slice(0, n),9 ...negativos.slice(0, n),10 ].sort(() => Math.random() - 0.5); // barajar11}8. Entrenar — train() y trainAsync()
train() — entrenamiento sincrono
El metodo train() recibe el array de datos y un objeto de opciones. Bloquea el hilo principal mientras entrena — usalo solo si el dataset es pequeno o en un contexto donde no importa bloquear la UI:
1const stats = red.train(datos, {2 iterations: 2000, // n° maximo de pasadas sobre todos los datos3 errorThresh: 0.005, // para automaticamente si el error baja de aqui4 learningRate: 0.01, // sobreescribe el del constructor5 log: true, // muestra el error en la consola cada logPeriod6 logPeriod: 100, // cada cuantas iteraciones muestra en consola7 callback: function(info) { // se llama cada callbackPeriod8 console.log('Error:', info.error);9 console.log('Iteracion:', info.iterations);10 },11 callbackPeriod: 50, // cada cuantas iteraciones llama al callback12});13 14// stats contiene el resultado final:15console.log(stats.error); // error final alcanzado16console.log(stats.iterations); // iteraciones que se ejecutarontrainAsync() — entrenamiento asincrono
La version asincrona es la recomendada para el navegador porque no bloquea la interfaz. El usuario puede ver el progreso en tiempo real mientras la red entrena:
1async function entrenarRed() {2 // Mostrar estado inicial en la UI3 document.getElementById('estado').textContent = 'Entrenando...';4 5 const stats = await red.trainAsync(datos, {6 iterations: 3000,7 errorThresh: 0.005,8 callback: function({ error, iterations }) {9 // Se llama cada 100 iteraciones — actualiza la UI10 const pct = Math.round((1 - error) * 100);11 document.getElementById('barra').style.width = pct + '%';12 document.getElementById('error').textContent =13 'Error: ' + error.toFixed(4);14 },15 callbackPeriod: 100,16 });17 18 document.getElementById('estado').textContent = '¡Entrenada!';19 habilitarJuego(); // activar boton de jugar20}Guardar y cargar el modelo entrenado
Entrenar puede tardar varios segundos. Para no repetirlo cada vez que el usuario carga la pagina, puedes guardar el modelo entrenado en localStorage:
1// ── GUARDAR ──────────────────────────────────────2const modeloJSON = red.toJSON();3localStorage.setItem('miModelo', JSON.stringify(modeloJSON));4 5// ── CARGAR ──────────────────────────────────────6const guardado = localStorage.getItem('miModelo');7if (guardado) {8 red.fromJSON(JSON.parse(guardado));9 console.log('Modelo cargado, listo para inferir');10} else {11 await entrenarRed(); // entrenar si no hay modelo guardado12}9. Inferir — run()
El metodo run() recibe un input con el mismo formato que los datos de entrenamiento y devuelve un objeto con las predicciones. La respuesta es siempre un numero entre 0 y 1:
1// Input como array:2const resultado = red.run([0, 1]);3console.log(resultado); // → { '0': 0.97 }4 5// Input como objeto con nombres:6const r = red.run({ temperatura: 0.7, humedad: 0.4 });7console.log(r.lluvia); // → 0.83 (probabilidad de lluvia)8 9// Convertir a booleano con un umbral:10const prob = red.run(input)[0];11const hayBarco = prob > 0.5; // true / false12 13// Encontrar el output mas probable entre varios:14const salidas = red.run(input);15const mejor = Object.entries(salidas)16 .sort((a, b) => b[1] - a[1])[0];17console.log(mejor[0], mejor[1]); // → 'deporte' 0.91run() es instantaneo
A diferencia de train() que puede tardar segundos, run() devuelve el resultado en microsegundos. Puedes llamarlo en cada frame de animacion, en cada clic del usuario o en cada turno de un juego sin ningun coste notable. Esto es lo que lo diferencia de una llamada a una API externa como Groq: la red vive en la memoria del navegador y no necesita conexion a internet.
Ejemplo completo — clasificacion de frases
1// Brain.js vectoriza strings automaticamente2const red = new brain.NeuralNetwork();3red.train([4 { input: 'el partido fue increible', output: { deporte: 1 } },5 { input: 'gol en el ultimo minuto', output: { deporte: 1 } },6 { input: 'la bolsa cae en picado', output: { economia: 1 } },7 { input: 'subida del euro esta tarde',output: { economia: 1 } },8]);9 10const r = red.run('gol de penalti en el descuento');11console.log(r);12// → { deporte: 0.91, economia: 0.04 }10. Problemas frecuentes y soluciones
Overfitting vs Underfitting
| Underfitting | Overfitting | |
|---|---|---|
| Error entrenamiento | Alto | Muy bajo (~0) |
| Error datos nuevos | Alto | Alto |
| Causa principal | Red muy pequena o pocas iteraciones | Muy pocos datos o demasiadas iteraciones |
| Solucion | Mas neuronas en hiddenLayers o mas iteraciones | Mas datos de entrenamiento o reducir iteraciones |
Diagnostico rapido
El error se queda estancado alto (> 0.2)
La red no converge. Prueba: aumentar hiddenLayers, cambiar la funcion de activacion, revisar que los datos estan normalizados entre 0 y 1, y aumentar el numero de iteraciones.
El error baja a 0 muy rapido pero la IA falla
Overfitting claro. La red memorizo los ejemplos en vez de aprender. Solucion: mas datos de entrenamiento (mas partidas simuladas, mas ejemplos variados) y/o reducir las iteraciones.
El error oscila — sube y baja sin converger
learningRate demasiado alto. Bajalo: prueba 0.005 o 0.001. Tambien puede ayudar aumentar el momentum a 0.2-0.3.
run() devuelve siempre el mismo valor
Los inputs probablemente no estan normalizados. Comprueba que todos los valores de entrada estan entre 0 y 1.
run() devuelve NaN
Algun input es Infinity, NaN o undefined. Anade validaciones antes de llamar a run() y verifica que la ventana 5x5 no contiene valores fuera de rango.
Trucos para mejores resultados
1// 1. Barajar los datos antes de entrenar2// evita que la red aprenda el orden de los ejemplos3datos.sort(() => Math.random() - 0.5);4 5// 2. Verificar configuracion con XOR primero6// si la red no aprende XOR, hay algo roto7const test = new brain.NeuralNetwork({ hiddenLayers: [4] });8test.train([{input:[0,1],output:[1]},{input:[1,1],output:[0]},...]);9console.log(test.run([0,1])[0] > 0.9); // debe ser true10 11// 3. Activar log para ver el error en consola12red.train(datos, { log: true, logPeriod: 100 });13// Abre DevTools → Console y observa como baja el error14 15// 4. Guardar modelo tras entrenar para no repetir16localStorage.setItem('modelo', JSON.stringify(red.toJSON()));11. Aplicacion: IA para Barquitos con brain.js
En vez de usar una API externa (Groq) para que la IA decida donde disparar, podemos entrenar una red neuronal local con brain.js que aprenda a jugar analizando el tablero. Ventaja: no necesita internet, es instantanea, y puedes ver como "piensa".
La idea — ventana local 5x5
Por que una ventana 5x5 y no el tablero completo?
Si le damos a la red el tablero completo (100 celdas de entrada), necesitaria procesar 100 inputs en vez de 25, lo que hace el entrenamiento mas lento y el modelo mas grande. Mas importante: la informacion relevante para decidir donde disparar es local. Si hay un tocado en la celda (5,3), las celdas relevantes son sus vecinas inmediatas, no lo que pasa en (0,0). La ventana 5x5 captura exactamente eso.
Esto imita el razonamiento humano: 'hay un tocado aqui, el barco continua en alguna direccion cercana'.
Encoding del tablero — convertir estados a numeros
| Estado de la celda | Valor numerico | Razonamiento |
|---|---|---|
| null (sin disparar) | 0.0 | Sin informacion — la red no sabe nada de esta celda |
| 'agua' (fallado) | 0.2 | Confirmado que no hay barco — senal debil pero util |
| 'tocado' | 1.0 | Hay barco — la senal mas fuerte posible |
| 'hundido' | 0.5 | Habia barco pero ya esta eliminado — valor medio |
| fuera del tablero | 0.3 | Borde — sin informacion, valor neutro |
extraerVentana() — los 25 inputs
1// Extrae los 25 valores de la ventana 5x5 centrada en (cx, cy)2function extraerVentana(tablero, cx, cy) {3 const ventana = [];4 for (let dy = -2; dy <= 2; dy++) {5 for (let dx = -2; dx <= 2; dx++) {6 const x = cx + dx;7 const y = cy + dy;8 // Si esta fuera del tablero, valor de borde9 if (x < 0 || x >= 10 || y < 0 || y >= 10) {10 ventana.push(0.3);11 } else {12 ventana.push(codificarCelda(tablero[y][x]));13 }14 }15 }16 return ventana; // array de exactamente 25 numeros17}18 19function codificarCelda(estado) {20 if (estado === 'tocado') return 1.0;21 if (estado === 'hundido') return 0.5;22 if (estado === 'agua') return 0.2;23 return 0.0; // null — sin disparar24}generarDatos() — simular partidas para entrenar
1function generarDatos(nPartidas) {2 const positivos = []; // celdas con barco3 const negativos = []; // celdas sin barco4 for (let i = 0; i < nPartidas; i++) {5 // Crear tablero con barcos aleatorios y simular disparos6 const tablero = simularPartida();7 for (let y = 0; y < 10; y++) {8 for (let x = 0; x < 10; x++) {9 const ejemplo = {10 input: extraerVentana(tablero, x, y),11 output: { esBarco: tablero[y][x] === 'barco' ? 1 : 0 }12 };13 tablero[y][x] === 'barco'14 ? positivos.push(ejemplo)15 : negativos.push(ejemplo);16 }17 }18 }19 // Equilibrar: mismo numero de positivos y negativos20 const n = positivos.length;21 const ds = [...positivos, ...negativos.slice(0, n)];22 return ds.sort(() => Math.random() - 0.5); // barajar23}elegirDisparo() — inferencia en cada turno
1// La IA evalua todas las celdas no disparadas2// y dispara donde la red predice mayor probabilidad de barco3function elegirDisparo(tablero) {4 let mejorCelda = null;5 let mejorScore = -1;6 for (let y = 0; y < 10; y++) {7 for (let x = 0; x < 10; x++) {8 if (tablero[y][x] !== null) continue; // ya disparada9 const ventana = extraerVentana(tablero, x, y);10 const prob = red.run(ventana).esBarco; // 0 a 111 if (prob > mejorScore) {12 mejorScore = prob;13 mejorCelda = { x, y };14 }15 }16 }17 return mejorCelda; // { x, y } con mayor probabilidad18}Heatmap — visualizar lo que piensa la red
El heatmap colorea cada celda del tablero enemigo segun la probabilidad que predice la red. Es la visualizacion mas impactante del ejercicio: el jugador puede ver literalmente lo que 'piensa' la IA.
1function pintarHeatmap(tablero) {2 document.querySelectorAll('#tablero-ia .celda')3 .forEach(celda => {4 const x = parseInt(celda.dataset.x);5 const y = parseInt(celda.dataset.y);6 if (tablero[y][x] !== null) return; // ya disparada7 const ventana = extraerVentana(tablero, x, y);8 const prob = red.run(ventana).esBarco;9 // Interpolar color: azul tenue (p=0) → rojo intenso (p=1)10 const r = Math.round(prob * 239);11 const b = Math.round((1 - prob) * 92);12 const a = 0.2 + prob * 0.7;13 celda.style.backgroundColor = 'rgba(' + r + ', 30, ' + b + ', ' + a + ')';14 });15}Llama a pintarHeatmap() despues de cada turno (tanto del jugador como de la IA) para que el mapa se actualice en tiempo real. Observa como la IA concentra el color rojo alrededor de los tocados — esta aprendiendo a cazar barcos.
12. brain.js vs APIs de IA — cuando usar cada una
brain.js (red local)
- + Sin internet, sin API key
- + run() instantaneo (microsegundos)
- + Gratis, sin limites de uso
- + Puedes visualizar como piensa (heatmap)
- - Problemas simples (numeros, clasificacion)
- - Necesitas generar datos de entrenamiento
API de IA (Groq, OpenAI...)
- + Razonamiento complejo, texto, dialogo
- + No necesitas entrenar nada
- + Modelos enormes ya entrenados
- - Necesita internet y API key
- - Latencia por llamada HTTP (~1-3s)
- - Limites de uso / coste
En resumen: brain.js te da las bases para entender ML y es perfecta para problemas con inputs numericos. Las APIs de IA son el mismo concepto pero a escala industrial, ideales para lenguaje natural. Ambas usan redes neuronales — solo cambia el tamano.
Resumen
Machine Learning
Programas que aprenden de datos
Supervisado, no supervisado, por refuerzo
Red neuronal
Capas de neuronas con pesos ajustables
Forward → Loss → Backward → Update
Entrenamiento
Datos normalizados + epocas
red.train(datos, opciones) Inferencia
Prediccion instantanea en el navegador
red.run(input) → prediccion Conexion: brain.js corre en el navegador para problemas con inputs numericos (como la ventana 5x5 de Barquitos). Para IA avanzada con lenguaje natural, usamos APIs de modelos grandes con fetch + async/await.