ValueError: для любой переменной не предусмотрено градиентов - Tensorflow 2.0 / Keras - PullRequest
0 голосов
/ 16 апреля 2020

Я пытаюсь реализовать простую модель последовательности к последовательности, используя 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, позволяют предположить, что это может что-то делать с функцией потери энтропии; но я не вижу, что я делаю здесь неправильно.

Я не думаю, что это проблема, но я хочу упомянуть, что я работаю на ночной сборке 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()

Обновление

1 Ответ

0 голосов
/ 21 апреля 2020

В вашем коде есть два разных набора проблем, которые могут быть классифицированы как синтаксические и архитектурные проблемы. Возникшая ошибка (т. Е. No gradients provided for any variable) связана с синтаксическими проблемами, которые я в основном рассмотрю ниже, но я постараюсь дать вам несколько советов об архитектурных проблемах и после этого.

Основная причина Синтаксические проблемы - использование именованных входов и выходов для модели. Именованные входы и выходы в Keras в основном полезны, когда модель имеет несколько входных и / или выходных слоев. Однако ваша модель имеет только один входной и один выходной слой. Поэтому здесь может быть не очень полезно использовать именованные входы и выходы, но если это ваше решение, я объясню, как это можно сделать правильно.

Прежде всего, вы должны иметь в виду, что при использовании Keras В моделях данные, генерируемые из любого входного конвейера (будь то генератор Python или tf.data.Dataset), должны быть представлены в виде кортежа, т.е. (input_batch, output_batch) или (input_batch, output_batch, sample_weights). И, как я уже сказал, это ожидаемый формат везде в Keras при работе с входными конвейерами, даже когда мы используем именованные входы и выходы в качестве словарей.

Например, если я хочу использовать именование входов / выходов, и в моей модели есть два входных слоя с именами «слова» и «важность», а также два выходных слоя с именами «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')

Следующее, что нужно учитывать, это то, что мы имеем дело с одномерными последовательностями (то есть последовательностью токенов / слов). Поэтому использование слоев 2D-свертки и 2D-пула здесь не имеет смысла. Вы можете использовать их одномерные аналоги или заменить их чем-то другим, например слоями RNN. В результате этого слой Lambda также должен быть удален. Кроме того, если вы хотите использовать свертку и объединение в пул, вам следует правильно отрегулировать количество фильтров в каждом слое, а также размер пула (т. Е. Один фильтр извлечения, Conv1D(1,...), вероятно, не оптимален, а размер пула 1 не делает смысл).

Кроме того, слой Dense перед последним слоем, который имеет только одну единицу, может серьезно ограничить репрезентативную способность модели (т. е. это, по сути, узкое место вашей модели). Либо увеличьте его количество единиц, либо удалите его.

Другое дело, что нет причин для того, чтобы не кодировать ярлыки набора dev-hot-up. Скорее, они должны быть закодированы как метки в тренировочном наборе. Следовательно, либо аргумент training make_generator должен быть полностью удален, либо, если у вас есть какой-то другой вариант использования, набор данных dev должен быть создан с аргументом training=True, переданным функции make_dataset.

Наконец, после всех этих изменений ваша модель может работать и начать подгонку к данным; но по прошествии нескольких партий вы снова можете получить ошибку несовместимых фигур. Это потому, что вы генерируете входные данные с неизвестным измерением, а также используете подход с мягким заполнением, чтобы заполнить каждый пакет настолько, насколько это необходимо (т. Е. С помощью (None,) для padded_shapes). Чтобы решить эту проблему, вы должны выбрать фиксированное измерение ввода / вывода (например, с учетом фиксированной длины для последовательностей ввода / вывода), а затем скорректировать архитектуру или гиперпараметры модели (например, размер ядра conv, дополнение заполнения, размер пула добавив больше слоев и т. д. c.), а также аргумент 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,)}
)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...