Как создать собственный слой в Keras с переменными / тензорами с отслеживанием состояния?

Я хотел бы попросить вас помочь в создании моего пользовательского слоя. То, что я пытаюсь сделать, на самом деле довольно простое: создание выходного слоя с переменными с сохранением состояния, то есть тензорами, значение которых обновляется при каждой партии.

Чтобы все было понятнее, вот отрывок того, что я хотел бы сделать:

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

Идея здесь довольно проста:

  • X_old инициализируется значением 0 в def__ init__(self, ...)
  • 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_new, значение X_old не обновится.

У вас, ребята, есть решение для этого? Я считаю, что это не должно быть так сложно, поскольку RNN с отслеживанием состояния также имеют «похожее» функционирование.

Заранее спасибо за помощь!


person d_gg    schedule 08.03.2020    source источник


Ответы (1)


Иногда определение настраиваемого слоя может сбивать с толку. Некоторые из методов, которые вы переопределяете, будут вызываться один раз, но создается впечатление, что, как и многие другие объектно-ориентированные библиотеки / фреймворки, они будут вызываться много раз.

Вот что я имею в виду: когда вы определяете слой и используете его в модели, код Python, который вы пишете для переопределения метода call, не будет напрямую вызываться при прямом или обратном проходе. Вместо этого он вызывается только один раз, когда вы вызываете model.compile. Он компилирует код Python в вычислительный граф, и этот граф, в котором будут течь тензоры, и есть то, что выполняет вычисления во время обучения и прогнозирования.

Вот почему, если вы хотите отладить свою модель, добавив оператор print, это не сработает; вам нужно использовать tf.print, чтобы добавить к графику команду печати.

То же самое и с переменной состояния, которую вы хотите иметь. Вместо простого присвоения old + update new вам нужно вызвать функцию Keras, которая добавляет эту операцию к графику.

И обратите внимание, что тензоры неизменяемы, поэтому вам нужно определить состояние как tf.Variable в методе __init__.

Поэтому я считаю, что этот код больше похож на то, что вы ищете:

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
person Mohammad Jafar Mashhadi    schedule 08.03.2020
comment
Привет, Мохаммад, я попробовал то, что вы предложили, но на самом деле у меня это не сработало. Я не знаю почему, но tf.keras.backend.update () не обновляет значение self.sate, если я не назначу это обновление новой переменной, например X_tmp = tf.keras.backend.update (self.state, ...), а затем я устанавливаю вывод сети равным X_tmp (но тогда я получаю ValueError). В любом случае спасибо за ответ! - person d_gg; 09.03.2020
comment
Перед отправкой ответа ознакомьтесь с записной книжкой, которую я сделал для проверки кода: colab.research.google.com/gist/MJafarMashhadi/ Он работает без присвоения возвращаемого значения update чему-либо еще - person Mohammad Jafar Mashhadi; 10.03.2020
comment
Плохо, похоже, что он работает. Еще один вопрос: как мне сбросить значение self.state обратно на 0 в конце каждой эпохи? В противном случае его ценность просто продолжала бы расти во время тренировки. Заранее спасибо! - person d_gg; 10.03.2020
comment
Это был бы новый вопрос. AFAIK есть обучающие обратные вызовы, которые вы можете использовать в своей функции соответствия, один из них - on_epoch_end. У вас есть доступ к модели и всем ее слоям, я не тестировал ее, но вы можете сбросить ее до нуля. - person Mohammad Jafar Mashhadi; 10.03.2020