【问题标题】:Use Scipy Optimizer with Tensorflow 2.0 for Neural Network training使用 Scipy Optimizer 和 Tensorflow 2.0 进行神经网络训练
【发布时间】:2020-03-20 15:04:23
【问题描述】:

在引入 Tensorflow 2.0 后,scipy 接口 (tf.contrib.opt.ScipyOptimizerInterface) 已被删除。但是,我仍然想使用 scipy 优化器 scipy.optimize.minimize(method='L-BFGS-B') 来训练神经网络(keras model sequence )。为了让优化器工作,它需要一个函数 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

这里 model 是一个对象 tf.keras.Sequential。

提前感谢您的帮助!

【问题讨论】:

    标签: python tensorflow keras neural-network scipy-optimize


    【解决方案1】:

    从 tf1 更改为 tf2 我遇到了同样的问题,经过一些试验后,我找到了下面的解决方案,该解决方案显示了如何在用 tf.function 装饰的函数和 scipy 优化器之间建立接口。与问题相比的重要变化是:

    1. 正如 Ives scipy 的 lbfgs 所述 需要获取函数值和梯度,所以需要提供两者都提供的函数,然后设置jac=True
    2. scipy 的 lbfgs 是一个 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 优化器 问题(有关网络,请参阅here)。请注意,对于这个问题,网络参数是固定的,输入信号是经过调整的。由于优化参数(输入信号)是一维的,因此不需要函数工厂。

    我比较了四种实现

    1. TF1.12:带有 ScipyOptimizerInterface 的 TF1
    2. TF2.0 (E):上述方法不使用 tf.function 装饰器
    3. TF2.0 (G):上述方法使用 tf.function 装饰器
    4. TF2.0/TFP:使用来自 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)) 工作正常,但比 TF1.12 基线版本慢约 20%。带有 tf.function 的 TF2.0(G) 运行良好,并且比 TF1.12 略快,这是一件好事。

    tensorflow_probability (TF2.0/TFP) 的优化器使用 scipy 的 lbfgs 比 TF2.0(G) 稍快,但没有达到同样的错误减少。事实上,随着时间的推移损失的减少并不是单调的,这似乎是一个不好的迹象。比较 lbfgs 的两种实现(scipy 和 tensorflow_probability=TFP)很明显,scipy 中的 Fortran 代码要复杂得多。 因此,无论是 TFP 中算法的简化在这里都有害,甚至 TFP 正在执行 float32 中的所有计算这一事实也可能是一个问题。

    【讨论】:

      【解决方案2】:

      这是一个使用库 (autograd_minimize) 的简单解决方案,我根据 Roebel 的回答编写了该库:

      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])]
      
      

      【讨论】:

        【解决方案3】:

        我猜 SciPy 不知道如何计算 TensorFlow 对象的梯度。尝试使用原来的函数工厂(也就是loss后也一起返回梯度),在scipy.optimize.minimize中设置jac=True

        我测试了原始 Gist 中的 python 代码,并用 SciPy 优化器替换了tfp.optimizer.lbfgs_minimize。它适用于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 array -> Fortran array 转换数据可能会出现一些问题。并且强制函数func 返回渐变的ndarray 版本解决了这个问题。但是这样就不能使用@tf.function了。

        【讨论】:

          猜你喜欢
          • 2018-02-08
          • 2017-05-23
          • 1970-01-01
          • 2011-04-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-01-08
          • 2020-09-10
          相关资源
          最近更新 更多