MISE À JOUR - 15/01/2020 : les meilleures pratiques actuelles pour les petites tailles de lots devrait être d'alimenter les entrées au modèle directement - par exemple preds = model(x)
, et si les couches se comportent différemment en train / inférence model(x, training=False)
. Par dernier commit, cela est désormais documenté .
Je ne les ai pas comparés, mais d'après la discussion de Git , cela vaut également la peine d'essayer predict_on_batch()
- en particulier avec les améliorations de TF 2.1.
ULTIMATE COUPABLE : self._experimental_run_tf_function = True
. C'est expérimental . Mais ce n'est pas vraiment mauvais.
Pour tous les développeurs TensorFlow qui lisent: nettoyez votre code . C'est le bordel. Et il viole d'importantes pratiques de codage, telles qu'une fonction fait une chose ; _process_inputs
fait beaucoup plus que les "entrées de processus", pareil pour _standardize_user_data
. « Je ne suis pas assez payé » - mais vous ne payer, dans le temps supplémentaire passé à comprendre vos propres trucs, et les utilisateurs de remplir votre page Problèmes avec des bugs plus facile résolus avec un code plus clair.
RÉSUMÉ : c'est seulement un peu plus lent avec compile()
.
compile()
définit un indicateur interne qui attribue une fonction de prédiction différente à predict
. Cette fonction construit un nouveau graphique à chaque appel, le ralentissant par rapport à non compilé. Cependant, la différence n'est prononcée que lorsque le temps de train est beaucoup plus court que le temps de traitement des données . Si nous augmentons la taille du modèle à au moins de taille moyenne, les deux deviennent égaux. Voir le code en bas.
Cette légère augmentation du temps de traitement des données est plus que compensée par la capacité de graphique amplifiée. Puisqu'il est plus efficace de ne conserver qu'un seul graphique de modèle, celui qui précompile est supprimé. Néanmoins : si votre modèle est petit par rapport aux données, il vaut mieux sans compile()
pour l'inférence du modèle. Voir mon autre réponse pour une solution de contournement.
QUE DEVRAIS-JE FAIRE?
Comparez les performances du modèle compilé vs non compilé comme je l'ai dans le code en bas.
- Compilé est plus rapide : exécuté
predict
sur un modèle compilé.
- Compilé est plus lent : exécuté
predict
sur un modèle non compilé.
Oui, les deux sont possibles et cela dépendra de (1) la taille des données; (2) taille du modèle; (3) matériel. Le code en bas montre en fait que le modèle compilé est plus rapide, mais 10 itérations est un petit échantillon. Voir «contournements» dans mon autre réponse pour le «comment faire».
DÉTAILS :
Cela a mis du temps à déboguer, mais c'était amusant. Ci-dessous, je décris les principaux coupables que j'ai découverts, cite quelques documents pertinents et montre les résultats du profileur qui ont conduit au goulot d'étranglement ultime.
( FLAG == self.experimental_run_tf_function
, par souci de concision)
Model
par défaut instancie avec FLAG=False
. compile()
le définit True
.
predict()
consiste à acquérir la fonction de prédiction, func = self._select_training_loop(x)
- Sans aucun kwarg spécial transmis à
predict
et compile
, tous les autres indicateurs sont tels que:
- (A)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- À partir du code source docstring , (A) est fortement tributaire des graphiques, utilise plus de stratégie de distribution et les opérations sont sujettes à la création et à la destruction d'éléments graphiques, ce qui "peut" (avoir) un impact sur les performances.
Véritable coupable : il _process_inputs()
représente 81% du temps d'exécution . Sa composante majeure? _create_graph_function()
, 72% du temps d'exécution . Cette méthode n'existe même pas pour (B) . Cependant, l'utilisation d'un modèle de taille moyenne _process_inputs
représente moins de 1% du temps d'exécution . Code en bas, et les résultats du profilage suivent.
PROCESSEURS DE DONNÉES :
(A) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
utilisé dans _process_inputs()
. Code source pertinent
(B) :, numpy.ndarray
retourné par convert_eager_tensors_to_numpy
. Code source pertinent , et ici
FONCTION D'EXÉCUTION DU MODÈLE (par ex. Prédire)
(A) : fonction de distribution , et ici
(B) : fonction de distribution (différente) , et ici
PROFILER : résultats pour le code dans mon autre réponse, "petit modèle", et dans cette réponse, "modèle moyen":
Petit modèle : 1000 itérations,compile()
Petit modèle : 1000 itérations, non compile()
Modèle moyen : 10 itérations
DOCUMENTATION (indirecte) sur les effets de compile()
: source
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 , par exemple en appelant g(2)
et g(3)
générera deux nouveaux graphiques
function
instancie un graphique séparé pour chaque ensemble unique de formes d'entrée et de types de données . Par exemple, l'extrait de code suivant entraînera le traçage de trois graphiques distincts, car chaque entrée a une forme différente
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 ) mais ne devrait pas affecter l'exactitude du programme.
CONTRE - EXEMPLE :
from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time
def timeit(func, arg, iterations):
t0 = time()
for _ in range(iterations):
func(arg)
print("%.4f sec" % (time() - t0))
batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt = Input(batch_shape=batch_shape)
x = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x = LSTM(512, activation='relu', return_sequences=True)(ipt)
x = Conv1D(128, 400, 1, 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)
X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)
Sorties :
34.8542 sec
34.7435 sec