Pular para o conteúdo principal

TensorFlow.js — Rodando IA direto no browser

· 9 min para ler
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.

dica

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​