Voltar para o Blog

TensorFlow.js: Machine Learning Diretamente no Browser Com JavaScript

Ola HaWkers, e se voce pudesse executar modelos de machine learning diretamente no navegador do usuario, sem precisar de servidor? Em 2025, isso nao e mais futuro - e realidade, e TensorFlow.js e a ferramenta que torna isso possivel.

Neste guia, vamos explorar como usar TensorFlow.js para criar aplicacoes inteligentes que rodam 100% no client-side.

Por Que Machine Learning no Browser

Executar ML no browser oferece vantagens unicas que muitos desenvolvedores ainda nao exploram.

Beneficios

Privacidade:

  • Dados nunca saem do dispositivo do usuario
  • Sem preocupacoes com LGPD/GDPR para processamento
  • Ideal para aplicacoes sensiveis

Performance:

  • Sem latencia de rede para inferencia
  • Usa GPU do dispositivo via WebGL
  • Respostas em tempo real

Custo:

  • Zero custo de servidor para inferencia
  • Escala ilimitada sem aumentar infraestrutura
  • Democratiza acesso a ML

Experiencia:

  • Funciona offline
  • Interatividade em tempo real
  • Nao depende de conexao

🧠 Contexto: TensorFlow.js e usado por milhoes de desenvolvedores e empresas como Google, Spotify e Airbnb para ML no browser.

Primeiros Passos Com TensorFlow.js

Vamos configurar um projeto e entender os conceitos basicos.

Instalacao

# Via NPM
npm install @tensorflow/tfjs

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

Conceitos Fundamentais

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

// Tensors: Arrays multidimensionais
const tensor1D = tf.tensor([1, 2, 3, 4]);
const tensor2D = tf.tensor([[1, 2], [3, 4]]);

// Shapes: Dimensoes do tensor
console.log(tensor2D.shape); // [2, 2]

// Operacoes basicas
const a = tf.tensor([1, 2, 3]);
const b = tf.tensor([4, 5, 6]);

const sum = a.add(b);      // [5, 7, 9]
const product = a.mul(b);  // [4, 10, 18]
const mean = a.mean();     // 2

// IMPORTANTE: Limpar memoria
tensor1D.dispose();
tensor2D.dispose();
// Ou usar tf.tidy() para limpeza automatica

Gerenciamento de Memoria

// tf.tidy() limpa tensors intermediarios automaticamente
const result = tf.tidy(() => {
  const a = tf.tensor([1, 2, 3]);
  const b = tf.tensor([4, 5, 6]);
  const c = a.add(b);
  const d = c.mul(tf.scalar(2));
  return d; // Apenas d e mantido
});

// Verificar uso de memoria
console.log(tf.memory());
// { numTensors: X, numDataBuffers: Y, numBytes: Z }

Usando Modelos Pre-Treinados

A forma mais rapida de comecar e usar modelos ja treinados.

Classificacao de Imagens Com MobileNet

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

async function classifyImage() {
  // Carrega o modelo (primeira vez pode demorar)
  const model = await mobilenet.load();

  // Elemento de imagem do DOM
  const img = document.getElementById('myImage');

  // Classifica a imagem
  const predictions = await model.classify(img);

  console.log(predictions);
  // [
  //   { className: 'golden retriever', probability: 0.89 },
  //   { className: 'Labrador retriever', probability: 0.08 },
  //   { className: 'cocker spaniel', probability: 0.02 }
  // ]
}

Deteccao de Objetos Com COCO-SSD

import * as cocoSsd from '@tensorflow-models/coco-ssd';

async function detectObjects() {
  const model = await cocoSsd.load();
  const img = document.getElementById('myImage');

  const predictions = await model.detect(img);

  predictions.forEach(prediction => {
    console.log(prediction);
    // {
    //   bbox: [x, y, width, height],
    //   class: 'person',
    //   score: 0.95
    // }
  });

  // Desenhar bounding boxes
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');

  predictions.forEach(pred => {
    ctx.strokeStyle = '#00FF00';
    ctx.lineWidth = 2;
    ctx.strokeRect(...pred.bbox);
    ctx.fillStyle = '#00FF00';
    ctx.fillText(
      `${pred.class} (${Math.round(pred.score * 100)}%)`,
      pred.bbox[0],
      pred.bbox[1] - 5
    );
  });
}

Deteccao de Pose Com PoseNet/MoveNet

Um dos casos de uso mais populares e a deteccao de pose corporal.

Implementacao Basica

import * as poseDetection from '@tensorflow-models/pose-detection';

async function detectPose() {
  // Configura o detector
  const detectorConfig = {
    modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
  };

  const detector = await poseDetection.createDetector(
    poseDetection.SupportedModels.MoveNet,
    detectorConfig
  );

  // Usa video da webcam
  const video = document.getElementById('webcam');

  async function detect() {
    const poses = await detector.estimatePoses(video);

    if (poses.length > 0) {
      const pose = poses[0];

      // Keypoints do corpo
      pose.keypoints.forEach(keypoint => {
        if (keypoint.score > 0.5) {
          console.log(`${keypoint.name}: (${keypoint.x}, ${keypoint.y})`);
          // nose: (320, 180)
          // left_eye: (310, 170)
          // right_shoulder: (280, 250)
          // etc.
        }
      });
    }

    requestAnimationFrame(detect);
  }

  detect();
}

Aplicacao Pratica: Contador de Exercicios

class ExerciseCounter {
  constructor() {
    this.squatCount = 0;
    this.isDown = false;
  }

  detectSquat(pose) {
    const leftHip = pose.keypoints.find(k => k.name === 'left_hip');
    const leftKnee = pose.keypoints.find(k => k.name === 'left_knee');
    const leftAnkle = pose.keypoints.find(k => k.name === 'left_ankle');

    if (!leftHip || !leftKnee || !leftAnkle) return;

    // Calcula angulo do joelho
    const angle = this.calculateAngle(
      leftHip,
      leftKnee,
      leftAnkle
    );

    // Logica de contagem
    if (angle < 90 && !this.isDown) {
      this.isDown = true;
    } else if (angle > 160 && this.isDown) {
      this.isDown = false;
      this.squatCount++;
      this.onSquatComplete(this.squatCount);
    }
  }

  calculateAngle(a, b, c) {
    const radians = Math.atan2(c.y - b.y, c.x - b.x) -
                    Math.atan2(a.y - b.y, a.x - b.x);
    let angle = Math.abs(radians * 180 / Math.PI);
    if (angle > 180) angle = 360 - angle;
    return angle;
  }

  onSquatComplete(count) {
    console.log(`Agachamento #${count} completo!`);
  }
}

Reconhecimento de Gestos de Mao

Outro caso de uso poderoso e detectar gestos das maos.

import * as handPoseDetection from '@tensorflow-models/hand-pose-detection';

async function detectHands() {
  const model = handPoseDetection.SupportedModels.MediaPipeHands;
  const detectorConfig = {
    runtime: 'tfjs',
    maxHands: 2
  };

  const detector = await handPoseDetection.createDetector(
    model,
    detectorConfig
  );

  const video = document.getElementById('webcam');

  async function detect() {
    const hands = await detector.estimateHands(video);

    hands.forEach(hand => {
      // 21 keypoints por mao
      const landmarks = hand.keypoints;

      // Detectar gesto de "joinha"
      if (isThumbsUp(landmarks)) {
        console.log('👍 Joinha detectado!');
      }

      // Detectar gesto de "paz"
      if (isPeaceSign(landmarks)) {
        console.log('✌️ Paz detectado!');
      }
    });

    requestAnimationFrame(detect);
  }

  detect();
}

function isThumbsUp(landmarks) {
  const thumbTip = landmarks[4];
  const thumbBase = landmarks[2];
  const indexTip = landmarks[8];

  // Polegar para cima e outros dedos fechados
  const thumbUp = thumbTip.y < thumbBase.y;
  const indexDown = indexTip.y > landmarks[5].y;

  return thumbUp && indexDown;
}

function isPeaceSign(landmarks) {
  const indexTip = landmarks[8];
  const middleTip = landmarks[12];
  const ringTip = landmarks[16];

  // Indicador e medio esticados, outros fechados
  const indexUp = indexTip.y < landmarks[5].y;
  const middleUp = middleTip.y < landmarks[9].y;
  const ringDown = ringTip.y > landmarks[13].y;

  return indexUp && middleUp && ringDown;
}

Classificacao de Texto Com Universal Sentence Encoder

TensorFlow.js nao e so para imagens - funciona otimo com texto.

import * as use from '@tensorflow-models/universal-sentence-encoder';

async function analyzeText() {
  const model = await use.load();

  // Embeddings de texto
  const sentences = [
    'Adorei este produto, muito bom!',
    'Pessimo, nao recomendo.',
    'O servico foi ok, nada excepcional.'
  ];

  const embeddings = await model.embed(sentences);
  console.log(embeddings.shape); // [3, 512]

  // Classificacao de sentimento simples
  const positiveRef = await model.embed(['Excelente, maravilhoso, perfeito']);
  const negativeRef = await model.embed(['Terrivel, horrivel, pessimo']);

  sentences.forEach(async (sentence, i) => {
    const sentenceEmbed = embeddings.slice([i, 0], [1, 512]);

    const positiveSim = cosineSimilarity(sentenceEmbed, positiveRef);
    const negativeSim = cosineSimilarity(sentenceEmbed, negativeRef);

    const sentiment = positiveSim > negativeSim ? 'Positivo' : 'Negativo';
    console.log(`"${sentence}" -> ${sentiment}`);
  });
}

function cosineSimilarity(a, b) {
  const dotProduct = tf.sum(tf.mul(a, b));
  const normA = tf.norm(a);
  const normB = tf.norm(b);
  return dotProduct.div(normA.mul(normB)).dataSync()[0];
}

Treinando Seu Proprio Modelo

Alem de usar modelos pre-treinados, voce pode criar os seus.

Modelo Simples de Classificacao

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

async function trainModel() {
  // Dados de exemplo: XOR problem
  const xs = tf.tensor2d([[0, 0], [0, 1], [1, 0], [1, 1]]);
  const ys = tf.tensor2d([[0], [1], [1], [0]]);

  // Arquitetura do modelo
  const model = tf.sequential({
    layers: [
      tf.layers.dense({
        inputShape: [2],
        units: 8,
        activation: 'relu'
      }),
      tf.layers.dense({
        units: 4,
        activation: 'relu'
      }),
      tf.layers.dense({
        units: 1,
        activation: 'sigmoid'
      })
    ]
  });

  // Compilar
  model.compile({
    optimizer: tf.train.adam(0.1),
    loss: 'binaryCrossentropy',
    metrics: ['accuracy']
  });

  // Treinar
  await model.fit(xs, ys, {
    epochs: 100,
    callbacks: {
      onEpochEnd: (epoch, logs) => {
        console.log(`Epoch ${epoch}: loss = ${logs.loss.toFixed(4)}`);
      }
    }
  });

  // Testar
  const prediction = model.predict(tf.tensor2d([[0, 1]]));
  prediction.print(); // ~0.95 (proximo de 1)

  // Salvar modelo
  await model.save('localstorage://meu-modelo');
}

// Carregar modelo salvo
async function loadModel() {
  const model = await tf.loadLayersModel('localstorage://meu-modelo');
  return model;
}

Transfer Learning Com MobileNet

async function customImageClassifier() {
  // Carrega MobileNet sem a camada final
  const mobilenet = await tf.loadLayersModel(
    'https://tfhub.dev/google/tfjs-model/mobilenet_v2_100_224/feature_vector/3/default/1',
    { fromTFHub: true }
  );

  // Congela pesos do MobileNet
  mobilenet.trainable = false;

  // Adiciona camadas customizadas
  const model = tf.sequential();
  model.add(mobilenet);
  model.add(tf.layers.dense({
    units: 128,
    activation: 'relu'
  }));
  model.add(tf.layers.dropout({ rate: 0.5 }));
  model.add(tf.layers.dense({
    units: 3, // Numero de classes
    activation: 'softmax'
  }));

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

  return model;
}

Otimizacao de Performance

Para ML no browser funcionar bem, otimizacao e crucial.

Backend WebGL vs CPU

// Verificar backend atual
console.log(tf.getBackend()); // 'webgl' ou 'cpu'

// Forcar WebGL (mais rapido)
await tf.setBackend('webgl');

// Ou usar WebGPU (mais rapido ainda, se disponivel)
if (navigator.gpu) {
  await tf.setBackend('webgpu');
}

Quantizacao de Modelos

// Ao converter modelo Python para TensorFlow.js
// Use quantizacao para reduzir tamanho

// tensorflowjs_converter \
//   --input_format=tf_saved_model \
//   --output_format=tfjs_graph_model \
//   --quantize_float16=* \
//   ./saved_model \
//   ./tfjs_model

// Resultado: modelo 50% menor, performance similar

Warm-up do Modelo

async function warmupModel(model) {
  // Primeira inferencia e mais lenta
  // Faca "warm-up" antes de uso real

  const dummyInput = tf.zeros([1, 224, 224, 3]);
  const warmupResult = model.predict(dummyInput);
  warmupResult.dispose();
  dummyInput.dispose();

  console.log('Modelo aquecido e pronto!');
}

Aplicacao Completa: Classificador em Tempo Real

Vamos juntar tudo em uma aplicacao funcional.

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

class RealTimeClassifier {
  constructor(videoElement, resultsElement) {
    this.video = videoElement;
    this.results = resultsElement;
    this.model = null;
    this.isRunning = false;
  }

  async init() {
    // Configura WebGL
    await tf.setBackend('webgl');

    // Carrega modelo
    console.log('Carregando modelo...');
    this.model = await mobilenet.load({
      version: 2,
      alpha: 1.0
    });

    // Aquece modelo
    await this.warmup();

    // Inicia webcam
    await this.setupCamera();

    console.log('Pronto!');
  }

  async setupCamera() {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'environment' }
    });
    this.video.srcObject = stream;
    await new Promise(resolve => {
      this.video.onloadedmetadata = resolve;
    });
    this.video.play();
  }

  async warmup() {
    const dummyInput = tf.zeros([1, 224, 224, 3]);
    const result = this.model.infer(dummyInput, 'conv_preds');
    result.dispose();
    dummyInput.dispose();
  }

  start() {
    this.isRunning = true;
    this.detect();
  }

  stop() {
    this.isRunning = false;
  }

  async detect() {
    if (!this.isRunning) return;

    const predictions = await this.model.classify(this.video, 3);

    this.displayResults(predictions);

    requestAnimationFrame(() => this.detect());
  }

  displayResults(predictions) {
    this.results.innerHTML = predictions
      .map(p => `
        <div class="prediction">
          <span class="label">${p.className}</span>
          <span class="score">${(p.probability * 100).toFixed(1)}%</span>
          <div class="bar" style="width: ${p.probability * 100}%"></div>
        </div>
      `)
      .join('');
  }
}

// Uso
const classifier = new RealTimeClassifier(
  document.getElementById('video'),
  document.getElementById('results')
);

document.getElementById('startBtn').onclick = async () => {
  await classifier.init();
  classifier.start();
};

Conclusao

TensorFlow.js democratiza o acesso a machine learning, permitindo que qualquer desenvolvedor JavaScript crie aplicacoes inteligentes sem precisar de infraestrutura de servidor ou conhecimento profundo de Python.

Em 2025, com GPUs mais poderosas nos dispositivos e modelos cada vez mais otimizados, o browser se torna uma plataforma legitima para ML - oferecendo privacidade, performance e escala sem custo de infraestrutura.

Se voce quer explorar outras formas de melhorar performance no browser, recomendo dar uma olhada no artigo WebAssembly e JavaScript: Performance Extrema onde exploramos como combinar essas tecnologias.

Bora pra cima! 🦅

Comentários (0)

Esse artigo ainda não possui comentários 😢. Seja o primeiro! 🚀🦅

Adicionar comentário