Как мне предварительно обработать и токенизировать TensorFlow CsvDataset внутри метода map? - PullRequest
0 голосов
/ 26 апреля 2020

Я сделал TensorFlow CsvDataset, и я пытаюсь токенизировать данные следующим образом:

import os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from tensorflow import keras
import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
import os
os.chdir('/home/nicolas/Documents/Datasets')

fname = 'rotten_tomatoes_reviews.csv'


def preprocess(target, inputs):
    tok = Tokenizer(num_words=5_000, lower=True)
    tok.fit_on_texts(inputs)
    vectors = tok.texts_to_sequences(inputs)
    return vectors, target


dataset = tf.data.experimental.CsvDataset(filenames=fname,
                                          record_defaults=[tf.int32, tf.string],
                                          header=True).map(preprocess)

Запустив это, выдает следующую ошибку:

ValueError : len требует нескалярного тензора, получил один из Tensor shape ("Shape: 0", shape = (0,), dtype = int32)

Что я пробовал : почти все в сфере возможностей. Обратите внимание, что все работает, если я уберу шаг предварительной обработки.

Как выглядят данные:

(<tf.Tensor: shape=(), dtype=int32, numpy=1>,
 <tf.Tensor: shape=(), dtype=string, numpy=b" Some movie critic review...">)

1 Ответ

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

Прежде всего, давайте выясним проблемы в вашем коде:

  • Первая проблема, которая также является причиной данной ошибки, заключается в том, что метод fit_on_texts принимает список текстов, а не одна текстовая строка. Следовательно, оно должно быть: tok.fit_on_texts([inputs]).

  • После исправления и повторного запуска кода вы получите еще одну ошибку: AttributeError: 'Tensor' object has no attribute 'lower'. Это связано с тем, что элементы в наборе данных являются объектами Tensor, и функция map должна иметь возможность обрабатывать их; однако класс Tokenizer не предназначен для работы с объектами Tensor (есть решение этой проблемы, но я не буду сейчас его решать из-за следующей проблемы).

  • Самая большая проблема заключается в том, что каждый раз, когда вызывается функция карты, то есть preprocess, создается новый экземпляр класса Tokenizer, который будет помещаться в одном текстовом документе. Однако, как следует из названия метода fit_on_texts, он предназначен для применения его к всем текстовым документам только один раз . Другими словами, применять его только к одному текстовому документу не имеет смысла, просто потому, что вы не строите словарь, основанный только на одном примере (однако, если был метод частичное соответствие , то, вероятно, он может быть сделано таким образом)! Следовательно, вы не можете использовать tf.keras.preprocessing.text.Tokenizer класс здесь, то есть он не применим в этом специфицированном c конвейере данных.


Итак, что нам делать? Как упомянуто выше, почти во всех моделях, которые имеют дело с текстовыми данными, мы сначала должны преобразовать тексты в числовые элементы, то есть кодировать их. Для выполнения кодирования сначала нам понадобится словарный набор или словарь токенов. Таким образом, мы должны предпринять следующие шаги:

  1. Если имеется готовый словарь, перейдите к следующему шагу. В противном случае сначала разбейте все текстовые данные и создайте словарь.

  2. Кодируйте текстовые данные с использованием набора слов.

Для выполнения первого шага мы используем tfds.features.text.Tokenizer для токенизации текстовых данных и построения словаря путем итерации по набору данных.

Для второго шага мы используем tfds.features.text.TokenTextEncoder для кодирования текстовых данных с использованием словарного набора, созданного в предыдущем шаге. Обратите внимание, что для этого шага мы используем метод map; однако, поскольку map работает только в графическом режиме, мы завернули нашу encode функцию в tf.py_function, чтобы ее можно было использовать с map.

Вот код (пожалуйста, прочтите комментарии в коде для дополнительных пунктов; я не включил их в ответ, потому что они не имеют прямого отношения, но они полезны и практичны):

import tensorflow as tf
import tensorflow_datasets as tfds
from collections import Counter

fname = "rotten_tomatoes_reviews.csv"
dataset = tf.data.experimental.CsvDataset(filenames=fname,
                                          record_defaults=[tf.int32, tf.string],
                                          header=True)

# Create a tokenizer instance to tokenize text data.
tokenizer = tfds.features.text.Tokenizer()

# Find unique tokens in the dataset.
lowercase = True  # set this to `False` if case-sensitivity is important.
vocabulary = Counter()
for _, text in dataset:
    if lowercase:
       text = tf.strings.lower(text)
    tokens = tokenizer.tokenize(text.numpy())
    vocabulary.update(tokens)

# Select the most common tokens as final vocabulary set.
# Note: if you want all the tokens to be included,
# set `vocab_size = len(vocabulary)` instead.
vocab_size = 5000
vocabulary, _ = zip(*vocabulary.most_common(vocab_size))

# Create an encoder instance given our vocabulary set.
encoder = tfds.features.text.TokenTextEncoder(vocabulary,
                                              lowercase=lowercase,
                                              tokenizer=tokenizer)

# Set this to a non-zero integer if you want the texts
# to be truncated when they have more than `max_len` tokens.
max_len = None

def encode(target, text):
    text_encoded = encoder.encode(text.numpy())
    if max_len:
        text_encoded = text_encoded[:max_len]
    return text_encoded, target

# Wrap `encode` function inside `tf.py_function` so that
# it could be used with `map` method.
def encode_pyfn(target, text):
    text_encoded, target = tf.py_function(encode,
                                          inp=[target, text],
                                          Tout=(tf.int32, tf.int32))

    # (optional) Set the shapes for efficiency.
    text_encoded.set_shape([None])
    target.set_shape([])

    return text_encoded, target

# Apply encoding and then padding.
# Note: if you want the sequences in all the batches 
# to have the same length, set `padded_shapes` argument accordingly.
dataset = dataset.map(encode_pyfn).padded_batch(batch_size=3,
                                                padded_shapes=([None,], []))

# Important Note: probably this dataset would be used as input to a model
# which uses an Embedding layer. Therefore, don't forget that you
# should set the vocabulary size for this layer properly, i.e. the
# current value of `vocab_size` does not include the padding (added
# by `padded_batch` method) and also the OOV token (added by encoder).

Примечание для будущих читателей: обратите внимание, что порядок аргументов, т.е. target, text, и типы данных основаны на наборе данных OP. Адаптируйте по мере необходимости на основе вашего собственного набора данных / задачи (хотя, в конце, например, return text_encoded, target, мы изменили это, чтобы сделать его совместимым с ожидаемым форматом fit метода).

...