Pytorch
TensorFlow

#
Sumarizare

În această secțiune vom analiza modul în care modelele Transformer pot fi utilizate pentru a condensa documente lungi în rezumate, o sarcină cunoscută sub numele de text summarization. Aceasta este una dintre cele mai dificile sarcini NLP, deoarece necesită o gamă largă de abilități, cum ar fi înțelegerea pasajelor lungi și generarea unui text coerent care integrează principalele subiecte dintr-un document. Cu toate acestea, atunci când este bine realizată, rezumarea textului este un instrument puternic care poate accelera diverse procese de business prin scutirea experților într-un anumit domeniu de a citi documente lungi în detaliu.

Deși există deja diverse modele bine puse la punct pentru sumarizare pe [Hugging Face Hub] (https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), aproape toate acestea sunt potrivite numai pentru documentele în limba engleză. Prin urmare, pentru a adăuga o întorsătură în această secțiune, vom antrena un model bilingv pentru engleză și spaniolă. Până la sfârșitul acestei secțiuni, veți avea un model care poate rezuma recenziile clienților precum cel prezentat aici:

După cum vom vedea, aceste rezumate sunt concise deoarece sunt învățate din titlurile pe care clienții le furnizează în recenziile lor despre produse. Să începem prin alcătuirea unui corpus bilingv adecvat pentru această sarcină.

##
Pregătirea unui corpus multilingv

Vom utiliza [Multilingual Amazon Reviews Corpus] (https://huggingface.co/datasets/amazon_reviews_multi) pentru a crea bilingv summarizerul nostru. Acest corpus este format din recenzii ale produselor Amazon în șase limbi și este utilizat de obicei pentru a evalua clasificatoarele multilingve. Cu toate acestea, deoarece fiecare recenzie este însoțită de un titlu scurt, putem folosi titlurile ca rezumate țintă din care modelul nostru să învețe! Pentru a începe, să descărcăm subseturile în engleză și spaniolă de la Hugging Face Hub:

    from datasets import load_dataset

    spanish_dataset = load_dataset("amazon_reviews_multi", "es")
    english_dataset = load_dataset("amazon_reviews_multi", "en")
    english_dataset

    DatasetDict({
        train: Dataset({
            features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
            num_rows: 200000
        })
        validation: Dataset({
            features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
            num_rows: 5000
        })
        test: Dataset({
            features: ['review_id', 'product_id', 'reviewer_id', 'stars', 'review_body', 'review_title', 'language', 'product_category'],
            num_rows: 5000
        })
    })

După cum puteți vedea, pentru fiecare limbă există 200.000 de recenzii pentru splitul `train` și 5.000 de recenzii pentru fiecare dintre spliturile `validation` și `test`. Informațiile despre recenzii care ne interesează sunt conținute în coloanele `review_body` și `review_title`. Să analizăm câteva exemple prin crearea unei funcții simple care preia un sample aleatoriu din setul de antrenare cu ajutorul tehnicilor învățate în Capitolul 5:

    def show_samples(dataset, num_samples=3, seed=42):
        sample = dataset["train"].shuffle(seed=seed).select(range(num_samples))
        for example in sample:
            print(f"\n'>> Title: {example['review_title']}'")
            print(f"'>> Review: {example['review_body']}'")

    show_samples(english_dataset)

    '>> Title: Worked in front position, not rear'
    '>> Review: 3 stars because these are not rear brakes as stated in the item description. At least the mount adapter only worked on the front fork of the bike that I got it for.'

    '>> Title: meh'
    '>> Review: Does it’s job and it’s gorgeous but mine is falling apart, I had to basically put it together again with hot glue'

    '>> Title: Can\'t beat these for the money'
    '>> Review: Bought this for handling miscellaneous aircraft parts and hanger "stuff" that I needed to organize; it really fit the bill. The unit arrived quickly, was well packaged and arrived intact (always a good sign). There are five wall mounts-- three on the top and two on the bottom. I wanted to mount it on the wall, so all I had to do was to remove the top two layers of plastic drawers, as well as the bottom corner drawers, place it when I wanted and mark it; I then used some of the new plastic screw in wall anchors (the 50 pound variety) and it easily mounted to the wall. Some have remarked that they wanted dividers for the drawers, and that they made those. Good idea. My application was that I needed something that I can see the contents at about eye level, so I wanted the fuller-sized drawers. I also like that these are the new plastic that doesn\'t get brittle and split like my older plastic drawers did. I like the all-plastic construction. It\'s heavy duty enough to hold metal parts, but being made of plastic it\'s not as heavy as a metal frame, so you can easily mount it to the wall and still load it up with heavy stuff, or light stuff. No problem there. For the money, you can\'t beat it. Best one of these I\'ve bought to date-- and I\'ve been using some version of these for over forty years.'

> ✏️ Încercați! Schimbați seedul aleatoriu în comanda `Dataset.shuffle()` pentru a explora alte recenzii din corpus. Dacă sunteți vorbitor de spaniolă, aruncați o privire la unele dintre recenziile din `spanish_dataset` pentru a vedea dacă și titlurile par a fi rezumate rezonabil.

Acest sample arată diversitatea recenziilor pe care le găsim de obicei online, variind de la pozitive la negative (și totul între ele!). Deși exemplul cu titlul “meh” nu este foarte informativ, celelalte titluri par a fi rezumate decente ale recenziilor în sine. Antrenarea unui model de rezumare pe toate cele 400 000 de recenzii ar dura mult prea mult pe un singur GPU, așa că ne vom concentra pe generarea de rezumate pentru un singur domeniu de produse. Pentru a avea o idee despre domeniile din care putem alege, să convertim `english_dataset` într-un `pandas.DataFrame` și să calculăm numărul de recenzii per categorie de produse:

    english_dataset.set_format("pandas")
    english_df = english_dataset["train"][:]
    # Show counts for top 20 products
    english_df["product_category"].value_counts()[:20]

    home                      17679
    apparel                   15951
    wireless                  15717
    other                     13418
    beauty                    12091
    drugstore                 11730
    kitchen                   10382
    toy                        8745
    sports                     8277
    automotive                 7506
    lawn_and_garden            7327
    home_improvement           7136
    pet_products               7082
    digital_ebook_purchase     6749
    pc                         6401
    electronics                6186
    office_product             5521
    shoes                      5197
    grocery                    4730
    book                       3756
    Name: product_category, dtype: int64

Cele mai populare produse din datasetul în limba engleză sunt despre articole de uz casnic, îmbrăcăminte și electronice fără fir. Cu toate acestea, pentru a rămâne la Amazontheme, să ne concentrăm pe rezumatul recenziilor de cărți - la urma urmei, acesta este motivul pentru care compania a fost fondată! Putem vedea două categorii de produse care se potrivesc (`book` și `digital_ebook_purchase`), deci să filtrăm dataseturile în ambele limbi doar pentru aceste produse. După cum am văzut în Capitolul 5, funcția `Dataset.filter()` ne permite să tăiem un dataset foarte eficient, deci putem defini o funcție simplă pentru a face acest lucru:

    def filter_books(example):
        return (
            example["product_category"] == "book"
            or example["product_category"] == "digital_ebook_purchase"
        )

Acum, când aplicăm această funcție la `english_dataset` și `spanish_dataset`, rezultatul va conține doar acele rânduri care implică categoriile de cărți. Înainte de a aplica filtrul, să schimbăm formatul din `english_dataset` din `"pandas"` înapoi în `"arrow"`:

    english_dataset.reset_format()

Putem aplica apoi funcția de filtrare și, ca o verificare a corectitudinii, să inspectăm un sample de recenzii pentru a vedea dacă acestea sunt într-adevăr despre cărți:

    spanish_books = spanish_dataset.filter(filter_books)
    english_books = english_dataset.filter(filter_books)
    show_samples(english_books)

    '>> Title: I\'m dissapointed.'
    '>> Review: I guess I had higher expectations for this book from the reviews. I really thought I\'d at least like it. The plot idea was great. I loved Ash but, it just didnt go anywhere. Most of the book was about their radio show and talking to callers. I wanted the author to dig deeper so we could really get to know the characters. All we know about Grace is that she is attractive looking, Latino and is kind of a brat. I\'m dissapointed.'

    '>> Title: Good art, good price, poor design'
    '>> Review: I had gotten the DC Vintage calendar the past two years, but it was on backorder forever this year and I saw they had shrunk the dimensions for no good reason. This one has good art choices but the design has the fold going through the picture, so it\'s less aesthetically pleasing, especially if you want to keep a picture to hang. For the price, a good calendar'

    '>> Title: Helpful'
    '>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.'

Bine, putem vedea că recenziile nu sunt strict despre cărți și se pot referi la lucruri precum calendare și aplicații electronice precum OneNote. Cu toate acestea, domeniul pare potrivit pentru a antrena un model de sumarizare. Înainte de a analiza diferitele modele care sunt potrivite pentru această sarcină, trebuie să mai pregătim puțin datele: să combinăm recenziile în engleză și spaniolă ca un singur obiect `DatasetDict`. 🤗 Datasets oferă o funcție utilă `concatenate_datasets()` care (după cum sugerează și numele) va concatena două obiecte `Dataset` unul peste celălalt. Așadar, pentru a crea datasetul nostru bilingv, vom parcurge în buclă fiecare împărțire, vom concatena dataseturile pentru acel split și vom amesteca rezultatul pentru a ne asigura că modelul nostru nu se adaptează excesiv la o singură limbă:

    from datasets import concatenate_datasets, DatasetDict

    books_dataset = DatasetDict()

    for split in english_books.keys():
        books_dataset[split] = concatenate_datasets(
            [english_books[split], spanish_books[split]]
        )
        books_dataset[split] = books_dataset[split].shuffle(seed=42)

    # Peek at a few examples
    show_samples(books_dataset)

    '>> Title: Easy to follow!!!!'
    '>> Review: I loved The dash diet weight loss Solution. Never hungry. I would recommend this diet. Also the menus are well rounded. Try it. Has lots of the information need thanks.'

    '>> Title: PARCIALMENTE DAÑADO'
    '>> Review: Me llegó el día que tocaba, junto a otros libros que pedí, pero la caja llegó en mal estado lo cual dañó las esquinas de los libros porque venían sin protección (forro).'

    '>> Title: no lo he podido descargar'
    '>> Review: igual que el anterior'

Acest lucru arată cu siguranță ca un amestec de recenzii în engleză și spaniolă! Acum că avem un corpus de antrenament, un ultim lucru de verificat este distribuția cuvintelor în recenzii și în titlurile acestora. Acest lucru este deosebit de important pentru sarcinile de sumarizare, în cazul în care rezumatele scurte de referință din date pot influența modelul să producă doar unul sau două cuvinte în rezumatele generate. Graficele de mai jos arată distribuția cuvintelor și putem observa că titlurile sunt puternic înclinate spre 1-2 cuvinte:

Pentru a rezolva acest lucru, vom filtra exemplele cu titluri foarte scurte, astfel încât modelul nostru să poată produce rezumate mai interesante. Deoarece avem de-a face cu texte în engleză și spaniolă, putem folosi rough heuristic pentru a face split titlurilor pe baza spațiului alb și apoi să folosim metoda noastră de încredere `Dataset.filter()` după cum urmează:

    books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2)

Now that we’ve prepared our corpus, let’s take a look at a few possible Transformer models that one might fine-tune on it!

##
Modele pentru sumarizarea textului

Dacă vă gândiți bine, rezumarea textului este o sarcină similară cu machine translation: avem un corp de text, cum ar fi o recenzie, pe care am dori să o “traducem” într-o versiune mai scurtă care să capteze caracteristicile principale ale datelor de intrare. În consecință, majoritatea modelelor Transformer pentru rezumare adoptă arhitectura codificator-decodificator pe care am întâlnit-o pentru prima dată în Capitolul 1, deși există unele excepții, cum ar fi familia de modele GPT, care poate fi, de asemenea, utilizată pentru rezumare în few-shot settings. Tabelul de mai jos enumeră câteva modele preantrenate populare care pot fi ajustate pentru sumarizare.

Transformer model | Description | Multilingual?
--- | --- | ---
GPT-2 | Deși este antrenat ca un model lingvistic autoregresiv, puteți face GPT-2 să genereze rezumate prin adăugarea “TL;DR” la sfârșitul textului de intrare. | ❌
PEGASUS | Utilizează un obiectiv de preantrenare pentru a prezice propoziții mascate în texte cu mai multe propoziții. Acest obiectiv de preantrenare este mai apropiat de rezumare decât de vanilla language modeling și obține scoruri ridicate la standardele populare. | ❌
T5 | O arhitectură Transformer universală care formulează toate sarcinile într-un framework text-text; de exemplu, formatul de intrare pentru modelul de rezumare a unui document este `summarize: ARTICOL`. | ❌
mT5 | O versiune multilingvă a T5, preantrenată pe corpusul multilingv Common Crawl (mC4), care acoperă 101 limbi. | ✅
BART | O nouă arhitectură Transformer cu un encoder și un stack de decodere antrenate pentru a reconstrui intrarea coruptă care combină schemele de preantrenare ale BERT și GPT-2. | ❌
mBART-50 | O versiune multilingvă a BART, preantrenată pe 50 de limbi. | ✅

După cum puteți vedea din acest tabel, majoritatea modelelor Transformer pentru rezumare (și, într-adevăr, majoritatea sarcinilor NLP) sunt monolingve. Acest lucru este grozav dacă sarcina voastrăeste într-o limbă cu multe resurse precum engleza sau germana, dar mai puțin pentru miile de alte limbi utilizate în întreaga lume. Din fericire, există o clasă de modele Transformer multilingve, precum mT5 și mBART, care vin în ajutor. Aceste modele sunt preantrenate folosind modelarea limbajului, dar cu o întorsătură: în loc să fie antrenate pe un corpus dintr-o singură limbă, ele sunt antrenate împreună pe texte în peste 50 de limbi deodată!

Ne vom concentra asupra mT5, o arhitectură interesantă bazată pe T5, care a fost preantrenată într-un cadru text-to-text. În T5, fiecare sarcină NLP este formulată în termenii unui prompt prefix precum `summarize:` care condiționează modelul să adapteze textul generat la prompt. După cum se arată în figura de mai jos, acest lucru face T5 extrem de versatil, deoarece puteți rezolva multe sarcini cu un singur model!

mT5 nu utilizează prefixe, dar împărtășește o mare parte din versatilitatea T5 și are avantajul de a fi multilingv. Acum că am ales un model, să aruncăm o privire la pregătirea datelor noastre pentru antrenare.

> ✏️ Încercați! După ce ați parcurs această secțiune, vedeți cât de bine se compară mT5 cu mBART prin aplicarea fine-tuningului acestuia din urmă cu aceleași tehnici. Pentru puncte bonus, puteți încerca, de asemenea, fine-tuningul a T5 doar pe recenziile în limba engleză. Deoarece T5 are un prefix prompt special, va trebui să adăugați `summarize:` la exemplele de intrare în pașii de preprocesare de mai jos.

##
Preprocessing the data

Următoarea noastră sarcină este să tokenizăm și să codificăm recenziile și titlurile acestora. Ca de obicei, începem prin încărcarea tokenizelui asociat cu checkpointul modelului preantrenat. Vom folosi `mt5-small` ca checkpoint, astfel încât să putem face fine-tune modelului într-un timp rezonabil:

    from transformers import AutoTokenizer

    model_checkpoint = "google/mt5-small"
    tokenizer = AutoTokenizer.from_pretrained(model_checkpoint)

> 💡 În stadiile inițiale ale proiectelor NLP, o bună practică este de a antrena o clasă de modele “mici” pe un sample mic de date. Acest lucru vă permite să faceți debug și să iterați mai rapid către un flux de lucru end-to-end. Odată ce sunteți încrezător în rezultate, puteți oricând să măriți modelul prin simpla schimbare a checkpointului modelului!

Să testăm tokenizerul mT5 pe un mic exemplu:

    inputs = tokenizer("I loved reading the Hunger Games!")
    inputs

    {'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}

Aici putem vedea binecunoscutele `input_ids` și `attention_mask` pe care le-am întâlnit în primele noastre experimente de fine-tuning în Capitolul 3. Să decodificăm aceste ID-uri de intrare cu funcția `convert_ids_to_tokens()` a tokenizerului pentru a vedea cu ce fel de tokenizer avem de-a face:

    tokenizer.convert_ids_to_tokens(inputs.input_ids)

    ['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', '</s>']

Caracterul Unicode special `▁` și tokenul de sfârșit de secvență `</s>` indică faptul că avem de-a face cu tokenizeerul SentencePiece, care se bazează pe algoritmul de segmentare Unigram discutat în Capitolul 6. Unigram este deosebit de util pentru corpusurile multilingve, deoarece permite SentencePiece să fie agnostic în ceea ce privește accentele, punctuația și faptul că multe limbi, precum japoneza, nu au caractere de spațiu alb.

Pentru a tokeniza corpusul nostru, trebuie să ne ocupăm de o subtilitate asociată cu rezumarea: deoarece labelurile noastre sunt, de asemenea, text, este posibil ca acestea să depășească dimensiunea maximă a contextului modelului. Acest lucru înseamnă că trebuie să aplicăm trunchierea atât a recenziilor, cât și a titlurilor acestora, pentru a ne asigura că nu trecem inputuri excesiv de lungi modelului nostru. Tokenizerele din 🤗 Transformers oferă un argument ingenios, `text_target`, care vă permite să tokenizați labelurile în paralel cu inputurile. Iată un exemplu al modului în care inputurile și targeturile sunt procesate pentru mT5:

    max_input_length = 512
    max_target_length = 30

    def preprocess_function(examples):
        model_inputs = tokenizer(
            examples["review_body"],
            max_length=max_input_length,
            truncation=True,
        )
        labels = tokenizer(
            examples["review_title"], max_length=max_target_length, truncation=True
        )
        model_inputs["labels"] = labels["input_ids"]
        return model_inputs

Să parcurgem acest cod pentru a înțelege ce se întâmplă. Primul lucru pe care l-am făcut a fost să definim valorile pentru `max_input_length` și `max_target_length`, care stabilesc limitele superioare pentru cât de lungi pot fi recenziile și titlurile noastre. Deoarece corpul recenziei este de obicei mult mai mare decât titlul, am mărit aceste valori în consecință.

Cu ajutorul funcției `preprocess_function()`, este simplu să tokenizăm întregul corpus cu ajutorul funcției practice `Dataset.map()` pe care am folosit-o la greu pe parcursul acestui curs:

    tokenized_datasets = books_dataset.map(preprocess_function, batched=True)

Acum că corpusul a fost preprocesat, să aruncăm o privire asupra unor metrici care sunt utilizate în mod obișnuit pentru sumarizare. După cum vom vedea, nu există un glonț de argint atunci când vine vorba de măsurarea calității textului generat de calculator.

> 💡 Poate ați observat că am folosit `batched=True` în funcția noastră `Dataset.map()` de mai sus. Aceasta codifică exemplele în batchuri de 1.000 (implicit) și vă permite să utilizați capacitățile multithreading ale tokenizerilor rapizi din 🤗 Transformers. Atunci când este posibil, încercați să utilizați `batched=True` pentru a profita la maximum de preprocesare!

##
Metrice pentru sumarizare

În comparație cu majoritatea celorlalte sarcini pe care le-am abordat în acest curs, măsurarea performanței sarcinilor de generare a textului, precum sumarizare sau traducerea, nu este la fel de simplă. De exemplu, având în vedere o recenzie precum “Mi-a plăcut să citesc Hunger Games”, există mai multe rezumate valide, precum “Mi-a plăcut Hunger Games” sau “Hunger Games este o lectură excelentă”. În mod clar, aplicarea unui exact match între rezumatul generat și label nu este o soluție bună - chiar și oamenii s-ar descurca prost cu un astfel de metric, deoarece toți avem propriul nostru stil de scriere.

Pentru rezumare, una dintre cele mai frecvent utilizate metrici este ROUGE score (prescurtarea de la Recall-Oriented Understudy for Gisting Evaluation). Ideea de bază din spatele acestei metrici este de a compara un rezumat generat cu un set de rezumate de referință care sunt de obicei create de oameni. Pentru a face acest lucru mai precis, să presupunem că dorim să comparăm următoarele două rezumate:

    generated_summary = "I absolutely loved reading the Hunger Games"
    reference_summary = "I loved reading the Hunger Games"

O modalitate de a le compara ar fi să numărați numărul de cuvinte care se suprapun, care în acest caz ar fi 6. Cu toate acestea, acest lucru este un pic crud, astfel încât, în schimb, ROUGE se bazează pe calcularea scorurilor preicision și recall pentru suprapunere.

> 🙋 Nu vă faceți griji dacă aceasta este prima dată când auziți de precision și recall - vom trece împreună prin câteva exemple explicite pentru a clarifica totul. Aceste metrici sunt de obicei întâlnite în sarcinile de clasificare, deci dacă doriți să înțelegeți cum sunt definite precizia și recallul în acest context, vă recomandăm să consultați [ghidurile `scikit-learn`] (https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html).

Pentru ROUGE, recall măsoară cât de mult din rezumatul de referință este capturat de cel generat. Dacă comparăm doar cuvinte, recall poate fi calculată conform următoarei formule: R e c a l l=N u m b e r o f o v e r l a p p i n g w o r d s T o t a l n u m b e r o f w o r d s i n r e f e r e n c e s u m m a r y \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} Recall=Total number of words in reference summary Number of overlapping words​

Pentru exemplul nostru simplu de mai sus, această formulă oferă un recall perfect de 6/6 = 1; adică, toate cuvintele din rezumatul de referință au fost produse de model. Acest lucru poate părea grozav, dar imaginați-vă dacă rezumatul nostru generat ar fi fost “Mi-a plăcut foarte mult să citesc Jocurile Foamei toată noaptea”. Aceasta ar avea, de asemenea, o reamintire perfectă, dar este, fără îndoială, un rezumat mai prost, deoarece are mai multe cuvinte. Pentru a face față acestor scenarii, calculăm și precizia, care, în contextul ROUGE, măsoară cât de mult din rezumatul generat a fost relevant:

P r e c i s i o n=N u m b e r o f o v e r l a p p i n g w o r d s T o t a l n u m b e r o f w o r d s i n g e n e r a t e d s u m m a r y \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} Precision=Total number of words in generated summary Number of overlapping words​

Aplicând acest lucru la rezumatul nostru cu mai multe cuvinte, se obține o precizie de 6/10 = 0,6, ceea ce este considerabil mai rău decât precizia de 6/7 = 0,86 obținută de rezumatul nostru mai scurt. În practică, se calculează de obicei atât precision, cât și recallul, iar apoi se raportează scorul F1 (media armonică a precision și recall). Putem face acest lucru cu ușurință în 🤗 Datasets instalând mai întâi biblioteca `rouge_score`:

    !pip install rouge_score

și apoi încărcând metrica ROUGE după cum urmează:

    import evaluate

    rouge_score = evaluate.load("rouge")

Apoi, putem utiliza funcția `rouge_score.compute()` pentru a calcula toate metricile odată:

    scores = rouge_score.compute(
        predictions=[generated_summary], references=[reference_summary]
    )
    scores

    {'rouge1': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
     'rouge2': AggregateScore(low=Score(precision=0.67, recall=0.8, fmeasure=0.73), mid=Score(precision=0.67, recall=0.8, fmeasure=0.73), high=Score(precision=0.67, recall=0.8, fmeasure=0.73)),
     'rougeL': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92)),
     'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))}

Whoa, există o mulțime de informații în aceast output - ce înseamnă toate acestea? În primul rând, 🤗 Datasets calculează de fapt confidence intervalurile pentru precision, recall și scorul F1; acestea sunt atributele `low`, `mid` și `high` pe care le puteți vedea aici. În plus, 🤗 Datasets calculează o varietate de scoruri ROUGE care se bazează pe diferite tipuri de granularitate a textului atunci când compară rezumatele generate și de referință. Varianta `rouge1` este suprapunerea unigramelor - acesta este doar un mod elegant de a spune suprapunerea cuvintelor și este exact metrica pe care am discutat-o mai sus. Pentru a verifica acest lucru, să extragem valoarea `mid` a scorurilor noastre:

    scores["rouge1"].mid

    Score(precision=0.86, recall=1.0, fmeasure=0.92)

Grozav, numerele de precision și de recall se potrivesc! Acum ce se întâmplă cu celelalte scoruri ROUGE? `rouge2` măsoară suprapunerea dintre bigrame (suprapunerea perechilor de cuvinte), în timp ce `rougeL` și `rougeLsum` măsoară cele mai lungi secvențe de cuvinte care se potrivesc, căutând cele mai lungi substraturi comune în rezumatele generate și de referință. Termenul “sum” din `rougeLsum` se referă la faptul că această metrică este calculată pentru un rezumat întreg, în timp ce `rougeL` este calculată ca medie a propozițiilor individuale.

> ✏️ Încercați! Creați propriul exemplu de rezumat generat și de referință și vedeți dacă scorurile ROUGE rezultate sunt în concordanță cu un calcul manual bazat pe formulele de precision și recall. Pentru puncte bonus, împărțiți textul în bigrame și comparați precizia și recallul pentru metrica `rouge2`.

Vom folosi aceste scoruri ROUGE pentru a urmări performanța modelului nostru, dar înainte de a face acest lucru, să facem ceva ce orice bun practician NLP ar trebui să facă: să creăm un baseline puternic, dar simplu!

###
Crearea unui baseline bun

un baseline obișnuit pentru rezumarea textului este de a lua pur și simplu primele trei propoziții ale unui articol, adesea numit lead-3 baseline. Am putea folosi puncte de oprire pentru a urmări limitele propoziției, dar acest lucru va eșua în cazul acronimelor precum “U.S.” sau “U.N.” - așa că vom folosi în schimb biblioteca `nltk`, care include un algoritm mai bun pentru a gestiona aceste cazuri. Puteți instala pachetul folosind `pip` după cum urmează:

    !pip install nltk

și apoi descărcați regulile de punctuație:

    import nltk

    nltk.download("punkt")

În continuare, importăm tokenizerul de propoziții din `nltk` și creăm o funcție simplă pentru a extrage primele trei propoziții dintr-o recenzie. Convenția în rezumarea textului este de a separa fiecare rezumat cu o linie nouă, deci să includem și aceasta și să o testăm pe un exemplu de antrenare:

    from nltk.tokenize import sent_tokenize

    def three_sentence_summary(text):
        return "\n".join(sent_tokenize(text)[:3])

    print(three_sentence_summary(books_dataset["train"][1]["review_body"]))

    'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.'
    'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.'
    'She found Strangers.'

Acest lucru pare să funcționeze, deci să implementăm acum o funcție care extrage aceste “rezumate” dintr-un dataset și calculează scorurile ROUGE pentru baseline:

    def evaluate_baseline(dataset, metric):
        summaries = [three_sentence_summary(text) for text in dataset["review_body"]]
        return metric.compute(predictions=summaries, references=dataset["review_title"])

Putem folosi apoi această funcție pentru a calcula scorurile ROUGE pe setul de validare și pentru a le înfrumuseța puțin folosind Pandas:

    import pandas as pd

    score = evaluate_baseline(books_dataset["validation"], rouge_score)
    rouge_names = ["rouge1", "rouge2", "rougeL", "rougeLsum"]
    rouge_dict = dict((rn, round(score[rn].mid.fmeasure * 100, 2)) for rn in rouge_names)
    rouge_dict

    {'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96}

Putem vedea că scorul `rouge2` este semnificativ mai mic decât restul; acest lucru reflectă probabil faptul că titlurile recenziilor sunt de obicei concise și, prin urmare, baselineul lead-3 are prea multe cuvinte. Acum, că avem un baseline bun de lucru, să ne îndreptăm atenția către fine-tuningul mT5!

##
Fine-tuningul mT5 cu API-ul Trainer

Fine-tuningul unui model pentru rezumare este foarte asemănător cu celelalte sarcini pe care le-am acoperit în acest capitol. Primul lucru pe care trebuie să îl facem este să încărcăm modelul preantrenat din checkpointul `mt5-small`. Deoarece sumarizarea este o sarcină de la secvență la secvență, putem încărca modelul cu clasa `AutoModelForSeq2SeqLM`, care va descărca automat și va stoca în cache weighturile:

    from transformers import AutoModelForSeq2SeqLM

    model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)

> 💡 Dacă vă întrebați de ce nu vedeți niciun avertisment cu privire la fine-tuningul modelului pe un downstream task, acest lucru se datorează faptului că pentru sarcinile secvență-la-secvență păstrăm toate weighturile rețelei. Comparați acest lucru cu modelul nostru de clasificare a textului din Capitolul 3, unde headul modelului preantrenat a fost înlocuit cu o rețea inițializată aleatoriu.

Următorul lucru pe care trebuie să îl facem este să ne conectăm la Hugging Face Hub. Dacă executați acest cod într-un notebook, puteți face acest lucru cu următoarea funcție:

    from huggingface_hub import notebook_login

    notebook_login()

care va afișa un widget în care puteți introduce credențialele. Alternativ, puteți rula această comandă în terminal și să vă conectați acolo:

    huggingface-cli login

Va trebui să generăm rezumate pentru a calcula scorurile ROUGE în timpul antrenării. Din fericire, 🤗 Transformers oferă clase dedicate `Seq2SeqTrainingArguments` și `Seq2SeqTrainer` care pot face acest lucru pentru noi în mod automat! Pentru a vedea cum funcționează acest lucru, să definim mai întâi hiperparametrii și alte argumente pentru experimentele noastre:

    from transformers import Seq2SeqTrainingArguments

    batch_size = 8
    num_train_epochs = 8
    # Show the training loss with every epoch
    logging_steps = len(tokenized_datasets["train"]) // batch_size
    model_name = model_checkpoint.split("/")[-1]

    args = Seq2SeqTrainingArguments(
        output_dir=f"{model_name}-finetuned-amazon-en-es",
        evaluation_strategy="epoch",
        learning_rate=5.6e-5,
        per_device_train_batch_size=batch_size,
        per_device_eval_batch_size=batch_size,
        weight_decay=0.01,
        save_total_limit=3,
        num_train_epochs=num_train_epochs,
        predict_with_generate=True,
        logging_steps=logging_steps,
        push_to_hub=True,
    )

Aici, argumentul `predict_with_generate` a fost setat pentru a indica faptul că ar trebui să generăm rezumate în timpul evaluării, astfel încât să putem calcula scorurile ROUGE pentru fiecare epocă. După cum s-a discutat în Capitolul 1, decodificatorul realizează inference-ul prin prezicerea tokenilor unul câte unul, iar acest lucru este implementat de metoda `generate()` a modelului. Setarea `predict_with_generate=True` îi spune lui `Seq2SeqTrainer` să utilizeze această metodă pentru evaluare. Am ajustat, de asemenea, unii dintre hiperparametrii impliciți, cum ar fi rata de învățare, numărul de epoci și scăderea weighturilor și am setat opțiunea `save_total_limit` pentru a salva numai până la 3 checkpointuri în timpul antrenamentului - acest lucru se datorează faptului că chiar și versiunea “mică” a mT5 utilizează aproximativ 1 GB de spațiu pe hard disk și putem economisi puțin spațiu prin limitarea numărului de copii salvate.

Argumentul `push_to_hub=True` ne va permite să trimitem modelul în Hub după antrenare; veți găsi repositoriul în profilul vostru de utilizator, în locația definită de `output_dir`. Rețineți că puteți specifica numele repositoriului către care doriți să trimiteți modelul cu argumentul `hub_model_id` (în special, va trebui să utilizați acest argument pentru a trimite modelul către o organizație). De exemplu, atunci când am trimis modelul către organizația `huggingface-course`, am adăugat `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` la `Seq2SeqTrainingArguments`.

Următorul lucru pe care trebuie să îl facem este să oferim trainerului o funcție `compute_metrics()`, astfel încât să ne putem evalua modelul în timpul antrenării. Pentru sumarizare, acest lucru este un pic mai complicat decât simpla apelare a funcției `rouge_score.compute()` pentru predicțiile modelului, deoarece trebuie să decodăm rezultatele și labelurile din text înainte de a putea calcula scorurile ROUGE. Următoarea funcție face exact acest lucru și, de asemenea, utilizează funcția `sent_tokenize()` din `nltk` pentru a separa propozițiile rezumate cu linii noi:

    import numpy as np

    def compute_metrics(eval_pred):
        predictions, labels = eval_pred
        # Decode generated summaries into text
        decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True)
        # Replace -100 in the labels as we can't decode them
        labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
        # Decode reference summaries into text
        decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)
        # ROUGE expects a newline after each sentence
        decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds]
        decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels]
        # Compute ROUGE scores
        result = rouge_score.compute(
            predictions=decoded_preds, references=decoded_labels, use_stemmer=True
        )
        # Extract the median scores
        result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
        return {k: round(v, 4) for k, v in result.items()}

În continuare, trebuie să definim un data collator pentru sarcina noastră secvență-la-secvență. Deoarece mT5 este un transformer model encoder-decoder, o subtilitate a pregătirii batch-urilor noastre este că, în timpul decodificării, trebuie să deplasăm labelurile la dreapta cu una. Acest lucru este necesar pentru a ne asigura că decodificatorul vede doar labelurile anterioare ale adevărului de bază și nu pe cele actuale sau viitoare, care ar fi ușor de memorat de către model. Acest lucru este similar cu modul în care masked self-attention este aplicată inputurilor într-o sarcină precum causal language modeling.

Din fericire, 🤗 Transformers oferă un collator `DataCollatorForSeq2Seq` care va face padding dinamic inputurilor și labelurilor pentru noi. Pentru a inițializa acest collator, trebuie doar să furnizăm `tokenizer` și `model`:

    from transformers import DataCollatorForSeq2Seq

    data_collator = DataCollatorForSeq2Seq(tokenizer, model=model)

Să vedem ce produce acest collator atunci când este alimentat cu un mic batch de exemple. În primul rând, trebuie să eliminăm coloanele cu șiruri de caractere, deoarece collatorul nu va ști cum să completeze aceste elemente:

    tokenized_datasets = tokenized_datasets.remove_columns(
        books_dataset["train"].column_names
    )

Deoarece collatorul așteaptă o listă de `dict`, unde fiecare `dict` reprezintă un singur exemplu din dataset, trebuie să transformăm datele în formatul așteptat înainte de a le transmite data collatorului:

    features = [tokenized_datasets["train"][i] for i in range(2)]
    data_collator(features)

    {'attention_mask': tensor([[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
             1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0],
            [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
             1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]), 'input_ids': tensor([[  1494,    259,   8622,    390,    259,    262,   2316,   3435,    955,
                772,    281,    772,   1617,    263,    305,  14701,    260,   1385,
               3031,    259,  24146,    332,   1037,    259,  43906,    305,    336,
                260,      1,      0,      0,      0,      0,      0,      0],
            [   259,  27531,  13483,    259,   7505,    260, 112240,  15192,    305,
              53198,    276,    259,  74060,    263,    260,    459,  25640,    776,
               2119,    336,    259,   2220,    259,  18896,    288,   4906,    288,
               1037,   3931,    260,   7083, 101476,   1143,    260,      1]]), 'labels': tensor([[ 7483,   259,  2364, 15695,     1,  -100],
            [  259, 27531, 13483,   259,  7505,     1]]), 'decoder_input_ids': tensor([[    0,  7483,   259,  2364, 15695,     1],
            [    0,   259, 27531, 13483,   259,  7505]])}

Principalul lucru care trebuie observat aici este că primul exemplu este mai lung decât al doilea, astfel încât `input_ids` și `attention_mask` din al doilea exemplu au primit padding în dreapta cu un simbol `[PAD]` (al cărui ID este `0`). În mod similar, putem vedea că `labels` au primit padding cu `-100`, pentru a ne asigura că tokenii de padding sunt ignorați de funcția de pierdere. În sfârșit, putem vedea un nou `decoder_input_ids` care a deplasat labelurile spre dreapta prin inserarea unui simbol `[PAD]` în prima intrare.

Avem în sfârșit toate ingredientele de care avem nevoie pentru a antrenare! Acum trebuie doar să inițializăm trainerul cu argumentele standard:

    from transformers import Seq2SeqTrainer

    trainer = Seq2SeqTrainer(
        model,
        args,
        train_dataset=tokenized_datasets["train"],
        eval_dataset=tokenized_datasets["validation"],
        data_collator=data_collator,
        tokenizer=tokenizer,
        compute_metrics=compute_metrics,
    )

și să lansăm cursa noastră de antrenare:

    trainer.train()

În timpul antrenamentului, ar trebui să vedeți cum training loss scade și scorurile ROUGE cresc cu fiecare epocă. După ce antrenamentul este complet, puteți vedea scorurile ROUGE finale executând `Trainer.evaluate()`:

    trainer.evaluate()

    {'eval_loss': 3.028524398803711,
     'eval_rouge1': 16.9728,
     'eval_rouge2': 8.2969,
     'eval_rougeL': 16.8366,
     'eval_rougeLsum': 16.851,
     'eval_gen_len': 10.1597,
     'eval_runtime': 6.1054,
     'eval_samples_per_second': 38.982,
     'eval_steps_per_second': 4.914}

Din scorurile obținute, putem vedea că modelul nostru a depășit cu mult modelul nostru de bază lead-3 - frumos! Ultimul lucru de făcut este să introducem weighturile modelului în Hub, după cum urmează:

    trainer.push_to_hub(commit_message="Training complete", tags="summarization")

    'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0'

Acest lucru va salva checkpointul și fișierele de configurare în `output_dir`, înainte de a încărca toate fișierele în Hub. Specificând argumentul `tags`, ne asigurăm, de asemenea, că widgetul de pe Hub va fi unul pentru un pipeline de rezumare în locul celui implicit de generare de text asociat arhitecturii mT5 (pentru mai multe informații despre labelurile modelului, consultați 🤗Documentația Hub). Rezultatul din `trainer.push_to_hub()` este o adresă URL către hash-ul Git commit, astfel încât să puteți vedea cu ușurință modificările care au fost făcute în repositoriul modelului!

Pentru a încheia această secțiune, să aruncăm o privire la modul în care putem, de asemenea, să facem fine-tune la mT5 folosind featururile de nivel scăzut oferite de 🤗 Accelerate.

##
Fine-tuningul mT5 cu 🤗 Accelerate

Fine-tuningul nostru cu 🤗 Accelerate este foarte asemănător cu exemplul de clasificare a textului pe care l-am întâlnit în Capitolul 3. Principalele diferențe vor fi necesitatea de a genera în mod explicit rezumatele noastre în timpul antrenării și de a defini modul în care calculăm scorurile ROUGE (reamintim că `Seq2SeqTrainer` a avut grijă de generare pentru noi). Să aruncăm o privire la modul în care putem implementa aceste două cerințe în cadrul 🤗 Accelerate!

###
Pregătirea pentru antrenare

Primul lucru pe care trebuie să-l facem este să creăm un `DataLoader` pentru fiecare dintre spliturile noastre. Deoarece dataloaders PyTorch așteaptă batchuri de tensori, trebuie să setăm formatul la `"torch"` în dataseturile noastre:

    tokenized_datasets.set_format("torch")

Acum că avem dataseturi formate doar din tensori, următorul lucru este inițializarea `DataCollatorForSeq2Seq`. Pentru aceasta trebuie să furnizăm o versiune nouă a modelului, așa că hai să îl încărcăm din nou din cache:

    model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint)

Putem apoi să instanțiem data collatorul și să îl folosim pentru a ne defini dataloaders:

    from torch.utils.data import DataLoader

    batch_size = 8
    train_dataloader = DataLoader(
        tokenized_datasets["train"],
        shuffle=True,
        collate_fn=data_collator,
        batch_size=batch_size,
    )
    eval_dataloader = DataLoader(
        tokenized_datasets["validation"], collate_fn=data_collator, batch_size=batch_size
    )

Următorul lucru este definirea optimizatorului pe care dorim să îl utilizăm. Ca și în celelalte exemple, vom folosi `AdamW`, care funcționează bine pentru majoritatea problemelor:

    from torch.optim import AdamW

    optimizer = AdamW(model.parameters(), lr=2e-5)

În cele din urmă, introducem modelul, optimizatorul și dataloaders în metoda `accelerator.prepare()`:

    from accelerate import Accelerator

    accelerator = Accelerator()
    model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare(
        model, optimizer, train_dataloader, eval_dataloader
    )

> 🚨 Dacă faceți antrenarea pe un TPU, va trebui să mutați tot codul de mai sus într-o funcție de antrenare aparte. Consultați Capitolul 3 pentru mai multe detalii.

Acum că ne-am pregătit obiectele, mai avem trei lucruri de făcut:

- Definirea learning rate schedule.
- Implementarea unei funcții de post-procesare a rezumatelor pentru evaluare.
- Crearea unui repositoriu pe Hub în care să putem trimite modelul nostru.

Pentru learning rate schedule, îl vom utiliza pe cel liniar standard din secțiunile anterioare:

    from transformers import get_scheduler

    num_train_epochs = 10
    num_update_steps_per_epoch = len(train_dataloader)
    num_training_steps = num_train_epochs * num_update_steps_per_epoch

    lr_scheduler = get_scheduler(
        "linear",
        optimizer=optimizer,
        num_warmup_steps=0,
        num_training_steps=num_training_steps,
    )

Pentru post-procesare, avem nevoie de o funcție care să împartă rezumatele generate în propoziții separate prin linii noi. Acesta este formatul pe care îl așteaptă metrica ROUGE, iar noi putem realiza acest lucru cu următorul fragment de cod:

    def postprocess_text(preds, labels):
        preds = [pred.strip() for pred in preds]
        labels = [label.strip() for label in labels]

        # ROUGE expects a newline after each sentence
        preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds]
        labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels]

        return preds, labels

Acest lucru ar trebui să vă pară familiar dacă vă amintiți cum am definit funcția `compute_metrics()` a `Seq2SeqTrainer`.

În cele din urmă, trebuie să creăm un repositoriu de modele pe Hugging Face Hub. Pentru aceasta, putem utiliza biblioteca 🤗 Hub intitulată corespunzător . Trebuie doar să definim un nume pentru repositoriul nostru, iar biblioteca are o funcție utilitară pentru a combina ID-ul repositoriul cu profilul utilizatorului:

    from huggingface_hub import get_full_repo_name

    model_name = "test-bert-finetuned-squad-accelerate"
    repo_name = get_full_repo_name(model_name)
    repo_name

    'lewtun/mt5-finetuned-amazon-en-es-accelerate'

Acum putem folosi numele repositoriului pentru a clona o versiune locală în folderul nostru cu rezultate care va stoca artefactele de antrenare:

    from huggingface_hub import Repository

    output_dir = "results-mt5-finetuned-squad-accelerate"
    repo = Repository(output_dir, clone_from=repo_name)

Acest lucru ne va permite să trimitem artefactele înapoi la Hub prin apelarea metodei `repo.push_to_hub()` în timpul antrenării! Să încheiem acum analiza noastră prin scrierea buclei de antrenare.

###
Bucla de antrenare

Bucla de formare pentru sumarizare este destul de asemănătoare cu celelalte exemple 🤗 Accelerate pe care le-am întâlnit și este împărțită aproximativ în patru etape principale:

1. Antrenarea modelului prin iterarea peste toate exemplele din `train_dataloader` pentru fiecare epocă.
2. Generarea rezumatelor modelului la sfârșitul fiecărei epoci, mai întâi prin generarea token-urilor și apoi prin decodarea lor (și a rezumatelor de referință) în text.
3. Calcularea scorurilor ROUGE folosind aceleași tehnici pe care le-am văzut mai devreme.
4. Salvați checkpointurile și încărcați totul pe Hub. Aici ne bazăm pe argumentul `blocking=False` al obiectului `Repository` astfel încât să putem împinge checkointurile pentru fiecare epocă asincron. Acest lucru ne permite să continuăm antrenamentul fără a fi nevoiți să așteptăm încărcarea oarecum lentă asociată cu un model de dimensiunea unui GB!

Acești pași pot fi observați în următorul bloc de cod:

    from tqdm.auto import tqdm
    import torch
    import numpy as np

    progress_bar = tqdm(range(num_training_steps))

    for epoch in range(num_train_epochs):
        # Training
        model.train()
        for step, batch in enumerate(train_dataloader):
            outputs = model(**batch)
            loss = outputs.loss
            accelerator.backward(loss)

            optimizer.step()
            lr_scheduler.step()
            optimizer.zero_grad()
            progress_bar.update(1)

        # Evaluation
        model.eval()
        for step, batch in enumerate(eval_dataloader):
            with torch.no_grad():
                generated_tokens = accelerator.unwrap_model(model).generate(
                    batch["input_ids"],
                    attention_mask=batch["attention_mask"],
                )

                generated_tokens = accelerator.pad_across_processes(
                    generated_tokens, dim=1, pad_index=tokenizer.pad_token_id
                )
                labels = batch["labels"]

                # If we did not pad to max length, we need to pad the labels too
                labels = accelerator.pad_across_processes(
                    batch["labels"], dim=1, pad_index=tokenizer.pad_token_id
                )

                generated_tokens = accelerator.gather(generated_tokens).cpu().numpy()
                labels = accelerator.gather(labels).cpu().numpy()

                # Replace -100 in the labels as we can't decode them
                labels = np.where(labels != -100, labels, tokenizer.pad_token_id)
                if isinstance(generated_tokens, tuple):
                    generated_tokens = generated_tokens[0]
                decoded_preds = tokenizer.batch_decode(
                    generated_tokens, skip_special_tokens=True
                )
                decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True)

                decoded_preds, decoded_labels = postprocess_text(
                    decoded_preds, decoded_labels
                )

                rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels)

        # Compute metrics
        result = rouge_score.compute()
        # Extract the median ROUGE scores
        result = {key: value.mid.fmeasure * 100 for key, value in result.items()}
        result = {k: round(v, 4) for k, v in result.items()}
        print(f"Epoch {epoch}:", result)

        # Save and upload
        accelerator.wait_for_everyone()
        unwrapped_model = accelerator.unwrap_model(model)
        unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save)
        if accelerator.is_main_process:
            tokenizer.save_pretrained(output_dir)
            repo.push_to_hub(
                commit_message=f"Training in progress epoch {epoch}", blocking=False
            )

    Epoch 0: {'rouge1': 5.6351, 'rouge2': 1.1625, 'rougeL': 5.4866, 'rougeLsum': 5.5005}
    Epoch 1: {'rouge1': 9.8646, 'rouge2': 3.4106, 'rougeL': 9.9439, 'rougeLsum': 9.9306}
    Epoch 2: {'rouge1': 11.0872, 'rouge2': 3.3273, 'rougeL': 11.0508, 'rougeLsum': 10.9468}
    Epoch 3: {'rouge1': 11.8587, 'rouge2': 4.8167, 'rougeL': 11.7986, 'rougeLsum': 11.7518}
    Epoch 4: {'rouge1': 12.9842, 'rouge2': 5.5887, 'rougeL': 12.7546, 'rougeLsum': 12.7029}
    Epoch 5: {'rouge1': 13.4628, 'rouge2': 6.4598, 'rougeL': 13.312, 'rougeLsum': 13.2913}
    Epoch 6: {'rouge1': 12.9131, 'rouge2': 5.8914, 'rougeL': 12.6896, 'rougeLsum': 12.5701}
    Epoch 7: {'rouge1': 13.3079, 'rouge2': 6.2994, 'rougeL': 13.1536, 'rougeLsum': 13.1194}
    Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13.7744}
    Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509}

Și asta e tot! După ce executați acest lucru, veți avea un model și rezultate care sunt destul de asemănătoare cu cele obținute cu `Trainer`.

##
Utilizarea modelului fine-tuned

Odată ce ați încărcat modelul în Hub, vă puteți juca cu el prin widgetul de inference, fie cu un obiect `pipeline`, după cum urmează:

    from transformers import pipeline

    hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es"
    summarizer = pipeline("summarization", model=hub_model_id)

Putem introduce câteva exemple din setul de testare (pe care modelul nu le-a văzut) în pipelineul noastru pentru a avea o idee despre calitatea rezumatelor. Mai întâi, să implementăm o funcție simplă pentru a afișa împreună recenzia, titlul și rezumatul generat:

    def print_summary(idx):
        review = books_dataset["test"][idx]["review_body"]
        title = books_dataset["test"][idx]["review_title"]
        summary = summarizer(books_dataset["test"][idx]["review_body"])[0]["summary_text"]
        print(f"'>>> Review: {review}'")
        print(f"\n'>>> Title: {title}'")
        print(f"\n'>>> Summary: {summary}'")

Să aruncăm o privire la unul dintre exemplele englezești pe care le primim:

    print_summary(100)

    '>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.'

    '>>> Title: Not impressed at all... buy something else'

    '>>> Summary: Nothing special at all about this product'

This is not too bad! We can see that our model has actually been able to perform abstractive summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews:

    print_summary(0)

    '>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada'

    '>>> Title: Buena literatura para adolescentes'

    '>>> Summary: Muy facil de leer'

Rezumatul se traduce prin “Very easy to read” în limba engleză, ceea ce putem vedea că în acest caz a fost extras direct din recenzie. Cu toate acestea, acest lucru arată versatilitatea modelului mT5 și v-a dat o idee despre cum este să aveți de-a face cu un corpus multilingv!

În continuare, ne vom îndrepta atenția către o sarcină puțin mai complexă: antrenarea unui model lingvistic de la zero.

Update on GitHub