Volver al blog

JavaScript y Machine Learning: TensorFlow.js Está Democratizando la IA

Hola HaWkers, imagina entrenar un modelo de Machine Learning y ejecutarlo directamente en el navegador, sin backend Python, sin servidores pesados. ¿Parece futurista? Es realidad con TensorFlow.js.

Tú, desarrollador JavaScript, ahora tienes acceso a capacidades de IA que antes eran exclusivas de científicos de datos. Vamos a explorar cómo funciona, casos de uso reales y código práctico que puedes ejecutar hoy.

¿Por Qué Machine Learning en el Navegador?

Ventajas del TensorFlow.js:

  1. Privacidad: Datos nunca salen del dispositivo del usuario
  2. Latencia cero: Sin roundtrip para servidor
  3. Costo: Procesamiento distribuido en los clientes (no tu servidor)
  4. Accesibilidad: Cualquier dev JavaScript puede comenzar
  5. Multiplataforma: Browser, Node.js, React Native, Electron

Casos de uso reales en 2025:

  • Filtros de cámara en tiempo real (Instagram, Snapchat)
  • Transcripción de audio offline (Zoom, Meet)
  • Detección de fraude en pagos
  • Recomendaciones personalizadas sin enviar datos
  • Accesibilidad (subtítulos, traducción en tiempo real)

Setup Básico: Tu Primer Modelo

Vamos a crear un detector de sentimiento de texto — clasifica si una frase es positiva o negativa.

Instalación:

npm install @tensorflow/tfjs
# O via CDN:
# <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>

Modelo preentrenado simple:

import * as tf from '@tensorflow/tfjs';

// 1. Crear modelo secuencial simple
const model = tf.sequential({
  layers: [
    // Input: Texto convertido en números (embedding)
    tf.layers.dense({ inputShape: [100], units: 16, activation: 'relu' }),

    // Hidden layer
    tf.layers.dense({ units: 8, activation: 'relu' }),

    // Output: 2 clases (positivo/negativo)
    tf.layers.dense({ units: 2, activation: 'softmax' })
  ]
});

// 2. Compilar modelo
model.compile({
  optimizer: tf.train.adam(0.001),
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

// 3. Entrenar con datos
async function trainModel(reviews, labels) {
  // reviews: array de textos
  // labels: array de [0, 1] o [1, 0]

  const xs = tf.tensor2d(reviews);
  const ys = tf.tensor2d(labels);

  await model.fit(xs, ys, {
    epochs: 50,
    batchSize: 32,
    validationSplit: 0.2,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        console.log(`Epoch ${epoch}: loss = ${logs.loss.toFixed(4)}`);
      }
    }
  });

  console.log('✓ ¡Modelo entrenado!');
}

// 4. Hacer predicciones
function predict(text) {
  // Convertir texto en vector numérico (simplificado)
  const vector = textToVector(text);
  const input = tf.tensor2d([vector]);

  const prediction = model.predict(input);
  const scores = prediction.dataSync();

  return {
    positive: scores[0],
    negative: scores[1],
    sentiment: scores[0] > scores[1] ? 'Positivo' : 'Negativo'
  };
}

// Ejemplo de uso
const result = predict('¡Me encantó este producto, muy bueno!');
console.log(result);
// { positive: 0.89, negative: 0.11, sentiment: 'Positivo' }

AI Technology

Caso Práctico: Reconocimiento de Imágenes en Tiempo Real

Vamos a usar un modelo preentrenado (MobileNet) para clasificar imágenes de la webcam.

import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';

class ImageClassifier {
  constructor() {
    this.model = null;
    this.video = null;
  }

  async init() {
    // 1. Cargar modelo (download automático)
    console.log('Cargando MobileNet...');
    this.model = await mobilenet.load();
    console.log('✓ ¡Modelo cargado!');

    // 2. Configurar webcam
    this.video = document.getElementById('webcam');

    const stream = await navigator.mediaDevices.getUserMedia({
      video: { width: 640, height: 480 }
    });

    this.video.srcObject = stream;
    await new Promise(resolve => {
      this.video.onloadedmetadata = resolve;
    });

    this.video.play();
  }

  async classify() {
    if (!this.model || !this.video) return;

    // 3. Hacer predicción en tiempo real
    const predictions = await this.model.classify(this.video);

    return predictions.map(p => ({
      class: p.className,
      probability: (p.probability * 100).toFixed(2) + '%'
    }));
  }

  async classifyLoop() {
    const resultsDiv = document.getElementById('results');

    setInterval(async () => {
      const predictions = await this.classify();

      resultsDiv.innerHTML = predictions
        .map(p => `<p>${p.class}: ${p.probability}</p>`)
        .join('');
    }, 1000); // Clasifica cada 1 segundo
  }
}

// HTML correspondiente
/*
<video id="webcam" autoplay></video>
<div id="results"></div>

<script>
  const classifier = new ImageClassifier();
  classifier.init().then(() => {
    classifier.classifyLoop();
  });
</script>
*/

Resultado: ¡Aplicación identifica objetos en la cámara sin enviar datos para servidor!

Transfer Learning: Entrenando Tu Propio Clasificador

¿Y si quieres clasificar cosas específicas? Usa Transfer Learning — toma modelo preentrenado y ajústalo para tu caso.

Ejemplo: Clasificador de poses (ejercicios físicos)

import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';

class PoseClassifier {
  constructor() {
    this.baseModel = null;
    this.model = null;
    this.classes = ['sentadilla', 'flexion', 'plancha', 'salto'];
  }

  async loadBaseModel() {
    // Cargar MobileNet sin la última capa
    const mobilenet = await tf.loadLayersModel(
      'https://storage.googleapis.com/tfjs-models/tfjs/mobilenet_v1_0.25_224/model.json'
    );

    // Remover última capa (clasificación genérica)
    const layer = mobilenet.getLayer('conv_pw_13_relu');
    this.baseModel = tf.model({
      inputs: mobilenet.inputs,
      outputs: layer.output
    });
  }

  createCustomModel() {
    // Añadir nuevas capas para tus clases
    const model = tf.sequential({
      layers: [
        tf.layers.flatten({
          inputShape: this.baseModel.outputs[0].shape.slice(1)
        }),
        tf.layers.dense({
          units: 128,
          activation: 'relu',
          kernelInitializer: 'varianceScaling'
        }),
        tf.layers.dropout({ rate: 0.5 }),
        tf.layers.dense({
          units: this.classes.length,
          activation: 'softmax'
        })
      ]
    });

    model.compile({
      optimizer: tf.train.adam(0.0001),
      loss: 'categoricalCrossentropy',
      metrics: ['accuracy']
    });

    this.model = model;
  }

  async train(images, labels) {
    // images: array de elementos <img>
    // labels: array de índices [0, 1, 2, 3]

    // Extraer features con modelo base
    const features = tf.tidy(() => {
      const imageTensors = images.map(img => {
        return tf.browser.fromPixels(img)
          .resizeBilinear([224, 224])
          .toFloat()
          .div(127.5)
          .sub(1);
      });

      const batched = tf.stack(imageTensors);
      return this.baseModel.predict(batched);
    });

    // Convertir labels para one-hot
    const ys = tf.oneHot(tf.tensor1d(labels, 'int32'), this.classes.length);

    // Entrenar apenas las nuevas capas
    await this.model.fit(features, ys, {
      epochs: 20,
      batchSize: 32,
      validationSplit: 0.2,
      callbacks: {
        onEpochEnd: (epoch, logs) => {
          console.log(
            `Epoch ${epoch + 1}: ` +
            `loss = ${logs.loss.toFixed(4)}, ` +
            `accuracy = ${logs.acc.toFixed(4)}`
          );
        }
      }
    });

    features.dispose();
    ys.dispose();
  }

  async predict(imageElement) {
    const processed = tf.tidy(() => {
      const img = tf.browser.fromPixels(imageElement)
        .resizeBilinear([224, 224])
        .toFloat()
        .div(127.5)
        .sub(1)
        .expandDims(0);

      const features = this.baseModel.predict(img);
      return this.model.predict(features);
    });

    const probabilities = await processed.data();
    const classIndex = processed.argMax(-1).dataSync()[0];

    processed.dispose();

    return {
      class: this.classes[classIndex],
      confidence: (probabilities[classIndex] * 100).toFixed(2) + '%',
      allProbabilities: this.classes.map((name, i) => ({
        name,
        probability: (probabilities[i] * 100).toFixed(2) + '%'
      }))
    };
  }
}

// Uso
const classifier = new PoseClassifier();

await classifier.loadBaseModel();
classifier.createCustomModel();

// Recolectar datos (tomar fotos de cada ejercicio)
const sentadillaImgs = [img1, img2, img3]; // 3 ejemplos
const flexionImgs = [img4, img5, img6];
// ... más ejemplos

const allImages = [...sentadillaImgs, ...flexionImgs, ...];
const labels = [0, 0, 0, 1, 1, 1, ...]; // Índices de las clases

await classifier.train(allImages, labels);

// Hacer predicción en nueva imagen
const result = await classifier.predict(newImageElement);
console.log(result);
// {
//   class: 'sentadilla',
//   confidence: '94.32%',
//   allProbabilities: [...]
// }

Performance: Aceleración GPU

TensorFlow.js usa WebGL para computación en la GPU — crucial para modelos grandes.

Optimizaciones importantes:

// 1. Usar tf.tidy() para gestionar memoria
function processData(input) {
  return tf.tidy(() => {
    // Todos los tensors creados aquí son liberados automáticamente
    const normalized = input.div(255);
    const reshaped = normalized.reshape([1, 224, 224, 3]);
    return model.predict(reshaped);
  });
}

// 2. Batch processing para múltiples imágenes
async function classifyMultiple(images) {
  const batch = tf.tidy(() => {
    const tensors = images.map(img =>
      tf.browser.fromPixels(img).resizeBilinear([224, 224])
    );
    return tf.stack(tensors);
  });

  const predictions = await model.predict(batch).array();

  batch.dispose();

  return predictions;
}

// 3. Cuantización para modelos menores
async function loadQuantizedModel() {
  // Modelo con 4x menos peso
  const model = await tf.loadGraphModel(
    'https://example.com/model_quantized/model.json'
  );

  return model;
}

// 4. Web Workers para no bloquear UI
// worker.js
importScripts('https://cdn.jsdelivr.net/npm/@tensorflow/tfjs');

self.onmessage = async (e) => {
  const { imageData } = e.data;

  const tensor = tf.browser.fromPixels(imageData);
  const prediction = await model.predict(tensor);

  self.postMessage({ prediction: await prediction.array() });

  tensor.dispose();
  prediction.dispose();
};

Casos de Uso Avanzados

1. Detección de Poses (PoseNet):

import * as posenet from '@tensorflow-models/posenet';

const net = await posenet.load({
  architecture: 'MobileNetV1',
  outputStride: 16,
  inputResolution: { width: 640, height: 480 },
  multiplier: 0.75
});

const pose = await net.estimateSinglePose(video);

// pose.keypoints contiene 17 puntos (nariz, ojos, hombros, etc)
const nose = pose.keypoints.find(kp => kp.part === 'nose');
console.log(`Nariz en: x=${nose.position.x}, y=${nose.position.y}`);

2. Segmentación de Personas (BodyPix):

import * as bodyPix from '@tensorflow-models/body-pix';

const net = await bodyPix.load();

const segmentation = await net.segmentPerson(video);

// Aplicar blur en el fondo (tipo Zoom)
const foregroundColor = { r: 0, g: 0, b: 0, a: 0 };
const backgroundColor = { r: 0, g: 0, b: 0, a: 255 };
const backgroundBlurAmount = 15;

const backgroundBlur = await bodyPix.blurBodyPart(
  canvas,
  video,
  segmentation,
  backgroundBlurAmount,
  foregroundColor,
  backgroundColor
);

3. Reconocimiento de Voz:

import * as speechCommands from '@tensorflow-models/speech-commands';

const recognizer = speechCommands.create('BROWSER_FFT');

await recognizer.ensureModelLoaded();

// Escuchar comandos
recognizer.listen(result => {
  const scores = result.scores;
  const maxScore = Math.max(...scores);
  const command = recognizer.wordLabels()[scores.indexOf(maxScore)];

  console.log(`Comando detectado: ${command} (${(maxScore * 100).toFixed(2)}%)`);
}, {
  includeSpectrogram: true,
  probabilityThreshold: 0.75
});

Limitaciones y Consideraciones

Lo que TensorFlow.js NO es ideal para:

  • Entrenamiento de modelos gigantes (GPT, DALL-E)
  • Procesamiento batch masivo (millones de imágenes)
  • Investigación de ML de punta

Ideal para:

  • Inferencia en tiempo real en el cliente
  • Modelos pequeños/medianos (<50MB)
  • Prototipos rápidos
  • Aplicaciones que necesitan privacidad

Trade-offs:

  • Performance: ~2-5x más lento que Python/C++ nativo
  • Tamaño: Modelos necesitan ser ligeros para web
  • Compatibilidad: No todas las APIs Python están disponibles

El Futuro del ML en JavaScript

Tendencias 2025:

  • WebGPU para performance aún mejor
  • Modelos cada vez menores (técnicas de compresión)
  • Edge computing (ML en IoT devices con JS)
  • Integración con frameworks (React, Vue components con ML)

Si quieres explorar más sobre cómo JavaScript se está expandiendo a áreas innovadoras, ve JavaScript y el Mundo del IoT: Integrando la Web al Ambiente Físico.

¡Vamos a por ello! 🦅

¿Quieres Profundizar Tus Conocimientos en JavaScript?

Este artículo cubrió Machine Learning con JavaScript, pero hay mucho más para explorar en el mundo del desarrollo moderno.

Desarrolladores que invierten en conocimiento sólido y estructurado tienden a tener más oportunidades en el mercado.

Material de Estudio Completo

Si quieres dominar JavaScript del básico al avanzado, preparé una guía completa:

Opciones de inversión:

  • $9.90 USD (pago único)

Conocer la Guía JavaScript

Material actualizado con las mejores prácticas del mercado

Comentarios (0)

Este artículo aún no tiene comentarios 😢. ¡Sé el primero! 🚀🦅

Añadir comentarios