我在尝试使用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上查看这个问题表明,这可能与交叉熵损失函数有关;但我无法看出我在这里做错了什么。
我认为这不是问题,但我还是想提一下,我使用的是TensorFlow的每日构建版本,确切地说是tf-nightly==2.2.0.dev20200410
。
以下代码是一个独立的示例,应该可以重现上述异常:
import randomfrom functools import partialimport tensorflow as tffrom tensorflow import kerasfrom tensorflow_datasets.core.features.text import SubwordTextEncoderEOS = '<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 xdef 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) returndef 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 datasetdef 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 exampledef 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
)与语法问题有关,我将在下面主要解决这些问题,但之后我也会尝试给你一些关于架构问题的建议。
语法问题的主要原因是关于模型的命名输入和输出。Keras中的命名输入和输出主要在模型有多个输入和/或输出层时有用。然而,你的模型只有一个输入层和一个输出层。因此,这里使用命名输入和输出可能不是很必要,但如果这是你的决定,我将解释如何正确地执行。
首先,你应该记住,当使用Keras模型时,从任何输入管道(无论是Python生成器还是tf.data.Dataset
)生成的数据应该以元组形式提供,即(input_batch, output_batch)
或(input_batch, output_batch, sample_weights)
。而且,正如我所说,这是Keras中处理输入管道时普遍期望的格式,即使我们使用命名输入和输出作为字典时也是如此。
例如,如果我想使用输入/输出的命名,我的模型有两个名为”words”和”importance”的输入层,以及两个名为”output1″和”output2″的输出层,它们应该像这样格式化:
({'words': words_data, 'importance': importance_data}, {'output1': output1_data, 'output2': output2_data})
如上所示,这是一个元组,其中元组的每个元素都是一个字典;第一个元素对应于模型的输入,第二个元素对应于模型的输出。现在,根据这一点,让我们看看应该对你的代码进行哪些修改:
-
在
sample_generator
中,我们应该返回一个字典元组,而不是一个字典。因此: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
子类化来定义我们的模型,这不是必需的。
好的,这些将解决输入/输出问题,并且与梯度相关的错误将消失;但是,如果你在应用上述修改后运行代码,你仍然会得到关于不兼容形状的错误。正如我之前所说,你的模型存在架构问题,我将在下面简要说明这些问题。
如你所述,这应该是一个序列到序列模型。因此,输出是一系列独热编码向量,其中每个向量的长度等于(目标序列)词汇量。因此,softmax分类器应该有与词汇量一样多的单元,像这样(注意:永远不要在任何模型或问题中使用只有一个单元的softmax层;那是完全错误的!想想为什么它是错误的!):
self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')
接下来要考虑的是我们处理的是一维序列(即一系列标记/词)。因此,这里使用二维卷积和二维池化层没有意义。你可以使用它们的一维对应物,或者用其他东西替换它们,比如RNN层。因此,Lambda
层也应该被移除。此外,如果你想使用卷积和池化,你还应该适当调整每层的过滤器数量以及池化大小(即一个卷积过滤器,Conv1D(1,...)
可能不是最佳选择,池化大小为1没有意义)。
此外,在最后一层之前的那个只有一个单元的Dense
层可能会严重限制模型的表示能力(即它本质上是你的模型的瓶颈)。要么增加它的单元数,要么移除它。
另一件事是,没有理由不对开发集的标签进行独热编码。相反,它们应该像训练集的标签一样进行独热编码。因此,make_generator
的training
参数应该完全移除,或者,如果你对它有其他用途,开发数据集应该使用传递给make_dataset
函数的training=True
参数创建。
最后,经过所有这些更改后,你的模型可能会工作并开始适应数据;但是在几批数据通过后,你可能会再次得到不兼容形状的错误。这是因为你在生成输入数据时使用了未知维度,并且使用了宽松的填充方法来根据需要填充每个批次(即通过使用(None,
)作为padded_shapes
)。要解决这个问题,你应该决定一个固定的输入/输出维度(例如,通过考虑输入/输出序列的固定长度),然后相应地调整模型的架构或超参数(例如卷积核大小,卷积填充,池化大小,添加更多层等)以及padded_shapes
参数。即使你希望你的模型支持可变长度的输入/输出序列,那么你也应该在模型的架构和超参数以及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,)})