我已经子类化了tf.keras.Model
,并在for循环中使用tf.keras.layers.GRUCell
来计算序列’y_t’(n, 时间步, 隐藏单元)和最终隐藏状态’h_t’(n, 隐藏单元)。为了让我的循环输出’y_t’,我在每次循环迭代后更新一个tf.Variable
。使用model(input)
调用模型没有问题,但是当我在call方法中使用for循环来拟合模型时,我会得到TypeError或ValueError错误。
请注意,我不能简单地使用tf.keras.layers.GRU
,因为我试图实现这篇论文。与其仅仅将x_t传递给RNN中的下一个单元,论文在for循环中执行了一些计算作为一个步骤(他们在PyTorch中实现),并将这些计算的结果传递给RNN单元。他们最终基本上是这样做的:h_t = f(special_x_t, h_t-1)。
请查看下面导致错误的模型:
class CustomGruRNN(tf.keras.Model): def __init__(self, batch_size, timesteps, hidden_units, features, **kwargs): # 继承 super().__init__(**kwargs) # 参数 self.batch_size = batch_size self.timesteps = timesteps self.hidden_units = hidden_units # 存储y_t self.rnn_outputs = tf.Variable(tf.zeros(shape=(batch_size, timesteps, hidden_units)), trainable=False) # 在call方法中的for循环中使用 self.gru_cell = tf.keras.layers.GRUCell(units=hidden_units) # 调整形状以匹配输入维度 self.dense = tf.keras.layers.Dense(units=features) def call(self, inputs): """Inputs是形状为(n, 时间步, 特征)的3阶张量 """ # 初始状态为gru单元 h_t = tf.zeros(shape=(self.batch_size, self.hidden_units)) for timestep in tf.range(self.timesteps): # 获取输入的时间步 x_t = tf.gather(inputs, timestep, axis=1) # 等同于x_t = inputs[:, timestep, :] # 计算输出和隐藏状态 y_t, h_t = self.gru_cell(x_t, h_t) # 在第t个时间步更新y_t self.rnn_outputs = self.rnn_outputs[:, timestep, :].assign(y_t) # 输出需要与输入的最后一个维度相同 outputs = self.dense(self.rnn_outputs) return outputs
一个会引发错误的示例:
# 数据集的任意值num_samples = 128batch_size = 4timesteps = 5features = 10# 任意数据集x = tf.random.uniform(shape=(num_samples, timesteps, features))y = tf.random.uniform(shape=(num_samples, timesteps, features))train_data = tf.data.Dataset.from_tensor_slices((x, y))train_data = train_data.shuffle(batch_size).batch(batch_size, drop_remainder=True)# 具有任意隐藏单元的模型model = CustomGruRNN(batch_size, timesteps, hidden_units=5)model.compile(loss=tf.keras.losses.MeanSquaredError(), optimizer=tf.keras.optimizers.Adam())
在急切运行时:
model.fit(train_data, epochs=2, run_eagerly=True)
第1/2个周期警告:tensorflow:当最小化损失时,变量[‘stack_overflow_gru_rnn/gru_cell/kernel:0′,’stack_overflow_gru_rnn/gru_cell/recurrent_kernel:0′,’stack_overflow_gru_rnn/gru_cell/bias:0’]的梯度不存在。ValueError: 未找到子字符串 ValueError
在非急切运行时:
model.fit(train_data, epochs=2, run_eagerly=False)
第1/2个周期TypeError: 在用户代码中:TypeError: 无法将NoneType转换为Tensor或Operation。
回答:
编辑:
虽然TensorFlow指南的答案足够,但我认为我自己回答的关于RNN的自定义单元的问题是一个更好的选择。请查看这个回答。使用自定义RNN单元可以避免使用tf.Transpose
和tf.TensorArray
,从而降低代码的复杂性,同时提高可读性。
原始自我回答:
TensorFlow的有效TensorFlow2指南末尾描述的DynamicRNN解决了我的问题。
简单扩展一下DynamicRNN的概念使用,定义一个RNN单元,在我的情况下是GRU,然后可以在tf.range
循环内定义任意数量的自定义步骤。变量应该在循环外部但在call方法内部使用tf.TensorArray
对象进行跟踪,这类数组的大小可以通过简单地调用(输入)张量的.shape
方法来确定。值得注意的是,DynamicRNN对象在模型拟合中工作,其默认执行模式是’图形’模式,而不是较慢的’急切执行’模式。
最后,可能需要使用’DynamicRNN’,因为默认情况下,tf.keras.layers.GRU
的计算可以大致描述为以下递归逻辑(假设’f’定义了一个GRU单元):
# 这里使用Numpy是为了便于索引,但通常应该使用张量并相应地转置它们(请参阅之前链接的指南)inputs = np.random.randn((batch, total_timesteps, features))# 用于跟踪输出的列表 - 仅为简单演示...再次请参阅指南以获取更多细节outputs = []# 初始化RNN单元的'隐藏状态'(通常称为h_naught并表示为h_0)state_at_t_minus_1 = tf.zeros(shape=(batch, hidden_cell_units))# 遍历输入,直到GRU单元函数'f'已经'看到'序列中的所有时间步for timestep_t in total_timesteps: # 这形状为(batch, features) input_at_t = inputs[:, timestep_t, :] # output_at_t形状为(batch, hidden_units_of_cell)和state_at_t (batch, hidden_units_of_cell) output_at_t, state_at_t = f(input_at_t, state_at_t_minus_1) outputs.append(output_at_t) # 当循环重新开始时,这个变量将在下一个GRU单元函数调用'f'中使用 state_at_t_minus_1 = state_at_t
可能希望在递归逻辑的for循环中添加其他步骤(例如,密集层,其他层等)来修改传递给GRU单元函数’f’的输入和状态。这是DynamicRNN的一个动机。