Assistez au symposium Women in ML le 7 décembre Inscrivez-vous maintenant

Modèles de formation

Restez organisé à l'aide des collections Enregistrez et classez les contenus selon vos préférences.

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

Dans TensorFlow.js, il existe deux façons d'entraîner un modèle de machine learning :

  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 former 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 mappe 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 dans le modèle.
  • Demander au modèle de faire une prédiction.
  • Comparer cette prédiction avec la "vraie" valeur.
  • Décider combien modifier chaque paramètre afin que le modèle puisse faire une meilleure prédiction à l'avenir pour ce lot.

Un modèle bien formé 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 ont des paramètres (souvent appelés pondérations ) qui peuvent être appris en s'entraînant 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 la sortie suivante :

> 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 sur 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 obtenir un aperçu de votre modèle et voir le nombre total de paramètres :

Couche (type) Forme de sortie Paramètre #
dense_Dense1 (Dense) [null,32] 25120
dense_Dense2 (Dense) [null,10] 330
Nombre total de paramètres : 25 450
Paramètres pouvant être entraînés : 25 450
Paramètres non entraînables : 0

Chaque pondération du modèle est backend par un objet Variable . Dans TensorFlow.js, une Variable est un tenseur à virgule Tensor 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. Pour des raisons 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 faire une formation, vous devez décider de trois choses :

  1. Un optimiseur . Le travail de l'optimiseur consiste à décider de l'ampleur de la modification de 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 tentera de minimiser. Son objectif est de donner un nombre unique pour "à 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 poids. 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 la performance 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 surveiller que notre perte diminue avec le temps. Cependant, nous voulons souvent une métrique 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 (comme 'accuracy' ), soit n'importe quelle 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 sur l'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 les unes avec les autres.

Entraînement

Il existe deux manières de former un LayersModel :

  • En utilisant model.fit() et en fournissant les données sous la forme d'un grand tenseur.
  • Utilisation model.fitDataset() et fourniture des données via un objet Dataset .

model.fit()

Si votre jeu de données tient dans la mémoire principale et est disponible en tant que tenseur unique, 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 la formation.
  • Mélange les données mais seulement après le fractionnement. Pour plus de sécurité, vous devez pré-mélanger les données avant de les transmettre à fit() .
  • Divise le grand tenseur de données en plus petits tenseurs de taille batchSize.
  • Appelle l' optimizer.minimize() tout en calculant la perte du modèle par rapport au lot de données.
  • Il peut vous informer du début et de la fin de chaque époque ou lot. Dans notre cas, nous sommes avertis à la fin de chaque lot à l'aide de l'option callbacks.onBatchEnd . Les autres options incluent : onTrainBegin , onTrainEnd , onEpochBegin , onEpochEnd et onBatchBegin .
  • Il cède au thread principal pour s'assurer 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.

model.fitDataset()

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

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 formé, 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 la vérification de la sécurité.

Vous pouvez utiliser l'API Core dans les cas suivants :

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

Pour plus d'informations sur cette API, lisez la section "Core API" dans le guide Models and Layers .

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 précédemment 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înons 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 :

  • Boucle 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 boucler sur vos lots.
  • Pour chaque lot, appelez optimizer.minimize(f) , qui exécute f et minimise sa sortie en calculant des 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.