aitech-eks-pub/wyk/14_pretrenowanie.ipynb
2021-06-14 15:39:15 +02:00

10 KiB

Pretrenowanie modeli

System AlphaZero uczy się grając sam ze sobą — wystarczy 24 godziny, by system nauczył się grać w szachy lub go na nadludzkim poziomie.

Pytanie: Dlaczego granie samemu ze sobą nie jest dobrym sposobem nauczenia się grania w szachy dla człowieka, a dla maszyny jest?

Co jest odpowiednikiem grania samemu ze sobą w świecie przetwarzania tekstu? Tzn. pretrenowanie (_pretraining) na dużym korpusie tekstu. (Tekst jest tani!)

Jest kilka sposobów na pretrenowanie modelu, w każdym razie sprowadza się do odgadywania następnego bądź zamaskowanego słowa. W każdym razie zawsze stosujemy softmax (być może ze „sztuczkami” takimi jak negatywne próbkowanie albo hierarchiczny softamx) na pewnej representecji kontekstowej:

$$\vec{p} = \operatorname{softmax}(f(\vec{c})).$$

Model jest karany używając funkcji log loss:

$$-\log(p_j),$$

gdzie $w_j$ jest wyrazem, który pojawił się rzeczywiście w korpusie.

Przewidywanie słowa (GPT-2)

Jeden ze sposobów pretrenowania modelu to po prostu przewidywanie następnego słowa.

Zainstalujmy najpierw bibliotekę transformers.

! pip install transformers
import torch
from transformers import GPT2Tokenizer, GPT2LMHeadModel
tokenizer = GPT2Tokenizer.from_pretrained('gpt2-large')
model = GPT2LMHeadModel.from_pretrained('gpt2-large')
text = "This issue depends"
encoded_input = tokenizer(text, return_tensors='pt')
output = model(**encoded_input)
next_token_probs = torch.softmax(output[0][:, -1, :][0], dim=0)

next_token_probs
nb_of_tokens = next_token_probs.size()[0]
print(nb_of_tokens)

_, top_k_indices = torch.topk(next_token_probs, 30, sorted=True)

words = tokenizer.convert_ids_to_tokens(top_k_indices)

top_probs = []

for ix in range(len(top_k_indices)):
     top_probs.append((words[ix], next_token_probs[top_k_indices[ix]].item()))

top_probs
50257
[('Ġon', 0.6786560416221619),
 ('Ġupon', 0.04339785501360893),
 ('Ġheavily', 0.02208443358540535),
 ('Ġin', 0.021049050614237785),
 (',', 0.020188499242067337),
 ('Ġa', 0.01833895780146122),
 ('Ġvery', 0.017935041338205338),
 ('Ġentirely', 0.017528969794511795),
 ('Ġlargely', 0.016769640147686005),
 ('Ġto', 0.01009418722242117),
 ('Ġgreatly', 0.010009866207838058),
 ('Ġnot', 0.009016563184559345),
 ('Ġmore', 0.005853226874023676),
 ('Ġprimarily', 0.005203146021813154),
 ('Ġstrongly', 0.0034501152113080025),
 ('Ġpartly', 0.0033184229396283627),
 ('Ġmuch', 0.0033095215912908316),
 ('Ġmostly', 0.0032150144688785076),
 ('Ġmainly', 0.0030899408739060163),
 ('Ġfor', 0.003034428460523486),
 ('.', 0.0028878094162791967),
 ('Ġboth', 0.0028405177872627974),
 ('Ġsomewhat', 0.0028194624464958906),
 ('Ġcru', 0.002263976726680994),
 ('Ġas', 0.00221616611815989),
 ('Ġof', 0.0022000609897077084),
 ('Ġalmost', 0.001968063646927476),
 ('Ġat', 0.0018015997484326363),
 ('Ġhighly', 0.0017461496172472835),
 ('Ġcompletely', 0.001692073536105454)]

Zalety tego podejścia:

  • prostota,
  • dobra podstawa do strojenia systemów generowania tekstu zwłaszcza „otwartego” (systemy dialogowe, generowanie (fake) newsów, streszczanie tekstu), ale niekoniecznie tłumaczenia maszynowego,
  • zaskakująca skuteczność przy uczeniu _few-shot i zero-shot.

Wady:

  • asymetryczność, przetwarzanie tylko z lewej do prawej, preferencja dla lewego kontekstu,
  • mniejsza skuteczność przy dostrajaniu do zadań klasyfikacji i innych zadań niepolegających na prostym generowaniu.

Przykłady modeli: GPT, GPT-2, GPT-3, DialoGPT.

Maskowanie słów (BERT)

Inną metodą jest maskowanie słów (_Masked Language Modeling, MLM).

W tym podejściu losowe wybrane zastępujemy losowe słowa specjalnym tokenem ([MASK]) i każemy modelowi odgadywać w ten sposób zamaskowane słowa (z uwzględnieniem również prawego kontekstu!).

Móciąc ściśle, w jednym z pierwszych modeli tego typu (BERT) zastosowano schemat, w którym również niezamaskowane słowa są odgadywane (!):

  • wybieramy losowe 15% wyrazów do odgadnięcia
  • 80% z nich zastępujemy tokenem [MASK],
  • 10% zastępujemy innym losowym wyrazem,
  • 10% pozostawiamy bez zmian.
from transformers import AutoModelWithLMHead, AutoTokenizer
import torch

tokenizer = AutoTokenizer.from_pretrained("xlm-roberta-large")
model = AutoModelWithLMHead.from_pretrained("xlm-roberta-large")

sequence = f'II wojna światowa zakończyła się w {tokenizer.mask_token} roku.'

input_ids = tokenizer.encode(sequence, return_tensors="pt")
mask_token_index = torch.where(input_ids == tokenizer.mask_token_id)[1]

token_logits = model(input_ids)[0]
mask_token_logits = token_logits[0, mask_token_index, :]
mask_token_logits = torch.softmax(mask_token_logits, dim=1)

top_10 = torch.topk(mask_token_logits, 10, dim=1)
top_10_tokens = zip(top_10.indices[0].tolist(), top_10.values[0].tolist())

for token, score in top_10_tokens:
    print(sequence.replace(tokenizer.mask_token, tokenizer.decode([token])), f"(score: {score})")
# Out[3]:

Przykłady: BERT, RoBERTa (również Polish RoBERTa).

Podejście generatywne (koder-dekoder).

System ma wygenerować odpowiedź na różne pytania (również odpowiadające zadaniu MLM), np.:

  • "translate English to German: That is good." => "Das ist gut."
  • "cola sentence: The course is jumping well." => "not acceptable"
  • "summarize: state authorities dispatched emergency crews tuesday to survey the damage after an onslaught of severe weather in mississippi…" => "six people hospitalized after a storm in attala county"
  • "Thank you for me to your party week." => for inviting last
from transformers import T5Tokenizer, T5Config, T5ForConditionalGeneration

T5_PATH = 't5-base'

t5_tokenizer = T5Tokenizer.from_pretrained(T5_PATH)
t5_config = T5Config.from_pretrained(T5_PATH)
t5_mlm = T5ForConditionalGeneration.from_pretrained(T5_PATH, config=t5_config)

slot = '<extra_id_0>'

text = f'Warsaw is the {slot} of Poland.'

encoded = t5_tokenizer.encode_plus(text, add_special_tokens=True, return_tensors='pt')
input_ids = encoded['input_ids']

outputs = t5_mlm.generate(input_ids=input_ids,
                          num_beams=200, num_return_sequences=5,
                          max_length=5)

_0_index = text.index(slot)
_result_prefix = text[:_0_index]
_result_suffix = text[_0_index+len(slot):]

def _filter(output, end_token='<extra_id_1>'):
    _txt = t5_tokenizer.decode(output[2:], skip_special_tokens=False, clean_up_tokenization_spaces=False)
    if end_token in _txt:
        _end_token_index = _txt.index(end_token)
        return _result_prefix + _txt[:_end_token_index] + _result_suffix
    else:
        return _result_prefix + _txt + _result_suffix


results = [_filter(out) for out in outputs]
results

(Zob. https://arxiv.org/pdf/1910.10683.pdf)

Przykład: T5, mT5