Оцените вероятность токена / логиты для данного предложения, не вычисляя все предложение

У меня есть такое предложение: "I like sitting in my new chair and _____ about life".

И у меня есть КОНКРЕТНЫЙ набор токенов, например ["watch", "run", "think", "apple", "light"]

Я хотел бы рассчитать вероятность того, что каждый из этих токенов появится следующим словом в этом неполном предложении. Надеюсь, я пойму, что вероятность "think" выше, чем, например, "apple".

Я работаю с pytorch-преобразователями (в частности, GPT2LMHeadModel), и возможное решение - оценить оценку полного предложения с каждым из токенов, но когда количество токенов для оценки составляет порядка 100 или 1000, тогда время вычисления начинает быть слишком длинным.

Должна быть возможность обработать предложение только один раз и каким-то образом использовать скрытые состояния для вычисления вероятностей набора токенов, но я не знаю, как это сделать.

Любые идеи? заранее спасибо


РЕДАКТИРОВАТЬ:

Фактический код выглядит так, как показано ниже (каждый раз оценивается вероятность полного предложения). Для каждого предложения требуется около 0,1 секунды для запуска метода score(), который превращается в часы, если я хочу оценить несколько тысяч слов.

from pytorch_transformers import GPT2Tokenizer, GPT2LMHeadModel
import pandas as pd

model = GPT2LMHeadModel.from_pretrained("gpt2")
model.eval()
tokenizer = GPT2Tokenizer.from_pretrained("gpt2")


def score(sentence):
    tokenize_input = tokenizer.tokenize(sentence)
    tensor_input = torch.tensor([tokenizer.convert_tokens_to_ids(tokenize_input)])
    loss = model(tensor_input, labels=tensor_input)
    return -loss[0].item()


candidates = ["watch", "run", "think", "apple", "light"]
sent_template = "I like sitting in my new chair and {} about life"
print({candidate: score(sent_template.format(candidate)) for candidate in candidates})

person Jacobo Lansac    schedule 02.07.2020    source источник
comment
Возможно, вы можете использовать параметр past, но я не уверен. Можете ли вы поделиться кодом того, что вы сейчас делаете?   -  person cronoik    schedule 04.07.2020
comment
Спасибо за подсказку @cronoik. Я немного читал о предыдущем параметре, но я тоже не могу заставить его работать. Я отредактировал вопрос, включая код, который я использую в настоящее время. Заранее большое спасибо   -  person Jacobo Lansac    schedule 04.07.2020
comment
Возможно, вы захотите подписаться на this, но я также отправлю ответ, как только он станет понятным мне.   -  person cronoik    schedule 08.07.2020
comment
Большое спасибо, очень признателен.   -  person Jacobo Lansac    schedule 09.07.2020


Ответы (1)


Ваш пример произвел следующий результат и занял около 48,5 секунд с 282 кандидатами на завершение в моей среде (я провел только 3 запуска):

{'watch': -5.406847953796387
, 'run': -5.533411502838135
, 'think': -4.525279521942139
, 'apple': -6.158637046813965
, 'light': -5.835141658782959}

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

import torch

from  transformers import GPT2TokenizerFast, GPT2LMHeadModel
from torch.nn import CrossEntropyLoss

model = GPT2LMHeadModel.from_pretrained("gpt2")
model.eval()
tokenizer = GPT2TokenizerFast.from_pretrained("gpt2")

###We calculate the hidden_states and the past of the common left part of the sentence
past = "I like sitting in my new chair and"
past_tokenize_input = tokenizer.tokenize(past)
past_tensor_input = torch.tensor([tokenizer.convert_tokens_to_ids(past_tokenize_input)])

past_last_hidden_state, past = model.transformer(past_tensor_input)

def score(sentence, past, past_last_hidden_state, past_tensor_input):
    tokenize_input = tokenizer.tokenize(sentence, )
    tensor_input = torch.tensor([tokenizer.convert_tokens_to_ids(tokenize_input)])

    ###the following code is slightly modified from https://github.com/huggingface/transformers/blob/09a2f40684f77e62d0fd8485fe9d2d610390453f/src/transformers/modeling_gpt2.py#L604
    ###now we calculate the right part of the sentence with the already calculated past
    transformer_outputs = model.transformer(
            tensor_input,
            past=past,
            attention_mask=None,
            token_type_ids=None,
            position_ids=None,
            head_mask=None,
            inputs_embeds=None,
            use_cache=None,
            output_attentions=None,
            output_hidden_states=None,
        )
    ###and concatenate the output of with the hidden_state of the left part of the sentence
    hidden_states = torch.cat((past_last_hidden_state, transformer_outputs[0]), dim=1)
    
    ###the following part is exactly the same as https://github.com/huggingface/transformers/blob/09a2f40684f77e62d0fd8485fe9d2d610390453f/src/transformers/modeling_gpt2.py#L604
    lm_logits = model.lm_head(hidden_states)

    labels_input = torch.cat((past_tensor_input, tensor_input), dim=1)

    # Shift so that tokens < n predict n
    shift_logits = lm_logits[..., :-1, :].contiguous()
    shift_labels = labels_input[..., 1:].contiguous()
    # Flatten the tokens
    loss_fct = CrossEntropyLoss()
    loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))
    return -loss.item()

candidates = ["watch", "run", "think", "apple", "light"]

sent_template = " {} about life"

print({candidate: score(sent_template.format(candidate), past, past_last_hidden_state, past_tensor_input) for candidate in candidates})

Выход:

{'watch': -5.406846046447754
, 'run': -5.533413887023926
, 'think': -4.525280952453613
, 'apple': -6.158637046813965
, 'light': -5.835141181945801}

Время выполнения здесь составило 40,5 секунды с 282 кандидатами (снова 3 цикла). Вы также видите, что я потерял некоторую точность.

Большое спасибо patrickvonplaten, который дал мне хороший объяснение прошлой реализации.

person cronoik    schedule 19.07.2020
comment
Огромное спасибо. Я проверю это, как только вернусь из отпуска. - person Jacobo Lansac; 23.07.2020
comment
Спасибо за решение. Все заработало правильно. Сокращение времени невелико, но оно уменьшило время вычислений с 0,1 секунды на предложение до 0,08 секунды на предложение. Спасибо за вашу помощь - person Jacobo Lansac; 03.08.2020
comment
@JacoboLansac Вы можете сэкономить больше времени, используя быстрый токенизатор . Я скорректировал свой ответ. - person cronoik; 03.08.2020
comment
Я использую вашу процедуру точно так, как вы представили ее в своем решении, с FastTokenizer, и я получил скорость, упомянутую в предыдущем комментарии. Возможно, это потому, что я не использую GPU. - person Jacobo Lansac; 07.08.2020
comment
Насколько мне известно, быстрые токенизаторы не используют графические процессоры. Они написаны ржавчиной и работают нитками. Улучшение на 1,5 секунды также может быть ошибкой измерения. - person cronoik; 07.08.2020
comment
На самом деле, процентное улучшение, которое мы оба получаем, очень хорошо совпадает. У нас обоих скорость увеличивается примерно на 20%. Абсолютное время может зависеть от наших процессоров и других заданий, которые мы выполняли в данный момент. У вас получилось: 48,5 секунды / 282 кандидата = 0,18 секунды. 40,5 секунды / 282 кандидата = 0,144 секунды. Это сокращение на 0,036 секунды на кандидата. При процентном соотношении = 0,036 / 0,18 = 20% сокращение времени я получил сначала 0,1 секунды на кандидата, а затем 0,08. Уменьшение 0,02 секунды. В процентах 0,02 / 0,1 = сокращение времени на 20% :) - person Jacobo Lansac; 09.08.2020