Desenvolva uma Inteligência Artificial que reconhece cores diretamente no navegador com TensorFlow.js

Olá Web Developer! Hoje vou te mostrar como criar uma inteligência artificial que reconhece cores, e isso você pode usar como base para criar várias outras coisas. Vamos criar diretamente no navegador usando o TensorFlow.js, biblioteca para trabalhar com Machine Learning desenvolvida pelo Google. Ao final deste post você poderá testar o resultado aqui no corpo do post mesmo, selecionando uma cor e o seu computador dizendo qual o nome dela.

Se você ainda não está familiarizado com Machine Learning, recomendo que você leia primeiro o post onde mostro O que é e como funciona Machine Learning?. Nele eu introduzo o que é Machine Learning e também mostro de forma simplificada como funciona o algoritmo KNN.

Coleta de Dados

Para que o computador aprenda o nome das cores, precisamos ensiná-lo, assim como fazemos com um bebê. Então vamos em busca de algumas cores. O nome dessa etapa se chama Coleta de Dados.

Essa etapa você pode fazer do jeito que quiser, pois o que importa é ter uma boa quantidade de dados para ensinar o computador. Portanto, é melhor encontrar um lugar em que você possa conseguir muitos dados da forma mais automática possível. Aproveite a sua criatividade caso não encontre um banco de dados pronto. Também não importa o formato em que você vai deixar esses dados no momento.

Eu escolhi criar um objeto com o seguinte formato:

const cores = [
    {
        "nome": "Vermelho",
        "valores": [
            [255,160,122],
            [250,128,114],
            [233,150,122]
        ]
    },
    {
        "nome": "Verde",
        "valores": [
            [124,252,0],
            [127,255,0],
            [50,205,50]
        ]
    }
    {
        "nome": "Azul",
        "valores": [
            [240,248,255],
            [230,230,250],
            [176,224,230]
        ]
    }
    ...
]

É um simples Array, onde cada objeto possui um campo nome que vai indicar o nome da cor e um Array valores que vai conter vários códigos daquela cor no formato RGB.

É claro que não vamos ficar escrevendo isso manualmente. Então vamos buscar um lugar para pegar essas cores. Eu encontrei no Google o site Rapid Tables com várias tabelas, incluindo códigos de cores.

Veja que a quarta coluna da tabela tem exatamente o que eu quero, os códigos no formato RGB. Se abrirmos o console do navegador poderemos executar um código para pegar essas cores. Eu executei o seguinte código:

// pega todos os elementos da 4ª coluna
var colunas = document.querySelectorAll('tr td:nth-child(4)');
var cores = Array.from(colunas).map(coluna =>
    let cor = coluna.innerText; // pega o texto do elemento
    cor = cor.replace(/[rgb()]/gi, ''); // deixa apenas os números e vírgulas
    cor = cor.split(','); // separa cada número pela vírgula, retornando um Array de strings
    cor = cor.map(numero => parseInt(numero, 10)); // converte cada string em número
    return cor;
);
console.log(JSON.stringify(cores)); // imprime o Array como um texto

Gosto de usar var ao executar um código no console porque let e const não podem ser redeclarados, então se eu quisesse reexecutar esse código eu teria que remover a declaração ou mudar o nome das variáveis.

Isso já vai me retornar um Array com cores no formato RGB do jeito que eu quero. Agora é só copiar do console, colar no código, e fazer o mesmo para verde, azul, etc. Eu fiz com várias cores, mas para simplificar a explicação vou fazer de conta que fiz apenas para vermelho, verde e azul.

JavaScript Avançado
Curso de JavaScript Avançado
CONHEÇA O CURSO

Pré-processamento de Dados

Assim que tivermos uma boa quantidade de dados, chegamos na fase de Pré-Processamento. Aqui nós arrumamos os nossos dados antes de ensinarmos a nossa máquina, então o modo como vamos arrumar vai depender de como nossos dados estão, qual o nosso objetivo, como vamos ensinar a máquina, qual biblioteca vamos utilizar, quais os tipos de dados que temos, etc.

É uma das etapas mais importantes e trabalhosas, havendo casos em que mais de 50% de tempo e ferramentas de um projeto são gastos nela. Um exemplo é tentar automatizar a remoção de dados que possam estar errados. No nosso caso, estamos analisando cores no formato RGB, em que cada valor varia de 0 a 255. Então se encontrássemos uma cor com algum valor -3 ou 450, poderíamos removê-la, pois é uma cor que não existe e atrapalharia o aprendizado da máquina.

Então vamos começar essa etapa e iniciar o pré-processamento de nossos dados. O que fazemos ao ensinar as cores para uma criança? Primeiro mostramos a cor e depois já damos a resposta, falando o nome da cor. E é isso que vamos fazer para o computador também.

A diferença é que ao invés de mostrar a imagem de uma cor, vamos passar o código RGB como [255,160,122], e ao invés de falar Vermelho, vamos indicar um número também, pois computadores trabalham melhor com números.

No nosso exemplo, estamos levando em conta que estou ensinando para o computador sobre vermelho, verde e azul, três cores nessa ordem. Então vou indicar ao computador que vermelho se chama [1,0,0], verde se chama [0,1,0] e azul [0,0,1].

Quando perguntarmos por uma nova cor, o computador dirá algo como: [0.93, 0.53, 0.15]. Isso significaria que ele tem 93% de certeza de que é o primeiro elemento, 53% de que é o segundo e 15% de que é o terceiro. Como a porcentagem maior é do primeiro elemento, que estamos combinando que vai ser vermelho, ele vai dizer que a cor é vermelha.

Para o código das cores, que é o que vamos ensinar, vou criar um Array e chamar de entradas, e o nome das cores vou chamar de respostas.

Então vamos ler nossos dados e jogá-los todos nos Arrays entradas e respostas.

const entradas = [],
    respostas = [];

cores.forEach((cor, indice) => {
    // criamos o código da cor
    const codigoCor = [0,0,0];
    codigoCor[indice] = 1;
    cor.valores.forEach(corRGB => {
        // jogamos no array cada codigo de cor
        entradas.push(corRGB);
        // e indicamos no outro array qual é a cor
        respostas.push(codigoCor);
    });
})

Teremos algo assim:

Lembrando que estou mostrando menos dados para ficar mais simples de explicar.

Futuramente, para saber se estamos com algo realmente inteligente, precisaremos testar. Então nesse momento devemos separar uma parte dos nossos dados para usar para testes. Aqui no JavaScript podemos usar o método .splice() para cortar uma parte do Array e jogar em um Array novo.

// apenas um exemplo de como poderíamos fazer
const entradasTeste = entradas.splice(5),
      respostasTeste = saidas.splice(5);
// como os nossos dados estão organizados,
// seria melhor embaralhar primeiro,
// senão corremos o risco de ensinar
// o computador o que é vermelho e verde
// e depois testar com azul.

Pegue pelo menos uns 20% dos dados para teste.

Até essa etapa você poderia fazer de várias maneiras com qualquer ferramenta ou linguagem de programação. A partir de agora vamos começar a utilizar a biblioteca TensorFlow.js.

Treinamento

Para iniciar a fase de treinamento, vamos adicionar o TensorFlow.js ao nosso código.

<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>

A primeira coisa a fazer é pegar nossos dados de entrada e resposta e tranformar em uma estrutura que o TensorFlow utiliza. Vou chamar de dadosTreino e dadosResposta.

const dadosTreino = tf.tensor2d(entradas, [entradas.length, 3]);
const dadosResposta = tf.tensor2d(respostas, [respostas.length, 3]);

No primeiro nós passamos o Array entradas e depois como segundo parâmetro nós passamos um Array que indica o formato dos dados. O primeiro número indica a quantidade de dados, e o segundo, quantos elementos cada dado possui. Como nossas cores estão em um formato como [255,160,122], indicamos o 3.

No segundo, dadosResposta, fizemos o mesmo. Na dimensão dos dados também passamos 3, pois aqui no exemplo estamos passando valores como [1,0,0]. Mas se você tivesse, por exemplo, 5 tipos de cores, seu formato estaria como [1,0,0,0,0], então ao invés de 3 você passaria 5.

Agora vamos preparar o nosso modelo. Um modelo pode ser a representação matemática de um processamento do mundo real. Complicado? Não se preocupe, como esse é um post apenas para demonstrar de forma simples como funciona o Machine Learning, não vamos nos aprofundar tanto (na verdade até aqui eu já pulei centenas de coisas hahaha). Entenda apenas que modelo é o resultado do processo de treinamento.

// criação do modelo
const modelo = tf.sequential();
// criação de uma camada de entrada
modelo.add(tf.layers.dense({
  inputShape: 3,
  units: 10,
  activation: 'sigmoid'
}))
// criação de uma camada de saída
modelo.add(
    tf.layers.dense({
      inputShape: 10,
      units: 3,
      activation: "softmax"
    })
  );
// compilação do modelo
modelo.compile({
    loss: "categoricalCrossentropy",
    optimizer: tf.train.adam()
})

O significado de cada parâmetro vai escapar do escopo do post, então vou deixar para explicar em um outro. Mas você pode ter uma ideia do que eles estão fazendo acessando o TensorFlow Playground, onde você terá uma ideia visual do que o TensorFlow faz.

Agora que nosso modelo está pronto, precisamos treiná-lo com os nossos dados. Para isso usamos o método fit(). Primeiro passamos os dados e depois as respostas. O parâmetro epochs significa épocas. Em cada época todos os dados são processados. Então é como se estivéssemos dizendo para ele processar 20 vezes. Quanto mais épocas, mais demorado o treinamento, porém, mais eficiente vai ficar a sua Inteligência Artificial.

async function treinarDados(){
    for(let i = 0; i < 15; i++){
        await modelo.fit(dadosTreino, dadosResposta, {epochs: 20});
    }
}

E vamos criar uma função que vai chamar esse treinamento.

async function iniciar(){
    await treinarDados();
}

Aplicação

Agora devíamos testar o nosso modelo. Mas para testar precisamos utilizar o modelo, então já vou mostrar como fazer isso para depois já termos tudo pronto para nossos testes.

Para utilizar o modelo executamos o método predict(), passando uma nova cor. A estrutura deve seguir a que utilizamos para criar os dados de entrada.

//vamos criar uma função "prever"
//que vai receber uma cor no formato RGB
// ex: [35, 15, 153]
async function prever(corRGB){
    // convertemos a cor para o formato
    // que o tensorflow utiliza
    const cor = tf.tensor2d([corRGB], [1, 3])
    // executamos o predict()
    const predicao = modelo.predict(cor);
    // teremos um resultado como
    // [0.95, 0.15, .45], indicando
    // a probabilidade de ser
    // cada uma das cores.
    // Vamos pegar o índice do maior
    const dados = await predicao.data();
    const maiorValor = Math.max(...dados);
    const indice = dados.indexOf(maiorValor);
    // com o índice podemos pegar o nome da cor
    return cores[indice].nome;
}

Podemos utilizar esta função para saber o nome de uma cor que ainda não passamos ao computador, como:

prever([15, 32, 153])
    .then(cor => console.log(`A cor é ${cor}`))
R - Introdução ao Machine Learning
Curso de R - Introdução ao Machine Learning
CONHEÇA O CURSO

Testes

Como saber se o treinamento foi bom? Testando!

Nós separamos alguns dados para testes. Então agora basta usar a nossa função de prever, passando os dados de teste e ver o que ele responde. Como também temos as respostas dos dados de teste, basta compararmos. Assim podemos ter uma noção da porcentagem de acertos.

async function testar(){
    // vamos transformar as respostas dos testes
    // que estão no formato [1, 0, 0] para
    // o nome das cores
    const respostas = respostasTeste.map(cor => {
        const maiorValor = Math.max(...cor);
        const indice = dados.indexOf(maiorValor);
        // com o índice podemos pegar o nome da cor
        return cores[indice].nome;
    });

    // Agora vamos executar nossa função "prever()"
    // para cada um dos dados de teste.
    const testes = await Promise.all(
        entradasTeste.map(prever)
    )
    // agora vamos comparar os testes
    // com as respostas
    let acertos = 0;
    testes.forEach((testeResposta, index) => {
        if(testeResposta === respostas[index]){
           acertos++;
        }
    });
    // com o número de acertos podemos calcular a
    // porcentagem de acertos
    return (acertos/testes.length) * 100;
}

Essa foi uma demonstração simples. Na prática os testes vão muito além de simplesmente comparar e calcular a porcentagem de acertos, e o modo de avaliação varia de acordo com o objetivo da sua inteligência artificial. Por exemplo: tudo bem deixar a máquina errar 10% das recomendações de filmes para usuário de serviços como Netflix, mas provavelmente 10% de erro é muito para um carro autônomo, pois isso poderia tirar milhares de vidas.

Salvando Modelos

Treinamos o nosso modelo, e você deve ter visto como pode demorar. Não faria sentido ficar treinando um modelo a cada momento que quisermos utilizá-lo. Isso seria o equivalente a você ter que aprender o alfabeto toda vez que quiser ler algo. Então, ao obter um modelo que os testes apresentem uma boa porcentagem de acertos, podemos salvar o modelo para utilizá-lo em outras aplicações.

Podemos salvar um modelo mesmo trabalhando no navegador.

async function salvarModelo(){
    await modelo.save('localstorage://meu-modelo-cores');
}

localstorage indica onde vamos salvar. Podemos utilizar também indexeddb. Isso salvará o modelo no armazenamento do navegador do usuário. Também é possível usar o valor downloads, que vai iniciar um download de um arquivo com o modelo.

Após // temos o nome que queremos dar ao nosso modelo para salvar. Assim podemos salvar vários modelos.

Como estamos trabalhando com JavaScript, tudo o que escrevemos até agora também pode ser executado no Node.js. Se você quiser salvar o seu modelo, localstorage, indexeddb e downloads são valores aceitos apenas no navegador. No Node.js você pode utilizar o valor file. Assim ele vai salvar o modelo na sua máquina.

// salvando no Node.js
async function salvarModelo(){
    await modelo.save('file:///nome/caminho/meu-modelo-cores');
}

Carregando Modelos Pré-Treinados

Do mesmo jeito que podemos salvar o modelo que treinamos, também podemos carregar um modelo já treinado. Isso significa que não vamos precisar ter todo aquele trabalho que tivemos até aqui para pegar dados, fazer o pré-processamento, criação do modelo, treinamento, testes, etc.

No navegador fazemos da seguinte maneira:

async function carregarModelo(){
    const modelo = await tf.loadLayersModel('localstorage://meu-modelo-cores');
}

E como já sabemos, podemos trocar o localstorage pelo indexeddb, dependendo onde salvamos.

Se o JSON gerado pelo modelo treinado estiver em um servidor, você pode carregá-lo assim:

async function carregarModelo(){
    const modelo = await tf.loadLayersModel('https://meu-site.com.br/meu-modelo-cores.json');
}

E se você estiver no Node.js e quiser carregar um modelo que está salvo na sua máquina, pode fazer assim:

async function carregarModelo(){
    const modelo = await tf.loadLayersModel('file://nome/caminho/meu-modelo-cores.json');
}

Já que tudo isso está substituindo nosso código de treinamento, podemos fazer um novo arquivo que vai utilizar um modelo já existente:

let modelo;

async function carregarModelo(){
    return await tf.loadLayersModel('localstorage://meu-modelo-cores');
}

async function iniciar(){
    modelo = await carregarModelo;
}

async function prever(corRGB){
    const cor = tf.tensor2d([corRGB], [1, 3]);
    const predicao = modelo.predict(cor);
    const dados = await predicao.data();
    const maiorValor = Math.max(...dados);
    const indice = dados.indexOf(maiorValor);
    // ainda precisamos de um modo para converter
    // o numero do indice para o nome da cor
    return cores[indice].nome;
}

Veja que carregando um modelo pré-treinado podemos começar a utilizar o modelo imediatamente.

O TensorFlow nos disponibiliza vários modelos para utilizarmos que já foram treinados com milhões de dados. Você pode dar uma olhada na página de Modelos, onde temos modelos para detectar objetos em imagens, classificar objetos presentes em uma imagem, poses de uma pessoa, etc.

Para utilizar, por exemplo, o MobileNet, adicione o modelo:

<script src="https://cdn.jsdelivr.net/npm/@tensorflow-models/mobilenet@1.0.0"> </script>
// carregamos o modelo
mobilenet.load().then(modelo => {
    // Classifica uma imagem
    // passando-a para o método "classify"
    modelo.classify(imagem).then(predicoes => {
        console.log(predicoes);
    });
    // neste exemplo, "imagem" pode ser
    // um elemento HTML <img />
});

Porém, não é porque estamos carregando um modelo pré-treinado que teremos uma aplicação mais rápida. Muitos modelos podem pesar vários MegaBytes. O nosso exemplo carrega rápido por ser mais simples e possuir poucos dados.

Algo muito legal também é a possibilidade de fazer um modelo pré-treinado aprender mais.

Você pode, por exemplo, pegar um modelo de reconhecimento facial e adicionar fotos suas para ele aprender a reconhecer bem quem é você e diferenciá-lo das outras pessoas. Utilizar um modelo pré-treinado como ponto de partida para outra tarefa é chamado de Transfer Learning (Transferência de Aprendizado). Vamos abordar sobre ele em um outro post.

Nosso Classificador de Cores

Utilizando tudo que foi mostrado acima criei uma pequena interface onde você pode escolher uma cor e ver o que ele dá de resposta.

Como o treino está sendo feito em tempo de execução, cada vez que você atualizar esta página o modelo será treinado de uma maneira diferente. Em alguns momentos ele pode dar resultados mais precisos e em outros ele pode dar resultados menos precisos. Isso também é causado pelo treinamento utilizar poucos dados.

E como foi dito no post que apresentei no início deste artigo sobre como funciona o Machine Learning, veja que não precisamos programar nenhuma lógica, não escrevemos if para determinar uma decisão. A lógica é criada pelo próprio computador durante seu processo de aprendizado, e a cada treinamento ele cria algo diferente. Nós “apenas” cuidamos dos dados que serão utilizados para ensinar o computador, definimos alguns parâmetros que o computador deve seguir para aprender e depois testamos. Assim podemos criar sistemas que a programação seria muito complexa para criarmos, como é o caso de reconhecimento facial ou carros autônomos.

Desenvolvedor Front-end Sênior
Formação: Desenvolvedor Front-end Sênior
HTML, CSS e JavaScript são a base de toda a web. Tudo o que você está vendo aqui agora depende deste tripé.Nesta formação aprenderemos Sass, Google Analytics, empacotar nossas aplicações com Webpack, criação de aplicações Desktop com Electron, UX/UI e uma introdução aos frameworks mais utilizados no mercado: Angular, React, Vue e Ember.
CONHEÇA A FORMAÇÃO
Deixe seu comentário

Graduado em Análise e Desenvolvimento de Sistemas, Pós-graduado em Projetos e Desenvolvimento de Aplicações Web e MBA em Machine Learning. Especializado em Front End e curte desenvolvimento de jogos.

© 2004 - 2019 TreinaWeb Tecnologia LTDA - CNPJ: 06.156.637/0001-58 Av. Paulista, 1765, Conj 71 e 72 - Bela Vista - São Paulo - SP - 01311-200