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.
Exemplos de treinamento
Clique em um dígito para colocá-lo na entrada e visualizar suas características.
Entrada e probabilidades
O desenho é convertido em 12 × 12 intensidades entre 0 e 1.
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.
Resultado para cada dígito
A confiança é a probabilidade Softmax da classe escolhida.

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.
// ============================================================
// 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));