3
votes

Si les résultats Keras ne sont pas reproductibles, quelle est la meilleure pratique pour comparer des modèles et choisir des hyper paramètres?

MISE À JOUR: Cette question concernait Tensorflow 1.x. J'ai mis à niveau vers 2.0 et (au moins sur le code simple ci-dessous) le problème de reproductibilité semble résolu sur 2.0. Donc cela résout mon problème; mais je suis toujours curieux de savoir quelles "meilleures pratiques" ont été utilisées pour ce problème sur 1.x.

Entraîner exactement le même modèle / paramètres / données sur keras / tensorflow ne donne pas de résultats reproductibles et la perte est significativement différente à chaque fois que vous entraînez le modèle. Il y a beaucoup de questions sur le stackoverflow à ce sujet (par exemple, Comment obtenir des résultats reproductibles dans les keras ) mais les solutions de contournement recommandées ne semblent pas fonctionner pour moi ou pour beaucoup d'autres personnes sur StackOverflow. OK, c'est ce que c'est.

Mais étant donné cette limitation de la non-reproductibilité avec les keras sur tensorflow - quelle est la meilleure pratique pour comparer des modèles et choisir des hyper paramètres? Je teste différentes architectures et activations, mais comme l'estimation de la perte est différente à chaque fois, je ne suis jamais sûr qu'un modèle soit meilleur que l'autre. Existe-t-il des meilleures pratiques pour résoudre ce problème?

Je ne pense pas que le problème ait quoi que ce soit à voir avec mon code, mais juste au cas où cela aiderait; voici un exemple de programme:

import os
#stackoverflow says turning off the GPU helps reproducibility, but it doesn't help for me
os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
os.environ["CUDA_VISIBLE_DEVICES"] = ""
os.environ['PYTHONHASHSEED']=str(1)

import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers 
import random
import pandas as pd
import numpy as np

#StackOverflow says this is needed for reproducibility but it doesn't help for me
from tensorflow.keras import backend as K
config = tf.ConfigProto(intra_op_parallelism_threads=1,inter_op_parallelism_threads=1)
sess = tf.Session(graph=tf.get_default_graph(), config=config)
K.set_session(sess)

#make some random data
NUM_ROWS = 1000
NUM_FEATURES = 10
random_data = np.random.normal(size=(NUM_ROWS, NUM_FEATURES))
df = pd.DataFrame(data=random_data, columns=['x_' + str(ii) for ii in range(NUM_FEATURES)])
y = df.sum(axis=1) + np.random.normal(size=(NUM_ROWS))

def run(x, y):
    #StackOverflow says you have to set the seeds but it doesn't help for me
    tf.set_random_seed(1)
    np.random.seed(1)
    random.seed(1)
    os.environ['PYTHONHASHSEED']=str(1)

    model = keras.Sequential([
            keras.layers.Dense(40, input_dim=df.shape[1], activation='relu'),
            keras.layers.Dense(20, activation='relu'),
            keras.layers.Dense(10, activation='relu'),
            keras.layers.Dense(1, activation='linear')
        ])
    NUM_EPOCHS = 500
    model.compile(optimizer='adam', loss='mean_squared_error')
    model.fit(x, y, epochs=NUM_EPOCHS, verbose=0)
    predictions = model.predict(x).flatten()
    loss = model.evaluate(x,  y) #This prints out the loss by side-effect

#Each time we run it gives a wildly different loss. :-(
run(df, y)
run(df, y)
run(df, y)

Compte tenu de la non-reproductibilité, comment puis-je évaluer si les modifications de mes hyper-paramètres et de mon architecture aident ou non?


0 commentaires

4 Réponses :


5
votes

C'est sournois, mais il manque en fait à votre code une étape pour une meilleure reproductibilité: réinitialiser les graphiques Keras et TensorFlow avant chaque exécution. Sans cela, tf.set_random_seed() ne fonctionnera pas correctement - voir l'approche correcte ci-dessous.

J'épuisais toutes les options avant de jeter l'éponge sur la non-reproductibilité; actuellement, je ne connais qu'une seule instance de ce type , et c'est probablement un bogue. Néanmoins, il est possible que vous obteniez des résultats sensiblement différents même si vous suivez toutes les étapes - dans ce cas, voir "Si rien ne fonctionne", mais chacune n'est clairement pas très productive, il est donc préférable de se concentrer sur la reproductibilité:

Améliorations définitives :

  • Utilisez reset_seeds(K) ci-dessous
  • Augmenter la précision numérique: K.set_floatx('float64')
  • Définissez PYTHONHASHSEED avant le démarrage du noyau Python - par exemple à partir du terminal
  • Mise à niveau vers TF 2, qui inclut quelques corrections de bogues de reproductibilité, mais attention aux performances
  • Exécuter le processeur sur un seul thread (extrêmement lent)
  • Ne pas importer depuis tf.python.keras - voir ici
  • Assurez-vous que toutes les importations sont cohérentes (c'est-à-dire ne faites pas à from keras.layers import ... et from tensorflow.keras.optimizers import ... )
  • Utilisez un processeur supérieur - par exemple, Google Colab, même si vous utilisez GPU, est beaucoup plus robuste contre l'imprécision numérique - voir ce SO

Voir aussi SO connexe sur la reproductibilité


Si rien ne fonctionne :

  • Relancez X fois avec exactement les mêmes hyperparamètres et graines, résultats moyens
  • Validation croisée K-Fold avec exactement les mêmes hyperparamètres et semences, résultats moyens - option supérieure, mais plus de travail impliqué

Méthode de réinitialisation correcte :

session_conf = tf.ConfigProto(
      intra_op_parallelism_threads=1,
      inter_op_parallelism_threads=1)
sess = tf.Session(config=session_conf)

Exécution de TF sur un seul thread CPU : (code pour TF1 uniquement)

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

    np.random.seed(1)
    random.seed(2)
    tf.compat.v1.set_random_seed(3)
    print("RANDOM SEEDS RESET")  # optional


0 commentaires

0
votes

Vous avez quelques options pour stabiliser les performances ...

1) Définissez la valeur de départ de vos initiateurs afin qu'ils soient toujours initialisés aux mêmes valeurs.

2) Plus de données se traduisent généralement par une convergence plus stable.

3) Des taux d'apprentissage plus faibles et des lots de plus grande taille sont également bons pour un apprentissage plus prévisible.

4) Formation basée sur un nombre fixe d'époques au lieu d'utiliser des rappels pour modifier les hyperparams pendant le train.

5) Validation du pli K pour s'entraîner sur différents sous-ensembles. La moyenne de ces plis devrait aboutir à une métrique assez prévisible.

6) Vous avez également la possibilité de vous entraîner plusieurs fois et de prendre une moyenne de cela.


0 commentaires

1
votes

Le problème semble être résolu dans Tensorflow 2.0 (du moins sur les modèles simples)! Voici un extrait de code qui semble donner des résultats reproductibles.

import os
####*IMPORANT*: Have to do this line *before* importing tensorflow
os.environ['PYTHONHASHSEED']=str(1)

import tensorflow as tf
import tensorflow.keras as keras
import tensorflow.keras.layers 
import random
import pandas as pd
import numpy as np

def reset_random_seeds():
   os.environ['PYTHONHASHSEED']=str(1)
   tf.random.set_seed(1)
   np.random.seed(1)
   random.seed(1)

#make some random data
reset_random_seeds()
NUM_ROWS = 1000
NUM_FEATURES = 10
random_data = np.random.normal(size=(NUM_ROWS, NUM_FEATURES))
df = pd.DataFrame(data=random_data, columns=['x_' + str(ii) for ii in range(NUM_FEATURES)])
y = df.sum(axis=1) + np.random.normal(size=(NUM_ROWS))

def run(x, y):
    reset_random_seeds()

    model = keras.Sequential([
            keras.layers.Dense(40, input_dim=df.shape[1], activation='relu'),
            keras.layers.Dense(20, activation='relu'),
            keras.layers.Dense(10, activation='relu'),
            keras.layers.Dense(1, activation='linear')
        ])
    NUM_EPOCHS = 500
    model.compile(optimizer='adam', loss='mean_squared_error')
    model.fit(x, y, epochs=NUM_EPOCHS, verbose=0)
    predictions = model.predict(x).flatten()
    loss = model.evaluate(x,  y) #This prints out the loss by side-effect

#With Tensorflow 2.0 this is now reproducible! 
run(df, y)
run(df, y)
run(df, y)


0 commentaires

0
votes

En ne mettant que le code ci-dessous, ça marche. La CLÉ de la question, TRÈS IMPORTANTE, est d'appeler la fonction reset_seeds () à chaque fois avant d'exécuter le modèle. En faisant cela, vous obtiendrez des résultats reproductibles comme je l'ai vérifié dans Google Collab.

import numpy as np
import tensorflow as tf
import random as python_random

def reset_seeds():
   np.random.seed(123) 
   python_random.seed(123)
   tf.random.set_seed(1234)

reset_seeds() 


0 commentaires