web-dev-qa-db-fra.com

Implémentation de réseau neuronal personnalisée sur MNIST à l'aide de Tensorflow 2.0?

J'ai essayé d'écrire une implémentation personnalisée d'un réseau neuronal de base avec deux couches cachées sur le jeu de données MNIST en utilisant *TensorFlow 2.0 beta* mais je ne sais pas ce qui a mal tourné ici, mais ma perte de formation et précision semble coincé à 1,5 et autour 85 respectivement. Mais si je construis le Keras en utilisant j'obtenais une très faible perte d'entraînement et une précision supérieure à 95% avec seulement 8-10 époques.

Je crois que je ne mets pas à jour mes poids ou quelque chose? Dois-je donc affecter mes nouveaux poids que je calcule dans les dos de la fonction backprop à leurs poids/variables de biais respectifs?

J'apprécie vraiment que quelqu'un puisse m'aider avec ceci et ces quelques autres questions que j'ai mentionnées ci-dessous.

Quelques questions supplémentaires :

1) Comment ajouter un abandon et couche de normalisation par lots dans cette coutume la mise en oeuvre? ( c'est-à-dire le faire fonctionner à la fois pour le train et le temps d'essai)

2) Comment puis-je utiliser les rappels dans ce code? c'est-à-dire (en utilisant les rappels EarlyStopping et ModelCheckpoint)

3) Y a-t-il autre chose dans mon code ci-dessous que je peux optimiser davantage dans ce code comme peut-être utiliser tensorflow 2.x @ tf.function decorator etc.)

4) Je voudrais également extraire les poids finaux que j'obtiens pour tracer et vérifier leurs distributions. Pour enquêter sur des problèmes tels que la disparition ou l'explosion du gradient. (Par exemple: peut-être Tensorboard)

5) Je veux également de l'aide pour écrire ce code de manière plus généralisée afin de pouvoir facilement implémenter d'autres réseaux comme ConvNets (c'est-à-dire Conv, MaxPool, etc. ) basé sur ce code facilement.

Voici mon code complet pour une reproductibilité facile :

Remarque: Je sais que je peux utiliser des API de haut niveau comme Keras pour construire le modèle beaucoup plus facilement mais ce n'est pas mon objectif ici. Veuillez comprendre.

import numpy as np
import os
import logging
logging.getLogger('tensorflow').setLevel(logging.ERROR)
import tensorflow as tf
import tensorflow_datasets as tfds

(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

# reshaping
x_train = tf.reshape(x_train, shape=(x_train.shape[0], 784))
x_test  = tf.reshape(x_test, shape=(x_test.shape[0], 784))

ds_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# rescaling
ds_train = ds_train.map(lambda x, y: (tf.cast(x, tf.float32)/255.0, y))

class Model(object):
    def __init__(self, hidden1_size, hidden2_size, device=None):
        # layer sizes along with input and output
        self.input_size, self.output_size, self.device = 784, 10, device
        self.hidden1_size, self.hidden2_size = hidden1_size, hidden2_size
        self.lr_rate = 1e-03

        # weights initializationg
        self.glorot_init = tf.initializers.glorot_uniform(seed=42)
        # weights b/w input to hidden1 --> 1
        self.w_h1 = tf.Variable(self.glorot_init((self.input_size, self.hidden1_size)))
        # weights b/w hidden1 to hidden2 ---> 2
        self.w_h2 = tf.Variable(self.glorot_init((self.hidden1_size, self.hidden2_size)))
        # weights b/w hidden2 to output ---> 3
        self.w_out = tf.Variable(self.glorot_init((self.hidden2_size, self.output_size)))

        # bias initialization
        self.b1 = tf.Variable(self.glorot_init((self.hidden1_size,)))
        self.b2 = tf.Variable(self.glorot_init((self.hidden2_size,)))
        self.b_out = tf.Variable(self.glorot_init((self.output_size,)))

        self.variables = [self.w_h1, self.b1, self.w_h2, self.b2, self.w_out, self.b_out]


    def feed_forward(self, x):
        if self.device is not None:
            with tf.device('gpu:0' if self.device=='gpu' else 'cpu'):
                # layer1
                self.layer1 = tf.nn.sigmoid(tf.add(tf.matmul(x, self.w_h1), self.b1))
                # layer2
                self.layer2 = tf.nn.sigmoid(tf.add(tf.matmul(self.layer1,
                                                             self.w_h2), self.b2))
                # output layer
                self.output = tf.nn.softmax(tf.add(tf.matmul(self.layer2,
                                                             self.w_out), self.b_out))
        return self.output

    def loss_fn(self, y_pred, y_true):
        self.loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y_true, 
                                                                  logits=y_pred)
        return tf.reduce_mean(self.loss)

    def acc_fn(self, y_pred, y_true):
        y_pred = tf.cast(tf.argmax(y_pred, axis=1), tf.int32)
        y_true = tf.cast(y_true, tf.int32)
        predictions = tf.cast(tf.equal(y_true, y_pred), tf.float32)
        return tf.reduce_mean(predictions)

    def backward_prop(self, batch_xs, batch_ys):
        optimizer = tf.keras.optimizers.Adam(learning_rate=self.lr_rate)
        with tf.GradientTape() as tape:
            predicted = self.feed_forward(batch_xs)
            step_loss = self.loss_fn(predicted, batch_ys)
        grads = tape.gradient(step_loss, self.variables)
        optimizer.apply_gradients(Zip(grads, self.variables))

n_shape = x_train.shape[0]
epochs = 20
batch_size = 128

ds_train = ds_train.repeat().shuffle(n_shape).batch(batch_size).prefetch(batch_size)

neural_net = Model(512, 256, 'gpu')

for Epoch in range(epochs):
    no_steps = n_shape//batch_size
    avg_loss = 0.
    avg_acc = 0.
    for (batch_xs, batch_ys) in ds_train.take(no_steps):
        preds = neural_net.feed_forward(batch_xs)
        avg_loss += float(neural_net.loss_fn(preds, batch_ys)/no_steps) 
        avg_acc += float(neural_net.acc_fn(preds, batch_ys) /no_steps)
        neural_net.backward_prop(batch_xs, batch_ys)
    print(f'Epoch: {Epoch}, Training Loss: {avg_loss}, Training ACC: {avg_acc}')

# output for 10 epochs:
Epoch: 0, Training Loss: 1.7005115111824125, Training ACC: 0.7603832868262543
Epoch: 1, Training Loss: 1.6052448933478445, Training ACC: 0.8524806404020637
Epoch: 2, Training Loss: 1.5905528008006513, Training ACC: 0.8664196092868224
Epoch: 3, Training Loss: 1.584107405738905, Training ACC: 0.8727630912326276
Epoch: 4, Training Loss: 1.5792385798413306, Training ACC: 0.8773203844903037
Epoch: 5, Training Loss: 1.5759121985174716, Training ACC: 0.8804754322627559
Epoch: 6, Training Loss: 1.5739163148682564, Training ACC: 0.8826455712551251
Epoch: 7, Training Loss: 1.5722616605926305, Training ACC: 0.8840812018606812
Epoch: 8, Training Loss: 1.569699136307463, Training ACC: 0.8867688354803249
Epoch: 9, Training Loss: 1.5679460542742163, Training ACC: 0.8885049475356936
18
user_6396

Je me suis demandé par où commencer avec votre multiquestion, et j'ai décidé de le faire avec une déclaration:

Votre code ne devrait certainement pas ressembler à cela et est loin des meilleures pratiques Tensorflow actuelles .

Désolé, mais le débogage étape par étape est une perte de temps pour tout le monde et ne profiterait à aucun de nous.

Passons maintenant au troisième point:

3) Y a-t-il autre chose dans mon code ci-dessous que je peux optimiser davantage dans ce code comme peut-être utiliser le décorateur tensorflow 2.x @ tf.function, etc.)

Oui, vous pouvez utiliser les fonctionnalités de tensorflow2.0 Et il semble que vous vous en éloignez (le décorateur tf.function Ne sert à rien ici en fait, laissez-le pour le moment).

Le fait de suivre de nouvelles directives atténuerait également vos problèmes avec votre 5ème point, à savoir:

5) Je veux également de l'aide pour écrire ce code d'une manière plus généralisée afin de pouvoir facilement implémenter d'autres réseaux comme ConvNets (c'est-à-dire Conv, MaxPool, etc.) basés sur ce code facilement.

car il est spécialement conçu pour cela. Après une petite introduction, je vais essayer de vous présenter ces concepts en quelques étapes:

1. Divisez votre programme en parties logiques

Tensorflow a fait beaucoup de mal en ce qui concerne la lisibilité du code; tout dans tf1.x était généralement regroupé en un seul endroit, les globaux suivis de la définition de la fonction suivie par un autre global ou peut-être le chargement des données, le tout dans le désordre. Ce n'est pas vraiment la faute des développeurs car la conception du système a encouragé ces actions.

Maintenant, dans tf2.0, Le programmeur est encouragé à diviser son travail de la même manière que la structure que l'on peut voir dans pytorch, chainer et d'autres cadres plus conviviaux.

1.1 Chargement des données

Vous étiez sur la bonne voie avec Tensorflow Datasets mais vous vous êtes détourné sans raison apparente.

Voici votre code avec des commentaires sur ce qui se passe:

# You already have tf.data.Dataset objects after load
(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

# But you are reshaping them in a strange manner...
x_train = tf.reshape(x_train, shape=(x_train.shape[0], 784))
x_test  = tf.reshape(x_test, shape=(x_test.shape[0], 784))

# And building from slices...
ds_train = tf.data.Dataset.from_tensor_slices((x_train, y_train))
# Unreadable rescaling (there are built-ins for that)

Vous pouvez facilement généraliser cette idée pour n'importe quel ensemble de données , placez-le dans un module séparé, dites datasets.py:

import tensorflow as tf
import tensorflow_datasets as tfds


class ImageDatasetCreator:
    @classmethod
    # More portable and readable than dividing by 255
    def _convert_image_dtype(cls, dataset):
        return dataset.map(
            lambda image, label: (
                tf.image.convert_image_dtype(image, tf.float32),
                label,
            )
        )

    def __init__(self, name: str, batch: int, cache: bool = True, split=None):
        # Load dataset, every dataset has default train, test split
        dataset = tfds.load(name, as_supervised=True, split=split)
        # Convert to float range
        try:
            self.train = ImageDatasetCreator._convert_image_dtype(dataset["train"])
            self.test = ImageDatasetCreator._convert_image_dtype(dataset["test"])
        except KeyError as exception:
            raise ValueError(
                f"Dataset {name} does not have train and test, write your own custom dataset handler."
            ) from exception

        if cache:
            self.train = self.train.cache()  # speed things up considerably
            self.test = self.test.cache()

        self.batch: int = batch

    def get_train(self):
        return self.train.shuffle().batch(self.batch).repeat()

    def get_test(self):
        return self.test.batch(self.batch).repeat()

Vous pouvez donc maintenant charger plus de mnist à l'aide d'une simple commande:

from datasets import ImageDatasetCreator

if __name__ == "__main__":
    dataloader = ImageDatasetCreator("mnist", batch=64, cache = True)
    train, test = dataloader.get_train(), dataloader.get_test()

Et vous pouvez désormais utiliser n'importe quel nom autre que mnist pour charger les jeux de données.

S'il vous plaît, arrêtez de faire tout ce qui concerne l'apprentissage en profondeur, vous êtes également programmeur .

1.2 Création de modèle

Puisque tf2.0 Il y a deux façons conseillées de procéder selon la complexité des modèles:

  • tensorflow.keras.models.Sequential - cette façon a été montrée par @ Stewart_R , pas besoin de réitérer ses points. Utilisé pour les modèles les plus simples (vous devez utiliser celui-ci avec votre feedforward).
  • Hériter tensorflow.keras.Model Et écrire un modèle personnalisé. Celui-ci doit être utilisé lorsque vous avez une sorte de logique à l'intérieur de votre module ou qu'il est plus compliqué (des choses comme ResNets, des réseaux à trajets multiples, etc.). Dans l'ensemble plus lisible et personnalisable.

Votre classe Model a essayé de ressembler à quelque chose comme ça, mais elle est repartie vers le sud; backprop ne fait définitivement pas partie du modèle lui-même, ni loss ni accuracy, les séparent en un autre module ou fonction, par défaut non un membre!

Cela dit, codons le réseau en utilisant la deuxième approche (vous devez placer ce code dans model.py Pour plus de brièveté). Avant cela, je vais coder la couche de feedforward YourDense à partir de zéro en héritant de tf.keras.Layers (Celle-ci pourrait aller dans le module layers.py):

import tensorflow as tf

class YourDense(tf.keras.layers.Layer):
    def __init__(self, units):
        # It's Python 3, you don't have to specify super parents explicitly
        super().__init__()
        self.units = units

    # Use build to create variables, as shape can be inferred from previous layers
    # If you were to create layers in __init__, one would have to provide input_shape
    # (same as it occurs in PyTorch for example)
    def build(self, input_shape):
        # You could use different initializers here as well
        self.kernel = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer="random_normal",
            trainable=True,
        )
        # You could define bias in __init__ as well as it's not input dependent
        self.bias = self.add_weight(shape=(self.units,), initializer="random_normal")
        # Oh, trainable=True is default

    def call(self, inputs):
        # Use overloaded operators instead of tf.add, better readability
        return tf.matmul(inputs, self.kernel) + self.bias

À propos de ton

1) Comment ajouter une couche Dropout et Batch Normalisation dans cette implémentation personnalisée? (c'est-à-dire le faire fonctionner à la fois pour le train et le temps d'essai)

Je suppose que vous souhaitez créer une implémentation personnalisée de ces couches. Sinon, vous pouvez simplement importer from tensorflow.keras.layers import Dropout Et l'utiliser où vous le souhaitez comme @ Leevo l'a souligné. Décrochage inversé avec un comportement différent pendant train et test ci-dessous:

class CustomDropout(layers.Layer):
    def __init__(self, rate, **kwargs):
        super().__init__(**kwargs)
        self.rate = rate

    def call(self, inputs, training=None):
        if training:
            # You could simply create binary mask and multiply here
            return tf.nn.dropout(inputs, rate=self.rate)
        # You would need to multiply by dropout rate if you were to do that
        return self.rate * inputs

Couches prises à partir d'ici et modifiées pour mieux correspondre au but de la présentation.

Maintenant, vous pouvez enfin créer votre modèle (simple double feedforward):

import tensorflow as tf

from layers import YourDense


class Model(tf.keras.Model):
    def __init__(self):
        super().__init__()
        # Use Sequential here for readability
        self.network = tf.keras.Sequential(
            [YourDense(100), tf.keras.layers.ReLU(), YourDense(10)]
        )

    def call(self, inputs):
        # You can use non-parametric layers inside call as well
        flattened = tf.keras.layers.Flatten()(inputs)
        return self.network(flattened)

Ofc, vous devez utiliser autant que possible les intégrés dans les implémentations générales.

Cette structure est assez extensible, donc généralisation aux réseaux convolutionnels, resnets, senets, tout ce qui devrait être fait via ce module . Vous pouvez en savoir plus ici .

Je pense que cela répond à votre 5ème point:

5) Je veux également de l'aide pour écrire ce code d'une manière plus généralisée afin de pouvoir facilement implémenter d'autres réseaux comme ConvNets (c'est-à-dire Conv, MaxPool, etc.) basés sur ce code facilement.

Dernière chose, vous devrez peut-être utiliser model.build(shape) pour construire le graphe de votre modèle.

model.build((None, 28, 28, 1))

Ce serait pour la forme d'entrée 28x28x1 Du MNIST, où None représente le lot.

1.3 Formation

Encore une fois, la formation pourrait se faire de deux manières distinctes:

  • Keras standard model.fit(dataset) - utile dans des tâches simples comme la classification
  • tf.GradientTape - des programmes de formation plus compliqués, l'exemple le plus frappant serait Generative Adversarial Networks , où deux modèles optimisent buts orthogonaux jouant au jeu minmax

Comme indiqué par @ Leevo encore une fois, si vous utilisez la deuxième méthode, vous ne pourrez pas simplement utiliser les rappels fournis par Keras, donc je vous conseille de vous en tenir à la première option dès que possible.

En théorie, vous pourriez appeler les fonctions de rappel manuellement comme on_batch_begin() et d'autres si nécessaire, mais ce serait lourd et je ne sais pas comment cela fonctionnerait.

En ce qui concerne la première option, vous pouvez utiliser des objets tf.data.Dataset Directement avec fit. Le voici présenté dans un autre module (de préférence train.py):

def train(
    model: tf.keras.Model,
    path: str,
    train: tf.data.Dataset,
    epochs: int,
    steps_per_Epoch: int,
    validation: tf.data.Dataset,
    steps_per_validation: int,
    stopping_epochs: int,
    optimizer=tf.optimizers.Adam(),
):
    model.compile(
        optimizer=optimizer,
        # I used logits as output from the last layer, hence this
        loss=tf.losses.SparseCategoricalCrossentropy(from_logits=True),
        metrics=[tf.metrics.SparseCategoricalAccuracy()],
    )

    model.fit(
        train,
        epochs=epochs,
        steps_per_Epoch=steps_per_Epoch,
        validation_data=validation,
        validation_steps=steps_per_validation,
        callbacks=[
            # Tensorboard logging
            tf.keras.callbacks.TensorBoard(
                pathlib.Path("logs")
                / pathlib.Path(datetime.datetime.now().strftime("%Y%m%d-%H%M%S")),
                histogram_freq=1,
            ),
            # Early stopping with best weights preserving
            tf.keras.callbacks.EarlyStopping(
                monitor="val_sparse_categorical_accuracy",
                patience=stopping_epochs,
                restore_best_weights=True,
            ),
        ],
    )
    model.save(path)

Une approche plus compliquée est très similaire (presque copier-coller) aux boucles de formation PyTorch, donc si vous les connaissez, elles ne devraient pas poser beaucoup de problème.

Vous pouvez trouver des exemples dans les documents tf2.0, Par exemple ici ou ici .

2. Autres choses

2.1 Questions sans réponse

4) Y a-t-il autre chose dans le code que je peux optimiser davantage dans ce code? c'est-à-dire (en utilisant le décorateur tensorflow 2.x @ tf.function, etc.)

Ci-dessus transforme déjà le modèle en graphiques, donc je ne pense pas qu'il serait avantageux de l'appeler dans ce cas. Et l'optimisation prématurée est la racine de tout mal, n'oubliez pas de mesurer votre code avant de le faire.

Vous gagneriez beaucoup plus avec une mise en cache correcte des données (comme décrit au début de # 1.1) et un bon pipeline plutôt que ceux-ci.

5) J'ai également besoin d'un moyen d'extraire tous mes poids finaux pour toutes les couches après l'entraînement afin de pouvoir les tracer et vérifier leurs distributions. Pour vérifier des problèmes tels que la disparition ou l'explosion du dégradé.

Comme indiqué par @ Leevo ci-dessus,

weights = model.get_weights()

Vous obtiendrait les poids. Vous pouvez les transformer en np.array Et tracer en utilisant seaborn, matplotlib, analyser, vérifier ou tout ce que vous voulez.

2.2 En résumé

Dans l'ensemble, votre main.py (Ou point d'entrée ou quelque chose de similaire) serait composé de ceci (plus ou moins):

from dataset import ImageDatasetCreator
from model import Model
from train import train

# You could use argparse for things like batch, epochs etc.
if __name__ == "__main__":
    dataloader = ImageDatasetCreator("mnist", batch=64, cache=True)
    train, test = dataloader.get_train(), dataloader.get_test()
    model = Model()
    model.build((None, 28, 28, 1))
    train(
        model, train, path epochs, test, len(train) // batch, len(test) // batch, ...
    )  # provide necessary arguments appropriately
    # Do whatever you want with those
    weights = model.get_weights()

Oh, rappelez-vous que les fonctions ci-dessus ne sont pas destinées au copier-coller et doivent être traitées plutôt comme des directives. Frappez-moi si vous avez des questions.

3. Questions des commentaires

3.1 Comment initialiser des couches personnalisées et intégrées

3.1.1 TLDR ce que vous allez lire

  • Fonction d'initialisation de Poisson personnalisée, mais elle prend trois arguments
  • tf.keras.initalization L'API a besoin de deux arguments (voir le dernier point dans leur documentation ), donc l'un est spécifié via Python lambda dans la couche personnalisée que nous avons déjà écrite
  • Un biais optionnel pour la couche est ajouté, qui peut être désactivé avec booléen

Pourquoi est-ce si inutilement compliqué? Pour montrer que dans tf2.0, Vous pouvez enfin utiliser les fonctionnalités de Python , plus de soucis graphiques, if au lieu de tf.cond Etc.

3.1.2 Du TLDR à la mise en œuvre

Les initialiseurs Keras peuvent être trouvés ici et la saveur de Tensorflow ici .

Veuillez noter les incohérences de l'API (lettres majuscules comme les classes, petites lettres avec des fonctions de type soulignement), en particulier dans tf2.0, Mais ce n'est pas la question.

Vous pouvez les utiliser en passant une chaîne (comme cela se fait dans YourDense ci-dessus) ou pendant la création d'objet.

Pour permettre l'initialisation personnalisée dans vos couches personnalisées, vous pouvez simplement ajouter un argument supplémentaire au constructeur (la classe tf.keras.Model Est toujours Python et c'est __init__ Devrait être utilisé comme Python).

Avant cela, je vais vous montrer comment créer une initialisation personnalisée:

# Poisson custom initialization because why not.
def my_dumb_init(shape, lam, dtype=None):
    return tf.squeeze(tf.random.poisson(shape, lam, dtype=dtype))

Remarquez que sa signature prend trois arguments, alors qu'elle ne devrait prendre que (shape, dtype). Pourtant, on peut "corriger" cela facilement en créant son propre calque, comme celui ci-dessous (étendu YourLinear):

import typing

import tensorflow as tf


class YourDense(tf.keras.layers.Layer):
    # It's still Python, use it as Python, that's the point of tf.2.0
    @classmethod
    def register_initialization(cls, initializer):
        # Set defaults if init not provided by user
        if initializer is None:
            # let's make the signature proper for init in tf.keras
            return lambda shape, dtype: my_dumb_init(shape, 1, dtype)
        return initializer

    def __init__(
        self,
        units: int,
        bias: bool = True,
        # can be string or callable, some typing info added as well...
        kernel_initializer: typing.Union[str, typing.Callable] = None,
        bias_initializer: typing.Union[str, typing.Callable] = None,
    ):
        super().__init__()
        self.units: int = units
        self.kernel_initializer = YourDense.register_initialization(kernel_initializer)
        if bias:
            self.bias_initializer = YourDense.register_initialization(bias_initializer)
        else:
            self.bias_initializer = None

    def build(self, input_shape):
        # Simply pass your init here
        self.kernel = self.add_weight(
            shape=(input_shape[-1], self.units),
            initializer=self.kernel_initializer,
            trainable=True,
        )
        if self.bias_initializer is not None:
            self.bias = self.add_weight(
                shape=(self.units,), initializer=self.bias_initializer
            )
        else:
            self.bias = None

    def call(self, inputs):
        weights = tf.matmul(inputs, self.kernel)
        if self.bias is not None:
            return weights + self.bias

J'ai ajouté my_dumb_initialization Par défaut (si l'utilisateur n'en fournit pas) et j'ai rendu le biais facultatif avec l'argument bias. Notez que vous pouvez utiliser if librement tant qu'il ne dépend pas des données. S'il est (ou dépend de tf.Tensor D'une manière ou d'une autre), il faut utiliser @tf.function Décorateur qui change le flux de Python en son homologue tensorflow (par exemple if pour tf.cond).

Voir ici pour en savoir plus sur les autographes, c'est très facile à suivre.

Si vous souhaitez incorporer les modifications d'initialisation ci-dessus dans votre modèle, vous devez créer l'objet approprié et c'est tout.

... # Previous of code Model here
self.network = tf.keras.Sequential(
    [
        YourDense(100, bias=False, kernel_initializer="lecun_uniform"),
        tf.keras.layers.ReLU(),
        YourDense(10, bias_initializer=tf.initializers.Ones()),
    ]
)
... # and the same afterwards

Avec les couches tf.keras.layers.Dense Intégrées, on peut faire la même chose (les noms des arguments diffèrent, mais l'idée tient).

3.2 Différenciation automatique en utilisant tf.GradientTape

3.2.1 Intro

Le point de tf.GradientTape Est de permettre aux utilisateurs normaux Python de contrôler le flux et le calcul du gradient des variables par rapport à une autre variable.

Exemple tiré de ici mais divisé en morceaux séparés:

def f(x, y):
  output = 1.0
  for i in range(y):
    if i > 1 and i < 5:
      output = tf.multiply(output, x)
  return output

Régulier python avec les instructions de contrôle de flux for et if

def grad(x, y):
  with tf.GradientTape() as t:
    t.watch(x)
    out = f(x, y)
  return t.gradient(out, x)

En utilisant une bande de dégradé, vous pouvez enregistrer toutes les opérations sur Tensors (et leurs états intermédiaires également) et les "lire" en arrière (effectuer une différenciation automatique en arrière en utilisant la règle de chaing).

Chaque Tensor dans tf.GradientTape() gestionnaire de contexte est enregistré automatiquement. Si certains Tensor sont hors de portée, utilisez la méthode watch() comme on peut le voir ci-dessus.

Enfin, gradient de output par rapport à x (l'entrée est retournée).

3.2.2 Connexion avec l'apprentissage en profondeur

Ce qui a été décrit ci-dessus est l'algorithme backpropagation. Les gradients par rapport aux sorties sont calculés pour chaque nœud du réseau (ou plutôt pour chaque couche). Ces gradients sont ensuite utilisés par divers optimiseurs pour effectuer des corrections et ainsi il se répète.

Continuons et supposons que votre tf.keras.Model, Instance d'optimiseur, tf.data.Dataset Et fonction de perte sont déjà configurés.

On peut définir une classe Trainer qui effectuera une formation pour nous. Veuillez lire les commentaires dans le code en cas de doute :

class Trainer:
    def __init__(self, model, optimizer, loss_function):
        self.model = model
        self.loss_function = loss_function
        self.optimizer = optimizer
        # You could pass custom metrics in constructor
        # and adjust train_step and test_step accordingly
        self.train_loss = tf.keras.metrics.Mean(name="train_loss")
        self.test_loss = tf.keras.metrics.Mean(name="train_loss")

    def train_step(self, x, y):
        # Setup tape
        with tf.GradientTape() as tape:
            # Get current predictions of network
            y_pred = self.model(x)
            # Calculate loss generated by predictions
            loss = self.loss_function(y, y_pred)
        # Get gradients of loss w.r.t. EVERY trainable variable (iterable returned)
        gradients = tape.gradient(loss, self.model.trainable_variables)
        # Change trainable variable values according to gradient by applying optimizer policy
        self.optimizer.apply_gradients(Zip(gradients, self.model.trainable_variables))
        # Record loss of current step
        self.train_loss(loss)

    def train(self, dataset):
        # For N epochs iterate over dataset and perform train steps each time
        for x, y in dataset:
            self.train_step(x, y)

    def test_step(self, x, y):
        # Record test loss separately
        self.test_loss(self.loss_function(y, self.model(x)))

    def test(self, dataset):
        # Iterate over whole dataset
        for x, y in dataset:
            self.test_step(x, y)

    def __str__(self):
        # You need Python 3.7 with f-string support
        # Just return metrics
        return f"Loss: {self.train_loss.result()}, Test Loss: {self.test_loss.result()}"

Maintenant, vous pouvez utiliser cette classe dans votre code comme suit:

EPOCHS = 5

# model, optimizer, loss defined beforehand
trainer = Trainer(model, optimizer, loss)
for _ in range(EPOCHS):
    trainer.train(train_dataset) # Same for training and test datasets
    trainer.test(test_dataset)
    print(f"Epoch {Epoch}: {trainer})")

L'impression vous dirait la perte d'entraînement et de test pour chaque époque. Vous pouvez mélanger formation et test comme vous le souhaitez (par exemple, 5 périodes pour la formation et 1 test), vous pouvez ajouter différentes mesures, etc.

Voir ici si vous voulez une approche orientée non-OOP (IMO moins lisible, mais à chacun sa propre).

20
Szymon Maszke

Aussi, s'il y a quelque chose que je pourrais améliorer dans le code, faites-le moi savoir également.

Adoptez l'API de haut niveau pour quelque chose comme ça. Vous pouvez le faire en quelques lignes de code et il est beaucoup plus facile de déboguer, de lire et de raisonner:

(x_train, y_train), (x_test, y_test) = tfds.load('mnist', split=['train', 'test'], 
                                                  batch_size=-1, as_supervised=True)

x_train = tf.cast(tf.reshape(x_train, shape=(x_train.shape[0], 784)), tf.float32)
x_test  = tf.cast(tf.reshape(x_test, shape=(x_test.shape[0], 784)), tf.float32)

model = tf.keras.models.Sequential([
  tf.keras.layers.Dense(512, activation='sigmoid'),
  tf.keras.layers.Dense(256, activation='sigmoid'),
  tf.keras.layers.Dense(10, activation='softmax')
])
model.fit(x_train, y_train, epochs=5)
model.evaluate(x_test, y_test)
5
Stewart_R

J'ai essayé d'écrire une implémentation personnalisée d'un réseau neuronal de base avec deux couches cachées sur le jeu de données MNIST en utilisant tensorflow 2.0 beta, mais je ne sais pas ce qui s'est mal passé ici, mais ma perte d'entraînement et ma précision semblent être bloquées respectivement à 1,5 et environ 85.

Où est la partie formation? Formation des modèles TF 2.0 soit la syntaxe de Keras ou Exécution désirée avec tf.GradientTape(). Pouvez-vous coller le code avec des couches conv et denses, et comment l'avez-vous formé?


D'autres questions:

1) Comment ajouter une couche Dropout dans cette implémentation personnalisée? c'est-à-dire (le faire fonctionner à la fois pour le train et le temps d'essai)

Vous pouvez ajouter un calque Dropout () avec:

from tensorflow.keras.layers import Dropout

Et puis vous l'insérez dans un modèle Sequential () juste avec:

Dropout(dprob)     # where dprob = dropout probability

2) Comment ajouter la normalisation par lots dans ce code?

Comme avant, avec:

from tensorflow.keras.layers import BatchNormalization

Le choix de pour mettre batchnorm dans le modèle, eh bien, c'est à vous. Il n'y a pas de règle d'or, je vous suggère de faire des expériences. Avec ML, c'est toujours un processus d'essai et d'erreur.


3) Comment utiliser les rappels dans ce code? c'est-à-dire (en utilisant les rappels EarlyStopping et ModelCheckpoint)

Si vous vous entraînez en utilisant la syntaxe de Keras, vous pouvez simplement l'utiliser. Veuillez vérifier ceci tutoriel très complet sur la façon de l'utiliser. Cela ne prend que quelques lignes de code. Si vous exécutez un modèle en exécution désirée , vous devez implémenter ces techniques vous-même, avec votre propre code. C'est plus complexe, mais cela vous donne également plus de liberté dans la mise en œuvre.


4) Y a-t-il autre chose dans le code que je peux optimiser davantage dans ce code? c'est-à-dire (en utilisant le décorateur tensorflow 2.x @ tf.function, etc.)

Ça dépend. Si vous utilisez la syntaxe Keras, je ne pense pas que vous ayez besoin d'en ajouter davantage. Dans le cas où vous entraînez le modèle à l'exécution Désireuse, je vous suggère d'utiliser le @tf.function décorateur sur une fonction pour accélérer un peu. Vous pouvez voir un exemple pratique de TF 2.0 sur la façon d'utiliser le décorateur dans ce Notebook .

En dehors de cela, je vous suggère de jouer avec les techniques de régularisation telles que les initialisations de poids, la perte L1-L2, etc.


5) J'ai également besoin d'un moyen d'extraire tous mes poids finaux pour toutes les couches après l'entraînement afin de pouvoir les tracer et vérifier leurs distributions. Pour vérifier des problèmes tels que la disparition ou l'explosion du dégradé.

Une fois le modèle formé, vous pouvez extraire ses poids avec:

weights = model.get_weights()

ou:

weights = model.trainable_weights

Si vous ne souhaitez en garder que des entraînables.


6) Je veux également de l'aide pour écrire ce code de manière plus généralisée afin de pouvoir facilement implémenter d'autres réseaux comme le réseau convolutionnel (c'est-à-dire Conv, MaxPool, etc.) basé sur ce code facilement.

Vous pouvez alors regrouper tout votre code dans une fonction. À la fin de ce Notebook j'ai fait quelque chose comme ça (c'est pour un NN à action directe, ce qui est beaucoup plus simple, mais c'est un début et vous pouvez changer le code selon vos besoins).

---

[~ # ~] mise à jour [~ # ~] :

Veuillez vérifier mon implémentation TensorFlow 2.0 d'un classificateur CNN . Cela peut être un indice utile: il est formé sur le jeu de données Fashion MNIST , ce qui le rend très similaire à votre tâche.

3
Leevo