ValueError: 没有为任何变量提供梯度 – 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上查看这个问题表明,这可能与交叉熵损失函数有关;但我无法看出我在这里做错了什么。

我认为这不是问题,但我还是想提一下,我使用的是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_generatortraining参数应该完全移除,或者,如果你对它有其他用途,开发数据集应该使用传递给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,)})

Related Posts

使用LSTM在Python中预测未来值

这段代码可以预测指定股票的当前日期之前的值,但不能预测…

如何在gensim的word2vec模型中查找双词组的相似性

我有一个word2vec模型,假设我使用的是googl…

dask_xgboost.predict 可以工作但无法显示 – 数据必须是一维的

我试图使用 XGBoost 创建模型。 看起来我成功地…

ML Tuning – Cross Validation in Spark

我在https://spark.apache.org/…

如何在React JS中使用fetch从REST API获取预测

我正在开发一个应用程序,其中Flask REST AP…

如何分析ML.NET中多类分类预测得分数组?

我在ML.NET中创建了一个多类分类项目。该项目可以对…

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注