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

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

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

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

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

import os
#stackru 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

#Stackru 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):
    #Stackru 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)

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

3 ответа

Решение

Проблема вроде бы решена в 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)

Это хитро, но в вашем коде на самом деле нет шага для лучшей воспроизводимости: сброса графиков 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

Также см. Соответствующий SO по воспроизводимости


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

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

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

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)

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

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

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

2) Больше данных обычно приводит к более стабильной сходимости.

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

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

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

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

Другие вопросы по тегам