Modelos de entrenamiento

Esta guía asume que ya ha 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. utilizando 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 utilizando la API principal.

Introducción

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

El entrenamiento implica varios pasos:

  • Obtener un lote de datos para el modelo.
  • Pedir 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'}),
 ]
});

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

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

Obtenemos la siguiente salida:

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

Hay 4 pesos 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 a través de 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 la cantidad total de parámetros:

Capa (tipo) Forma de salida Parámetro #
dense_Dense1 (Denso) [nulo, 32] 25120
dense_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 de assign() que se usa para actualizar sus valores. La API de capas inicializa automáticamente los pesos utilizando las mejores prácticas. En aras de la demostración, podríamos sobrescribir los pesos llamando a 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);
});

Optimizer, pérdida y métrica

Antes de realizar cualquier entrenamiento, debe 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 usar 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 solo número para "cuán equivocada" fue la predicción del modelo. La pérdida se calcula en cada lote de datos para que el modelo pueda actualizar sus pesos. Al usar 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 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 los datos completos al final de cada época. Como mínimo, queremos monitorear que nuestra pérdida se reduzca con el tiempo. Sin embargo, a menudo queremos una métrica más amigable para los humanos, como la precisión. Al usar 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 verdadero y devuelva una puntuación. Vea una lista de métricas disponibles en nuestros documentos de API.

Cuando haya decidido, compile un LayersModel llamando a model.compile() con las opciones proporcionadas:

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

Durante la compilación, el modelo hará una validación para asegurarse de que las opciones que elija sean compatibles entre sí.

Capacitación

Hay dos formas de entrenar un LayersModel :

  • Usando model.fit() y proporcionando los datos como un gran tensor.
  • 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);
 });

Bajo el capó, model.fit() puede hacer mucho por nosotros:

  • Divide los datos en un conjunto de entrenamiento y validación, y usa el conjunto de validación para medir el progreso durante el entrenamiento.
  • Mezcla los datos pero solo después de la división. Para estar seguro, debe mezclar previamente los datos antes de pasarlos a fit() .
  • Divide el tensor de datos grandes 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 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 se puedan manejar de manera oportuna.

Para obtener más información, consulta la documentación de fit() . Tenga en cuenta que si elige usar la API central, tendrá que implementar esta lógica usted mismo.

modelo.fitDataset()

Si sus datos no caben por completo en la memoria o se transmiten, 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 envuelve 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 ha entrenado el modelo, puede llamar a model.predict() para hacer predicciones sobre datos no vistos:

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

API central

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

La regla general es tratar de usar la API de capas primero, ya que está modelada a partir de la bien adoptada API de Keras. La API de capas 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 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 que el anterior escrito usando la API Core 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 hace 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 cuando se entrena un modelo con Core API:

  • Recorre el número de épocas.
  • Dentro de cada época, recorra sus lotes de datos. Cuando se usa un conjunto de 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.