Modèles de formation

Ce guide suppose que vous avez déjà lu le guide des modèles et des calques .

Dans TensorFlow.js, il existe deux manières d'entraîner un modèle d'apprentissage automatique :

  1. en utilisant l'API Layers avec LayersModel.fit() ou LayersModel.fitDataset() .
  2. en utilisant l'API Core avec Optimizer.minimize() .

Tout d’abord, nous examinerons l’API Layers, qui est une API de niveau supérieur pour la création et la formation de modèles. Ensuite, nous montrerons comment entraîner le même modèle à l’aide de l’API Core.

Introduction

Un modèle d'apprentissage automatique est une fonction avec des paramètres apprenables qui mappent une entrée à une sortie souhaitée. Les paramètres optimaux sont obtenus en entraînant le modèle sur les données.

La formation comporte plusieurs étapes :

  • Obtenir un lot de données sur le modèle.
  • Demander au modèle de faire une prédiction.
  • Comparer cette prédiction avec la « vraie » valeur.
  • Décider dans quelle mesure modifier chaque paramètre afin que le modèle puisse faire une meilleure prédiction à l'avenir pour ce lot.

Un modèle bien entraîné fournira une cartographie précise de l’entrée à la sortie souhaitée.

Paramètres du modèle

Définissons un modèle simple à 2 couches à l'aide de l'API Layers :

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

Sous le capot, les modèles comportent des paramètres (souvent appelés poids ) qui peuvent être appris par entraînement sur les données. Imprimons les noms des poids associés à ce modèle et leurs formes :

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

Nous obtenons le résultat suivant :

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

Il y a 4 poids au total, 2 par couche dense. Ceci est attendu puisque les couches denses représentent une fonction qui mappe le tenseur d'entrée x à un tenseur de sortie y via l'équation y = Ax + bA (le noyau) et b (le biais) sont des paramètres de la couche dense.

REMARQUE : Par défaut, les calques denses incluent un biais, mais vous pouvez l'exclure en spécifiant {useBias: false} dans les options lors de la création d'un calque dense.

model.summary() est une méthode utile si vous souhaitez avoir un aperçu de votre modèle et voir le nombre total de paramètres :

Calque (type) Forme de sortie Paramètre #
dense_Dense1 (Dense) [nul,32] 25120
dense_Dense2 (Dense) [nul,10] 330
Paramètres totaux : 25 450
Paramètres entraînables : 25 450
Paramètres non entraînables : 0

Chaque poids du modèle est backend par un objet Variable . Dans TensorFlow.js, une Variable est un Tensor à virgule flottante avec une méthode supplémentaire assign() utilisée pour mettre à jour ses valeurs. L'API Layers initialise automatiquement les pondérations en utilisant les meilleures pratiques. Par souci de démonstration, nous pourrions écraser les poids en appelant assign() sur les variables sous-jacentes :

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

Optimiseur, perte et métrique

Avant de suivre une formation, vous devez décider de trois choses :

  1. Un optimiseur . Le travail de l'optimiseur est de décider dans quelle mesure modifier chaque paramètre du modèle, compte tenu de la prédiction actuelle du modèle. Lorsque vous utilisez l'API Layers, vous pouvez fournir soit un identifiant de chaîne d'un optimiseur existant (tel que 'sgd' ou 'adam' ), soit une instance de la classe Optimizer .
  2. Une fonction de perte . Un objectif que le modèle va tenter de minimiser. Son objectif est de donner un chiffre unique indiquant « à quel point » la prédiction du modèle était erronée. La perte est calculée sur chaque lot de données afin que le modèle puisse mettre à jour ses pondérations. Lorsque vous utilisez l'API Layers, vous pouvez fournir soit un identifiant de chaîne d'une fonction de perte existante (telle que 'categoricalCrossentropy' ), soit toute fonction qui prend une valeur prédite et une valeur vraie et renvoie une perte. Consultez la liste des pertes disponibles dans nos documents API.
  3. Liste des métriques. Semblables aux pertes, les métriques calculent un nombre unique, résumant les performances de notre modèle. Les métriques sont généralement calculées sur l'ensemble des données à la fin de chaque époque. À tout le moins, nous voulons veiller à ce que nos pertes diminuent avec le temps. Cependant, nous souhaitons souvent une mesure plus conviviale, telle que la précision. Lorsque vous utilisez l'API Layers, vous pouvez fournir soit un identifiant de chaîne d'une métrique existante (telle que 'accuracy' ), soit toute fonction qui prend une valeur prédite et une valeur vraie et renvoie un score. Consultez la liste des métriques disponibles dans nos documents API.

Lorsque vous avez décidé, compilez un LayersModel en appelant model.compile() avec les options fournies :

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

Lors de la compilation, le modèle effectuera une validation pour s'assurer que les options que vous avez choisies sont compatibles entre elles.

Entraînement

Il existe deux manières d'entraîner un LayersModel :

  • Utiliser model.fit() et fournir les données sous la forme d'un grand tenseur.
  • Utiliser model.fitDataset() et fournir les données via un objet Dataset .

modèle.fit()

Si votre ensemble de données tient dans la mémoire principale et est disponible sous la forme d'un seul tenseur, vous pouvez entraîner un modèle en appelant la méthode 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);
 });

Sous le capot, model.fit() peut faire beaucoup pour nous :

  • Divise les données en un ensemble d'entraînement et de validation, et utilise l'ensemble de validation pour mesurer les progrès pendant l'entraînement.
  • Mélange les données mais seulement après la division. Pour être sûr, vous devez pré-mélanger les données avant de les transmettre à fit() .
  • Divise le tenseur de données volumineux en tenseurs plus petits de taille batchSize.
  • Appelle optimizer.minimize() lors du calcul de la perte du modèle par rapport au lot de données.
  • Il peut vous avertir du début et de la fin de chaque époque ou lot. Dans notre cas, nous sommes avertis à la fin de chaque lot grâce à l'option callbacks.onBatchEnd . Les autres options incluent : onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd et onBatchBegin .
  • Il cède le pas au thread principal pour garantir que les tâches mises en file d'attente dans la boucle d'événements JS peuvent être traitées en temps opportun.

Pour plus d'informations, consultez la documentation de fit() . Notez que si vous choisissez d'utiliser l'API Core, vous devrez implémenter cette logique vous-même.

modèle.fitDataset()

Si vos données ne tiennent pas entièrement en mémoire ou sont diffusées en continu, vous pouvez entraîner un modèle en appelant fitDataset() , qui prend un objet Dataset . Voici le même code de formation mais avec un ensemble de données qui encapsule une fonction génératrice :

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

Pour plus d'informations sur les ensembles de données, consultez la documentation de model.fitDataset() .

Prédire de nouvelles données

Une fois le modèle entraîné, vous pouvez appeler model.predict() pour faire des prédictions sur des données invisibles :

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

API de base

Plus tôt, nous avons mentionné qu'il existe deux façons de former un modèle d'apprentissage automatique dans TensorFlow.js.

La règle générale est d'essayer d'abord d'utiliser l'API Layers, car elle est calquée sur l'API Keras bien adoptée. L'API Layers propose également diverses solutions prêtes à l'emploi telles que l'initialisation du poids, la sérialisation des modèles, la formation à la surveillance, la portabilité et le contrôle de sécurité.

Vous souhaiterez peut-être utiliser l'API Core à chaque fois :

  • Vous avez besoin d’un maximum de flexibilité ou de contrôle.
  • Et vous n'avez pas besoin de sérialisation et vous pouvez implémenter votre propre logique de sérialisation.

Pour plus d'informations sur cette API, lisez la section « API principale » dans le guide Modèles et couches .

Le même modèle que ci-dessus, écrit à l'aide de l'API Core, ressemble à ceci :

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

En plus de l'API Layers, l'API Data fonctionne également de manière transparente avec l'API Core. Réutilisons l'ensemble de données que nous avons défini plus tôt dans la section model.fitDataset() , qui effectue le brassage et le traitement par lots pour nous :

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

Entraîneons le modèle :

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

Le code ci-dessus est la recette standard lors de la formation d'un modèle avec l'API Core :

  • Bouclez sur le nombre d’époques.
  • À l’intérieur de chaque époque, parcourez vos lots de données. Lorsque vous utilisez un Dataset , dataset.forEachAsync() est un moyen pratique de parcourir vos lots.
  • Pour chaque lot, appelez optimizer.minimize(f) , qui exécute f et minimise sa sortie en calculant les gradients par rapport aux quatre variables que nous avons définies précédemment.
  • f calcule la perte. Il appelle l'une des fonctions de perte prédéfinies en utilisant la prédiction du modèle et la valeur réelle.