Как создать ансамбль в тензорном потоке?

Я пытаюсь создать ансамбль многих обучаемых моделей. Все модели имеют один и тот же график и просто отличаются его весами. Я создаю диаграмму модели, используя tf.get_variable. У меня есть несколько разных контрольных точек (с разными весами) для одной и той же архитектуры графа, и я хочу создать одну модель экземпляра для каждой контрольной точки.

Как загрузить много контрольных точек без перезагрузки предыдущих загруженных весов?

Когда я создал свои графики с помощью tf.get_variable, единственный способ создать несколько графов - передать аргумент reuse = True. Теперь, если я попытаюсь изменить имена переменных моего графа, включив метод сборки в новую область (чтобы они стали неотделимыми с другими созданными графами) перед загрузкой, тогда это не сработает, потому что новые имена будут отличаться от сохраненных весов, и я не смогу загрузить его.

Ответ 1

Это требует нескольких хаков. Давайте сохраним несколько простых моделей

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import tensorflow as tf


def build_graph(init_val=0.0):
    x = tf.placeholder(tf.float32)
    w = tf.get_variable('w', initializer=init_val)
    y = x + w
    return x, y


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--init', help='dummy string', type=float)
    parser.add_argument('--path', help='dummy string', type=str)
    args = parser.parse_args()

    x1, y1 = build_graph(args.init)

    saver = tf.train.Saver()
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())
        print(sess.run(y1, {x1: 10}))  # outputs: 10 + i

        save_path = saver.save(sess, args.path)
        print("Model saved in path: %s" % save_path)

# python ensemble.py --init 1 --path ./models/model1.chpt
# python ensemble.py --init 2 --path ./models/model2.chpt
# python ensemble.py --init 3 --path ./models/model3.chpt

Эти модели производят выходные данные "10 + i", где я = 1, 2, 3. Обратите внимание, что этот скрипт создает, запускает и сохраняет несколько раз одну и ту же структуру графика. Загрузка этих значений и восстановление каждого графика индивидуально - это фольклор, и это можно сделать

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import argparse
import tensorflow as tf


def build_graph(init_val=0.0):
    x = tf.placeholder(tf.float32)
    w = tf.get_variable('w', initializer=init_val)
    y = x + w
    return x, y


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--path', help='dummy string', type=str)
    args = parser.parse_args()

    x1, y1 = build_graph(-5.)

    saver = tf.train.Saver()
    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())

        saver.restore(sess, args.path)
        print("Model loaded from path: %s" % args.path)

        print(sess.run(y1, {x1: 10}))

# python ensemble_load.py --path ./models/model1.chpt  # gives 11
# python ensemble_load.py --path ./models/model2.chpt  # gives 12
# python ensemble_load.py --path ./models/model3.chpt  # gives 13

Они снова воспроизводят выходы 11, 12, 13, как и ожидалось. Теперь трюк состоит в том, чтобы создать для каждой модели из ансамбля свой собственный масштаб, например

def build_graph(x, init_val=0.0):
    w = tf.get_variable('w', initializer=init_val)
    y = x + w
    return x, y


if __name__ == '__main__':
    models = ['./models/model1.chpt', './models/model2.chpt', './models/model3.chpt']
    x = tf.placeholder(tf.float32)
    outputs = []
    for k, path in enumerate(models):
        # THE VARIABLE SCOPE IS IMPORTANT
        with tf.variable_scope('model_%03i' % (k + 1)):
            outputs.append(build_graph(x, -100 * np.random.rand())[1])

Следовательно, каждая модель живет под другой переменной-областью, т.е. мы имеем переменные model_001/w: 0, model_002/w: 0, model_003/w: 0 ', хотя они имеют похожий (не тот же) субграф, эти переменные действительно являются разными объектами. Теперь трюк состоит в том, чтобы управлять двумя наборами переменных (графиками под текущей областью действия и с контрольной точкой):

def restore_collection(path, scopename, sess):
    # retrieve all variables under scope
    variables = {v.name: v for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scopename)}
    # retrieves all variables in checkpoint
    for var_name, _ in tf.contrib.framework.list_variables(path):
        # get the value of the variable
        var_value = tf.contrib.framework.load_variable(path, var_name)
        # construct expected variablename under new scope
        target_var_name = '%s/%s:0' % (scopename, var_name)
        # reference to variable-tensor
        target_variable = variables[target_var_name]
        # assign old value from checkpoint to new variable
        sess.run(target_variable.assign(var_value))

Полное решение будет

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import tensorflow as tf


def restore_collection(path, scopename, sess):
    # retrieve all variables under scope
    variables = {v.name: v for v in tf.get_collection(tf.GraphKeys.GLOBAL_VARIABLES, scopename)}
    # retrieves all variables in checkpoint
    for var_name, _ in tf.contrib.framework.list_variables(path):
        # get the value of the variable
        var_value = tf.contrib.framework.load_variable(path, var_name)
        # construct expected variablename under new scope
        target_var_name = '%s/%s:0' % (scopename, var_name)
        # reference to variable-tensor
        target_variable = variables[target_var_name]
        # assign old value from checkpoint to new variable
        sess.run(target_variable.assign(var_value))


def build_graph(x, init_val=0.0):
    w = tf.get_variable('w', initializer=init_val)
    y = x + w
    return x, y


if __name__ == '__main__':
    models = ['./models/model1.chpt', './models/model2.chpt', './models/model3.chpt']
    x = tf.placeholder(tf.float32)
    outputs = []
    for k, path in enumerate(models):
        with tf.variable_scope('model_%03i' % (k + 1)):
            outputs.append(build_graph(x, -100 * np.random.rand())[1])

    with tf.Session() as sess:
        sess.run(tf.global_variables_initializer())

        print(sess.run(outputs[0], {x: 10}))  # random output -82.4929
        print(sess.run(outputs[1], {x: 10}))  # random output -63.65792
        print(sess.run(outputs[2], {x: 10}))  # random output -19.888203

        print(sess.run(W[0]))  # randomly initialize value -92.4929
        print(sess.run(W[1]))  # randomly initialize value -73.65792
        print(sess.run(W[2]))  # randomly initialize value -29.888203

        restore_collection(models[0], 'model_001', sess)  # restore all variables from different checkpoints
        restore_collection(models[1], 'model_002', sess)  # restore all variables from different checkpoints
        restore_collection(models[2], 'model_003', sess)  # restore all variables from different checkpoints

        print(sess.run(W[0]))  # old values from different checkpoints: 1.0
        print(sess.run(W[1]))  # old values from different checkpoints: 2.0
        print(sess.run(W[2]))  # old values from different checkpoints: 3.0

        print(sess.run(outputs[0], {x: 10}))  # what we expect: 11.0
        print(sess.run(outputs[1], {x: 10}))  # what we expect: 12.0
        print(sess.run(outputs[2], {x: 10}))  # what we expect: 13.0

# python ensemble_load_all.py

Теперь, имея список выходов, вы можете усреднять эти значения в TensorFlow или делать некоторые другие предсказания ансамбля.

Редактировать:

  • Легче хранить модель в виде numpy-словаря с помощью NumPy (npz) и загружать эти значения, например, в моем ответе: fooobar.com/questions/15628479/...
  • Вышеприведенный код просто иллюстрирует решение. В нем нет проверок здравомыслия (например, действительно ли переменная существует). Может попытаться поймать попытку.

Ответ 2

Есть несколько вопросов по этой теме и множество возможных ответов/способов сделать это. Здесь я хотел бы показать, как я придумал самый элегантный и самый чистый способ создания ансамбля из N моделей, где N произвольно. Это решение было протестировано с TF 1.12.0, Python 2.7

Следующий фрагмент кода - это то, что вы ищете (комментарии ниже):

import tensorflow as tf
import numpy as np

num_of_ensembles = N
savers = list()
palceholders = list()
inference_ops = list()

for i in xrange(num_of_ensembles):
    with tf.name_scope('model_{}'.format(i)):
        savers.append(tf.train.import_meta_graph('saved_model.ckpt.meta'))

graph = tf.get_default_graph()

for i in xrange(num_of_ensembles):
    placeholders.append(graph.get_operation_by_name('model_{}/input_ph'.format(i)).outputs[0])
    inference_ops.append(graph.get_operation_by_name('model_{}/last_operation_in_the_network'.format(i)).outputs[0])


with tf.Session() as sess:
    for i in xrange(num_of_ensembles):
        savers[i].restore(sess, 'saved_model.ckpt')
        prediction = sess.run(inference_ops[i], feed_dict={placeholders[i]: np.random.rand(your_input.shape)})

Итак, первое, что нужно сделать, это импортировать мета граф каждой модели. Как предложено в комментариях выше, ключ заключается в том, чтобы создать для каждой модели из ансамбля свою собственную область видимости, чтобы добавить префикс типа model_001/, model_002/... для каждой области переменных. Это позволит вам восстановить N разных моделей, со своими независимыми переменными.

Все эти графики будут жить в текущем графике по умолчанию. Теперь, когда вы загружаете модель, вы должны извлечь входные, выходные данные и операции, которые вы хотите использовать из графика, в новые переменные. Для этого вам нужно знать имена этих тензоров из старой модели. Вы можете проверить все сохраненные операции с помощью команды: ops = graph.get_operations(). В приведенном выше примере первой операцией является присвоение заполнителя /input_ph, в то время как последняя операция была названа /last_operation_in_the_network (обычно, если автор сети не указывает name поля для каждого слоя, вы найдете что-то вроде /density_3,/conv2d_1 и т.д.). Обратите внимание, что это должна быть точная конечная операция вашей модели, а также вы должны .outputs[0] тензор, который является значением .outputs[0] самой операции.

Наконец, вы можете запустить сеанс с правильной операцией логического вывода и заполнителем, получая прогноз в виде пустого массива и делая все, что вы хотите (усреднение, большинство голосов и т.д.)

Полезные ссылки, которые вы можете проверить:

Ответ 3

Я также ответил на это в Reddit, но на случай, если другие люди столкнутся с этой проблемой, я перепрограммирую ее здесь и добавил дополнительные разъяснения.

Если вы используете Scikit Flow, вы сможете легко сохранять и восстанавливать модели, делать разные прогнозы из разных моделей, а затем построить ансамбль из этих предсказаний. Посмотрите, как вы можете сохранить и восстановить их на нашей странице примеров.

Там также predict_proba() вы можете использовать, начиная с predict(), для получения прогнозируемых вероятностей. Тогда должно быть легко построить простое усреднение, укладку и т.д., Различные виды ансамблей.

Надеюсь, это поможет.