Используйте Scipy Optimizer с Tensorflow 2.0 для обучения нейронной сети

После появления Tensorflow 2.0 интерфейс scipy (tf.contrib.opt.ScipyOptimizerInterface) был удален. Тем не менее, я все равно хотел бы использовать оптимизатор scipy scipy.optimize.minimize(method='L-BFGS-B') для обучения нейронной сети (последовательная модель keras ). Чтобы оптимизатор работал, ему требуется в качестве входных данных функция fun(x0), где x0 представляет собой массив формы (n,). Следовательно, первым шагом будет «выравнивание» матриц весов для получения вектора требуемой формы. С этой целью я изменил код, предоставленный https://pychao.com/2019/11/02/optimize-tensorflow-keras-models-with-l-bfgs-from-tensorflow-probability/. Это обеспечивает фабрику функций, предназначенную для создания такой функции fun(x0). Однако код не работает, и функция потерь не уменьшается. Я был бы очень признателен, если бы кто-то помог мне разобраться в этом.

Вот кусок кода, который я использую:

func = function_factory(model, loss_function, x_u_train, u_train)

# convert initial model parameters to a 1D tf.Tensor
init_params = tf.dynamic_stitch(func.idx, model.trainable_variables)
init_params = tf.cast(init_params, dtype=tf.float32)

# train the model with L-BFGS solver
results = scipy.optimize.minimize(fun=func, x0=init_params, method='L-BFGS-B')


def loss_function(x_u_train, u_train, network):
    u_pred = tf.cast(network(x_u_train), dtype=tf.float32)
    loss_value = tf.reduce_mean(tf.square(u_train - u_pred))
    return tf.cast(loss_value, dtype=tf.float32)


def function_factory(model, loss_f, x_u_train, u_train):
    """A factory to create a function required by tfp.optimizer.lbfgs_minimize.

    Args:
        model [in]: an instance of `tf.keras.Model` or its subclasses.
        loss [in]: a function with signature loss_value = loss(pred_y, true_y).
        train_x [in]: the input part of training data.
        train_y [in]: the output part of training data.

    Returns:
        A function that has a signature of:
            loss_value, gradients = f(model_parameters).
    """

    # obtain the shapes of all trainable parameters in the model
    shapes = tf.shape_n(model.trainable_variables)
    n_tensors = len(shapes)

    # we'll use tf.dynamic_stitch and tf.dynamic_partition later, so we need to
    # prepare required information first
    count = 0
    idx = [] # stitch indices
    part = [] # partition indices

    for i, shape in enumerate(shapes):
        n = np.product(shape)
        idx.append(tf.reshape(tf.range(count, count+n, dtype=tf.int32), shape))
        part.extend([i]*n)
        count += n

    part = tf.constant(part)


    def assign_new_model_parameters(params_1d):
        """A function updating the model's parameters with a 1D tf.Tensor.

        Args:
            params_1d [in]: a 1D tf.Tensor representing the model's trainable parameters.
        """

        params = tf.dynamic_partition(params_1d, part, n_tensors)
        for i, (shape, param) in enumerate(zip(shapes, params)):

            model.trainable_variables[i].assign(tf.cast(tf.reshape(param, shape), dtype=tf.float32))

    # now create a function that will be returned by this factory

    def f(params_1d):
        """
        This function is created by function_factory.
        Args:
            params_1d [in]: a 1D tf.Tensor.

        Returns:
            A scalar loss.
        """

        # update the parameters in the model
        assign_new_model_parameters(params_1d)
        # calculate the loss
        loss_value = loss_f(x_u_train, u_train, model)

        # print out iteration & loss
        f.iter.assign_add(1)
        tf.print("Iter:", f.iter, "loss:", loss_value)

        return loss_value

    # store these information as members so we can use them outside the scope
    f.iter = tf.Variable(0)
    f.idx = idx
    f.part = part
    f.shapes = shapes
    f.assign_new_model_parameters = assign_new_model_parameters

    return f

Здесь модель — это объект tf.keras.Sequential.

Спасибо заранее за любую помощь!


person Roberto    schedule 25.11.2019    source источник


Ответы (3)


Переходя с tf1 на tf2, я столкнулся с тем же вопросом, и после небольшого эксперимента я нашел решение ниже, которое показывает, как установить интерфейс между функцией, украшенной tf.function, и оптимизатором scipy. Важные изменения по сравнению с вопросом:

  1. Как упоминалось Айвсом, lbfgs scipy должен получить значение функции и градиент, поэтому вам нужно предоставить функцию, которая обеспечивает и то, и другое, а затем установить jac=True
  2. lbfgs scipy — это функция Fortran, которая ожидает, что интерфейс предоставит массивы np.float64, в то время как tensorflow tf.function использует tf.float32. Таким образом, нужно использовать ввод и вывод.

Ниже я привожу пример того, как это можно сделать для игрушечной задачи.

import tensorflow as tf
import numpy as np
import scipy.optimize as sopt

def model(x):
    return tf.reduce_sum(tf.square(x-tf.constant(2, dtype=tf.float32)))

@tf.function
def val_and_grad(x):
    with tf.GradientTape() as tape:
        tape.watch(x)
        loss = model(x)
    grad = tape.gradient(loss, x)
    return loss, grad

def func(x):
    return [vv.numpy().astype(np.float64)  for vv in val_and_grad(tf.constant(x, dtype=tf.float32))]

resdd= sopt.minimize(fun=func, x0=np.ones(5),
                                      jac=True, method='L-BFGS-B')

print("info:\n",resdd)

дисплеи

info:
       fun: 7.105427357601002e-14
 hess_inv: <5x5 LbfgsInvHessProduct with dtype=float64>
      jac: array([-2.38418579e-07, -2.38418579e-07, -2.38418579e-07, -2.38418579e-07,
       -2.38418579e-07])
  message: b'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'
     nfev: 3
      nit: 2
   status: 0
  success: True
        x: array([1.99999988, 1.99999988, 1.99999988, 1.99999988, 1.99999988])

Ориентир

Для сравнения скорости я использую оптимизатор lbfgs для задачи передачи стиля (см. здесь для сети). Обратите внимание, что для этой задачи параметры сети фиксированы, а входной сигнал адаптирован. Поскольку оптимизированные параметры (входной сигнал) являются одномерными, фабрика функций не требуется.

Я сравниваю четыре реализации

  1. TF1.12: TF1 с ScipyOptimizerInterface
  2. TF2.0 (E): описанный выше подход без использования декораторов tf.function
  3. TF2.0 (G): описанный выше подход с использованием декораторов tf.function
  4. TF2.0/TFP: использование минимизатора lbfgs из tensorflow_probability

Для этого сравнения оптимизация останавливается после 300 итераций (обычно для сходимости задача требует 3000 итераций).

Полученные результаты

Method       runtime(300it)      final loss         
TF1.12          240s                0.045     (baseline)
TF2.0 (E)       299s                0.045
TF2.0 (G)       233s                0.045
TF2.0/TFP       226s                0.053

Активный режим TF2.0 (TF2.0(E)) работает правильно, но примерно на 20% медленнее, чем базовая версия TF1.12. TF2.0(G) с tf.function работает нормально и немного быстрее, чем TF1.12, что полезно знать.

Оптимизатор из tensorflow_probability (TF2.0/TFP) работает немного быстрее, чем TF2.0(G), используя lbfgs scipy, но не обеспечивает такого же уменьшения ошибок. На самом деле уменьшение потерь с течением времени не является монотонным, что кажется плохим признаком. Сравнивая две реализации lbfgs (scipy и tensorflow_probability=TFP), становится ясно, что код Fortran в scipy значительно сложнее. Так что либо упрощение алгоритма в TFP здесь вредит, либо даже тот факт, что TFP выполняет все вычисления в float32, тоже может быть проблемой.

person A Roebel    schedule 27.02.2020

Вот простое решение с использованием библиотеки (autograd_minimize), которую я написал на основе ответа Робеля. :

import tensorflow as tf
from autograd_minimize import minimize

def rosen_tf(x):
    return tf.reduce_sum(100.0*(x[1:] - x[:-1]**2.0)**2.0 + (1 - x[:-1])**2.0)

res = minimize(rosen_tf, np.array([0.,0.]))
print(res.x)
>>> array([0.99999912, 0.99999824])

Он также работает с моделями keras, как показано на этом наивном примере линейной регрессии:

import numpy as np
from tensorflow import keras
from tensorflow.keras import layers
from autograd_minimize.tf_wrapper import tf_function_factory
from autograd_minimize import minimize 
import tensorflow as tf

#### Prepares data
X = np.random.random((200, 2))
y = X[:,:1]*2+X[:,1:]*0.4-1

#### Creates model
model = keras.Sequential([keras.Input(shape=2),
                          layers.Dense(1)])

# Transforms model into a function of its parameter
func, params = tf_function_factory(model, tf.keras.losses.MSE, X, y)

# Minimization
res = minimize(func, params, method='L-BFGS-B')

print(res.x)
>>> [array([[2.0000016 ],
 [0.40000062]]), array([-1.00000164])]

person bruno    schedule 08.04.2021

Я предполагаю, что SciPy не знает, как вычислять градиенты объектов TensorFlow. Попробуйте использовать исходную фабрику функций (т.е. та, которая также возвращает градиенты вместе после потери) и установите jac=True в scipy.optimize.minimize.

Я протестировал код Python из оригинального Gist и заменил tfp.optimizer.lbfgs_minimize оптимизатором SciPy. Он работал с методом BFGS:

results = scipy.optimize.minimize(fun=func, x0=init_params, jac=True, method='BFGS')

jac=True означает, что SciPy знает, что func также возвращает градиенты.

Однако для L-BFGS-B это сложно. После некоторых усилий у меня наконец получилось. Я должен закомментировать @tf.function строк и позволить func вернуть grads.numpy() вместо необработанного тензора TF. Я предполагаю, что это связано с тем, что базовая реализация L-BFGS-B является функцией Fortran, поэтому могут возникнуть проблемы с преобразованием данных из tf.Tensor -> массив numpy -> массив Fortran. А принуждение функции func к возврату ndarray версии градиентов решает проблему. Но тогда нельзя использовать @tf.function.

person Ives    schedule 06.12.2019