web-dev-qa-db-fra.com

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

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.

Vous trouverez ci-dessous les performances de l'analyse de code, TF1 contre TF2 - TF1 fonctionnant de 47% à 276% plus rapidement .

Ma question est: qu'est-ce qui, au niveau du graphique ou du matériel, produit un ralentissement si 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 [~ # ~] : Désactiver l'exécution désirée par le code ci-dessous fait pas aider. Le comportement, cependant, est incohérent: parfois, l'exécution en mode graphique aide considérablement, d'autres fois il s'exécute plus lent 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 : 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))
134
OverLordGoldDragon

MISE À JOUR 18/02/2020 : J'ai mis les bancs 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 de façon spectaculaire est Large-Large - esp. dans l'exécution du graphe ( 1,6x à 2,5x plus lent ).

De plus, il existe extrême des différences de reproductibilité entre Graph et Eager pour un grand modèle que j'ai testé - une qui ne peut pas être expliquée 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 avec Git, mais j'ai fait un commentaire sur le original - pas encore de réponse. Je mettrai à jour la ou les réponses une fois les progrès réalisés.


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


CETTE RÉPONSE : vise à fournir une description de haut niveau du problème, ainsi que des directives pour 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 ou mes réponses 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 confirmé par un développeur de 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 un balayage changements 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 de vitesse de train comprennent:

  1. TF2 contre TF1
  2. Désireux vs mode graphique
  3. keras contre tf.keras
  4. numpy vs tf.data.Dataset vs ...
  5. train_on_batch() vs 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 DOIS-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 sont 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 + keras pour 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

  • [Majeur] 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 les importations d'utilisation "prêtes à l'emploi" associées; les opérations, les utilitaires et les importations "privées" connexes sont corrects - mais pour être sûr, vérifiez les alts, et s'ils sont utilisés dans tf.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; inférence, est également soumis à des différences de vitesse importantes, même dans la même version TF, importation, format de données, etc. - voir ce 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 Conv1D Et Dense - pas de RNN, données/cibles éparses, entrées 4/5D et autres configurations
  • Les données d'entrée sont limitées à numpy et tf.data.Dataset, Tandis que de nombreux autres formats existent; voir autre réponse
  • GPU a été utilisé; Les résultats seront diffèrent 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 rapide? 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"; avec impatience, 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 à 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 SUPPLÉMENTAIRES :

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

DEMANDES DE DEVS TENSORFLOW :

  1. Veuillez corriger train_on_batch(), et l'aspect performance de l'appel de fit() de manière itérative; 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 à


[~ # ~] met à jour [~ # ~] :

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

  • 17/11/19 - les développeurs ont mis à jour les méthodes on_batch() dans un commit récent , indiquant 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 testées; dans TF2, par exemple, les temps de prédiction du processeur peuvent impliquer pics périodiques
72

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 Exécutions en mode vs vs graphique. Pour un résumé du problème et des directives de résolution, voir 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 est impatient plus lent que TF1, selon mes tests. Détails plus bas.

La différence fondamentale entre les deux est: Graph met en place un réseau de calcul de manière proactive, et s'exécute quand on lui dit - alors 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 surtout graphique, contrairement aux attentes. Ce qu'il est en grande partie, c'est Graphique exécuté - 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 graphe à 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 ; par ce commentaire Git & code, 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 dev TF, Q. Scott Zhu, réponse - w/bit de mon accentuation 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 le fait être lent .

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 pure fonction python, plutôt qu'un tf. fonction enveloppée python. 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 que ce sont des frais généraux uniques, plutôt qu'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 renvoyant 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 tf.data.Dataset En 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 significatif de changements pour supporter les boucles Keras v2 .


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

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: fit vs train_on_batch, keras vs tf.keras: chacun 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_batch peut utiliser K.function(). tf.keras A une hiérarchie plus sophistiquée décrite en partie dans la section précédente.


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

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

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 devrait être visible uniquement en tant que performances (les graphiques de suivi ont un coût de calcul et de mémoire différent de zéro )


Processeurs de données d'entrée : similaire à 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, données format, 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.
  • Taille des données par rapport à la taille du modèle est importante; pour les petites données et le modèle, 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_tensor Dans "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 - secondes
  • % calculé comme (1 - longer_time / shorter_time)*100; justification: nous sommes intéressés par quel facteur l'un est plus rapide que l'autre; shorter / longer Est 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 (Graphique vs Désireux): + Si le Graphique est plus rapide
  • TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5

[~ # ~] profileur [~ # ~] :


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 p.r.t. runtime 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 leurs noms original, malheureusement (c'est-à-dire que _func = func Sera profilé comme func), ce qui se mélange dans le temps de construction - d'où la nécessité de l'exclure

ENVIRONNEMENT DE TEST :

  • 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 les mêmes batch_size Et num_channels
  • Les modèles utilisent uniquement les couches Conv1D, Dense '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 exemple SparseCategoricalCrossEntropy()

[~ # ~] limitations [~ # ~] : une réponse "complète" expliquerait chaque boucle de train et itérateur possible, mais cela dépasse certainement mon temps capacité, chèque de paie inexistant ou 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)
45