Modelos de entrenamiento

Esta guía asume que ya has leído la guía de modelos y capas .

En TensorFlow.js hay dos formas de entrenar un modelo de aprendizaje automático:

  1. usando la API de capas con LayersModel.fit() o LayersModel.fitDataset() .
  2. usando la API principal con Optimizer.minimize() .

Primero, veremos la API de capas, que es una API de nivel superior para crear y entrenar modelos. Luego, mostraremos cómo entrenar el mismo modelo usando Core API.

Introducción

Un modelo de aprendizaje automático es una función con parámetros que se pueden aprender y que asigna una entrada a una salida deseada. Los parámetros óptimos se obtienen entrenando el modelo con datos.

La formación implica varios pasos:

  • Obtener un lote de datos para el modelo.
  • Pedirle al modelo que haga una predicción.
  • Comparando esa predicción con el valor "verdadero".
  • Decidir cuánto cambiar cada parámetro para que el modelo pueda hacer una mejor predicción en el futuro para ese lote.

Un modelo bien entrenado proporcionará un mapeo preciso desde la entrada hasta la salida deseada.

Parámetros del modelo

Definamos un modelo simple de 2 capas usando la API de capas:

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

En el fondo, los modelos tienen parámetros (a menudo denominados pesos ) que se pueden aprender mediante entrenamiento con datos. Imprimamos los nombres de los pesos asociados a este modelo y sus formas:

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

Obtenemos el siguiente resultado:

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

Hay 4 pesas en total, 2 por capa densa. Esto es de esperar ya que las capas densas representan una función que asigna el tensor de entrada x a un tensor de salida y mediante la ecuación y = Ax + b donde A (el núcleo) y b (el sesgo) son parámetros de la capa densa.

NOTA: De forma predeterminada, las capas densas incluyen un sesgo, pero puede excluirlo especificando {useBias: false} en las opciones al crear una capa densa.

model.summary() es un método útil si desea obtener una descripción general de su modelo y ver el número total de parámetros:

Capa (tipo) Forma de salida Parámetro #
denso_Dense1 (denso) [nulo,32] 25120
denso_Dense2 (denso) [nulo,10] 330
Parámetros totales: 25450
Parámetros entrenables: 25450
Parámetros no entrenables: 0

Cada peso en el modelo está respaldado por un objeto Variable . En TensorFlow.js, una Variable es un Tensor de punto flotante con un método adicional assign() utilizado para actualizar sus valores. La API de capas inicializa automáticamente los pesos utilizando las mejores prácticas. A modo de demostración, podríamos sobrescribir los pesos llamando assign() en las variables subyacentes:

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

Optimizador, pérdida y métrica.

Antes de realizar cualquier formación, debes decidir tres cosas:

  1. Un optimizador . El trabajo del optimizador es decidir cuánto cambiar cada parámetro en el modelo, dada la predicción actual del modelo. Al utilizar la API de capas, puede proporcionar un identificador de cadena de un optimizador existente (como 'sgd' o 'adam' ) o una instancia de la clase Optimizer .
  2. Una función de pérdida . Un objetivo que el modelo intentará minimizar. Su objetivo es dar un número único que indique "qué tan errónea" fue la predicción del modelo. La pérdida se calcula en cada lote de datos para que el modelo pueda actualizar sus ponderaciones. Al utilizar la API de capas, puede proporcionar un identificador de cadena de una función de pérdida existente (como 'categoricalCrossentropy' ) o cualquier función que tome un valor predicho y uno verdadero y devuelva una pérdida. Vea una lista de pérdidas disponibles en nuestros documentos API.
  3. Lista de métricas. De manera similar a las pérdidas, las métricas calculan un solo número, que resume qué tan bien está funcionando nuestro modelo. Las métricas generalmente se calculan sobre todos los datos al final de cada época. Como mínimo, queremos controlar que nuestras pérdidas disminuyan con el tiempo. Sin embargo, a menudo queremos una métrica más amigable para los humanos, como la precisión. Al utilizar la API de capas, puede proporcionar un identificador de cadena de una métrica existente (como 'accuracy' ) o cualquier función que tome un valor predicho y uno verdadero y devuelva una puntuación. Vea una lista de métricas disponibles en nuestros documentos API.

Cuando lo hayas decidido, compila un LayersModel llamando model.compile() con las opciones proporcionadas:

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

Durante la compilación, el modelo realizará algunas validaciones para asegurarse de que las opciones que eligió sean compatibles entre sí.

Capacitación

Hay dos formas de entrenar un LayersModel :

  • Usando model.fit() y proporcionando los datos como un tensor grande.
  • Usando model.fitDataset() y proporcionando los datos a través de un objeto Dataset .

modelo.fit()

Si su conjunto de datos cabe en la memoria principal y está disponible como un tensor único, puede entrenar un modelo llamando al 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);
 });

Debajo del capó, model.fit() puede hacer mucho por nosotros:

  • Divide los datos en un conjunto de entrenamiento y validación, y utiliza el conjunto de validación para medir el progreso durante el entrenamiento.
  • Mezcla los datos pero sólo después de la división. Para estar seguro, debes mezclar previamente los datos antes de pasarlos a fit() .
  • Divide el tensor de datos grande en tensores más pequeños de tamaño batchSize.
  • Llama optimizer.minimize() mientras calcula la pérdida del modelo con respecto al lote de datos.
  • Puede notificarle sobre el inicio y el final de cada época o lote. En nuestro caso, se nos notifica al final de cada lote mediante la opción callbacks.onBatchEnd . Otras opciones incluyen: onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd y onBatchBegin .
  • Cede al hilo principal para garantizar que las tareas en cola en el bucle de eventos JS puedan manejarse de manera oportuna.

Para obtener más información, consulte la documentación de fit() . Tenga en cuenta que si elige utilizar la API principal, deberá implementar esta lógica usted mismo.

modelo.fitDataset()

Si sus datos no caben completamente en la memoria o se están transmitiendo, puede entrenar un modelo llamando a fitDataset() , que toma un objeto Dataset . Aquí está el mismo código de entrenamiento pero con un conjunto de datos que incluye una función de generador:

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 obtener más información sobre conjuntos de datos, consulte la documentación de model.fitDataset() .

Predecir nuevos datos

Una vez que se haya entrenado el modelo, puede llamar model.predict() para hacer predicciones sobre datos invisibles:

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

API principal

Anteriormente mencionamos que hay dos formas de entrenar un modelo de aprendizaje automático en TensorFlow.js.

La regla general es intentar utilizar primero la API de capas, ya que está modelada a partir de la bien adoptada API de Keras. La API de Layers también ofrece varias soluciones listas para usar, como inicialización de peso, serialización de modelos, capacitación en monitoreo, portabilidad y verificación de seguridad.

Es posible que desee utilizar la API principal siempre que:

  • Necesita la máxima flexibilidad o control.
  • Y no necesita serialización o puede implementar su propia lógica de serialización.

Para obtener más información sobre esta API, lea la sección "API principal" en la guía Modelos y capas .

El mismo modelo escrito anteriormente usando Core API se ve así:

// 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);
}

Además de la API de capas, la API de datos también funciona a la perfección con la API principal. Reutilicemos el conjunto de datos que definimos anteriormente en la sección model.fitDataset() , que realiza la mezcla y el procesamiento por lotes por nosotros:

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);

Entrenemos el 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);
}

El código anterior es la receta estándar al entrenar un modelo con Core API:

  • Recorra el número de épocas.
  • Dentro de cada época, recorra sus lotes de datos. Cuando se utiliza un Dataset , dataset.forEachAsync() es una forma conveniente de recorrer sus lotes.
  • Para cada lote, llame optimizer.minimize(f) , que ejecuta f y minimiza su salida calculando gradientes con respecto a las cuatro variables que definimos anteriormente.
  • f calcula la pérdida. Llama a una de las funciones de pérdida predefinidas utilizando la predicción del modelo y el valor real.