如何在Keras中创建一个带有“stateful”变量/张量的自定义层?

5
我希望你能帮我创建自定义层。 我要做的事情实际上相当简单:生成一个具有“stateful”变量的输出层,即在每个批次更新值的张量。
为了使一切更加清晰,请看下面的代码片段:
def call(self, inputs)

   c = self.constant
   m = self.extra_constant

   update = inputs*m + c 
   X_new = self.X_old + update 

   outputs = X_new

   self.X_old = X_new   

   return outputs

这里的想法非常简单:
  • def__init__(self, ...)中,将X_old初始化为0
  • update根据层输入计算得出
  • 计算层输出(即X_new
  • X_old的值设置为X_new,以便在下一批次中,X_old不再等于零,而是等于前一批次中的X_new
我发现K.update可以完成这项工作,如示例所示:
 X_new = K.update(self.X_old, self.X_old + update)

这里的问题是,如果我试图将该层的输出定义为:
outputs = X_new

return outputs

当我尝试使用model.fit()时,会收到以下错误:

ValueError: An operation has `None` for gradient. Please make sure that all of your ops have 
gradient defined (i.e. are differentiable). Common ops without gradient: K.argmax, K.round, K.eval.

尽管我已经强制将 layer.trainable = False,并且没有为该层定义任何偏置或权重,但我仍然遇到了这个错误。另一方面,如果我只是执行self.X_old = X_newX_old的值并不会得到更新。
你们有解决办法来实现这个吗?我相信这不应该很难,因为状态RNN也具有“类似”的功能。
提前感谢你的帮助!
1个回答

4
定义自定义层有时可能会变得混乱。您重写的一些方法只会被调用一次,但它会让您感觉到,就像许多其他OO库/框架一样,它们将被调用多次。
这就是我的意思:当您定义一个层并在模型中使用它时,您为覆盖"call"方法编写的Python代码不会直接在前向或后向传递中调用。相反,它仅在调用"model.compile"时被调用一次。它将Python代码编译成计算图,在该图中张量将流动,在训练和预测期间执行计算。
这就是为什么如果您想通过添加"print"语句来调试模型,它不起作用;您需要使用"tf.print"将打印命令添加到图形中。
对于您想要拥有的状态变量的情况也是如此。您需要调用一个Keras函数,将操作添加到图形中,而不是简单地将"old + update"分配给"new"。
请注意,张量是不可变的,因此您需要在"__init__"方法中将状态定义为"tf.Variable"。
所以我认为这段代码更符合您的要求:
class CustomLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(CustomLayer, self).__init__(**kwargs)
    self.state = tf.Variable(tf.zeros((3,3), 'float32'))
    self.constant = tf.constant([[1,1,1],[1,0,-1],[-1,0,1]], 'float32')
    self.extra_constant = tf.constant([[1,1,1],[1,0,-1],[-1,0,1]], 'float32')
    self.trainable = False

  def call(self, X):
    m = self.constant    
    c = self.extra_constant
    outputs = self.state + tf.matmul(X, m) + c
    tf.keras.backend.update(self.state, tf.reduce_sum(outputs, axis=0))

    return outputs

嗨,Mohammad,我尝试了你建议的方法,但实际上它对我没有起作用。我不知道为什么,但是 tf.keras.backend.update() 不会更新 self.state 的值,除非我将此更新分配给一个新变量,例如 X_tmp = tf.keras.backend.update(self.state, ...),然后我强制网络输出等于 X_tmp(但是这样我会得到 ValueError)。无论如何,还是谢谢你的回复! - d_gg
请在发布答案之前查看我制作的笔记本以测试代码:https://colab.research.google.com/gist/MJafarMashhadi/7fe9e90e615ab6fa749e60555a92de34/sotest.ipynb。它可以正常工作,而不需要将“update”的返回值分配给其他任何内容。 - Mohammad Jafar Mashhadi
1
我的错,实际上它看起来正在工作。还有一个问题:我如何在每个时期结束时将self.state的值重置为0呢?否则训练过程中其值会不断增加。提前致谢! - d_gg
那将是一个新问题。据我所知,您可以在拟合函数上使用训练回调函数之一是 on_epoch_end。您可以在那里访问模型及其所有层,我尚未测试过,但您可能能够在那里将其重置为零。 - Mohammad Jafar Mashhadi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接