Если результаты Keras не воспроизводятся, как лучше всего сравнивать модели и выбирать гиперпараметры?

ОБНОВЛЕНИЕ: этот вопрос касался Tensorflow 1.x. Я обновился до 2.0, и (по крайней мере, в простом коде ниже) проблема воспроизводимости кажется исправленной в 2.0. Так что это решает мою проблему; но мне все еще интересно, какие «передовые методы» использовались для решения этой проблемы в 1.x.

Обучение одной и той же модели / параметров / данных на keras / tenorflow не дает воспроизводимых результатов, и потери значительно отличаются каждый раз, когда вы тренируете модель. По этому поводу есть много вопросов о stackoverflow (например, Как получить воспроизводимые результаты в keras), но рекомендуемые обходные пути, похоже, не работают для меня или многих других людей в StackOverflow. Хорошо, это то, что есть.

Но учитывая это ограничение невоспроизводимости с keras на тензорном потоке - каков наилучший способ сравнения моделей и выбора гиперпараметров? Я тестирую разные архитектуры и активации, но поскольку оценка потерь каждый раз разная, я никогда не уверен, что одна модель лучше другой. Есть ли лучший способ справиться с этим?

Я не думаю, что проблема связана с моим кодом, но на всякий случай это помогает; вот пример программы:

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)

Учитывая невоспроизводимость, как я могу оценить, помогают ли изменения в моих гиперпараметрах и архитектуре?


person user2543623    schedule 27.11.2019    source источник


Ответы (4)


Это хитро, но на самом деле в вашем коде нет шага для лучшей воспроизводимости: сброса графиков Keras и TensorFlow перед каждым запуском. Без этого tf.set_random_seed() не будет работать должным образом - см. Правильный подход ниже.

Я бы исчерпал все варианты, прежде чем бросить полотенце на невоспроизводимость; в настоящее время мне известен только один такой экземпляр, и это, скорее всего, ошибка. Тем не менее, возможно, вы получите заметно отличающиеся результаты, даже если выполните все шаги - в этом случае см. «Если ничего не работает», но каждый из них явно не очень продуктивен, поэтому лучше всего сосредоточиться на достижении воспроизводимости:

Окончательные улучшения:

  • Используйте reset_seeds(K) ниже
  • Увеличить числовую точность: K.set_floatx('float64')
  • Установите PYTHONHASHSEED перед запуском ядра Python - например, с терминала
  • Выполните обновление до TF 2, в котором исправлены некоторые ошибки воспроизводимости, но помните: производительность
  • Запустить процессор в одном потоке (мучительно медленно)
  • Не импортировать из tf.python.keras - см. здесь
  • Убедитесь, что весь импорт согласован (т. Е. Не выполняйте from keras.layers import ... и from tensorflow.keras.optimizers import ...)
  • Используйте превосходный ЦП - например, Google Colab, даже при использовании графического процессора, намного более устойчив к неточности чисел - см. это ТАК

Также см. связанных SO по воспроизводимости


Если ничего не работает:

  • Повторить X раз с точно такими же гиперпараметрами и семенами, средние результаты
  • Перекрестная проверка K-Fold с точно такими же гиперпараметрами и начальными значениями, средние результаты - лучший вариант, но требуется больше работы

Правильный метод сброса:

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

Запуск TF в одном потоке ЦП: (код только для TF1)

session_conf = tf.ConfigProto(
      intra_op_parallelism_threads=1,
      inter_op_parallelism_threads=1)
sess = tf.Session(config=session_conf)
person OverLordGoldDragon    schedule 27.11.2019
comment
Ключ действительно состоит в том, чтобы помещать код сброса перед КАЖДЫМ ЗАПУСКОМ, то есть перед созданием каждой отдельной модели. Например, для настройки гиперпараметра с GridSearchCV код сброса следует поставить в самом начале создания модели. - person Moore; 21.05.2021

Проблема вроде бы решена в Tensorflow 2.0 (по крайней мере, на простых моделях)! Вот фрагмент кода, который, кажется, дает повторяемые результаты.

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)
person user2543623    schedule 27.11.2019

Положив только код ниже, он работает. КЛЮЧ к вопросу, ОЧЕНЬ ВАЖНО, - вызывать функцию reset_seeds () каждый раз перед запуском модели. Поступая так, вы получите воспроизводимые результаты, как я проверял в 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() 
person Oscar Monge    schedule 18.11.2020

У вас есть пара вариантов для стабилизации производительности ...

1) Установите начальное значение для ваших инициализаторов, чтобы они всегда инициализировались одинаковыми значениями.

2) Чем больше данных, тем стабильнее сходимость.

3) Более низкая скорость обучения и большие размеры пакетов также хороши для более предсказуемого обучения.

4) Обучение на основе фиксированного количества эпох вместо использования обратных вызовов для изменения гиперпарам во время обучения.

5) K-кратная проверка для обучения на разных подмножествах. Среднее значение этих складок должно привести к довольно предсказуемой метрике.

6) Также у вас есть возможность просто тренироваться несколько раз и брать среднее значение.

person learningthemachine    schedule 27.11.2019