LABORATÓRIO · MINI CNN

Desenhe um dígito e abra cada etapa da classificação.

Quatro filtros percorrem uma imagem 12 × 12. Depois de ReLU e Max Pooling, uma camada Softmax aprende a reconhecer os dez dígitos.

12 × 12entrada4 mapasconv 3 × 34 × 5 × 5pooling10Softmax
Escopo didático: os quatro filtros são fixos para facilitar a visualização. O treinamento ajusta a camada densa. Em uma CNN completa, o backpropagation também aprende os valores dos filtros.
ÉPOCAS0
LOSS MÉDIA
ACURÁCIA
PREVISÃO

Exemplos de treinamento

Clique em um dígito para colocá-lo na entrada e visualizar suas características.

Preparando imagens

Entrada e probabilidades

O desenho é convertido em 12 × 12 intensidades entre 0 e 1.

classe previstatreine a saída

Mapas depois de Convolução + ReLU

Branco significa ativação baixa; cores escuras indicam onde o filtro respondeu.

Vertical

Horizontal

Diagonal ↘

Diagonal ↙

Forward e treinamento, passo a passo

Abra um patch, a convolução, a ReLU, o pooling, a Softmax e a atualização.

Erro × resposta

Compare a cross-entropy normalizada com a acurácia sobre os dez exemplos-base.

loss normalizadaacurácia

Resultado para cada dígito

A confiança é a probabilidade Softmax da classe escolhida.

Números manuscritos usados no laboratório

Da imagem para o dataset

Os dez recortes são reduzidos para 12 × 12. Deslocamentos de um pixel criam 50 exemplos simples. O MNIST completo possui 60 mil imagens de treino e 10 mil imagens de teste; este laboratório usa uma miniatura para expor todas as contas.

CÓDIGO COMPLETO E EXECUTÁVEL

Treine uma pequena CNN para reconhecer linhas

O programa contém imagens, filtros, Convolução, ReLU, pooling, camada Softmax, treinamento e previsão. Os filtros são definidos manualmente para concentrar a explicação no fluxo; redes reais também aprendem os filtros pelo backpropagation.

convoluir()extrairCaracteristicas()treinar()prever()avaliar()
// ============================================================
// MINI CNN: imagem 5 × 5 → 2 filtros → Softmax → classe
// Classe 0 = linha vertical; classe 1 = linha horizontal.
// ============================================================
var vertical1 = [
  [0,0,1,0,0], [0,0,1,0,0], [0,0,1,0,0],
  [0,0,1,0,0], [0,0,1,0,0]
];
var vertical2 = [
  [0,1,1,0,0], [0,0,1,0,0], [0,0,1,0,0],
  [0,0,1,0,0], [0,1,1,0,0]
];
var horizontal1 = [
  [0,0,0,0,0], [0,0,0,0,0], [1,1,1,1,1],
  [0,0,0,0,0], [0,0,0,0,0]
];
var horizontal2 = [
  [0,0,0,0,0], [1,1,1,1,0], [1,1,1,1,1],
  [0,0,0,0,0], [0,0,0,0,0]
];

var treino = [
  [vertical1, 0], [vertical2, 0],
  [horizontal1, 1], [horizontal2, 1]
];

var filtroVertical = [
  [-1, 2, -1], [-1, 2, -1], [-1, 2, -1]
];
var filtroHorizontal = [
  [-1,-1,-1], [ 2, 2, 2], [-1,-1,-1]
];
var filtros = [filtroVertical, filtroHorizontal];
var pesosSaida = [[0, 0], [0, 0]];
var biasSaida = [0, 0];

function convoluir(imagem, filtro) {
  var mapa = [];
  var linhasSaida = imagem.length - filtro.length + 1;
  var colunasSaida = imagem[0].length - filtro[0].length + 1;

  for (var linha = 0; linha < linhasSaida; linha++) {
    mapa[linha] = [];
    for (var coluna = 0; coluna < colunasSaida; coluna++) {
      var soma = 0;
      for (var r = 0; r < filtro.length; r++) {
        for (var c = 0; c < filtro[0].length; c++) {
          soma += imagem[linha + r][coluna + c] * filtro[r][c];
        }
      }
      mapa[linha][coluna] = Math.max(0, soma); // ReLU
    }
  }
  return mapa;
}

// Global Max Pooling: guarda a maior ativação de cada mapa.
function maiorValor(mapa) {
  var maior = mapa[0][0];
  for (var linha = 0; linha < mapa.length; linha++) {
    for (var coluna = 0; coluna < mapa[linha].length; coluna++) {
      if (mapa[linha][coluna] > maior) maior = mapa[linha][coluna];
    }
  }
  return maior;
}

function extrairCaracteristicas(imagem) {
  var caracteristicas = [];
  for (var f = 0; f < filtros.length; f++) {
    caracteristicas[f] = maiorValor(convoluir(imagem, filtros[f]));
  }
  return caracteristicas;
}

function softmax(logits) {
  var maior = Math.max(logits[0], logits[1]);
  var e0 = Math.exp(logits[0] - maior);
  var e1 = Math.exp(logits[1] - maior);
  return [e0 / (e0 + e1), e1 / (e0 + e1)];
}

function forward(imagem) {
  var x = extrairCaracteristicas(imagem);
  var logits = [biasSaida[0], biasSaida[1]];
  for (var classe = 0; classe < 2; classe++) {
    for (var j = 0; j < 2; j++) logits[classe] += pesosSaida[classe][j] * x[j];
  }
  return [softmax(logits), x];
}

function treinarAmostra(imagem, alvo, taxa) {
  var resultado = forward(imagem);
  var probabilidades = resultado[0];
  var x = resultado[1];

  for (var classe = 0; classe < 2; classe++) {
    var esperado = classe === alvo ? 1 : 0;
    var gradiente = probabilidades[classe] - esperado;
    for (var j = 0; j < 2; j++) pesosSaida[classe][j] -= taxa * gradiente * x[j];
    biasSaida[classe] -= taxa * gradiente;
  }
}

function treinar(dados, epocas, taxa) {
  for (var epoca = 0; epoca < epocas; epoca++) {
    for (var i = 0; i < dados.length; i++) {
      treinarAmostra(dados[i][0], dados[i][1], taxa);
    }
  }
}

function prever(imagem) {
  var p = forward(imagem)[0];
  if (p[0] > p[1]) return 0;
  return 1;
}

function avaliar(dados) {
  var acertos = 0;
  for (var i = 0; i < dados.length; i++) {
    if (prever(dados[i][0]) === dados[i][1]) acertos++;
  }
  return acertos / dados.length;
}

// ----------------------- USO DO MODELO -----------------------
treinar(treino, 200, 0.03);
console.log('Acurácia:', avaliar(treino));
console.log('Características da vertical:', extrairCaracteristicas(vertical1));
console.log('Previsão (0=vertical, 1=horizontal):', prever(vertical1));