【问题标题】:Second derivative in KerasKeras 中的二阶导数
【发布时间】:2018-09-30 19:47:56
【问题描述】:

对于 NN 的自定义损失,我使用函数 u,给定一对 (t,x),两个点都在一个区间内,是我的 NN 的输出。问题是我被困在如何使用K.gradient 计算二阶导数(K 是 TensorFlow 后端):

def custom_loss(input_tensor, output_tensor):
    def loss(y_true, y_pred):

        # so far, I can only get this right, naturally:            
        gradient = K.gradients(output_tensor, input_tensor)

        # here I'm falling badly:

        # d_t = K.gradients(output_tensor, input_tensor)[0]
        # dd_x = K.gradient(K.gradients(output_tensor, input_tensor),
        #                   input_tensor[1])

        return gradient # obviously not useful, just for it to work
    return loss  

我所有的尝试,基于Input(shape=(2,)),都是上面sn-p中注释行的变体,主要是试图找到结果张量的正确索引。

果然我不知道张量是如何工作的。顺便说一句,我知道在 TensorFlow 本身中我可以简单地使用 tf.hessian,但我注意到它在使用 TF 作为后端时不存在。

【问题讨论】:

  • 您的方程式中的 tx 到底是什么意思?另外,当你在第一句话中说“方程”时,你的意思是这两项之和是你的损失函数吗?然后这么说,请丢掉“=0”部分。在尝试回答之前,首先要确保我理解了这个问题...... :)
  • @PeterSzoldan tx 都是区间中的点,例如,都来自 numpy 的 linspacemeshgrid。是的,这两个术语的总和是我的损失,与您建议的更改一起明确说明。
  • 好的,谢谢。然后你会使用这个损失函数来训练你的网络吗?换句话说,你打算把它插入到model.fit()中吗?
  • @PeterSzoldan 没错!
  • 花了一些时间,为您添加了一些选项的答案。

标签: python tensorflow keras


【解决方案1】:

为了让K.gradients() 层像这样工作,您必须将其包含在Lambda() 层中,否则不会创建完整的 Keras 层,并且您无法链接或训练它。所以这段代码可以工作(经过测试):

import keras
from keras.models import *
from keras.layers import *
from keras import backend as K
import tensorflow as tf

def grad( y, x ):
    return Lambda( lambda z: K.gradients( z[ 0 ], z[ 1 ] ), output_shape = [1] )( [ y, x ] )

def network( i, d ):
    m = Add()( [ i, d ] )
    a = Lambda(lambda x: K.log( x ) )( m )
    return a

fixed_input = Input(tensor=tf.constant( [ 1.0 ] ) )
double = Input(tensor=tf.constant( [ 2.0 ] ) )

a = network( fixed_input, double )

b = grad( a, fixed_input )
c = grad( b, fixed_input )
d = grad( c, fixed_input )
e = grad( d, fixed_input )

model = Model( inputs = [ fixed_input, double ], outputs = [ a, b, c, d, e ] )

print( model.predict( x=None, steps = 1 ) )

def network 模型 f( x ) = log( x + 2 )x = 1 . def grad 是完成梯度计算的地方。此代码输出:

[array([1.0986123], dtype=float32), array([0.33333334], dtype=float32), array([-0.11111112], dtype=float32), array([0.07407408], dtype=float32), array ([-0.07407409], dtype=float32)]

log( 3 ) 的正确值-1 / 32, 2 / 33, -6 / 34.


参考 TensorFlow 代码

供参考,纯TensorFlow中的相同代码(用于测试):

import tensorflow as tf

a = tf.constant( 1.0 )
a2 = tf.constant( 2.0 )

b = tf.log( a + a2 )
c = tf.gradients( b, a )
d = tf.gradients( c, a )
e = tf.gradients( d, a )
f = tf.gradients( e, a )

with tf.Session() as sess:
    print( sess.run( [ b, c, d, e, f ] ) )

输出相同的值:

[1.0986123, [0.33333334], [-0.11111112], [0.07407408], [-0.07407409]]

黑森州

tf.hessians() 确实返回二阶导数,这是链接两个 tf.gradients() 的简写。 Keras 后端没有hessians,所以你必须链接两个K.gradients()

数值近似

如果由于某种原因上述方法都不起作用,那么您可能需要考虑通过在较小的 ε 距离上取差值来对二阶导数进行数值近似。这基本上将网络对于每个输入增加了三倍,因此该解决方案除了缺乏准确性外,还引入了严重的效率考虑。无论如何,代码(已测试):

import keras
from keras.models import *
from keras.layers import *
from keras import backend as K
import tensorflow as tf

def network( i, d ):
    m = Add()( [ i, d ] )
    a = Lambda(lambda x: K.log( x ) )( m )
    return a

fixed_input = Input(tensor=tf.constant( [ 1.0 ], dtype = tf.float64 ) )
double = Input(tensor=tf.constant( [ 2.0 ], dtype = tf.float64 ) )

epsilon = Input( tensor = tf.constant( [ 1e-7 ], dtype = tf.float64 ) )
eps_reciproc = Input( tensor = tf.constant( [ 1e+7 ], dtype = tf.float64 ) )

a0 = network( Subtract()( [ fixed_input, epsilon ] ), double )
a1 = network(               fixed_input,              double )
a2 = network(      Add()( [ fixed_input, epsilon ] ), double )

d0 = Subtract()( [ a1, a0 ] )
d1 = Subtract()( [ a2, a1 ] )

dv0 = Multiply()( [ d0, eps_reciproc ] )
dv1 = Multiply()( [ d1, eps_reciproc ] )

dd0 = Multiply()( [ Subtract()( [ dv1, dv0 ] ), eps_reciproc ] )

model = Model( inputs = [ fixed_input, double, epsilon, eps_reciproc ], outputs = [ a0, dv0, dd0 ] )

print( model.predict( x=None, steps = 1 ) )

输出:

[数组([1.09861226]),数组([0.33333334]),数组([-0.1110223])]

(这只得到二阶导数。)

【讨论】:

  • 多么好的答案和令人敬畏的解释,非常感谢您非常,彼得。这是一堂课,顺便说一句,这是一堂非常好的课。
  • 感谢大家的好评! :) 很乐意提供帮助。
【解决方案2】:

Peter Szoldan 发布的解决方案非常出色。但似乎 keras.layers.Input() 接受参数的方式自带有 tf2 后端的最新版本以来发生了变化。不过,以下简单的修复方法将起作用:

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import backend as K
import numpy as np

class CustomModel(tf.keras.Model):

    def __init__(self):
        super(CustomModel, self).__init__()
        self.input_layer = Lambda(lambda x: K.log( x + 2 ) )

    def findGrad(self,func,argm):
        return keras.layers.Lambda(lambda x: K.gradients(x[0],x[1])) ([func,argm])
    
    def call(self, inputs):
        log_layer = self.input_layer(inputs)
        gradient_layer = self.findGrad(log_layer,inputs)
        hessian_layer = self.findGrad(gradient_layer, inputs)
        return hessian_layer


custom_model = CustomModel()
x = np.array([[0.],
            [1],
            [2]])
custom_model.predict(x) 

  • 遍历层:输入层-> lambda 层应用 log(x+2) -> lambda 层应用梯度 -> 另一个 lambda 层应用gradeint -> 输出。
  • 请注意,此解决方案适用于通用自定义模型,如果您使用的是函数式 api,则应该类似。
  • 如果您使用 tf 后端,那么使用 tf.hessians 而不是两次应用 K.gradients 也可以。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-23
    相关资源
    最近更新 更多