ValueError:没有为任何变量提供梯度 - Tensorflow 2.0/Keras

Posted

技术标签:

【中文标题】ValueError:没有为任何变量提供梯度 - Tensorflow 2.0/Keras【英文标题】:ValueError: No gradients provided for any variable - Tensorflow 2.0/Keras 【发布时间】:2020-07-29 15:51:59 【问题描述】:

我正在尝试使用 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'].

其他问题,如 this 或查看 Github 上的 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()

更新

要点link Github 问题link

【问题讨论】:

你测试给定的答案了吗?真的能解决问题吗? @today 嗨!抱歉我还没回答。我目前正被其他事情分心,但我会确保今天或明天对此进行测试并让你知道!提前感谢您的努力! 【参考方案1】:

您的代码中有两组不同的问题,可分为句法问题和架构问题。引发的错误(即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
1234563在这里子类化来定义我们的模型,这不是必须的。

好的,这些将解决输入/输出问题,并且与梯度相关的错误将消失;但是,如果您在应用上述修改后运行代码,您仍然会收到有关不兼容形状的错误。正如我之前所说,您的模型中存在架构问题,我将在下面简要说明。


正如您所提到的,这应该是一个 seq-to-seq 模型。因此,输出是 one-hot 编码向量的序列,其中每个向量的长度等于(目标序列)词汇量大小。因此,softmax 分类器的单元应该与词汇量一样多,就像这样(注意:在任何模型或问题中,永远不要使用只有一个单元的 softmax 层;这都是错误的!想想为什么错了!):

self.out_layer = keras.layers.Dense(params['vocab_size'], activation='softmax')

接下来要考虑的是我们正在处理一维序列(即标记/单词序列)的事实。因此,在这里使用 2D 卷积和 2D 池化层没有意义。您可以使用它们的 1D 对应物,也可以将它们替换为 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,)
)

【讨论】:

嗨!不幸的是,由于我正忙于其他任务,因此我仍然没有时间进行您建议的更改。 Howeer,这一次我将利用 ***s 的信誉系统。您的回答听起来很合理,因此我会接受您的回答并在赏金到期之前奖励您。感谢您的回答,一旦我有空继续工作,我肯定会回来。 :)

以上是关于ValueError:没有为任何变量提供梯度 - Tensorflow 2.0/Keras的主要内容,如果未能解决你的问题,请参考以下文章

Tensorflow:实现新的损失函数返回“ValueError:没有为任何变量提供梯度”

简单的 TensorFlow LSTM 网络:ValueError:没有为任何变量提供梯度

Tensorflow Autoencoder ValueError:没有为任何变量提供梯度

TensorFlow 自定义损失 ValueError:“没有为任何变量提供渐变:[....'Layers'....]”

TensorFlow 2 自定义损失:“没有为任何变量提供梯度”错误

tensorflow2.0中的任何变量均未提供梯度