Tensorflow 2.0 Hugging Face Transformers, TFBertForSequenceClassification, Неожиданные выходные размеры в выводе - PullRequest
1 голос
/ 07 мая 2020

Сводка:

Я хочу настроить BERT для классификации предложений в пользовательском наборе данных. Я следил за некоторыми найденными мною примерами, например, этот , что было очень полезно. Я также просмотрел эту суть .

Проблема, с которой я столкнулся, заключается в том, что при выполнении вывода для некоторых образцов результат имеет другие размеры, чем я ожидал. получить кортеж с массивом размеров numpy (1472, 42), где 42 - количество классов. Я бы ожидал размеров (23, 42).

Код и другие детали:

Я выполняю вывод на обученной модели с помощью Keras следующим образом:

preds = model.predict(features)

Где функции токенизируются и преобразуются в набор данных:

for sample, ground_truth in tests:
    test_examples.append(InputExample(text=sample, category_index=ground_truth))

features = convert_examples_to_tf_dataset(test_examples, tokenizer)

Где sample может быть, например, "A test sentence I want classified", а ground_truth может быть, например, 12 это закодированная метка. Поскольку я делаю вывод, то, что я предлагаю как основную истину, конечно, не должно иметь значения. *

def convert_examples_to_tf_dataset(
    examples: List[Tuple[str, int]],
    tokenizer,
    max_length=64,
):
    """
    Loads data into a tf.data.Dataset for finetuning a given model.

    Args:
        examples: List of tuples representing the examples to be fed
        tokenizer: Instance of a tokenizer that will tokenize the examples
        max_length: Maximum string length

    Returns:
        a ``tf.data.Dataset`` containing the condensed features of the provided sentences
    """
    features = [] # -> will hold InputFeatures to be converted later

    for e in examples:
        # Documentation is really strong for this method, so please take a look at it
        input_dict = tokenizer.encode_plus(
            e.text,
            add_special_tokens=True,
            max_length=max_length, # truncates if len(s) > max_length
            return_token_type_ids=True,
            return_attention_mask=True,
            pad_to_max_length=True, # pads to the right by default
        )

        # input ids = token indices in the tokenizer's internal dict
        # token_type_ids = binary mask identifying different sequences in the model
        # attention_mask = binary mask indicating the positions of padded tokens so the model does not attend to them

        input_ids, token_type_ids, attention_mask = (input_dict["input_ids"],
            input_dict["token_type_ids"], input_dict['attention_mask'])

        features.append(
            InputFeatures(
                input_ids=input_ids, attention_mask=attention_mask, token_type_ids=token_type_ids, label=e.category_index
            )
        )

    def gen():
        for f in features:
            yield (
                {
                    "input_ids": f.input_ids,
                    "attention_mask": f.attention_mask,
                    "token_type_ids": f.token_type_ids,
                },
                f.label,
            )

    return tf.data.Dataset.from_generator(
        gen,
        ({"input_ids": tf.int32, "attention_mask": tf.int32, "token_type_ids": tf.int32}, tf.int64),
        (
            {
                "input_ids": tf.TensorShape([None]),
                "attention_mask": tf.TensorShape([None]),
                "token_type_ids": tf.TensorShape([None]),
            },
            tf.TensorShape([]),
        ),
    )

with tf.device('/cpu:0'):
    train_data = convert_examples_to_tf_dataset(train_examples, tokenizer)
    train_data = train_data.shuffle(buffer_size=len(train_examples), reshuffle_each_iteration=True) \
                           .batch(BATCH_SIZE) \
                           .repeat(-1)

    val_data = convert_examples_to_tf_dataset(val_examples, tokenizer)
    val_data = val_data.shuffle(buffer_size=len(val_examples), reshuffle_each_iteration=True) \
                           .batch(BATCH_SIZE) \
                           .repeat(-1)

Он работает так, как я ожидал, и запуск print(list(features.as_numpy_iterator())[1]) дает следующее:

({'input_ids': array([  101, 11639, 19962, 23288, 13264, 35372, 10410,   102,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0,     0,     0,     0,     0,     0,     0,     0,     0,
           0], dtype=int32), 'attention_mask': array([1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      dtype=int32), 'token_type_ids': array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
      dtype=int32)}, 6705)

Пока все выглядит так, как я ожидал. И похоже, что токенизатор работает как надо; 3 массива длиной 64 (что соответствует моей максимальной длине) и метка в виде целого числа.

Модель была обучена следующим образом:

config = BertConfig.from_pretrained(
    'bert-base-multilingual-cased',
    num_labels=len(label_encoder.classes_),
    output_hidden_states=False,
    output_attentions=False
)
model = TFBertForSequenceClassification.from_pretrained('bert-base-multilingual-cased', config=config)

# train_data is then a tf.data.Dataset we can pass to model.fit()
optimizer = tf.keras.optimizers.Adam(learning_rate=3e-05, epsilon=1e-08)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy(name='accuracy')
model.compile(optimizer=optimizer,
              loss=loss,
              metrics=[metric])

model.summary()

history = model.fit(train_data,
                    epochs=EPOCHS,
                    steps_per_epoch=train_steps,
                    validation_data=val_data,
                    validation_steps=val_steps,
                    shuffle=True,
                    )

Результаты

Проблема теперь в том, что при выполнении прогноза preds = model.predict(features) выходные размеры не соответствуют тому, что написано в документации : logits (Numpy array or tf.Tensor of shape (batch_size, config.num_labels)):. Я получаю кортеж, содержащий массив numpy с размерами: (1472,42).

42 имеет смысл, поскольку это мое количество классов. Я отправил 23 образца для теста, и 23 x 64 = 1472. 64 - моя максимальная длина предложения, так что это звучит знакомо. Этот вывод неверен? Как я могу преобразовать этот вывод в фактическое предсказание класса для каждой входной выборки? Я получаю 1472 прогноза, хотя ожидал 23.

Пожалуйста, дайте мне знать, могу ли я предоставить более подробную информацию, которая могла бы помочь решить эту проблему.

Ответы [ 2 ]

1 голос
/ 11 мая 2020

Я обнаружил проблему - если вы получаете неожиданные размеры при использовании наборов данных Tensorflow (tf.data.Dataset), это может быть из-за того, что .batch.

Так в моем примере:

features = convert_examples_to_tf_dataset(test_examples, tokenizer)

Добавление:

features = features.batch(BATCH_SIZE)

делает эту работу так, как я ожидал. Таким образом, это не проблема, связанная с TFBertForSequenceClassification, и только из-за неправильного ввода. Я также хочу добавить ссылку на этот ответ , который заставил меня найти проблему.

1 голос
/ 11 мая 2020

Я сообщил о своем примере, в котором я пытаюсь предсказать 3 образца текста и получить (3, 42) в качестве формы вывода

### define model
config = BertConfig.from_pretrained(
    'bert-base-multilingual-cased',
    num_labels=42,
    output_hidden_states=False,
    output_attentions=False
)
model = TFBertForSequenceClassification.from_pretrained('bert-base-multilingual-cased', config=config)

optimizer = tf.keras.optimizers.Adam(learning_rate=3e-05, epsilon=1e-08)
loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
metric = tf.keras.metrics.SparseCategoricalAccuracy(name='accuracy')
model.compile(optimizer=optimizer,
              loss=loss,
              metrics=[metric])

### import tokenizer
tokenizer = AutoTokenizer.from_pretrained("bert-base-multilingual-cased")

### utility functions for text encoding
def return_id(str1, str2, length):

    inputs = tokenizer.encode_plus(str1, str2,
        add_special_tokens=True,
        max_length=length)

    input_ids =  inputs["input_ids"]
    input_masks = [1] * len(input_ids)
    input_segments = inputs["token_type_ids"]

    padding_length = length - len(input_ids)
    padding_id = tokenizer.pad_token_id

    input_ids = input_ids + ([padding_id] * padding_length)
    input_masks = input_masks + ([0] * padding_length)
    input_segments = input_segments + ([0] * padding_length)

    return [input_ids, input_masks, input_segments]

### encode 3 sentences
input_ids, input_masks, input_segments = [], [], []
for instance in ['hello hello', 'ciao ciao', 'marco marco']:

    ids, masks, segments = \
    return_id(instance, None, 100)

    input_ids.append(ids)
    input_masks.append(masks)
    input_segments.append(segments)

input_ = [np.asarray(input_ids, dtype=np.int32), 
          np.asarray(input_masks, dtype=np.int32), 
          np.asarray(input_segments, dtype=np.int32)]

### make prediction
model.predict(input_).shape # ===> (3,42)
...