Фон
Мой вопрос основан на примере из «Практического машинного обучения» Герона, глава 12: Пользовательские модели.
Цель этого примера — создать пользовательскую модель нейронной сети. Модель имеет 5 Dense
скрытых слоев. Пользовательская часть заключается в том, что мы добавляем слой reconstruction
перед выводом. Целью слоя реконструкции является реконструкция входных данных. Затем мы берем разницу reconstruction-inputs
, получаем MSE и применяем это значение к функции потерь. Это должен быть шаг регуляризации.
Минимальный (должен быть) рабочий пример
Следующий код почти прямо из учебника, но он не работает.
import numpy as np
num_training=10;
num_dim=2;
X = np.random.random((10,2))
y = np.random.random(10)
import tensorflow as tf
import tensorflow.keras as keras
class ReconstructingRegressor(keras.models.Model):
def __init__(self, output_dim, **kwargs):
super().__init__(**kwargs)
self.hidden = [keras.layers.Dense(30, activation = "selu",
kernel_initializer = "lecun_normal")
for _ in range(5)]
self.out = keras.layers.Dense(output_dim)
def build(self, batch_input_shape):
n_inputs = batch_input_shape[-1]
self.reconstruct = keras.layers.Dense(n_inputs)
super().build(batch_input_shape)
def call(self, inputs, training=None):
Z = inputs
for layer in self.hidden:
Z = layer(Z)
reconstruction = self.reconstruct(Z)
recon_loss = tf.reduce_mean(tf.square(reconstruction - inputs))
self.add_loss(0.05 * recon_loss)
return self.out(Z)
model = ReconstructingRegressor(1)
model.compile(loss = "mse", optimizer = "nadam")
history = model.fit(X, y, epochs=2)
Сообщение об ошибке
Однако при вызове model.fit()
я получаю следующую ошибку:
---------------------------------------------------------------------------
InaccessibleTensorError Traceback (most recent call last)
<ipython-input-10-b7211d3022fa> in <module>
34 model = ReconstructingRegressor(1)
35 model.compile(loss = "mse", optimizer = "nadam")
---> 36 history = model.fit(X, y, epochs=2)
и в конце сообщения об ошибке:
InaccessibleTensorError: тензор «Tensor («mul: 0», shape =(), dtype = float32)» здесь недоступен: он определен в другой функции или блоке кода. Используйте возвращаемые значения, явные локальные переменные Python или коллекции TensorFlow для доступа к ним. Определено в: FuncGraph(name=build_graph, id=140602287140624); доступ из: FuncGraph (имя = функция поезда, идентификатор = 140602287108640).
Поиск неисправностей
Если я закомментирую код, вычисляющий потери, т.е.
#recon_loss = tf.reduce_mean(tf.square(reconstruction - inputs))
#self.add_loss(0.05 * recon_loss)
в call
, но все остальное остается прежним, тогда я получаю следующее предупреждение
ВНИМАНИЕ:tensorflow:градиенты не существуют для переменных ['dense/kernel:0', 'dense/bias:0'] при минимизации потерь.
Не уверен, что это актуально.
Я пытался переместить определение self.reconstruct
в init()
, но, похоже, это ничего не меняет. Обратите внимание, что перемещение self.reconstruct
противоречит цели примера, потому что n_inputs
известно только тогда, когда вы попадаете внутрь build
.
Я не уверен на 100%, но я считаю, что проблема связана с тем, что потери, которые вы добавляете через self.add_loss
, относятся к слоям, которые не используются при расчете основных потерь и, возможно, оптимизированы вне основного. график. Следовательно, когда вы хотите получить к ним доступ, тензор недоступен.
Я думаю, что проще всего переписать сеть немного по-другому:
model.train_step
, чтобы по-прежнему можно было использовать fit
. (См. руководство: Настройте то, что происходит в Model.fit).Используя training
аргумент model.call
, мы настраиваем слой reconstruct
только во время обучения и заставляем сеть возвращать боту предсказание и реконструкцию. Однако когда мы хотим сделать прогноз, мы возвращаем только прогноз.
Переопределение train_step
нужно только для того, чтобы можно было по-прежнему использовать fit
, а не писать цикл обучения с нуля. В этом случае нам не нужно переопределять test_step
, потому что вариант использования довольно прост.
import tensorflow as tf
import tensorflow.keras as keras
import numpy as np
num_training = 10
num_dim = 2
X = np.random.random((10, 2)).astype(np.float32)
y = np.random.random((10,)).astype(np.float32)
class ReconstructingRegressor(keras.models.Model):
def __init__(self, output_dim, **kwargs):
super().__init__(**kwargs)
self.hidden = [
keras.layers.Dense(
30,
activation = "selu",
kernel_initializer = "lecun_normal",
name=f"hidden_{idx}",
)
for idx in range(5)
]
self.out = keras.layers.Dense(output_dim, name = "output")
def build(self, batch_input_shape):
n_inputs = batch_input_shape[-1]
self.reconstruct = keras.layers.Dense(n_inputs, name = "reconstruct")
super().build(batch_input_shape)
@staticmethod
def reconstruction_loss(reconstruction, inputs, rate=0.05):
return tf.reduce_mean(tf.square(reconstruction - inputs)) * rate
def train_step(self, data):
x, y = data
with tf.GradientTape() as tape:
y_pred, recon = self(x, training=True)
loss = self.compiled_loss(y, y_pred)
loss += self.reconstruction_loss(recon, x)
gradients = tape.gradient(loss, self.trainable_variables)
# Update weights
self.optimizer.apply_gradients(zip(gradients, self.trainable_variables))
# Update metrics (includes the metric that tracks the loss)
self.compiled_metrics.update_state(y, y_pred)
# Return a dict mapping metric names to current value
return {m.name: m.result() for m in self.metrics}
def call(self, inputs, training=None):
Z = inputs
for layer in self.hidden:
Z = layer(Z)
if training:
return self.out(Z), self.reconstruct(Z)
return self.out(Z)
model = ReconstructingRegressor(1)
model.compile(optimizer = "nadam", loss = "mse")
history = model.fit(X, y, epochs=10)
history = model.evaluate(X, y)
Это полный код.
self.add_loss
— это метод родительского классаkeras.models.Model
. Мы объявляем переменную-членself.reconstruct
в функцииbuild
. Он определяется как слойKeras
.