TensorFlow.js: crie sua própria "máquina que aprende" usando aprendizado por transferência com o TensorFlow.js

1. Antes de começar

O uso de modelos do TensorFlow.js cresceu exponencialmente nos últimos anos, e muitos desenvolvedores de JavaScript agora estão tentando treiná-los novamente para trabalhar com dados personalizados exclusivos do setor deles. O ato de pegar um modelo existente (geralmente chamado de modelo base) e usá-lo em um domínio semelhante, mas diferente, é conhecido como aprendizado por transferência.

O aprendizado por transferência tem muitas vantagens em comparação com um modelo completamente em branco. É possível reutilizar o conhecimento já aprendido de um modelo treinado anterior e você precisa de menos exemplos do novo item que quer classificar. Além disso, o treinamento costuma ser significativamente mais rápido porque precisa apenas retreinar as últimas camadas da arquitetura do modelo, em vez de toda a rede. Por esse motivo, o aprendizado por transferência é muito adequado para ambientes de navegadores da Web, em que os recursos podem variar de acordo com o dispositivo de execução, mas também tem acesso direto aos sensores para fácil aquisição de dados.

Este codelab mostra como criar um app da Web usando uma tela em branco, recriando a famosa " Máquina que Aprende" site. O site permite criar um app da Web funcional que qualquer usuário pode usar para reconhecer um objeto personalizado com apenas alguns exemplos de imagens da webcam. O site foi propositalmente reduzido para que você possa se concentrar nos aspectos de machine learning deste codelab. No entanto, assim como no site original da Teachable Machine, há muito escopo para aplicar sua experiência de desenvolvedor Web atual e melhorar a UX.

Pré-requisitos

Este codelab foi escrito para desenvolvedores Web que estão um pouco familiarizados com os modelos predefinidos do TensorFlow.js e com o uso básico da API e que querem começar a usar o aprendizado por transferência no TensorFlow.js.

  • Este laboratório exige conhecimento básico do TensorFlow.js, HTML5, CSS e JavaScript.

Se você ainda não conhece o Tensorflow.js, faça este curso sem custo financeiro básico. Ele não exige experiência com machine learning ou TensorFlow.js e ensina tudo o que você precisa saber em etapas menores.

O que você vai aprender

  • O que é o TensorFlow.js e por que você deve usá-lo no seu próximo app da Web.
  • Como criar uma página da Web simplificada em HTML/CSS /JS que replica a experiência do usuário da Máquina que Aprende.
  • Como usar o TensorFlow.js para carregar um modelo base pré-treinado, especificamente o MobileNet, para gerar atributos de imagem que podem ser usados no aprendizado por transferência.
  • Como coletar dados da webcam de um usuário para várias classes de dados que você quer reconhecer.
  • Como criar e definir um perceptron de várias camadas que usa os atributos da imagem para aprender a classificar novos objetos.

Vamos começar...

O que é necessário

  • É preferível usar uma conta do Glitch.com, ou você pode usar um ambiente de veiculação na Web que se sinta à vontade para editar e executar.

2. O que é o TensorFlow.js?

54e81d02971f53e8.png

O TensorFlow.js é uma biblioteca de machine learning de código aberto que pode ser executada em qualquer lugar com JavaScript. Ele é baseado na biblioteca original do TensorFlow escrita em Python e tem como objetivo recriar essa experiência de desenvolvedor e o conjunto de APIs para o ecossistema do JavaScript.

Onde pode ser usado?

Devido à portabilidade do JavaScript, agora é possível escrever em um idioma e realizar machine learning em todas as seguintes plataformas com facilidade:

  • Lado do cliente no navegador da Web usando JavaScript básico
  • lado do servidor e até dispositivos de IoT, como o Raspberry Pi, que usam Node.js
  • Aplicativos para computador que usam o Electron
  • Aplicativos nativos para dispositivos móveis que usam o React Native

O TensorFlow.js também oferece suporte a vários back-ends em cada um desses ambientes, ou seja, os ambientes reais baseados em hardware em que ele pode executar, como a CPU ou o WebGL, por exemplo. Um "back-end" neste contexto não significa um ambiente no lado do servidor (o back-end para execução pode ser no lado do cliente no WebGL, por exemplo) para garantir a compatibilidade e manter tudo funcionando rapidamente. Atualmente, o TensorFlow.js é compatível com:

  • Execução de WebGL na placa de vídeo do dispositivo (GPU): essa é a maneira mais rápida de executar modelos maiores (com mais de 3 MB) com aceleração de GPU.
  • Execução do Web Assembly (WASM) na CPU: para melhorar o desempenho da CPU em vários dispositivos, incluindo smartphones das gerações mais antigas, por exemplo. Isso é mais adequado para modelos menores (com menos de 3 MB), que podem ser executados mais rapidamente na CPU com o WASM do que com o WebGL, devido à sobrecarga do upload de conteúdo para um processador gráfico.
  • Execução da CPU: o substituto não deverá estar disponível para nenhum dos outros ambientes. Este é o mais lento dos três, mas está sempre lá para você.

Observação:é possível forçar um desses back-ends se você souber em qual dispositivo vai executar a execução. Também é possível deixar o TensorFlow.js decidir por você se você não especificar isso.

Superpoderes do lado do cliente

Executar o TensorFlow.js no navegador da Web da máquina cliente pode gerar vários benefícios que valem a pena considerar.

Privacidade

Você pode treinar e classificar os dados na máquina cliente sem nunca enviá-los a um servidor da Web de terceiros. Em alguns casos, isso pode ser um requisito para obedecer à legislação local, como o GDPR, ou ao processar dados que o usuário quer manter na máquina e não enviar a terceiros.

Speed

Como você não precisa enviar dados a um servidor remoto, a inferência (o ato de classificar os dados) pode ser mais rápida. Melhor ainda, você tem acesso direto aos sensores do dispositivo, como a câmera, o microfone, o GPS, o acelerômetro, entre outros, caso o usuário permita o acesso.

Alcance e escala

Com um clique, qualquer pessoa no mundo pode clicar em um link que você enviou, abrir a página da Web no navegador e utilizar o que você criou. Para usar o sistema de machine learning, você não precisa de uma configuração complexa do Linux no lado do servidor com drivers CUDA e muito mais.

Custo

A ausência de servidores significa que você só precisa pagar por uma CDN para hospedar seus arquivos HTML, CSS, JS e de modelo. O custo de uma CDN é muito mais barato do que manter um servidor (possivelmente com uma placa de vídeo conectada) funcionando 24 horas por dia, 7 dias por semana.

Recursos do servidor

Utilizar a implementação do TensorFlow.js em Node.js ativa os recursos a seguir.

Suporte completo para CUDA

No lado do servidor, para acelerar a placa gráfica, é necessário instalar os drivers NVIDIA CUDA para permitir que o TensorFlow funcione com a placa de vídeo (ao contrário do navegador que usa WebGL. Não é necessário instalar). No entanto, com o suporte total a CUDA, você aproveita ao máximo os recursos de nível mais baixo da placa de vídeo, o que agiliza o treinamento e a inferência. O desempenho está em paridade com a implementação do TensorFlow em Python, já que ambos compartilham o mesmo back-end em C++.

Tamanho do modelo

Para modelos modernos de pesquisa, você pode estar trabalhando com modelos muito grandes, talvez em gigabytes. No momento, esses modelos não podem ser executados no navegador da web devido às limitações de uso da memória por guia do navegador. Para executar esses modelos maiores, use o Node.js no seu próprio servidor com as especificações de hardware necessárias para executar esse modelo com eficiência.

I/T

O Node.js é compatível com computadores de placa única conhecidos, como o Raspberry Pi (link em inglês), o que significa que você também pode executar modelos do TensorFlow.js nesses dispositivos.

Speed

O Node.js é escrito em JavaScript, o que significa que ele se beneficia da compilação no momento certo. Isso significa que muitas vezes é possível notar aumentos de desempenho ao usar o Node.js, já que ele será otimizado no tempo de execução, especialmente para qualquer pré-processamento que você esteja fazendo. Um ótimo exemplo disso é este estudo de caso, que mostra como a Hugging Face usou o Node.js para dobrar o desempenho do modelo de processamento de linguagem natural.

Agora que você já conhece os conceitos básicos do TensorFlow.js, onde ele pode ser executado e alguns dos benefícios, vamos começar a usá-lo de forma prática.

3. Aprendizado por transferência

O que é aprendizado por transferência?

O aprendizado por transferência envolve adquirir conhecimento que já foi aprendido para ajudar a aprender algo diferente, mas semelhante.

Nós, humanos, fazemos isso o tempo todo. Você tem uma vida inteira de experiências contidas em seu cérebro, que podem ser usadas para ajudar a reconhecer coisas novas que você nunca viu antes. Veja este salgueiro, por exemplo:

e28070392cd4afb9.png

Dependendo de onde você estiver no mundo, há uma chance de você não ter visto esse tipo de árvore antes.

No entanto, se eu pedir para me dizer se há salgueiros na nova imagem abaixo, você provavelmente conseguirá vê-los bem rápido, mesmo que estejam em um ângulo diferente, um pouco diferente do original que mostrei.

d9073a0d5df27222.png

Você já tem vários neurônios no cérebro que sabem identificar objetos semelhantes a árvores, além de outros que são bons em encontrar linhas retas longas. Você pode reutilizar esse conhecimento para classificar rapidamente um salgueiro, que é um objeto semelhante a uma árvore que tem muitos galhos verticais longos e retos.

Da mesma forma, se você tiver um modelo de machine learning treinado em um domínio, como o reconhecimento de imagens, poderá reutilizá-lo para executar uma tarefa diferente, mas relacionada.

É possível fazer o mesmo com um modelo avançado como o MobileNet, um modelo de pesquisa muito conhecido que pode realizar reconhecimento de imagem em mil tipos diferentes de objetos. De cães a carros, ele foi treinado em um enorme conjunto de dados conhecido como ImageNet, que tem milhões de imagens rotuladas.

Nesta animação, você pode ver o grande número de camadas que tem neste modelo MobileNet V1:

7d4e1e35c1a89715.gif

Durante o treinamento, esse modelo aprendeu a extrair atributos comuns que importam para todos esses mil objetos, e muitos dos atributos de nível mais baixo que ele usa para identificar esses objetos também podem ser úteis para detectar novos objetos que ele nunca viu antes. Afinal, tudo é basicamente uma combinação de linhas, texturas e formas.

Vamos analisar uma arquitetura tradicional de rede neural convolucional (CNN, na sigla em inglês) (semelhante ao MobileNet) e ver como o aprendizado por transferência pode usar essa rede treinada para aprender algo novo. A imagem abaixo mostra a arquitetura de modelo típica de uma CNN que, neste caso, foi treinada para reconhecer dígitos escritos à mão de 0 a 9:

baf4e3d434576106.png

Se você puder separar as camadas de nível inferior pré-treinadas de um modelo treinado como este mostrado à esquerda, das camadas de classificação próximas ao final do modelo mostrado à direita (às vezes chamada de chefe de classificação do modelo), você pode usar as camadas de nível inferior para produzir atributos de saída para qualquer imagem com base nos dados originais em que foi treinado. Esta é a mesma rede com o cabeçalho de classificação removido:

369a8a9041c6917d.png

Supondo que a nova coisa que você está tentando reconhecer também possa usar os atributos de saída que o modelo anterior aprendeu, há uma boa chance de eles serem reutilizados para uma nova finalidade.

No diagrama acima, esse modelo hipotético foi treinado com dígitos, então talvez o que foi aprendido sobre dígitos também possa ser aplicado a letras como a, b e c.

Agora é possível adicionar um novo cabeçalho de classificação que tente prever a, b ou c, conforme mostrado abaixo:

db97e5e60ae73bbd.png

Aqui, as camadas de nível inferior são congeladas e não são treinadas. Apenas o novo cabeçalho de classificação vai se atualizar para aprender com os atributos fornecidos pelo modelo pré-treinado dividido à esquerda.

Esse processo é conhecido como aprendizado por transferência e é o que a Máquina que Aprende faz nos bastidores.

O perceptron de várias camadas só precisa ser treinado no final da rede e é muito mais rápido do que treinar toda a rede do zero.

Mas como colocar as subpartes de um modelo em prática? Vá para a próxima seção para descobrir.

4. TensorFlow Hub: modelos base

Encontrar um modelo base adequado para usar

Para modelos de pesquisa mais avançados e conhecidos, como o MobileNet, acesse o TensorFlow hub e filtre os modelos adequados ao TensorFlow.js que usam a arquitetura MobileNet v3 para encontrar resultados como estes:

c5dc1420c6238c14.png

Alguns desses resultados são do tipo "classificação de imagem" (detalhados no canto superior esquerdo de cada resultado de card de modelo), e outros são do tipo "vetor de recurso de imagem".

Esses resultados do vetor de recurso de imagem são essencialmente as versões pré-cortadas do MobileNet que você pode usar para obter os vetores de recursos de imagem em vez da classificação final.

Modelos como esse costumam ser chamados de "modelos base", que você pode usar para realizar o aprendizado por transferência da mesma forma que mostrado na seção anterior, adicionando um novo cabeçalho de classificação e treinando-o com seus próprios dados.

O próximo passo é verificar um determinado modelo base de interesse em qual formato do TensorFlow.js o modelo foi lançado. Se você abrir a página de um desses modelos de vetor de recurso do MobileNet v3, verá na documentação do JS que ele está na forma de um modelo gráfico com base no exemplo de snippet de código na documentação que usa tf.loadGraphModel().

f97d903d2e46924b.png

Também é importante notar que, se você encontrar um modelo no formato de camadas em vez de gráfico, é possível escolher quais camadas congelar e quais descongelar para treinamento. Isso pode ser muito útil ao criar um modelo para uma nova tarefa, muitas vezes chamada de "modelo de transferência". Por enquanto, no entanto, você vai usar o tipo de modelo de gráfico padrão para este tutorial, em que a maioria dos modelos do TF Hub é implantada. Para saber mais sobre como trabalhar com modelos de camadas, confira o curso TensorFlow.js do zero to hero.

Vantagens do aprendizado por transferência

Quais são as vantagens de usar o aprendizado por transferência em vez de treinar toda a arquitetura do modelo do zero?

Primeiro, o tempo de treinamento é uma das principais vantagens de usar uma abordagem de aprendizado por transferência, porque você já tem um modelo base treinado para construir.

Em segundo lugar, é possível mostrar muito menos exemplos do novo item que você está tentando classificar devido ao treinamento que já ocorreu.

Isso é ótimo se você tem tempo e recursos limitados para coletar dados de exemplo do que quer classificar e precisa fazer um protótipo rapidamente antes de coletar mais dados de treinamento para torná-lo mais robusto.

Devido à necessidade de menos dados e à velocidade de treinamento de uma rede menor, o aprendizado por transferência consome menos recursos. Isso a torna muito adequada para ambientes de navegadores, levando apenas dezenas de segundos em uma máquina moderna em vez de horas, dias ou semanas para treinamento de modelo completo.

Muito bem! Agora que você sabe a essência do aprendizado por transferência, é hora de criar sua própria versão da Máquina que Aprende. Vamos começar!

5. Comece a programar

O que é necessário

  • Um navegador da Web moderno.
  • Conhecimento básico de HTML, CSS, JavaScript e Chrome DevTools (visualização da saída do console).

Vamos programar

Os modelos de texto padronizado para começar foram criados para o Glitch.com ou o Codepen.io. Basta clonar um modelo como seu estado base para este codelab com apenas um clique.

No Glitch, clique no botão remixar para bifurcar o conteúdo e criar um conjunto de arquivos que você pode editar.

Como alternativa, no Codepen, clique em " fork" no canto inferior direito da tela.

Esse esqueleto muito simples fornece os seguintes arquivos:

  • Página HTML (index.html)
  • Folha de estilo (style.css)
  • Arquivo para escrever nosso código JavaScript (script.js)

Para sua conveniência, há uma importação adicional no arquivo HTML da biblioteca do TensorFlow.js. Esta é a aparência dela:

index.html

<!-- Import TensorFlow.js library -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs/dist/tf.min.js" type="text/javascript"></script>

Alternativa: use seu editor da Web preferido ou trabalhe localmente

Se você quiser fazer o download do código e trabalhar localmente ou em um editor on-line diferente, basta criar os três arquivos nomeados acima no mesmo diretório e copiar e colar o código do código boilerplate do Glitch em cada um deles.

6. Código boilerplate de HTML do app

Por onde eu começo?

Todos os protótipos requerem alguma estrutura HTML básica na qual você possa renderizar suas descobertas. Faça isso agora. Você vai adicionar:

  • Um título para a página.
  • Alguns textos descritivos.
  • Um parágrafo de status.
  • Um vídeo para manter o feed da webcam quando estiver pronto.
  • Diversos botões para iniciar a câmera, coletar dados ou redefinir a experiência.
  • Importações para arquivos TensorFlow.js e JS que você vai codificar depois.

Abra index.html e cole o código abaixo usando o comando a seguir para configurar os recursos acima:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Transfer Learning - TensorFlow.js</title>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- Import the webpage's stylesheet -->
    <link rel="stylesheet" href="/style.css">
  </head>  
  <body>
    <h1>Make your own "Teachable Machine" using Transfer Learning with MobileNet v3 in TensorFlow.js using saved graph model from TFHub.</h1>
    
    <p id="status">Awaiting TF.js load</p>
    
    <video id="webcam" autoplay muted></video>
    
    <button id="enableCam">Enable Webcam</button>
    <button class="dataCollector" data-1hot="0" data-name="Class 1">Gather Class 1 Data</button>
    <button class="dataCollector" data-1hot="1" data-name="Class 2">Gather Class 2 Data</button>
    <button id="train">Train &amp; Predict!</button>
    <button id="reset">Reset</button>

    <!-- Import TensorFlow.js library -->
    <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js" type="text/javascript"></script>

    <!-- Import the page's JavaScript to do some stuff -->
    <script type="module" src="/script.js"></script>
  </body>
</html>

Cai na roda

Vamos separar alguns dos códigos HTML acima para destacar alguns itens importantes que você adicionou.

  • Você adicionou uma tag <h1> para o título da página, além de uma tag <p> com o ID "status", que é onde você vai imprimir informações, porque usa diferentes partes do sistema para conferir as saídas.
  • Você adicionou um elemento <video> com um ID de "webcam". ao qual você renderizará a transmissão da webcam mais tarde.
  • Você adicionou 5 elementos <button>. A primeira, com o ID "enableCam", ativa a câmera. Os dois botões seguintes têm a classe "dataCollector", que permite coletar imagens de exemplo para os objetos que você deseja reconhecer. O código que você escrever mais tarde será projetado para que você possa adicionar qualquer número desses botões e eles funcionarão automaticamente como pretendido.

Esses botões também têm um atributo especial definido pelo usuário chamado data-1hot, com um valor inteiro a partir de 0 para a primeira classe. Esse é o índice numérico que você vai usar para representar os dados de uma determinada classe. O índice será usado para codificar as classes de saída corretamente com uma representação numérica em vez de uma string, já que os modelos de ML só funcionam com números.

Há também um atributo data-name que contém o nome legível por humanos que você quer usar para essa classe, que permite fornecer um nome mais significativo ao usuário em vez de um valor de índice numérico da codificação 1 hot.

Por fim, há um botão de treinamento e de redefinição para iniciar o processo de treinamento depois que os dados forem coletados ou para redefinir o app, respectivamente.

  • Você também adicionou duas importações <script>. Um para o TensorFlow.js e outro para o script.js, que você vai definir em breve.

7. Adic. estilo

Padrões do elemento

Adicione estilos aos elementos HTML que você acabou de incluir para garantir que eles sejam renderizados corretamente. Aqui estão alguns estilos que são adicionados para posicionar e dimensionar os elementos corretamente. Nada muito especial. Você certamente poderia adicionar isso mais tarde para criar uma UX ainda melhor, como você viu no vídeo sobre máquinas que ensinam.

style.css

body {
  font-family: helvetica, arial, sans-serif;
  margin: 2em;
}

h1 {
  font-style: italic;
  color: #FF6F00;
}


video {
  clear: both;
  display: block;
  margin: 10px;
  background: #000000;
  width: 640px;
  height: 480px;
}

button {
  padding: 10px;
  float: left;
  margin: 5px 3px 5px 10px;
}

.removed {
  display: none;
}

#status {
  font-size:150%;
}

Ótimo! Isso é tudo o que você precisa. Ao visualizar a saída agora, ela deve ser semelhante a esta:

81909685d7566dcb.png

8. JavaScript: constantes e listeners de chave

Definir constantes de chave

Primeiro, adicione algumas constantes importantes que serão usadas em todo o app. Comece substituindo o conteúdo de script.js por estas constantes:

script.js

const STATUS = document.getElementById('status');
const VIDEO = document.getElementById('webcam');
const ENABLE_CAM_BUTTON = document.getElementById('enableCam');
const RESET_BUTTON = document.getElementById('reset');
const TRAIN_BUTTON = document.getElementById('train');
const MOBILE_NET_INPUT_WIDTH = 224;
const MOBILE_NET_INPUT_HEIGHT = 224;
const STOP_DATA_GATHER = -1;
const CLASS_NAMES = [];

Vamos explicar para que servem:

  • STATUS simplesmente contém uma referência à tag de parágrafo em que você vai gravar atualizações de status.
  • VIDEO contém uma referência ao elemento de vídeo HTML que renderizará o feed da webcam.
  • ENABLE_CAM_BUTTON, RESET_BUTTON e TRAIN_BUTTON capturam referências do DOM para todos os botões de chave da página HTML.
  • MOBILE_NET_INPUT_WIDTH e MOBILE_NET_INPUT_HEIGHT definem a largura e a altura de entrada esperadas do modelo MobileNet, respectivamente. Ao armazenar isso em uma constante próxima ao topo do arquivo dessa forma, se você decidir usar uma versão diferente mais tarde, será mais fácil atualizar os valores uma vez, em vez de ter que substituí-la em muitos lugares diferentes.
  • STOP_DATA_GATHER é definido como -1. Isso armazena um valor de estado para que você saiba quando o usuário parou de clicar em um botão para coletar dados do feed da webcam. Dar a esse número um nome mais significativo torna o código mais legível posteriormente.
  • CLASS_NAMES atua como uma pesquisa e contém os nomes legíveis das possíveis previsões de classe. Essa matriz será preenchida mais tarde.

Agora que você tem referências aos elementos principais, é hora de associar alguns listeners de eventos a eles.

Adicionar listeners de eventos principais

Comece adicionando manipuladores de eventos de clique aos botões principais, conforme mostrado:

script.js

ENABLE_CAM_BUTTON.addEventListener('click', enableCam);
TRAIN_BUTTON.addEventListener('click', trainAndPredict);
RESET_BUTTON.addEventListener('click', reset);


function enableCam() {
  // TODO: Fill this out later in the codelab!
}


function trainAndPredict() {
  // TODO: Fill this out later in the codelab!
}


function reset() {
  // TODO: Fill this out later in the codelab!
}

ENABLE_CAM_BUTTON: chama a função enableCam quando clicado.

TRAIN_BUTTON: chama trainAndPredict quando clicado.

RESET_BUTTON: as chamadas são redefinidas quando clicadas.

Por fim, nesta seção, você encontra todos os botões que têm a classe "dataCollector" usando document.querySelectorAll(). Isso retorna uma matriz de elementos encontrados no documento que correspondem:

script.js

let dataCollectorButtons = document.querySelectorAll('button.dataCollector');
for (let i = 0; i < dataCollectorButtons.length; i++) {
  dataCollectorButtons[i].addEventListener('mousedown', gatherDataForClass);
  dataCollectorButtons[i].addEventListener('mouseup', gatherDataForClass);
  // Populate the human readable names for classes.
  CLASS_NAMES.push(dataCollectorButtons[i].getAttribute('data-name'));
}


function gatherDataForClass() {
  // TODO: Fill this out later in the codelab!
}

Explicação do código:

Em seguida, você itera pelos botões encontrados e associa dois listeners de eventos a cada um deles. um para "mousedown" e outro para "mouseup". Isso permite que você continue gravando amostras enquanto o botão for pressionado, o que é útil para a coleta de dados.

Ambos os eventos chamam uma função gatherDataForClass que você vai definir mais tarde.

Neste ponto, também é possível enviar os nomes de classes legíveis por humanos encontrados do atributo data-name do botão HTML para a matriz CLASS_NAMES.

Em seguida, adicione algumas variáveis para armazenar itens importantes que serão usados mais tarde.

script.js

let mobilenet = undefined;
let gatherDataState = STOP_DATA_GATHER;
let videoPlaying = false;
let trainingDataInputs = [];
let trainingDataOutputs = [];
let examplesCount = [];
let predict = false;

Vamos analisar cada uma delas.

Primeiro, você tem uma variável mobilenet para armazenar o modelo mobilenet carregado. Inicialmente, defina como indefinido.

Em seguida, você tem uma variável chamada gatherDataState. Se um elemento "dataCollector" for pressionado, ele passará a ser o ID especial desse botão, conforme definido no HTML, para que você saiba qual classe de dados está coletando no momento. Inicialmente, ele é definido como STOP_DATA_GATHER para que o loop de coleta de dados que você escrever depois não reúna nenhum dado quando nenhum botão estiver sendo pressionado.

O videoPlaying monitora se o stream da webcam foi carregado e reproduzido e se está disponível para uso. Inicialmente, defina como false, pois a webcam não estará ligada até que você pressione o ENABLE_CAM_BUTTON.

Em seguida, defina duas matrizes, trainingDataInputs e trainingDataOutputs. Eles armazenam os valores de dados de treinamento coletados, à medida que você clica no botão botões para os atributos de entrada gerados pelo modelo base MobileNet e a classe de saída amostrada, respectivamente.

Uma matriz final, examplesCount,, é definida para acompanhar quantos exemplos estão contidos em cada classe assim que você começar a adicioná-las.

Por fim, você tem uma variável chamada predict que controla o loop de previsão. Inicialmente, ele é definido como false. Nenhuma previsão pode ocorrer até que ele seja definido como true depois.

Agora que todas as principais variáveis foram definidas, vamos carregar o modelo base MobileNet v3 pré-cortado que fornece vetores de recursos de imagem em vez de classificações.

9. Carregar o modelo base do MobileNet

Primeiro, defina uma nova função com o nome loadMobileNetFeatureModel, conforme mostrado abaixo. Essa função precisa ser assíncrona, já que o ato de carregar um modelo é assíncrono:

script.js

/**
 * Loads the MobileNet model and warms it up so ready for use.
 **/
async function loadMobileNetFeatureModel() {
  const URL = 
    'https://tfhub.dev/google/tfjs-model/imagenet/mobilenet_v3_small_100_224/feature_vector/5/default/1';
  
  mobilenet = await tf.loadGraphModel(URL, {fromTFHub: true});
  STATUS.innerText = 'MobileNet v3 loaded successfully!';
  
  // Warm up the model by passing zeros through it once.
  tf.tidy(function () {
    let answer = mobilenet.predict(tf.zeros([1, MOBILE_NET_INPUT_HEIGHT, MOBILE_NET_INPUT_WIDTH, 3]));
    console.log(answer.shape);
  });
}

// Call the function immediately to start loading.
loadMobileNetFeatureModel();

Neste código, defina o URL em que o modelo a ser carregado está localizado na documentação do TFHub.

Em seguida, você pode carregar o modelo usando await tf.loadGraphModel(), lembrando de definir a propriedade especial fromTFHub como true ao carregar um modelo desse site do Google. Esse é um caso especial apenas para o uso de modelos hospedados no TF Hub em que essa propriedade extra precisa ser definida.

Depois que o carregamento for concluído, defina o innerText do elemento STATUS com uma mensagem para confirmar que ele foi carregado corretamente e que você já pode começar a coletar dados.

Agora só falta aquecer o modelo. Com modelos maiores como esse, a primeira vez que você usa o modelo pode levar um tempo para configurar tudo. Por isso, é útil passar zeros pelo modelo para evitar esperas no futuro, quando o tempo for mais importante.

É possível usar tf.zeros() unidos em um tf.tidy() para garantir que os tensores sejam descartados corretamente, com um tamanho de lote de 1 e a altura e a largura corretas que você definiu nas constantes no início. Por fim, você também especifica os canais de cores, que nesse caso são 3, já que o modelo espera imagens RGB.

Em seguida, registre a forma resultante do tensor retornado usando answer.shape() para ajudar a entender o tamanho dos atributos de imagem que esse modelo produz.

Depois de definir essa função, você pode chamá-la imediatamente para iniciar o download do modelo no carregamento de página.

Se você acessar a visualização ao vivo agora, após alguns instantes, o texto do status mudará de "Aguardando carregamento do TF.js". para exibir a mensagem "MobileNet v3 carregado". conforme mostrado abaixo. Verifique se isso funciona antes de continuar.

a28b734e190afff.png

Também é possível verificar a saída do console para ver o tamanho impresso dos recursos de saída que o modelo produz. Depois de executar zeros com o modelo MobileNet, você verá uma forma de [1, 1024] exibida. O primeiro item é apenas o tamanho do lote 1. Como você pode ver, ele retorna 1.024 atributos que podem ser usados para ajudar a classificar novos objetos.

10. Definir o novo cabeçalho do modelo

Agora é hora de definir o cabeçalho do seu modelo, que é basicamente um perceptron de várias camadas mínimo.

script.js

let model = tf.sequential();
model.add(tf.layers.dense({inputShape: [1024], units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: CLASS_NAMES.length, activation: 'softmax'}));

model.summary();

// Compile the model with the defined optimizer and specify a loss function to use.
model.compile({
  // Adam changes the learning rate over time which is useful.
  optimizer: 'adam',
  // Use the correct loss function. If 2 classes of data, must use binaryCrossentropy.
  // Else categoricalCrossentropy is used if more than 2 classes.
  loss: (CLASS_NAMES.length === 2) ? 'binaryCrossentropy': 'categoricalCrossentropy', 
  // As this is a classification problem you can record accuracy in the logs too!
  metrics: ['accuracy']  
});

Vamos analisar esse código. Comece definindo um modelo tf.Sequential em que você vai adicionar camadas de modelo.

Em seguida, adicione uma camada densa como entrada para esse modelo. Ele tem um formato de entrada de 1024, porque as saídas dos recursos do MobileNet v3 são desse tamanho. Você descobriu isso na etapa anterior depois de passar alguns pelo modelo. Essa camada tem 128 neurônios que usam a função de ativação ReLU.

Se você não conhece as funções de ativação e as camadas de modelo, faça o curso detalhado no início deste workshop para entender o que essas propriedades fazem nos bastidores.

A próxima camada a ser adicionada é a camada final. O número de neurônios precisa ser igual ao número de classes que você está tentando prever. Para isso, use CLASS_NAMES.length para descobrir quantas classes você planeja classificar, o que é igual ao número de botões de coleta de dados encontrados na interface do usuário. Como esse é um problema de classificação, use a ativação softmax nessa camada de saída, que deve ser usada ao tentar criar um modelo para resolver problemas de classificação em vez de regressão.

Agora, exiba uma model.summary() para exibir a visão geral do modelo recém-definido no console.

Por fim, compile o modelo para que ele esteja pronto para ser treinado. Aqui, o otimizador é definido como adam, e a perda será binaryCrossentropy se CLASS_NAMES.length for igual a 2 ou vai usar categoricalCrossentropy se houver três ou mais classes para classificar. As métricas de precisão também são solicitadas para que possam ser monitoradas nos registros mais tarde para fins de depuração.

No console, você verá algo assim:

22eaf32286fea4bb.png

Ele tem mais de 130 mil parâmetros treináveis. Mas como essa é uma camada simples densa de neurônios regulares, ele é treinado muito rápido.

Como uma atividade a ser realizada quando o projeto for concluído, tente alterar o número de neurônios na primeira camada para ver o quanto é possível torná-lo baixo e, ao mesmo tempo, conseguir um desempenho adequado. Muitas vezes, no machine learning há um certo nível de tentativa e erro para encontrar os valores de parâmetro ideais para oferecer o melhor equilíbrio entre uso de recursos e velocidade.

11. Ativar a webcam

Agora, é hora de detalhar a função enableCam() que você definiu anteriormente. Adicione uma nova função chamada hasGetUserMedia(), conforme mostrado abaixo, e substitua o conteúdo da função enableCam() definida anteriormente pelo código correspondente abaixo.

script.js

function hasGetUserMedia() {
  return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia);
}

function enableCam() {
  if (hasGetUserMedia()) {
    // getUsermedia parameters.
    const constraints = {
      video: true,
      width: 640, 
      height: 480 
    };

    // Activate the webcam stream.
    navigator.mediaDevices.getUserMedia(constraints).then(function(stream) {
      VIDEO.srcObject = stream;
      VIDEO.addEventListener('loadeddata', function() {
        videoPlaying = true;
        ENABLE_CAM_BUTTON.classList.add('removed');
      });
    });
  } else {
    console.warn('getUserMedia() is not supported by your browser');
  }
}

Primeiro, crie uma função chamada hasGetUserMedia() para conferir se o navegador oferece suporte a getUserMedia(), verificando a existência das principais propriedades das APIs do navegador.

Na função enableCam(), use a função hasGetUserMedia() que você acabou de definir acima para verificar se há suporte a ela. Se não estiver, imprima um aviso no console.

Se ele oferecer suporte, defina algumas restrições para a chamada getUserMedia(). Por exemplo, você quer apenas o stream de vídeo e prefere que o width do vídeo tenha 640 pixels e o height de 480 pixels. Por quê? Bem, não faz muito sentido deixar um vídeo maior do que isso, já que ele precisaria ser redimensionado para 224 por 224 pixels para ser alimentado no modelo MobileNet. Também é possível economizar alguns recursos de computação solicitando uma resolução menor. A maioria das câmeras oferece suporte a uma resolução desse tamanho.

Em seguida, chame navigator.mediaDevices.getUserMedia() com a constraints detalhada acima e aguarde o retorno de stream. Depois que o stream for retornado, você poderá fazer com que seu elemento VIDEO reproduza stream definindo-o como o valor srcObject.

Também é necessário adicionar um eventListener ao elemento VIDEO para saber quando o stream foi carregado e está sendo reproduzido corretamente.

Depois que o stream for carregado, você poderá definir videoPlaying como verdadeiro e remover o ENABLE_CAM_BUTTON para evitar que ele seja clicado novamente, definindo sua classe como "removed".

Agora execute o código, clique no botão de ativar câmera e permita o acesso à webcam. Se for a primeira vez que você faz isso, verá que é renderizado para o elemento de vídeo na página, conforme mostrado:

b378eb1affa9b883.png

Agora, é hora de adicionar uma função para lidar com os cliques no botão dataCollector.

12. Manipulador de eventos do botão de coleta de dados

Agora, é hora de preencher a função vazia atualmente chamada gatherDataForClass().. Ela é a que você atribuiu como função de manipulador de eventos para os botões dataCollector no início do codelab.

script.js

/**
 * Handle Data Gather for button mouseup/mousedown.
 **/
function gatherDataForClass() {
  let classNumber = parseInt(this.getAttribute('data-1hot'));
  gatherDataState = (gatherDataState === STOP_DATA_GATHER) ? classNumber : STOP_DATA_GATHER;
  dataGatherLoop();
}

Primeiro, verifique o atributo data-1hot no botão clicado chamando this.getAttribute() com o nome do atributo. Neste caso, data-1hot como o parâmetro. Como essa é uma string, você pode usar parseInt() para convertê-la em um número inteiro e atribuir esse resultado a uma variável chamada classNumber..

Em seguida, defina a variável gatherDataState corretamente. Se o gatherDataState atual for igual a STOP_DATA_GATHER (que você definiu como -1), isso significa que você não está coletando dados e que um evento mousedown foi disparado. Defina a gatherDataState como o classNumber que você acabou de encontrar.

Caso contrário, isso significa que você está coletando dados, e o evento disparado foi mouseup, e quer parar de coletar dados para essa classe. Basta defini-lo novamente para o estado STOP_DATA_GATHER para encerrar o loop de coleta de dados que você vai definir em breve.

Por fim, inicie a chamada para dataGatherLoop(),, que realiza a gravação dos dados da classe.

13. Coleta de dados

Agora, defina a função dataGatherLoop(). Essa função é responsável pela amostragem de imagens do vídeo da webcam, passando-as pelo modelo MobileNet e capturando as saídas desse modelo (os vetores de recurso 1024).

Em seguida, ele os armazena junto com o ID da gatherDataState do botão que está sendo pressionado para que você saiba qual classe esses dados representam.

Veja como fazer isso:

script.js

function dataGatherLoop() {
  if (videoPlaying && gatherDataState !== STOP_DATA_GATHER) {
    let imageFeatures = tf.tidy(function() {
      let videoFrameAsTensor = tf.browser.fromPixels(VIDEO);
      let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor, [MOBILE_NET_INPUT_HEIGHT, 
          MOBILE_NET_INPUT_WIDTH], true);
      let normalizedTensorFrame = resizedTensorFrame.div(255);
      return mobilenet.predict(normalizedTensorFrame.expandDims()).squeeze();
    });

    trainingDataInputs.push(imageFeatures);
    trainingDataOutputs.push(gatherDataState);
    
    // Intialize array index element if currently undefined.
    if (examplesCount[gatherDataState] === undefined) {
      examplesCount[gatherDataState] = 0;
    }
    examplesCount[gatherDataState]++;

    STATUS.innerText = '';
    for (let n = 0; n < CLASS_NAMES.length; n++) {
      STATUS.innerText += CLASS_NAMES[n] + ' data count: ' + examplesCount[n] + '. ';
    }
    window.requestAnimationFrame(dataGatherLoop);
  }
}

Você só vai continuar a execução dessa função se videoPlaying for verdadeiro, o que significa que a webcam está ativa, gatherDataState não for igual a STOP_DATA_GATHER e um botão para coleta de dados da classe estiver sendo pressionado.

Em seguida, una seu código em um tf.tidy() para descartar todos os tensores criados no código a seguir. O resultado dessa execução de código tf.tidy() é armazenado em uma variável chamada imageFeatures.

Agora você pode capturar um frame da webcam VIDEO usando tf.browser.fromPixels(). O tensor resultante que contém os dados da imagem é armazenado em uma variável chamada videoFrameAsTensor.

Em seguida, redimensione a variável videoFrameAsTensor para ter o formato correto da entrada do modelo MobileNet. Use uma chamada tf.image.resizeBilinear() com o tensor que você quer remodelar como o primeiro parâmetro e, em seguida, uma forma que defina a nova altura e largura, conforme definido pelas constantes já criadas. Por fim, defina "align ups" como "true" passando o terceiro parâmetro para evitar problemas de alinhamento ao redimensionar. O resultado desse redimensionamento é armazenado em uma variável chamada resizedTensorFrame.

Esse redimensionamento primitivo estende a imagem, já que a imagem da webcam tem 640 por 480 pixels e o modelo precisa de uma imagem quadrada de 224 por 224 pixels.

Para esta demonstração, isso deve funcionar corretamente. No entanto, depois de concluir este codelab, você pode tentar cortar um quadrado dessa imagem para ter resultados ainda melhores para qualquer sistema de produção que possa ser criado posteriormente.

Em seguida, normalize os dados da imagem. Os dados de imagem estão sempre no intervalo de 0 a 255 ao usar tf.browser.frompixels(), então você pode simplesmente dividir o redimensionadoTensorFrame por 255 para garantir que todos os valores estejam entre 0 e 1, que é o que o modelo MobileNet espera como entradas.

Por fim, na seção tf.tidy() do código, envie esse tensor normalizado pelo modelo carregado chamando mobilenet.predict(). Você vai transmitir a versão expandida do normalizedTensorFrame usando expandDims() para que seja um lote de 1, já que o modelo espera um lote de entradas para processamento.

Quando o resultado for retornado, você poderá chamar imediatamente squeeze() nesse resultado retornado para comprimi-lo de volta em um tensor 1D, que você retorna e atribui à variável imageFeatures que captura o resultado de tf.tidy().

Agora que você tem as imageFeatures do modelo MobileNet, é possível gravá-las enviando-as para a matriz trainingDataInputs definida anteriormente.

Você também pode registrar o que essa entrada representa enviando o gatherDataState atual para a matriz trainingDataOutputs.

Observe que a variável gatherDataState teria sido definida como o ID numérico da classe atual para a qual você está registrando dados quando o botão foi clicado na função gatherDataForClass() definida anteriormente.

Neste ponto, você também pode incrementar o número de exemplos que tem para uma determinada classe. Para fazer isso, primeiro verifique se o índice na matriz examplesCount foi inicializado antes ou não. Se ela for indefinida, defina-a como 0 para inicializar o contador para o ID numérico de uma determinada classe e, em seguida, incremente o examplesCount para o gatherDataState atual.

Agora, atualize o texto do elemento STATUS na página da Web para mostrar as contagens atuais de cada classe à medida que são capturadas. Para fazer isso, faça uma repetição na matriz CLASS_NAMES e mostre o nome legível por humanos combinado com a contagem de dados no mesmo índice em examplesCount.

Por fim, chame window.requestAnimationFrame() com dataGatherLoop transmitido como um parâmetro para chamar essa função de novo recursivamente. Isso vai continuar usando a amostragem de frames do vídeo até que o mouseup do botão seja detectado, e o gatherDataState seja definido como STOP_DATA_GATHER,. Nesse ponto, o loop de coleta de dados será encerrado.

Se você executar o código agora, poderá clicar no botão de ativar câmera, aguardar a webcam carregar e, em seguida, clicar e segurar cada um dos botões de coleta de dados para coletar exemplos para cada classe de dados. Aqui, podemos observar que reuni os dados do meu smartphone e da minha mão, respectivamente.

541051644a45131f.gif

Você verá o texto de status atualizado enquanto armazena todos os tensores na memória, conforme mostrado na captura de tela acima.

14. Treinar e prever

A próxima etapa é implementar o código para a função trainAndPredict() atualmente vazia, em que o aprendizado por transferência ocorre. Vamos dar uma olhada no código:

script.js

async function trainAndPredict() {
  predict = false;
  tf.util.shuffleCombo(trainingDataInputs, trainingDataOutputs);
  let outputsAsTensor = tf.tensor1d(trainingDataOutputs, 'int32');
  let oneHotOutputs = tf.oneHot(outputsAsTensor, CLASS_NAMES.length);
  let inputsAsTensor = tf.stack(trainingDataInputs);
  
  let results = await model.fit(inputsAsTensor, oneHotOutputs, {shuffle: true, batchSize: 5, epochs: 10, 
      callbacks: {onEpochEnd: logProgress} });
  
  outputsAsTensor.dispose();
  oneHotOutputs.dispose();
  inputsAsTensor.dispose();
  predict = true;
  predictLoop();
}

function logProgress(epoch, logs) {
  console.log('Data for epoch ' + epoch, logs);
}

Primeiro, certifique-se de interromper as previsões atuais, definindo predict como false.

Em seguida, embaralhe suas matrizes de entrada e saída usando tf.util.shuffleCombo() para garantir que a ordem não cause problemas no treinamento.

Converta sua matriz de saída, trainingDataOutputs,, para ser um tensor1d do tipo int32 para que esteja pronto para ser usado em uma codificação one-hot. Ela é armazenada em uma variável chamada outputsAsTensor.

Use a função tf.oneHot() com essa variável outputsAsTensor e o número máximo de classes a serem codificadas, que é apenas o CLASS_NAMES.length. Suas saídas de codificação quentes agora são armazenadas em um novo tensor chamado oneHotOutputs.

Observe que, atualmente, trainingDataInputs é uma matriz de tensores registrados. Para usá-los no treinamento, você precisará converter a matriz de tensores para torná-los um tensor 2D regular.

Para isso, há uma ótima função na biblioteca do TensorFlow.js chamada tf.stack(),

que pega uma matriz de tensores e os empilha para produzir um tensor dimensional mais alto como saída. Nesse caso, um tensor 2D é retornado, que é um lote de entradas unidimensionais com 1.024 de comprimento, contendo os atributos gravados, que é o que você precisa para o treinamento.

Em seguida, await model.fit() para treinar o cabeçalho do modelo personalizado. Aqui, você transmite sua variável inputsAsTensor com oneHotOutputs para representar os dados de treinamento a serem usados em exemplos de entradas e saídas de destino, respectivamente. No objeto de configuração do terceiro parâmetro, defina shuffle como true, use batchSize de 5, com epochs definido como 10, e especifique um callback para onEpochEnd para a função logProgress que você vai definir em breve.

Por fim, você pode descartar os tensores criados enquanto o modelo é treinado. É possível definir predict novamente como true para permitir que as previsões ocorram novamente e, em seguida, chamar a função predictLoop() para começar a prever imagens de webcams ao vivo.

Também é possível definir a função logProcess() para registrar o estado de treinamento, que é usado no model.fit() acima e que imprime os resultados no console após cada rodada de treinamento.

Falta pouco. É hora de adicionar a função predictLoop() para fazer previsões.

Loop de previsão principal

Aqui você vai implementar o loop de previsão principal que faz a amostragem de frames de uma webcam e prevê continuamente o que há em cada frame com resultados em tempo real no navegador.

Vamos verificar o código:

script.js

function predictLoop() {
  if (predict) {
    tf.tidy(function() {
      let videoFrameAsTensor = tf.browser.fromPixels(VIDEO).div(255);
      let resizedTensorFrame = tf.image.resizeBilinear(videoFrameAsTensor,[MOBILE_NET_INPUT_HEIGHT, 
          MOBILE_NET_INPUT_WIDTH], true);

      let imageFeatures = mobilenet.predict(resizedTensorFrame.expandDims());
      let prediction = model.predict(imageFeatures).squeeze();
      let highestIndex = prediction.argMax().arraySync();
      let predictionArray = prediction.arraySync();

      STATUS.innerText = 'Prediction: ' + CLASS_NAMES[highestIndex] + ' with ' + Math.floor(predictionArray[highestIndex] * 100) + '% confidence';
    });

    window.requestAnimationFrame(predictLoop);
  }
}

Primeiro, verifique se predict é verdadeiro, para que as previsões sejam feitas somente depois que um modelo for treinado e estiver disponível para uso.

Em seguida, você pode acessar os recursos da imagem atual, assim como fez na função dataGatherLoop(). Basicamente, você pega um frame da webcam usando tf.browser.from pixels(), normaliza, redimensiona para 224 por 224 pixels e transmite esses dados pelo modelo MobileNet para conseguir os recursos de imagem resultantes.

Agora, no entanto, é possível usar o cabeçalho do modelo recém-treinado para realizar uma previsão, transmitindo o imageFeatures resultante que você acabou de encontrar usando a função predict() do modelo treinado. Em seguida, é possível apertar o tensor resultante para torná-lo unidimensional novamente e atribuí-lo a uma variável chamada prediction.

Com esse prediction, é possível encontrar o índice com o valor mais alto usando argMax() e, em seguida, converter esse tensor resultante em uma matriz usando arraySync() para acessar os dados subjacentes no JavaScript e descobrir a posição do elemento com maior valor. Esse valor é armazenado na variável chamada highestIndex.

Também é possível receber as pontuações de confiança reais da previsão da mesma maneira, chamando arraySync() diretamente no tensor prediction.

Agora você tem tudo o que precisa para atualizar o texto STATUS com os dados do prediction. Para conseguir a string legível por humanos para a classe, basta procurar o highestIndex na matriz CLASS_NAMES e, em seguida, extrair o valor de confiança do predictionArray. Para facilitar a leitura como porcentagem, basta multiplicar por 100 e math.floor() o resultado.

Por fim, você pode usar window.requestAnimationFrame() para chamar predictionLoop() novamente quando estiver pronto, para ter classificação em tempo real no seu stream de vídeo. Isso vai continuar até que predict seja definido como false se você optar por treinar um novo modelo com dados novos.

E isso leva você à última peça do quebra-cabeça. Implementação do botão de redefinição.

15. Implementar o botão de redefinição

Quase pronto! A peça final do quebra-cabeça é implementar um botão de redefinição para começar de novo. Confira abaixo o código da função reset() vazia. Atualize da seguinte forma:

script.js

/**
 * Purge data and start over. Note this does not dispose of the loaded 
 * MobileNet model and MLP head tensors as you will need to reuse 
 * them to train a new model.
 **/
function reset() {
  predict = false;
  examplesCount.length = 0;
  for (let i = 0; i < trainingDataInputs.length; i++) {
    trainingDataInputs[i].dispose();
  }
  trainingDataInputs.length = 0;
  trainingDataOutputs.length = 0;
  STATUS.innerText = 'No data collected';
  
  console.log('Tensors in memory: ' + tf.memory().numTensors);
}

Primeiro, interrompa qualquer loop de previsão em execução definindo predict como false. Em seguida, exclua todo o conteúdo na matriz examplesCount definindo o comprimento como 0. Essa é uma maneira prática de limpar todo o conteúdo de uma matriz.

Agora, percorra todas as trainingDataInputs registradas atuais e garanta dispose() de cada tensor contido nele para liberar memória novamente, já que os tensores não são limpos pelo coletor de lixo do JavaScript.

Depois de fazer isso, você pode definir com segurança o comprimento da matriz como 0 nas matrizes trainingDataInputs e trainingDataOutputs para limpá-las também.

Por fim, defina o texto STATUS como algo sensível e mostre os tensores deixados na memória como uma verificação de integridade.

Observe que ainda haverá algumas centenas de tensores ainda na memória, porque o modelo MobileNet e o perceptron de várias camadas que você definiu não são descartados. Se você decidir treinar novamente após essa redefinição, vai precisar reutilizá-los com novos dados de treinamento.

16. Vamos testar

Chegou a hora de testar sua versão da Máquina que Aprende!

Vá para a visualização ao vivo, ative a webcam, reúna pelo menos 30 amostras da classe 1 para algum objeto na sua sala e faça o mesmo para a classe 2 para outro objeto, clique em "Treinar" e verifique o registro do console para ver o progresso. O treinamento será bem rápido:

bf1ac3cc5b15740.gif

Depois de treinado, mostre os objetos para a câmera para receber previsões em tempo real que vão ser impressas na área de texto de status na página da Web, perto da parte de cima. Se você estiver com problemas, verifique meu código de trabalho concluído para ver se deixou de copiar algo.

17. Parabéns

Parabéns! Você acabou de concluir seu primeiro exemplo de aprendizado por transferência usando o TensorFlow.js ativo no navegador.

Experimente e teste em diversos objetos. Você poderá perceber que algumas coisas são mais difíceis de reconhecer do que outras, especialmente se forem semelhantes a outra coisa. Talvez seja necessário adicionar mais classes ou dados de treinamento para conseguir diferenciá-los.

Resumo

Neste codelab, você aprendeu a:

  1. O que é aprendizado por transferência e as vantagens dele em relação ao treinamento de um modelo completo.
  2. Como conseguir modelos para reutilização do TensorFlow Hub.
  3. Como configurar um app da Web adequado para aprendizado por transferência.
  4. Como carregar e usar um modelo base para gerar recursos de imagem.
  5. Como treinar uma nova cabeça de previsão que pode reconhecer objetos personalizados de imagens de webcam.
  6. Como usar os modelos resultantes para classificar dados em tempo real.

Qual é a próxima etapa?

Agora que você tem uma base de trabalho para começar, quais ideias criativas você pode ter para estender esse modelo de modelo de machine learning para um caso de uso real em que talvez esteja trabalhando? Talvez você possa revolucionar o setor em que trabalha atualmente para ajudar as pessoas da sua empresa a treinar modelos para classificar coisas que são importantes no trabalho diário? As possibilidades são infinitas.

Para saber mais, faça este curso completo sem custo financeiro, que mostra como combinar os dois modelos que você tem atualmente neste codelab em um único modelo para aumentar a eficiência.

Além disso, se você quiser saber mais sobre a teoria por trás do aplicativo original de máquina com ensino, confira este tutorial (em inglês).

Compartilhe o que você sabe conosco

É possível estender facilmente o que você criou hoje para outros casos de uso criativos também. Incentivamos você a pensar fora da caixa e continuar a inovar.

Não se esqueça de nos marcar nas redes sociais usando a hashtag #MadeWithTFJS para que seu projeto apareça no blog do TensorFlow ou em eventos futuros. Adoraríamos saber o que você faz.

Sites para conferir