ValueError: No gradients provided for any variable - Tensorflow 2.0/Keras
ValueError: No gradients provided for any variable - Tensorflow 2.0/Keras
我正在尝试使用 Keras 实现一个简单的序列到序列模型。但是,我一直看到以下 ValueError
:
ValueError: No gradients provided for any variable: ['simple_model/time_distributed/kernel:0', 'simple_model/time_distributed/bias:0', 'simple_model/embedding/embeddings:0', 'simple_model/conv2d/kernel:0', 'simple_model/conv2d/bias:0', 'simple_model/dense_1/kernel:0', 'simple_model/dense_1/bias:0'].
Github上的this or looking at this issue等其他问题表明,这可能与交叉熵损失函数有关;但我看不出我做错了什么。
我不认为这是问题所在,但我想说的是,我正在进行 TensorFlow 的夜间构建,准确地说 tf-nightly==2.2.0.dev20200410
。
以下代码是一个独立的示例,应该重现上面的异常:
import random
from functools import partial
import tensorflow as tf
from tensorflow import keras
from tensorflow_datasets.core.features.text import SubwordTextEncoder
EOS = '<eos>'
PAD = '<pad>'
RESERVED_TOKENS = [EOS, PAD]
EOS_ID = RESERVED_TOKENS.index(EOS)
PAD_ID = RESERVED_TOKENS.index(PAD)
dictionary = [
'verstehen',
'verstanden',
'vergessen',
'verlegen',
'verlernen',
'vertun',
'vertan',
'verloren',
'verlieren',
'verlassen',
'verhandeln',
]
dictionary = [word.lower() for word in dictionary]
class SimpleModel(keras.models.Model):
def __init__(self, params, *args, **kwargs):
super().__init__(*args, **kwargs)
self.params = params
self.out_layer = keras.layers.Dense(1, activation='softmax')
self.model_layers = [
keras.layers.Embedding(params['vocab_size'], params['vocab_size']),
keras.layers.Lambda(lambda l: tf.expand_dims(l, -1)),
keras.layers.Conv2D(1, 4),
keras.layers.MaxPooling2D(1),
keras.layers.Dense(1, activation='relu'),
keras.layers.TimeDistributed(self.out_layer)
]
def call(self, example, training=None, mask=None):
x = example['inputs']
for layer in self.model_layers:
x = layer(x)
return x
def sample_generator(text_encoder: SubwordTextEncoder, max_sample: int = None):
count = 0
while True:
random.shuffle(dictionary)
for word in dictionary:
for i in range(1, len(word)):
inputs = word[:i]
targets = word
example = dict(
inputs=text_encoder.encode(inputs) + [EOS_ID],
targets=text_encoder.encode(targets) + [EOS_ID],
)
count += 1
yield example
if max_sample is not None and count >= max_sample:
print('Reached max_samples (%d)' % max_sample)
return
def make_dataset(generator_fn, params, training):
dataset = tf.data.Dataset.from_generator(
generator_fn,
output_types={
'inputs': tf.int64,
'targets': tf.int64,
}
).padded_batch(
params['batch_size'],
padded_shapes={
'inputs': (None,),
'targets': (None,)
},
)
if training:
dataset = dataset.map(partial(prepare_example, params=params)).repeat()
return dataset
def prepare_example(example: dict, params: dict):
# Make sure targets are one-hot encoded
example['targets'] = tf.one_hot(example['targets'], depth=params['vocab_size'])
return example
def main():
text_encoder = SubwordTextEncoder.build_from_corpus(
iter(dictionary),
target_vocab_size=1000,
max_subword_length=6,
reserved_tokens=RESERVED_TOKENS
)
generator_fn = partial(sample_generator, text_encoder=text_encoder, max_sample=10)
params = dict(
batch_size=20,
vocab_size=text_encoder.vocab_size,
hidden_size=32,
max_input_length=30,
max_target_length=30
)
model = SimpleModel(params)
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
)
train_dataset = make_dataset(generator_fn, params, training=True)
dev_dataset = make_dataset(generator_fn, params, training=False)
# Peek data
for train_batch, dev_batch in zip(train_dataset, dev_dataset):
print(train_batch)
print(dev_batch)
break
model.fit(
train_dataset,
epochs=1000,
steps_per_epoch=100,
validation_data=dev_dataset,
validation_steps=100,
)
if __name__ == '__main__':
main()
更新
您的代码中存在两组不同的问题,可归类为句法问题和体系结构问题。引发的错误(即 No gradients provided for any variable
)与我将在下面主要解决的语法问题有关,但我也会尝试为您提供一些有关架构问题的指示。
语法问题的主要原因是关于为模型使用命名的输入和输出。当模型具有多个输入 and/or 输出层时,Keras 中的命名输入和输出最有用。但是,您的模型只有一个输入层和一个输出层。因此,在这里使用命名输入和输出可能不是很有用,但如果这是你的决定,我会解释如何正确完成。
首先,您应该记住,在使用 Keras 模型时,从任何输入管道(无论是 Python 生成器还是 tf.data.Dataset
)生成的数据都应作为元组提供即 (input_batch, output_batch)
或 (input_batch, output_batch, sample_weights)
。而且,正如我所说,在处理输入管道时,这是 Keras 中所有地方的预期格式,即使我们将命名输入和输出用作字典时也是如此。
例如,如果我想使用 inputs/outputs 命名并且我的模型有两个输入层命名为 "words" 和 "importance",还有两个输出层命名为 "output1" 和 "output2",它们的格式应该是这样的:
({'words': words_data, 'importance': importance_data},
{'output1': output1_data, 'output2': output2_data})
正如您在上面看到的,它是一个元组,其中元组的每个元素都是一个字典;第一个元素对应于模型的输入,第二个元素对应于模型的输出。现在,根据这一点,让我们看看您的代码应该做哪些修改:
在 sample_generator
中,我们应该 return 一个字典元组,而不是一个字典。所以:
example = tuple([
{'inputs': text_encoder.encode(inputs) + [EOS_ID]},
{'targets': text_encoder.encode(targets) + [EOS_ID]},
])
在 make_dataset
函数中,tf.data.Dataset
的输入参数应该遵守:
output_types=(
{'inputs': tf.int64},
{'targets': tf.int64}
)
padded_shapes=(
{'inputs': (None,)},
{'targets': (None,)}
)
prepare_example
的签名和它的正文也要修改:
def prepare_example(ex_inputs: dict, ex_outputs: dict, params: dict):
# Make sure targets are one-hot encoded
ex_outputs['targets'] = tf.one_hot(ex_outputs['targets'], depth=params['vocab_size'])
return ex_inputs, ex_outputs
最后,子类模型的call
方法:
return {'targets': x}
还有一件事:我们还应该在构建层时使用 name
参数将这些名称放在相应的输入和输出层上(如 Dense(..., name='output')
;但是,因为我们在这里使用 Model
子类来定义我们的模型,所以没有必要这样做。
好的,这些将解决 input/output 问题,并且与梯度相关的错误将消失;但是,如果在应用上述修改后 运行 代码,您仍然会收到关于不兼容形状的错误。正如我之前所说,您的模型中存在架构问题,我将在下面简要说明。
如你所说,这应该是一个seq-to-seq模型。因此,输出是一个单热编码向量序列,其中每个向量的长度等于(目标序列)词汇量大小。因此,softmax 分类器的单位应该与词汇表的大小一样多,就像这样(注意:在任何模型或问题中都不要使用只有一个单位的 softmax 层;那都是错误的!想想为什么它是错误的!):
self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')
接下来要考虑的是我们正在处理一维序列(即 tokens/words 的序列)这一事实。因此,在这里使用 2D 卷积和 2D 池化层没有意义。您可以使用它们的 1D 对应物,也可以将它们替换为 RNN 层之类的其他东西。因此,Lambda
层也应该被删除。另外,如果你想使用卷积和池化,你应该适当地调整每一层中的过滤器数量以及池大小(即一个卷积过滤器,Conv1D(1,...)
可能不是最佳的,池大小为 1 确实没有意义)。
此外,只有一个单元的最后一层之前的 Dense
层可能会严重限制模型的表示能力(即它本质上是模型的瓶颈)。要么增加其单位数量,要么删除它。
另一件事是,没有理由不对开发集的标签进行one-hot编码。相反,它们应该像训练集的标签一样被单热编码。因此,应该完全删除 make_generator
的 training
参数,或者,如果您有其他用例,应该使用传递给 [=22] 的 training=True
参数创建开发数据集=]函数。
最后,在所有这些更改之后,您的模型可能会起作用并开始拟合数据;但是经过几批之后,您可能会再次遇到形状不兼容的错误。那是因为您正在生成具有未知维度的输入数据,并且还使用宽松的填充方法根据需要填充每个批次(即通过使用 (None,
)来填充 padded_shapes
)。要解决这个问题,您应该决定一个固定的 input/output 维度(例如考虑 input/output 序列的固定长度),然后调整模型的架构或超参数(例如 conv 内核大小、conv填充、池大小、添加更多层等)以及相应的 padded_shapes
参数。即使您希望您的模型改为支持 input/output 可变长度序列,您也应该在模型的体系结构和超参数以及 padded_shapes
参数中考虑它。由于此解决方案取决于您心中的任务和所需设计,并且没有万能的解决方案,因此我不会对此进一步评论并留给您自己解决。但这里有一个可行的解决方案(可能不是,也可能根本不是最佳的)只是为了给你一个想法:
self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')
self.model_layers = [
keras.layers.Embedding(params['vocab_size'], params['vocab_size']),
keras.layers.Conv1D(32, 4, padding='same'),
keras.layers.TimeDistributed(self.out_layer)
]
# ...
padded_shapes=(
{'inputs': (10,)},
{'targets': (10,)}
)
我正在尝试使用 Keras 实现一个简单的序列到序列模型。但是,我一直看到以下 ValueError
:
ValueError: No gradients provided for any variable: ['simple_model/time_distributed/kernel:0', 'simple_model/time_distributed/bias:0', 'simple_model/embedding/embeddings:0', 'simple_model/conv2d/kernel:0', 'simple_model/conv2d/bias:0', 'simple_model/dense_1/kernel:0', 'simple_model/dense_1/bias:0'].
Github上的this or looking at this issue等其他问题表明,这可能与交叉熵损失函数有关;但我看不出我做错了什么。
我不认为这是问题所在,但我想说的是,我正在进行 TensorFlow 的夜间构建,准确地说 tf-nightly==2.2.0.dev20200410
。
以下代码是一个独立的示例,应该重现上面的异常:
import random
from functools import partial
import tensorflow as tf
from tensorflow import keras
from tensorflow_datasets.core.features.text import SubwordTextEncoder
EOS = '<eos>'
PAD = '<pad>'
RESERVED_TOKENS = [EOS, PAD]
EOS_ID = RESERVED_TOKENS.index(EOS)
PAD_ID = RESERVED_TOKENS.index(PAD)
dictionary = [
'verstehen',
'verstanden',
'vergessen',
'verlegen',
'verlernen',
'vertun',
'vertan',
'verloren',
'verlieren',
'verlassen',
'verhandeln',
]
dictionary = [word.lower() for word in dictionary]
class SimpleModel(keras.models.Model):
def __init__(self, params, *args, **kwargs):
super().__init__(*args, **kwargs)
self.params = params
self.out_layer = keras.layers.Dense(1, activation='softmax')
self.model_layers = [
keras.layers.Embedding(params['vocab_size'], params['vocab_size']),
keras.layers.Lambda(lambda l: tf.expand_dims(l, -1)),
keras.layers.Conv2D(1, 4),
keras.layers.MaxPooling2D(1),
keras.layers.Dense(1, activation='relu'),
keras.layers.TimeDistributed(self.out_layer)
]
def call(self, example, training=None, mask=None):
x = example['inputs']
for layer in self.model_layers:
x = layer(x)
return x
def sample_generator(text_encoder: SubwordTextEncoder, max_sample: int = None):
count = 0
while True:
random.shuffle(dictionary)
for word in dictionary:
for i in range(1, len(word)):
inputs = word[:i]
targets = word
example = dict(
inputs=text_encoder.encode(inputs) + [EOS_ID],
targets=text_encoder.encode(targets) + [EOS_ID],
)
count += 1
yield example
if max_sample is not None and count >= max_sample:
print('Reached max_samples (%d)' % max_sample)
return
def make_dataset(generator_fn, params, training):
dataset = tf.data.Dataset.from_generator(
generator_fn,
output_types={
'inputs': tf.int64,
'targets': tf.int64,
}
).padded_batch(
params['batch_size'],
padded_shapes={
'inputs': (None,),
'targets': (None,)
},
)
if training:
dataset = dataset.map(partial(prepare_example, params=params)).repeat()
return dataset
def prepare_example(example: dict, params: dict):
# Make sure targets are one-hot encoded
example['targets'] = tf.one_hot(example['targets'], depth=params['vocab_size'])
return example
def main():
text_encoder = SubwordTextEncoder.build_from_corpus(
iter(dictionary),
target_vocab_size=1000,
max_subword_length=6,
reserved_tokens=RESERVED_TOKENS
)
generator_fn = partial(sample_generator, text_encoder=text_encoder, max_sample=10)
params = dict(
batch_size=20,
vocab_size=text_encoder.vocab_size,
hidden_size=32,
max_input_length=30,
max_target_length=30
)
model = SimpleModel(params)
model.compile(
optimizer='adam',
loss='categorical_crossentropy',
)
train_dataset = make_dataset(generator_fn, params, training=True)
dev_dataset = make_dataset(generator_fn, params, training=False)
# Peek data
for train_batch, dev_batch in zip(train_dataset, dev_dataset):
print(train_batch)
print(dev_batch)
break
model.fit(
train_dataset,
epochs=1000,
steps_per_epoch=100,
validation_data=dev_dataset,
validation_steps=100,
)
if __name__ == '__main__':
main()
更新
您的代码中存在两组不同的问题,可归类为句法问题和体系结构问题。引发的错误(即 No gradients provided for any variable
)与我将在下面主要解决的语法问题有关,但我也会尝试为您提供一些有关架构问题的指示。
语法问题的主要原因是关于为模型使用命名的输入和输出。当模型具有多个输入 and/or 输出层时,Keras 中的命名输入和输出最有用。但是,您的模型只有一个输入层和一个输出层。因此,在这里使用命名输入和输出可能不是很有用,但如果这是你的决定,我会解释如何正确完成。
首先,您应该记住,在使用 Keras 模型时,从任何输入管道(无论是 Python 生成器还是 tf.data.Dataset
)生成的数据都应作为元组提供即 (input_batch, output_batch)
或 (input_batch, output_batch, sample_weights)
。而且,正如我所说,在处理输入管道时,这是 Keras 中所有地方的预期格式,即使我们将命名输入和输出用作字典时也是如此。
例如,如果我想使用 inputs/outputs 命名并且我的模型有两个输入层命名为 "words" 和 "importance",还有两个输出层命名为 "output1" 和 "output2",它们的格式应该是这样的:
({'words': words_data, 'importance': importance_data},
{'output1': output1_data, 'output2': output2_data})
正如您在上面看到的,它是一个元组,其中元组的每个元素都是一个字典;第一个元素对应于模型的输入,第二个元素对应于模型的输出。现在,根据这一点,让我们看看您的代码应该做哪些修改:
在
sample_generator
中,我们应该 return 一个字典元组,而不是一个字典。所以:example = tuple([ {'inputs': text_encoder.encode(inputs) + [EOS_ID]}, {'targets': text_encoder.encode(targets) + [EOS_ID]}, ])
在
make_dataset
函数中,tf.data.Dataset
的输入参数应该遵守:output_types=( {'inputs': tf.int64}, {'targets': tf.int64} ) padded_shapes=( {'inputs': (None,)}, {'targets': (None,)} )
prepare_example
的签名和它的正文也要修改:def prepare_example(ex_inputs: dict, ex_outputs: dict, params: dict): # Make sure targets are one-hot encoded ex_outputs['targets'] = tf.one_hot(ex_outputs['targets'], depth=params['vocab_size']) return ex_inputs, ex_outputs
最后,子类模型的
call
方法:return {'targets': x}
还有一件事:我们还应该在构建层时使用
name
参数将这些名称放在相应的输入和输出层上(如Dense(..., name='output')
;但是,因为我们在这里使用Model
子类来定义我们的模型,所以没有必要这样做。
好的,这些将解决 input/output 问题,并且与梯度相关的错误将消失;但是,如果在应用上述修改后 运行 代码,您仍然会收到关于不兼容形状的错误。正如我之前所说,您的模型中存在架构问题,我将在下面简要说明。
如你所说,这应该是一个seq-to-seq模型。因此,输出是一个单热编码向量序列,其中每个向量的长度等于(目标序列)词汇量大小。因此,softmax 分类器的单位应该与词汇表的大小一样多,就像这样(注意:在任何模型或问题中都不要使用只有一个单位的 softmax 层;那都是错误的!想想为什么它是错误的!):
self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')
接下来要考虑的是我们正在处理一维序列(即 tokens/words 的序列)这一事实。因此,在这里使用 2D 卷积和 2D 池化层没有意义。您可以使用它们的 1D 对应物,也可以将它们替换为 RNN 层之类的其他东西。因此,Lambda
层也应该被删除。另外,如果你想使用卷积和池化,你应该适当地调整每一层中的过滤器数量以及池大小(即一个卷积过滤器,Conv1D(1,...)
可能不是最佳的,池大小为 1 确实没有意义)。
此外,只有一个单元的最后一层之前的 Dense
层可能会严重限制模型的表示能力(即它本质上是模型的瓶颈)。要么增加其单位数量,要么删除它。
另一件事是,没有理由不对开发集的标签进行one-hot编码。相反,它们应该像训练集的标签一样被单热编码。因此,应该完全删除 make_generator
的 training
参数,或者,如果您有其他用例,应该使用传递给 [=22] 的 training=True
参数创建开发数据集=]函数。
最后,在所有这些更改之后,您的模型可能会起作用并开始拟合数据;但是经过几批之后,您可能会再次遇到形状不兼容的错误。那是因为您正在生成具有未知维度的输入数据,并且还使用宽松的填充方法根据需要填充每个批次(即通过使用 (None,
)来填充 padded_shapes
)。要解决这个问题,您应该决定一个固定的 input/output 维度(例如考虑 input/output 序列的固定长度),然后调整模型的架构或超参数(例如 conv 内核大小、conv填充、池大小、添加更多层等)以及相应的 padded_shapes
参数。即使您希望您的模型改为支持 input/output 可变长度序列,您也应该在模型的体系结构和超参数以及 padded_shapes
参数中考虑它。由于此解决方案取决于您心中的任务和所需设计,并且没有万能的解决方案,因此我不会对此进一步评论并留给您自己解决。但这里有一个可行的解决方案(可能不是,也可能根本不是最佳的)只是为了给你一个想法:
self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')
self.model_layers = [
keras.layers.Embedding(params['vocab_size'], params['vocab_size']),
keras.layers.Conv1D(32, 4, padding='same'),
keras.layers.TimeDistributed(self.out_layer)
]
# ...
padded_shapes=(
{'inputs': (10,)},
{'targets': (10,)}
)