Skip to main content

TensorFlow.js — Rodando IA direto no browser

· 9 min read
Bruno Carneiro
Fundador da @TautornTech

Machine learning sempre pareceu algo distante do frontend — Python, servidores potentes, GPUs caríssimas. Mas isso mudou. Hoje é possível rodar um modelo de IA treinado diretamente no browser, com zero backend, usando JavaScript puro e a GPU do próprio usuário.

Este artigo explica como isso funciona na prática, usando o TensorFlow.js como base — e como apliquei isso em um projeto chamado SeeFood, um classificador que resolve o maior problema da humanidade: saber se o que você está prestes a comer é um hot dog ou não.

O que é o TensorFlow.js?

O TensorFlow.js é a versão JavaScript do TensorFlow — a biblioteca de machine learning open-source do Google. Com ele, dá para:

  • Rodar modelos pré-treinados diretamente no browser, sem servidor;
  • Treinar modelos do zero usando JavaScript;
  • Fazer transfer learning — pegar um modelo pronto e ajustar para o seu caso;
  • Converter modelos treinados em Python para rodar no browser.

A proposta é poderosa: a inferência (o processo de fazer uma previsão) acontece na máquina do usuário, usando os recursos locais. Nenhum dado é enviado para servidor. Nenhuma latência de rede. Privacidade por padrão.

Tensores: a base de tudo

Antes de falar em modelos, precisa entender o conceito central: o tensor.

Um tensor é basicamente um array multidimensional — a generalização de um número (0D), vetor (1D), matriz (2D) e assim por diante.

import * as tf from '@tensorflow/tfjs'

// Escalar (0D)
const a = tf.scalar(5)

// Vetor (1D)
const b = tf.tensor1d([1, 2, 3])

// Matriz (2D)
const c = tf.tensor2d([[1, 2], [3, 4]])

// Tensor 4D — formato de imagem: [batch, altura, largura, canais]
const imagem = tf.zeros([1, 224, 224, 3])

No contexto de imagens, uma foto colorida de 224×224px é representada como um tensor 4D com shape [1, 224, 224, 3]: 1 imagem, 224 pixels de altura, 224 de largura, 3 canais (R, G, B).

Todo o processamento dentro de uma rede neural — da entrada até a saída — é feito com operações sobre tensores.

Como funciona um modelo de IA?

Uma rede neural é uma sequência de camadas (layers) que transformam um tensor de entrada em um tensor de saída. Cada camada aplica uma transformação matemática usando seus pesos.

O que são pesos?

Pesos são os parâmetros internos do modelo — números que foram ajustados durante o treinamento para que o modelo aprenda a reconhecer padrões.

Imagine um detector de bordas simples: os pesos definem qual padrão de pixels ativa esse detector. O treinamento é o processo de encontrar os melhores valores para esses pesos, usando um dataset e um algoritmo chamado backpropagation com gradient descent.

No final do treino, esses pesos são salvos em arquivo. Quando você carrega um modelo pré-treinado, está carregando exatamente esses pesos — o "conhecimento" que o modelo acumulou.

Exemplo simplificado do fluxo:

Imagem (224x224x3)

Camada Convolucional (detecta bordas, texturas...)

Pooling (reduz dimensionalidade)

... mais camadas ...

Camada Densa (classificação)

Softmax[prob_classe_0, prob_classe_1, ..., prob_classe_999]

No final, para classificação de imagens, o modelo retorna um array de probabilidades — uma por classe. A maior probabilidade indica a predição.

WebGL: a GPU a serviço do JavaScript

Aqui está o segredo que torna tudo isso viável no browser: o WebGL.

O TensorFlow.js usa o WebGL como backend padrão para executar operações matemáticas na GPU do dispositivo. Isso é fundamental porque operações sobre tensores (multiplicação de matrizes, convoluções) são paralelizáveis por natureza — exatamente o que GPUs fazem melhor.

TensorFlow.js

WebGL Backend

GPU (via shaders GLSL)

Na prática, quando você roda um modelo no browser, o TensorFlow.js compila as operações em shaders WebGL e executa tudo na GPU do usuário. O resultado é entregue de volta ao JavaScript como um tensor.

O TensorFlow.js tem outros backends disponíveis — cpu (puro JavaScript), wasm (WebAssembly), e webgpu (o futuro, para GPUs mais modernas) — mas o WebGL é o mais amplamente suportado hoje.

Para ver qual backend está ativo:

import * as tf from '@tensorflow/tfjs'

console.log(tf.getBackend()) // 'webgl'

Modelos pré-treinados: não precisar treinar do zero

Treinar um modelo de classificação de imagens do zero exige milhões de imagens e dias de processamento em GPUs potentes. Para a maioria dos casos de uso, não faz sentido.

A alternativa é usar um modelo pré-treinado — um modelo que já foi treinado em um dataset enorme e cujos pesos já estão prontos para uso.

O dataset mais usado para isso é o ImageNet: 1,2 milhão de imagens em 1000 classes diferentes. Modelos treinados no ImageNet já "entendem" bordas, texturas, formas e padrões visuais complexos.

Um dos modelos mais utilizados para aplicações no browser é o MobileNet.

Por que MobileNet?

O MobileNet foi projetado especificamente para ser eficiente em dispositivos com recursos limitados — mobile, IoT, browser. Ele usa depthwise separable convolutions no lugar das convoluções padrão, reduzindo drasticamente o número de operações sem perder muito em precisão.

A versão MobileNet V1 com input 224×224 tem:

  • ~4,2 milhões de parâmetros (pesos)
  • 13 blocos de convolução separável
  • Output: vetor de 1000 probabilidades (uma por classe do ImageNet)

Para referência, o VGG-16 — um modelo clássico — tem 138 milhões de parâmetros. O MobileNet cabe no browser. O VGG-16, não.

SeeFood: tudo isso na prática

O SeeFood é um classificador que roda 100% no browser. Sem servidor, sem API, sem custo de infraestrutura. Você aponta a câmera (ou faz upload de uma foto) e o modelo responde: é um hot dog ou não.

A referência é óbvia — quem assistiu Silicon Valley sabe do que se trata. Mas por trás da piada tem uma implementação real e que funciona.

Stack utilizada

  • TensorFlow.js v4.10.0 — carregado via CDN
  • MobileNet 1.0 — modelo pré-treinado no ImageNet
  • WebGL — backend de execução
  • getUserMedia API — acesso à câmera do dispositivo
  • Canvas API — captura e processamento dos frames

Como o modelo foi carregado

O modelo é servido junto com a aplicação no formato JSON + arquivos de pesos binários:

/model/
model.json ← arquitetura + referência para os pesos
group1-shard1of1 ← pesos (tensor binário)
group2-shard1of1
...
group55-shard1of1 ← 55 grupos de pesos no total

O carregamento no browser:

const model = await tf.loadLayersModel('/model/model.json')

Esse único await baixa a arquitetura e todos os 55 arquivos de pesos. Depois disso, o modelo está em memória, pronto para fazer inferência localmente.

Pré-processamento da imagem

O MobileNet espera um tensor com shape [1, 224, 224, 3] e valores normalizados entre -1 e 1. A imagem capturada da câmera ou upload precisa passar por isso antes de entrar no modelo:

function preprocessImage(imageElement) {
return tf.tidy(() => {
return tf.browser
.fromPixels(imageElement) // converte para tensor [H, W, 3]
.resizeBilinear([224, 224]) // redimensiona para o tamanho esperado
.toFloat()
.div(127.5) // normaliza para [0, 2]
.sub(1) // normaliza para [-1, 1]
.expandDims(0) // adiciona dimensão de batch → [1, 224, 224, 3]
})
}

O tf.tidy() é importante: ele cuida do gerenciamento de memória, descartando tensores intermediários que não são mais necessários. Sem isso, a GPU vaza memória com cada inferência.

A inferência

const tensor = preprocessImage(imageElement)
const predictions = model.predict(tensor)
const data = await predictions.data() // Float32Array com 1000 valores

O resultado é um Float32Array com 1000 números — as probabilidades para cada uma das 1000 classes do ImageNet.

Detectando o hot dog

No ImageNet, a classe de índice 934 é hotdog. Simples assim:

const HOT_DOG_CLASS_INDEX = 934
const HOT_DOG_THRESHOLD = 0.15

const hotdogProbability = data[HOT_DOG_CLASS_INDEX]
const isHotDog = hotdogProbability > HOT_DOG_THRESHOLD

console.log(isHotDog ? '✓ Hot Dog' : '✗ Not Hot Dog')

O threshold de 0.15 foi escolhido empiricamente — modelos raramente chegam a 90%+ de confiança para hot dogs em imagens reais. Um threshold muito alto faz o modelo nunca detectar nada; muito baixo, detecta qualquer coisa.

Por que isso importa para devs frontend?

Pode parecer que rodar IA no browser é um nicho exótico, mas as aplicações práticas são muitas:

  • Reconhecimento facial/de gestos para interfaces sem toque
  • Moderação de conteúdo client-side antes do upload
  • Acessibilidade — leitura de cenas, reconhecimento de texto em imagens
  • Filtros de câmera em tempo real (tipo aqueles do Instagram)
  • Análise de documentos sem enviar dados sensíveis para servidor

E tem um argumento de privacidade muito forte: se a inferência acontece no browser, o dado bruto nunca sai da máquina do usuário. Para domínios sensíveis (saúde, finanças, documentos pessoais), isso pode ser decisivo.

Considerações de performance

Alguns pontos práticos antes de sair colocando modelos em produção:

Tamanho do modelo: O MobileNet tem ~16MB em pesos. Isso precisa ser baixado na primeira visita. Pense em lazy loading e cache adequado.

Tempo de carregamento: Mesmo com WebGL, a primeira inferência é mais lenta porque o TensorFlow.js precisa compilar os shaders na GPU. As subsequentes são muito mais rápidas.

Gerenciamento de memória: Tensores precisam ser descartados manualmente com tensor.dispose() ou usando tf.tidy(). Vazamento de tensores é um problema real.

Dispositivos mobile: A GPU dos celulares é menos potente. Teste em dispositivos reais antes de assumir que a performance será boa.

// Monitorar uso de memória
console.log(tf.memory())
// { numTensors: 12, numDataBuffers: 12, numBytes: 1234567 }

Conclusão

O TensorFlow.js prova que machine learning no browser deixou de ser curiosidade para se tornar uma opção real. Com modelos pré-treinados como o MobileNet, aceleração via WebGL e uma API JavaScript bem projetada, dá pra construir aplicações de IA sem precisar de nenhum servidor — e sem abrir mão de performance.

O SeeFood é um exemplo pequeno, com um objetivo obviamente ridículo. Mas o mesmo pipeline — carregar modelo, pré-processar entrada, inferência, interpretar saída — é o que roda por baixo de coisas muito mais sérias.

Vale a pena explorar. A barreira de entrada é muito menor do que parece.

tip

Quer entender melhor como otimizar aplicações frontend para cenários de uso intensivo de recursos? Tem um capítulo dedicado a performance no meu livro.

https://www.tautorn.com.br/react-beyond

Referências