Pourquoi TensorFlow 2 est-il beaucoup plus lent que TensorFlow 1?

137

De nombreux utilisateurs l'ont cité comme raison de passer à Pytorch, mais je n'ai pas encore trouvé de justification / explication pour sacrifier la qualité pratique la plus importante, la vitesse, pour une exécution rapide.

Ci-dessous, les performances de l'analyse comparative du code, TF1 contre TF2 - avec TF1 fonctionnant de 47% à 276% plus rapidement .

Ma question est: qu'est-ce qui, au niveau du graphique ou du matériel, engendre un ralentissement aussi important?


Vous cherchez une réponse détaillée - je connais déjà les concepts généraux. Git pertinent

Spécifications : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070


Résultats de référence :


MISE À JOUR : La désactivation de l'exécution désirée par le code ci-dessous n'aide pas . Le comportement, cependant, est incohérent: parfois, l'exécution en mode graphique aide considérablement, d'autres fois, elle s'exécute plus lentement par rapport à Eager.

Comme les développeurs TF n'apparaissent nulle part, je vais enquêter moi-même sur cette question - je peux suivre les progrès du problème lié à Github.

MISE À JOUR 2 : des tonnes de résultats expérimentaux à partager, accompagnés d'explications; devrait être fait aujourd'hui.


Code de référence :

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

Fonctions utilisées :

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), np.random.randint(0, 2, (batch_shape[0], 1))
OverLordGoldDragon
la source
Avez-vous déjà utilisé cProfile un tel outil pour analyser quelle partie les rend si différents?
zihaozhihao
@zihaozhihao J'ai , mais pas spécifiquement pour cela; par lien précédent et en écrivant un optimiseur personnalisé, je connais déjà les différences d'appels, mais je ne comprends pas pourquoi l'un est plus lent que l'autre - et aucun expert non-TF ne peut le comprendre à partir de la source, qui, en plus d'être un désordre emmêlé, ne documente pas les performances relatives. Des informations graphiques / matérielles sont requises, que les profileurs ne fourniront pas (pour autant que je puisse les utiliser)
OverLordGoldDragon
la version numpy est-elle la même dans les deux tests?
chabir
Ouch .... Si le vieux Keras seul était déjà beaucoup plus lent que PyTorch, imaginez maintenant.
Daniel Möller
le problème évolue-t-il avec la taille du modèle? avez-vous également essayé d'exécuter la même référence sur d'autres systèmes d'exploitation?
okawo

Réponses:

76

MISE À JOUR 18/02/2020 : J'ai bancé 2.1 et 2.1 tous les soirs; les résultats sont mitigés. Toutes les configurations sauf une (modèle et taille des données) sont aussi rapides ou beaucoup plus rapides que les meilleures de TF2 et TF1. Celui qui est le plus lent et le plus lent, est Large-Large - esp. dans l'exécution du graphique ( 1,6x à 2,5x plus lent ).

De plus, il existe des différences de reproductibilité extrêmes entre Graph et Eager pour un grand modèle que j'ai testé - un qui ne s'explique pas par le hasard / calcul-parallélisme. Je ne peux pas actuellement présenter de code reproductible pour ces revendications par contraintes de temps, donc je recommande fortement de le tester pour vos propres modèles.

Je n'ai pas encore ouvert de problème Git à ce sujet, mais j'ai commenté l' original - pas encore de réponse. Je mettrai à jour la ou les réponses une fois les progrès réalisés.


VERDICT : ce n'est pas le cas , SI vous savez ce que vous faites. Mais si vous ne le faites pas , cela pourrait vous coûter beaucoup, de quelques mises à niveau de GPU en moyenne et de plusieurs GPU dans le pire des cas.


CETTE RÉPONSE : vise à fournir une description de haut niveau du problème, ainsi que des directives sur la façon de décider de la configuration de la formation spécifique à vos besoins. Pour une description détaillée et de bas niveau, qui inclut tous les résultats d'analyse comparative + code utilisé, voir mon autre réponse.

Je mettrai à jour ma (mes) réponse (s) avec plus d'informations si j'en apprends - je peux mettre en signet / "mettre en vedette" cette question pour référence.


RÉSUMÉ DU PROBLÈME : comme l'a confirmé un développeur TensorFlow, Q. Scott Zhu, TF2 a concentré le développement sur l'exécution désireuse et l'intégration étroite avec Keras, ce qui impliquait des changements radicaux dans la source TF - y compris au niveau du graphique. Avantages: capacités de traitement, de distribution, de débogage et de déploiement considérablement étendues. Le coût de certains d'entre eux, cependant, est la vitesse.

La question est cependant assez complexe. Ce n'est pas seulement TF1 vs TF2 - les facteurs entraînant des différences significatives dans la vitesse du train comprennent:

  1. TF2 contre TF1
  2. Désireux vs mode graphique
  3. keras contre. tf.keras
  4. numpyvs tf.data.Datasetvs ...
  5. train_on_batch() contre. fit()
  6. GPU vs CPU
  7. model(x)vs model.predict(x)vs ...

Malheureusement, presque rien de ce qui précède n'est indépendant de l'autre, et chacun peut au moins doubler le temps d'exécution par rapport à un autre. Heureusement, vous pouvez déterminer ce qui fonctionnera le mieux systématiquement et avec quelques raccourcis - comme je vais le montrer.


QUE DEVRAIS-JE FAIRE? Actuellement, la seule façon est d'expérimenter pour votre modèle, vos données et votre matériel spécifiques. Aucune configuration unique ne fonctionnera toujours mieux - mais il y a des choses à faire et à ne pas faire pour simplifier votre recherche:

>> À FAIRE:

  • train_on_batch()+ numpy+ tf.keras+ TF1 + Désireux / Graphique
  • train_on_batch()+ numpy+ tf.keras+ TF2 + graphique
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + Graphique + grand modèle et données

>> NE PAS:

  • fit()+ numpy+ keraspour les petits et moyens modèles et données
  • fit()+ numpy+ tf.keras+ TF1 / TF2 + Désireux
  • train_on_batch()+ numpy+ keras+ TF1 + Désireux

  • [Major] tf.python.keras ; il peut fonctionner 10 à 100 fois plus lentement et avec beaucoup de bogues; Plus d'informations

    • Cela inclut layers, models, optimizers, et services connexes "out-of-box" les importations d'utilisation; les opérations, les utilitaires et les importations «privées» connexes sont correctes - mais pour être sûr, vérifiez les altérations et si elles sont utilisées danstf.keras

Reportez-vous au code au bas de mon autre réponse pour un exemple de configuration d'analyse comparative. La liste ci-dessus est basée principalement sur les tableaux "BENCHMARKS" dans l'autre réponse.


LIMITATIONS des choses à faire et à ne pas faire ci-dessus:

  • Cette question est intitulée "Pourquoi TF2 est-elle beaucoup plus lente que TF1?", Et bien que son corps concerne explicitement l'entraînement, la question ne se limite pas à cela; l'inférence , elle aussi, est sujette à des différences de vitesse importantes, même au sein d'une même version TF, importation, format de données, etc. - voir cette réponse .
  • Les RNN sont susceptibles de modifier notablement la grille de données dans l'autre réponse, car ils ont été améliorés dans TF2
  • Modèles principalement utilisés Conv1Det Dense- pas de RNN, données / cibles clairsemées, entrées 4 / 5D et autres configurations
  • Les données d'entrée sont limitées à numpyet tf.data.Dataset, bien que de nombreux autres formats existent; voir autre réponse
  • GPU a été utilisé; les résultats seront différents sur un CPU. En fait, lorsque j'ai posé la question, mon CUDA n'était pas correctement configuré et certains des résultats étaient basés sur le processeur.

Pourquoi TF2 a-t-il sacrifié la qualité la plus pratique, la vitesse, pour une exécution soignée? Ce n'est pas le cas, clairement - le graphique est toujours disponible. Mais si la question est "pourquoi tant de désir":

  • Débogage supérieur : vous avez probablement rencontré une multitude de questions demandant «comment puis-je obtenir des sorties de couche intermédiaire» ou «comment inspecter les poids»; avide, c'est (presque) aussi simple que .__dict__. Le graphique, en revanche, nécessite une familiarité avec les fonctions spéciales de backend - compliquant grandement tout le processus de débogage et d'introspection.
  • Prototypage plus rapide : selon des idées similaires à celles ci-dessus; compréhension plus rapide = plus de temps pour la DL réelle.

COMMENT ACTIVER / DÉSACTIVER EAGER?

tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

INFORMATIONS COMPLÉMENTAIRES :

  • Attention aux _on_batch()méthodes dans TF2; selon le développeur TF, ils utilisent toujours une implémentation plus lente, mais pas intentionnellement - c'est-à-dire qu'elle doit être corrigée. Voir l'autre réponse pour plus de détails.

DEMANDES DE TENSORFLOW DEVS :

  1. Veuillez corriger train_on_batch()et l'aspect performances de l'appel fit()itératif; les boucles de train personnalisées sont importantes pour beaucoup, surtout pour moi.
  2. Ajoutez la mention documentation / docstring de ces différences de performances pour la connaissance des utilisateurs.
  3. Améliorez la vitesse d'exécution générale pour empêcher les coups d'œil de sauter vers Pytorch.

REMERCIEMENTS : Merci à


MISES À JOUR :

  • 14/11/19 - a trouvé un modèle (dans ma vraie application) qui fonctionne plus lentement sur TF2 pour toutes les configurations * avec les données d'entrée Numpy. Les différences variaient de 13 à 19%, avec une moyenne de 17%. Les différences entre keraset tf.keras, cependant, étaient plus dramatiques: 18-40% , moy. 32% (TF1 & 2). (* - sauf Eager, pour lequel TF2 OOM'd)

  • 17/11/19 - devs a mis à jour les on_batch()méthodes dans un commit récent , déclarant avoir une vitesse améliorée - à publier dans TF 2.1, ou disponible maintenant sous tf-nightly. Comme je ne parviens pas à faire fonctionner ce dernier, cela retardera le benching jusqu'à 2.1.

  • 20/02/20 - les performances de prédiction valent également la peine d'être évaluées; dans TF2, par exemple, les temps de prédiction du processeur peuvent impliquer des pics périodiques
OverLordGoldDragon
la source
3
Et alors fit_generator? ... Je ne veux pratiquement jamais train_on_batchet gérer ma propre boucle d'entraînement sur plusieurs lots est un énorme, énorme anti-modèle à éviter même à grand prix.
le
@ely Il reste à tester, comme indiqué dans mon autre réponse - mais je prévois que ce sera fitw / petit supplément de traitement des données. En ce qui concerne les boucles de train, j'ai écrit ma propre boucle personnalisée qui s'est finalement transformée en une sorte d'API; fit_generatormanque d'introspection, de personnalisation et de sauvegarde / chargement - donc un non absolu pour moi. Je publierai ma boucle d'entraînement par la suite, sur Github.
OverLordGoldDragon
Le manque d'introspection et de personnalisation est une fonctionnalité pour moi, pas un bug. IDK à quoi fait référence le commentaire de sauvegarde / chargement? Sauvegarde / chargement intermédiaire pendant une boucle non contrôlée par le générateur de données? (Je suis également personnellement heureux de ne compter que sur les rappels pour cela, et je verrais la nécessité d'une personnalisation supplémentaire comme une odeur de code que ma boucle de formation est conçue de manière incorrecte).
le
@ely Ce n'est pas simple, mais il est nécessaire pour la formation avec des pipelines de données d'entrée complexes, des fonctions objectives et des configurations de modèle non API (par exemple, des ensembles). L'introspection est indispensable à de nombreuses fins de débogage et d'ingénierie des fonctionnalités. Absence d'une sauvegarde / charge externe et d'une pause et d'une reprise de la boucle de train pour les modèles coûteux en calcul - un cauchemar. Quoi qu'il en soit, cela dépend en fin de compte de vos besoins spécifiques et de votre éloignement du sujet; la façon la plus sûre de tester les performances avec fit_generatorvotre application est de bien les tester.
OverLordGoldDragon
47

CETTE RÉPONSE : vise à fournir une description détaillée, au niveau graphique / matériel, du problème, y compris les boucles de train TF2 vs TF1, les processeurs de données d'entrée et les exécutions en mode Désir vs Graphique. Pour un résumé des problèmes et des directives de résolution, consultez mon autre réponse.


VERDICT DE PERFORMANCE : parfois l'un est plus rapide, parfois l'autre, selon la configuration. En ce qui concerne TF2 vs TF1, ils sont à peu près au pair en moyenne, mais des différences importantes basées sur la configuration existent, et TF1 l'emporte sur TF2 plus souvent que l'inverse. Voir "BENCHMARKING" ci-dessous.


EAGER VS. GRAPH : la chair de cette réponse entière pour certains: TF2 impatient est plus lent que TF1, selon mes tests. Détails plus bas.

La différence fondamentale entre les deux est la suivante: Graph met en place un réseau informatique de manière proactive , et s'exécute lorsqu'il lui est «demandé» - tandis que Eager exécute tout lors de la création. Mais l'histoire commence seulement ici:

  • Désireux n'est PAS dépourvu de graphique , et peut en fait être principalement graphique, contrairement aux attentes. Ce qu'il est en grande partie, est exécuté Graphique - cela inclut les poids du modèle et de l'optimiseur, comprenant une grande partie du graphique.

  • Désireux reconstruit une partie de son propre graphique à l'exécution ; conséquence directe de la construction incomplète de Graph - voir les résultats du profileur. Cela a une surcharge de calcul.

  • Désireux est plus lent avec les entrées Numpy ; selon ce commentaire et code Git , les entrées Numpy dans Eager incluent les frais généraux de copie des tenseurs du CPU au GPU. En parcourant le code source, les différences de gestion des données sont claires; Désireux passe directement Numpy, tandis que Graph passe des tenseurs qui évaluent ensuite Numpy; incertain du processus exact, mais ce dernier devrait impliquer des optimisations au niveau du GPU

  • TF2 Eager est plus lent que TF1 Eager - c'est ... inattendu. Voir les résultats d'analyse comparative ci-dessous. Les différences vont de négligeable à significatif, mais sont cohérentes. Je ne sais pas pourquoi c'est le cas - si un développeur TF clarifie, mettra à jour la réponse.


TF2 vs TF1 : citant les parties pertinentes d'un développement TF, Q. Scott Zhu, réponse - avec un peu de mon accent et reformulation:

En mode avide, le runtime doit exécuter les opérations et renvoyer la valeur numérique pour chaque ligne de code python. La nature de l' exécution en une seule étape la rend lente .

Dans TF2, Keras utilise tf.function pour construire son graphique pour la formation, l'évaluation et la prédiction. Nous les appelons "fonction d'exécution" pour le modèle. Dans TF1, la "fonction d'exécution" était un FuncGraph, qui partageait un composant commun en tant que fonction TF, mais avec une implémentation différente.

Au cours du processus, nous avons en quelque sorte laissé une implémentation incorrecte pour train_on_batch (), test_on_batch () et Predict_on_batch () . Ils sont toujours numériquement corrects , mais la fonction d'exécution de x_on_batch est une fonction python pure, plutôt qu'une fonction python enveloppée par tf.function. Cela entraînera une lenteur

Dans TF2, nous convertissons toutes les données d'entrée en un tf.data.Dataset, par lequel nous pouvons unifier notre fonction d'exécution pour gérer le type unique des entrées. Il peut y avoir des frais généraux dans la conversion de l'ensemble de données , et je pense qu'il s'agit d'un surcoût unique, plutôt que d'un coût par lot

Avec la dernière phrase du dernier paragraphe ci-dessus et la dernière clause du paragraphe ci-dessous:

Pour surmonter la lenteur en mode impatient, nous avons @ tf.function, qui transformera une fonction python en graphique. Lorsque vous alimentez une valeur numérique comme un tableau np, le corps de la fonction tf.function est converti en graphique statique, optimisé et renvoie la valeur finale, qui est rapide et devrait avoir des performances similaires à celles du mode graphique TF1.

Je ne suis pas d'accord - d'après mes résultats de profilage, qui montrent que le traitement des données d'entrée d'Eager est sensiblement plus lent que celui de Graph. En outre, vous n'êtes pas sûr de cela tf.data.Dataseten particulier, mais Eager appelle à plusieurs reprises plusieurs des mêmes méthodes de conversion de données - voir profileur.

Enfin, le commit lié du développeur: nombre important de modifications pour prendre en charge les boucles Keras v2 .


Boucles de train : selon (1) Désireux vs Graphique; (2) le format des données d'entrée, la formation procédera à une boucle de train distinct - dans TF2, _select_training_loop(), training.py , l' un de:

training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
              training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
            training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators 
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph

Chacun gère l'allocation des ressources différemment et a des conséquences sur les performances et les capacités.


Boucles de train: fitvs train_on_batch, kerasvstf.keras : chacune des quatre utilise des boucles de train différentes, mais peut-être pas dans toutes les combinaisons possibles. keras' fit, par exemple, utilise une forme de fit_loop, par exemple training_arrays.fit_loop(), et son train_on_batchpeut utiliser K.function(). tf.kerasa une hiérarchie plus sophistiquée décrite en partie dans la section précédente.


Train Loops: documentation - documentation source pertinente sur certaines des différentes méthodes d'exécution:

Contrairement à d'autres opérations TensorFlow, nous ne convertissons pas les entrées numériques python en tenseurs. De plus, un nouveau graphique est généré pour chaque valeur numérique python distincte

function instancie un graphique séparé pour chaque ensemble unique de formes d'entrée et de types de données .

Un seul objet tf.function peut devoir être mappé à plusieurs graphiques de calcul sous le capot. Cela ne devrait être visible qu'en tant que performances (le traçage des graphiques a un coût de calcul et de mémoire différent de zéro )


Processeurs de données d'entrée : comme ci-dessus, le processeur est sélectionné au cas par cas, en fonction des indicateurs internes définis en fonction des configurations d'exécution (mode d'exécution, format de données, stratégie de distribution). Le cas le plus simple est avec Eager, qui fonctionne directement avec les tableaux Numpy. Pour quelques exemples spécifiques, voir cette réponse .


TAILLE DU MODÈLE, TAILLE DES DONNÉES:

  • Est décisif; aucune configuration unique ne s'est couronnée au-dessus de toutes les tailles de modèle et de données.
  • La taille des données par rapport à la taille du modèle est importante; pour les petites données et les modèles, le transfert de données (par exemple CPU vers GPU) peut dominer. De même, les petits processeurs généraux peuvent fonctionner plus lentement sur les grandes données par temps de conversion de données dominant (voir convert_to_tensordans "PROFILER")
  • La vitesse diffère selon les boucles des trains et les différents moyens utilisés par les processeurs de traitement des données pour gérer les ressources.

REPÈRES : la viande hachée. - Document Word - Feuille de calcul Excel


Terminologie :

  • les nombres sans% sont tous des secondes
  • % calculé comme (1 - longer_time / shorter_time)*100; justification: nous nous intéressons à quel facteur l' un est plus rapide que l'autre; shorter / longerest en fait une relation non linéaire, pas utile pour une comparaison directe
  • Détermination du signe%:
    • TF2 vs TF1: +si TF2 est plus rapide
    • GvE (Graph vs. Eager): +si Graph est plus rapide
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5

PROFILER :


PROFILER - Explication : Spyder 3.3.6 IDE profiler.

  • Certaines fonctions sont répétées dans les nids des autres; par conséquent, il est difficile de retrouver la séparation exacte entre les fonctions de «traitement des données» et de «formation», il y aura donc un certain chevauchement - comme cela a été prononcé dans le tout dernier résultat.

  • % chiffres calculés par rapport au temps d'exécution moins le temps de construction

  • Temps de construction calculé en additionnant tous les temps d'exécution (uniques) qui ont été appelés 1 ou 2 fois
  • Temps de train calculé en additionnant tous les temps d'exécution (uniques) qui ont été appelés le même nombre de fois que le nombre d'itérations et certains temps d'exécution de leurs nids
  • Les fonctions sont profilées en fonction de leur nom d' origine , malheureusement (c.-à _func = func-d. Profilera comme func), ce qui se mélange dans le temps de construction - d'où la nécessité de l'exclure

ENVIRONNEMENT D'ESSAI :

  • Code exécuté en bas avec un minimum de tâches en arrière-plan en cours d'exécution
  • Le GPU a été "réchauffé" avec quelques itérations avant de chronométrer les itérations, comme suggéré dans ce post
  • CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0 et TensorFlow 2.0.0 construits à partir de la source, plus Anaconda
  • Python 3.7.4, Spyder 3.3.6 IDE
  • GTX 1070, Windows 10, 24 Go de RAM DDR4 2,4 MHz, processeur i7-7700HQ 2,8 GHz

MÉTHODOLOGIE :

  • Benchmark «petit», «moyen» et «grand» modèle et tailles de données
  • Correction du nombre de paramètres pour chaque taille de modèle, indépendamment de la taille des données d'entrée
  • Le modèle "plus grand" a plus de paramètres et de couches
  • Les données "plus grandes" ont une séquence plus longue, mais identique batch_size etnum_channels
  • Les modèles utilisent uniquement Conv1D,Dense couches « » apprenables; RNN évités par implément de version TF. différences
  • Toujours exécuté un train adapté à l'extérieur de la boucle d'analyse comparative, pour omettre la construction du modèle et du graphique d'optimisation
  • Ne pas utiliser de données éparses (par exemple layers.Embedding()) ou de cibles éparses (par exempleSparseCategoricalCrossEntropy()

LIMITATIONS : une réponse «complète» expliquerait chaque boucle de train et itérateur possible, mais cela dépasse certainement mes capacités de temps, mon chèque de paie inexistant ou ma nécessité générale. Les résultats ne sont aussi bons que la méthodologie - interpréter avec un esprit ouvert.


CODE :

import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time

from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model 
#from keras.optimizers import Adam
#import keras.backend as K

#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()

def reset_seeds(reset_graph_with_backend=None, verbose=1):
    if reset_graph_with_backend is not None:
        K = reset_graph_with_backend
        K.clear_session()
        tf.compat.v1.reset_default_graph()
        if verbose:
            print("KERAS AND TENSORFLOW GRAPHS RESET")

    np.random.seed(1)
    random.seed(2)
    if tf.__version__[0] == '2':
        tf.random.set_seed(3)
    else:
        tf.set_random_seed(3)
    if verbose:
        print("RANDOM SEEDS RESET")

print("TF version: {}".format(tf.__version__))
reset_seeds()

def timeit(func, iterations, *args, _verbose=0, **kwargs):
    t0 = time()
    for _ in range(iterations):
        func(*args, **kwargs)
        print(end='.'*int(_verbose))
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_model_small(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 40, strides=4, padding='same')(ipt)
    x     = GlobalAveragePooling1D()(x)
    x     = Dropout(0.5)(x)
    x     = Dense(64, activation='relu')(x)
    out   = Dense(1,  activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_medium(batch_shape):
    ipt = Input(batch_shape=batch_shape)
    x = ipt
    for filters in [64, 128, 256, 256, 128, 64]:
        x  = Conv1D(filters, 20, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_model_large(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(64,  400, strides=4, padding='valid')(ipt)
    x     = Conv1D(128, 200, strides=1, padding='valid')(x)
    for _ in range(40):
        x = Conv1D(256,  12, strides=1, padding='same')(x)
    x     = Conv1D(512,  20, strides=2, padding='valid')(x)
    x     = Conv1D(1028, 10, strides=2, padding='valid')(x)
    x     = Conv1D(256,   1, strides=1, padding='valid')(x)
    x     = GlobalAveragePooling1D()(x)
    x     = Dense(256, activation='relu')(x)
    x     = Dropout(0.5)(x)
    x     = Dense(128, activation='relu')(x)
    x     = Dense(64,  activation='relu')(x)    
    out   = Dense(1,   activation='sigmoid')(x)
    model = Model(ipt, out)
    model.compile(Adam(lr=1e-4), 'binary_crossentropy')
    return model

def make_data(batch_shape):
    return np.random.randn(*batch_shape), \
           np.random.randint(0, 2, (batch_shape[0], 1))

def make_data_tf(batch_shape, n_batches, iters):
    data = np.random.randn(n_batches, *batch_shape),
    trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
    return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)

batch_shape_small  = (32, 140,   30)
batch_shape_medium = (32, 1400,  30)
batch_shape_large  = (32, 14000, 30)

batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data",  "Medium data",  "Large data"]
model_names = ["Small model", "Medium model", "Large model"]

def test_all(fit=False, tf_dataset=False):
    for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
        for batch_shape, shape_name in zip(batch_shapes, shape_names):
            if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
                continue
            reset_seeds(reset_graph_with_backend=K)
            if tf_dataset:
                data = make_data_tf(batch_shape, iters, iters)
            else:
                data = make_data(batch_shape)
            model = model_fn(batch_shape)

            if fit:
                if tf_dataset:
                    model.train_on_batch(data.take(1))
                    t0 = time()
                    model.fit(data, steps_per_epoch=iters)
                    print("Time/iter: %.4f sec" % ((time() - t0) / iters))
                else:
                    model.train_on_batch(*data)
                    timeit(model.fit, iters, *data, _verbose=1, verbose=0)
            else:
                model.train_on_batch(*data)
                timeit(model.train_on_batch, iters, *data, _verbose=1)
            cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
            del model

test_all(fit=True, tf_dataset=False)
OverLordGoldDragon
la source
Je ne sais pas si votre code est correct. Je pense que vos modèles fonctionnent toujours en mode graphique puisque vous appelez model.compilesans run_eagerly=Trueargument. Si vous êtes en mode impatient, vous pouvez exécuter une partie de votre code en mode graphique à l'aide de tf.function. Par conséquent, je pense que l'implémentation par défaut de compileest de créer un graphe de calcul au lieu de l'exécuter avec impatience pour des raisons de performances. Notez également que si votre modèle est convolutif, vous ne voyez pas d'accélération en mode graphique car l'interaction python est minimale. Si vous effectuez de nombreuses opérations mathématiques, cela peut faire une grande différence (également dans l'utilisation de la mémoire).
user2781994
@OverLordGoldDragon mais dans TF 2, le mode avide est par défaut mais model.compilesans run_eagerly=Trueassure le mode graphique, ou pas?
user2781994
@OverLordGoldDragon Je suis d'accord que toutes les méthodes importées ne s'exécutent pas en mode graphique mais je pense que model.compileou model.fitdoit s'assurer que la formation s'exécute en mode graphique en interne.
user2781994
@OverLordGoldDragon TRUE - "tf.keras.Model.compile prend trois arguments importants: ... De plus, pour vous assurer que le modèle s'entraîne et s'évalue avec impatience, vous pouvez vous assurer de passer run_eagerly=Truecomme paramètre à compiler." (source tensorflow.org/guide/keras/overview ) Par conséquent, si je ne passe pas le run_eagerly=Truemodèle, je peux exécuter en mode graphique. Je ne sais pas quel est le facteur décisif, mais pourquoi ne fonctionnerait-il pas en mode graphique s'il est plus efficace que désireux.
user2781994
Voulez-vous plus de preuves? :) "Par défaut, nous tenterons de compiler votre modèle sur un graphique statique pour offrir les meilleures performances d'exécution." ( github.com/tensorflow/tensorflow/blob/r2.0/tensorflow/python/… )
user2781994