Modelos de treinamento

Este guia pressupõe que você já leu o guia de modelos e camadas .

No TensorFlow.js, há duas maneiras de treinar um modelo de aprendizado de máquina:

  1. usando a API Layers com LayersModel.fit() ou LayersModel.fitDataset() .
  2. usando a API principal com Optimizer.minimize() .

Primeiro, veremos a API Layers, que é uma API de nível superior para construção e treinamento de modelos. A seguir, mostraremos como treinar o mesmo modelo usando a API Core.

Introdução

Um modelo de aprendizado de máquina é uma função com parâmetros que podem ser aprendidos que mapeia uma entrada para uma saída desejada. Os parâmetros ideais são obtidos treinando o modelo nos dados.

O treinamento envolve várias etapas:

  • Obtendo um lote de dados para o modelo.
  • Pedir ao modelo para fazer uma previsão.
  • Comparando essa previsão com o valor "verdadeiro".
  • Decidir quanto alterar cada parâmetro para que o modelo possa fazer uma previsão melhor no futuro para aquele lote.

Um modelo bem treinado fornecerá um mapeamento preciso da entrada até a saída desejada.

Parâmetros do modelo

Vamos definir um modelo simples de 2 camadas usando a API Layers:

const model = tf.sequential({
 layers: [
   tf.layers.dense({inputShape: [784], units: 32, activation: 'relu'}),
   tf.layers.dense({units: 10, activation: 'softmax'}),
 ]
});

Nos bastidores, os modelos têm parâmetros (geralmente chamados de pesos ) que podem ser aprendidos por meio do treinamento em dados. Vamos imprimir os nomes dos pesos associados a este modelo e suas formas:

model.weights.forEach(w => {
 console.log(w.name, w.shape);
});

Obtemos a seguinte saída:

> dense_Dense1/kernel [784, 32]
> dense_Dense1/bias [32]
> dense_Dense2/kernel [32, 10]
> dense_Dense2/bias [10]

Existem 4 pesos no total, 2 por camada densa. Isso é esperado, pois as camadas densas representam uma função que mapeia o tensor de entrada x para um tensor de saída y por meio da equação y = Ax + b onde A (o kernel) e b (o viés) são parâmetros da camada densa.

NOTA: Por padrão, as camadas densas incluem uma tendência, mas você pode excluí-la especificando {useBias: false} nas opções ao criar uma camada densa.

model.summary() é um método útil se você deseja obter uma visão geral do seu modelo e ver o número total de parâmetros:

Camada (tipo) Forma de saída Parâmetro #
denso_Dense1 (denso) [nulo,32] 25120
denso_Dense2 (denso) [nulo,10] 330
Parâmetros totais: 25450
Parâmetros treináveis: 25450
Parâmetros não treináveis: 0

Cada peso no modelo é backend por um objeto Variable . No TensorFlow.js, uma Variable é um Tensor de ponto flutuante com um método adicional assign() usado para atualizar seus valores. A API Layers inicializa automaticamente os pesos usando as práticas recomendadas. Para fins de demonstração, poderíamos substituir os pesos chamando assign() nas variáveis ​​subjacentes:

model.weights.forEach(w => {
  const newVals = tf.randomNormal(w.shape);
  // w.val is an instance of tf.Variable
  w.val.assign(newVals);
});

Otimizador, perda e métrica

Antes de fazer qualquer treinamento, você precisa decidir sobre três coisas:

  1. Um otimizador . A tarefa do otimizador é decidir quanto alterar cada parâmetro do modelo, dada a previsão atual do modelo. Ao usar a API Layers, você pode fornecer um identificador de string de um otimizador existente (como 'sgd' ou 'adam' ) ou uma instância da classe Optimizer .
  2. Uma função de perda . Um objetivo que o modelo tentará minimizar. Seu objetivo é fornecer um único número para “quão errada” estava a previsão do modelo. A perda é calculada em cada lote de dados para que o modelo possa atualizar seus pesos. Ao usar a API Layers, você pode fornecer um identificador de string de uma função de perda existente (como 'categoricalCrossentropy' ) ou qualquer função que receba um valor previsto e um valor verdadeiro e retorne uma perda. Veja uma lista de perdas disponíveis em nossos documentos de API.
  3. Lista de métricas. Semelhante às perdas, as métricas calculam um único número, resumindo o desempenho do nosso modelo. As métricas são geralmente calculadas sobre todos os dados no final de cada época. No mínimo, queremos monitorar se nossa perda está diminuindo com o tempo. No entanto, muitas vezes queremos uma métrica mais amigável, como a precisão. Ao usar a API Layers, você pode fornecer um identificador de string de uma métrica existente (como 'accuracy' ) ou qualquer função que receba um valor previsto e um valor verdadeiro e retorne uma pontuação. Veja uma lista de métricas disponíveis em nossos documentos de API.

Quando você decidir, compile um LayersModel chamando model.compile() com as opções fornecidas:

model.compile({
  optimizer: 'sgd',
  loss: 'categoricalCrossentropy',
  metrics: ['accuracy']
});

Durante a compilação, o modelo fará algumas validações para garantir que as opções escolhidas sejam compatíveis entre si.

Treinamento

Existem duas maneiras de treinar um LayersModel :

  • Usando model.fit() e fornecendo os dados como um grande tensor.
  • Usando model.fitDataset() e fornecendo os dados por meio de um objeto Dataset .

modelo.fit()

Se o seu conjunto de dados cabe na memória principal e está disponível como um único tensor, você pode treinar um modelo chamando o método fit() :

// Generate dummy data.
const data = tf.randomNormal([100, 784]);
const labels = tf.randomUniform([100, 10]);

function onBatchEnd(batch, logs) {
  console.log('Accuracy', logs.acc);
}

// Train for 5 epochs with batch size of 32.
model.fit(data, labels, {
   epochs: 5,
   batchSize: 32,
   callbacks: {onBatchEnd}
 }).then(info => {
   console.log('Final accuracy', info.history.acc);
 });

Nos bastidores, model.fit() pode fazer muito por nós:

  • Divide os dados em um conjunto de treinamento e validação e usa o conjunto de validação para medir o progresso durante o treinamento.
  • Embaralha os dados, mas somente após a divisão. Para estar seguro, você deve embaralhar previamente os dados antes de passá-los para fit() .
  • Divide o tensor de dados grandes em tensores menores de tamanho batchSize.
  • Chama optimizer.minimize() enquanto calcula a perda do modelo em relação ao lote de dados.
  • Ele pode notificá-lo no início e no final de cada época ou lote. No nosso caso, somos notificados ao final de cada lote usando a opção callbacks.onBatchEnd . Outras opções incluem: onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd e onBatchBegin .
  • Ele cede ao thread principal para garantir que as tarefas enfileiradas no loop de eventos JS possam ser tratadas em tempo hábil.

Para obter mais informações, consulte a documentação de fit() . Observe que se você optar por usar a API Core, terá que implementar essa lógica sozinho.

modelo.fitDataset()

Se seus dados não couberem inteiramente na memória ou estiverem sendo transmitidos, você poderá treinar um modelo chamando fitDataset() , que usa um objeto Dataset . Aqui está o mesmo código de treinamento, mas com um conjunto de dados que envolve uma função geradora:

function* data() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomNormal([784]);
 }
}

function* labels() {
 for (let i = 0; i < 100; i++) {
   // Generate one sample at a time.
   yield tf.randomUniform([10]);
 }
}

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// We zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

// Train the model for 5 epochs.
model.fitDataset(ds, {epochs: 5}).then(info => {
 console.log('Accuracy', info.history.acc);
});

Para obter mais informações sobre conjuntos de dados, consulte a documentação de model.fitDataset() .

Previsão de novos dados

Depois que o modelo for treinado, você pode chamar model.predict() para fazer previsões sobre dados não vistos:

// Predict 3 random samples.
const prediction = model.predict(tf.randomNormal([3, 784]));
prediction.print();

API principal

Anteriormente, mencionamos que existem duas maneiras de treinar um modelo de aprendizado de máquina no TensorFlow.js.

A regra geral é tentar usar a API Layers primeiro, uma vez que ela é modelada a partir da bem adotada API Keras. A API Layers também oferece várias soluções prontas para uso, como inicialização de peso, serialização de modelo, treinamento de monitoramento, portabilidade e verificação de segurança.

Você pode querer usar a API Core sempre que:

  • Você precisa de flexibilidade ou controle máximo.
  • E você não precisa de serialização ou pode implementar sua própria lógica de serialização.

Para obter mais informações sobre esta API, leia a seção "API Core" no guia Modelos e Camadas .

O mesmo modelo acima escrito usando a API Core é assim:

// The weights and biases for the two dense layers.
const w1 = tf.variable(tf.randomNormal([784, 32]));
const b1 = tf.variable(tf.randomNormal([32]));
const w2 = tf.variable(tf.randomNormal([32, 10]));
const b2 = tf.variable(tf.randomNormal([10]));

function model(x) {
  return x.matMul(w1).add(b1).relu().matMul(w2).add(b2);
}

Além da API Layers, a API Data também funciona perfeitamente com a API Core. Vamos reutilizar o conjunto de dados que definimos anteriormente na seção model.fitDataset() , que embaralha e agrupa em lote para nós:

const xs = tf.data.generator(data);
const ys = tf.data.generator(labels);
// Zip the data and labels together, shuffle and batch 32 samples at a time.
const ds = tf.data.zip({xs, ys}).shuffle(100 /* bufferSize */).batch(32);

Vamos treinar o modelo:

const optimizer = tf.train.sgd(0.1 /* learningRate */);
// Train for 5 epochs.
for (let epoch = 0; epoch < 5; epoch++) {
  await ds.forEachAsync(({xs, ys}) => {
    optimizer.minimize(() => {
      const predYs = model(xs);
      const loss = tf.losses.softmaxCrossEntropy(ys, predYs);
      loss.data().then(l => console.log('Loss', l));
      return loss;
    });
  });
  console.log('Epoch', epoch);
}

O código acima é a receita padrão ao treinar um modelo com a API Core:

  • Faça um loop sobre o número de épocas.
  • Dentro de cada época, faça um loop em seus lotes de dados. Ao usar um Dataset , dataset.forEachAsync() é uma maneira conveniente de fazer um loop em seus lotes.
  • Para cada lote, chame optimizer.minimize(f) , que executa f e minimiza sua saída calculando gradientes em relação às quatro variáveis ​​que definimos anteriormente.
  • f calcula a perda. Ele chama uma das funções de perda predefinidas usando a previsão do modelo e o valor verdadeiro.