diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index b09b32f58..bd7ce48d9 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -16,4 +16,4 @@ jobs: additional_args: --not_python_module languages: ar bn de en es fa fr gj he hi id it ja ko pt ru th tr vi zh-CN zh-TW secrets: - token: ${{ secrets.HUGGINGFACE_PUSH }} + hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 45e3b3e09..a568b16f4 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -17,4 +17,3 @@ jobs: path_to_docs: course/chapters/ additional_args: --not_python_module languages: ar bn de en es fa fr gj he hi id it ja ko pt ru th tr vi zh-CN zh-TW - hub_base_path: https://moon-ci-docs.huggingface.co diff --git a/.github/workflows/delete_doc_comment.yml b/.github/workflows/delete_doc_comment.yml deleted file mode 100644 index 9ec2aaf44..000000000 --- a/.github/workflows/delete_doc_comment.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: Delete dev documentation - -on: - pull_request: - types: [ closed ] - - -jobs: - delete: - uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@main - with: - pr_number: ${{ github.event.number }} - package: course \ No newline at end of file diff --git a/.github/workflows/upload_pr_documentation.yml b/.github/workflows/upload_pr_documentation.yml new file mode 100644 index 000000000..446433459 --- /dev/null +++ b/.github/workflows/upload_pr_documentation.yml @@ -0,0 +1,17 @@ +name: Upload PR Documentation + +on: + workflow_run: + workflows: ["Build PR Documentation"] + types: + - completed + +jobs: + build: + uses: huggingface/doc-builder/.github/workflows/upload_pr_documentation.yml@main + with: + package_name: course + hub_base_path: https://moon-ci-docs.huggingface.co + secrets: + hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }} + comment_bot_token: ${{ secrets.COMMENT_BOT_TOKEN }} \ No newline at end of file diff --git a/README.md b/README.md index b41485d8d..d48e97773 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Japanese](https://huggingface.co/course/ja/chapter1/1) (WIP) | [`chapters/ja`](https://github.com/huggingface/course/tree/main/chapters/ja) | [@hiromu166](https://github.com/@hiromu166), [@younesbelkada](https://github.com/@younesbelkada), [@HiromuHota](https://github.com/@HiromuHota) | | [Korean](https://huggingface.co/course/ko/chapter1/1) (WIP) | [`chapters/ko`](https://github.com/huggingface/course/tree/main/chapters/ko) | [@Doohae](https://github.com/Doohae), [@wonhyeongseo](https://github.com/wonhyeongseo), [@dlfrnaos19](https://github.com/dlfrnaos19), [@nsbg](https://github.com/nsbg) | | [Portuguese](https://huggingface.co/course/pt/chapter1/1) (WIP) | [`chapters/pt`](https://github.com/huggingface/course/tree/main/chapters/pt) | [@johnnv1](https://github.com/johnnv1), [@victorescosta](https://github.com/victorescosta), [@LincolnVS](https://github.com/LincolnVS) | -| [Russian](https://huggingface.co/course/ru/chapter1/1) (WIP) | [`chapters/ru`](https://github.com/huggingface/course/tree/main/chapters/ru) | [@pdumin](https://github.com/pdumin), [@svv73](https://github.com/svv73) | +| [Russian](https://huggingface.co/course/ru/chapter1/1) (WIP) | [`chapters/ru`](https://github.com/huggingface/course/tree/main/chapters/ru) | [@pdumin](https://github.com/pdumin), [@svv73](https://github.com/svv73), [@blademoon](https://github.com/blademoon) | | [Thai](https://huggingface.co/course/th/chapter1/1) (WIP) | [`chapters/th`](https://github.com/huggingface/course/tree/main/chapters/th) | [@peeraponw](https://github.com/peeraponw), [@a-krirk](https://github.com/a-krirk), [@jomariya23156](https://github.com/jomariya23156), [@ckingkan](https://github.com/ckingkan) | | [Turkish](https://huggingface.co/course/tr/chapter1/1) (WIP) | [`chapters/tr`](https://github.com/huggingface/course/tree/main/chapters/tr) | [@tanersekmen](https://github.com/tanersekmen), [@mertbozkir](https://github.com/mertbozkir), [@ftarlaci](https://github.com/ftarlaci), [@akkasayaz](https://github.com/akkasayaz) | | [Vietnamese](https://huggingface.co/course/vi/chapter1/1) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | diff --git a/chapters/ar/chapter0/1.mdx b/chapters/ar/chapter0/1.mdx index 7951b9d94..8cec15b6e 100644 --- a/chapters/ar/chapter0/1.mdx +++ b/chapters/ar/chapter0/1.mdx @@ -105,7 +105,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ```
diff --git a/chapters/bn/chapter0/1.mdx b/chapters/bn/chapter0/1.mdx index ab323044e..9235ad8a3 100644 --- a/chapters/bn/chapter0/1.mdx +++ b/chapters/bn/chapter0/1.mdx @@ -87,7 +87,7 @@ ls -a source .env/bin/activate # virtual environment টি deactivate করার কমান্ড -source .env/bin/deactivate +deactivate ``` `which python` কমান্ড চালিয়ে নিশ্চিত করতে পারেন যে virtual environment টি activate হয়েছে কিনা। diff --git a/chapters/de/_toctree.yml b/chapters/de/_toctree.yml index c43192ca8..cf4af10c2 100644 --- a/chapters/de/_toctree.yml +++ b/chapters/de/_toctree.yml @@ -52,6 +52,13 @@ title: Verwendung vortrainierter Modelle - local: chapter4/3 title: Vortrainierte Modelle teilen + - local: chapter4/4 + title: Erstellung einer Modellkarte ("model card") + - local: chapter4/5 + title: Teil 1 abgeschlossen! + - local: chapter4/6 + title: Quiz am Ende des Kapitels + quiz: 4 - title: Wörterverzeichnis sections: diff --git a/chapters/de/chapter0/1.mdx b/chapters/de/chapter0/1.mdx index 9d1610de4..9b9fd8805 100644 --- a/chapters/de/chapter0/1.mdx +++ b/chapters/de/chapter0/1.mdx @@ -86,7 +86,7 @@ Mit den Skripten "activate" und "deactivate" kannst du in deine virtuelle Umgebu source .env/bin/activate # Deaktivieren der virtuellen Umgebung -source .env/bin/deactivate +deactivate ``` Du kannst dich vergewissern, dass die Umgebung aktiviert ist, indem du den Befehl `which python` ausführst: Wenn er auf die virtuelle Umgebung verweist, dann hast du sie erfolgreich aktiviert! diff --git a/chapters/de/chapter1/3.mdx b/chapters/de/chapter1/3.mdx index c58ba5d64..7db9cb48a 100644 --- a/chapters/de/chapter1/3.mdx +++ b/chapters/de/chapter1/3.mdx @@ -68,7 +68,7 @@ Wenn du einen Text an eine Pipeline übergibst, gibt es drei wichtige Schritte: 3. Die Vorhersagen des Modells werden so nachverarbeitet, sodass du sie nutzen kannst. -Einige der derzeit [verfügbaren Pipelines](https://huggingface.co/transformers/main_classes/pipelines.html) sind: +Einige der derzeit [verfügbaren Pipelines](https://huggingface.co/transformers/main_classes/pipelines) sind: - `feature-extraction` (Vektordarstellung eines Textes erhalten) - `fill-mask` diff --git a/chapters/de/chapter1/5.mdx b/chapters/de/chapter1/5.mdx index 502ab5d89..5f1b68f53 100644 --- a/chapters/de/chapter1/5.mdx +++ b/chapters/de/chapter1/5.mdx @@ -15,8 +15,8 @@ Rein Encoder-basierte Modelle eignen sich am besten für Aufgaben, die ein Verst Zu dieser Modellfamilie gehören unter anderem: -- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html) -- [BERT](https://huggingface.co/transformers/model_doc/bert.html) -- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html) -- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html) -- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html) +- [ALBERT](https://huggingface.co/transformers/model_doc/albert) +- [BERT](https://huggingface.co/transformers/model_doc/bert) +- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert) +- [ELECTRA](https://huggingface.co/transformers/model_doc/electra) +- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta) diff --git a/chapters/de/chapter1/6.mdx b/chapters/de/chapter1/6.mdx index 3e0e5768a..948c010a2 100644 --- a/chapters/de/chapter1/6.mdx +++ b/chapters/de/chapter1/6.mdx @@ -15,7 +15,7 @@ Diese Modelle sind am besten für Aufgaben geeignet, bei denen es um die Generie Zu dieser Modellfamilie gehören unter anderem: -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl) - [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt) -- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl) diff --git a/chapters/de/chapter1/7.mdx b/chapters/de/chapter1/7.mdx index 0ce8561cf..4bf04585f 100644 --- a/chapters/de/chapter1/7.mdx +++ b/chapters/de/chapter1/7.mdx @@ -15,7 +15,7 @@ Sequence-to-Sequence-Modelle eignen sich am besten für Aufgaben, bei denen es d Vertreter dieser Modellfamilie sind u. a.: -- [BART](https://huggingface.co/transformers/model_doc/bart.html) -- [mBART](https://huggingface.co/transformers/model_doc/mbart.html) -- [Marian](https://huggingface.co/transformers/model_doc/marian.html) -- [T5](https://huggingface.co/transformers/model_doc/t5.html) +- [BART](https://huggingface.co/transformers/model_doc/bart) +- [mBART](https://huggingface.co/transformers/model_doc/mbart) +- [Marian](https://huggingface.co/transformers/model_doc/marian) +- [T5](https://huggingface.co/transformers/model_doc/t5) diff --git a/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 49a025c1a..ddfcb101d 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -84,9 +84,12 @@ In diesem Abschnitt verwenden wir den MRPC-Datensatz (Microsoft Research Paraphr {/if} -Das Hub enthält nicht nur Modelle; Es hat auch mehrere Datensätze in vielen verschiedenen Sprachen. Du kannst die Datensätze [hier](https://huggingface.co/datasets) durchsuchen, und wir empfehlen, einen weiteren Datensatz zu laden und zu verarbeiten, sobald Sie diesen Abschnitt abgeschlossen haben (die Dokumentation befindet sich [hier](https: //huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Aber jetzt konzentrieren wir uns auf den MRPC-Datensatz! Dies ist einer der 10 Datensätze, aus denen sich das [GLUE-Benchmark](https://gluebenchmark.com/) zusammensetzt. Dies ist ein akademisches Benchmark, das verwendet wird, um die Performance von ML-Modellen in 10 verschiedenen Textklassifizierungsaufgaben zu messen. +Das Hub enthält nicht nur Modelle; Es hat auch mehrere Datensätze in vielen verschiedenen Sprachen. Du kannst die Datensätze [hier](https://huggingface.co/datasets) durchsuchen, und wir empfehlen, einen weiteren Datensatz zu laden und zu verarbeiten, sobald Sie diesen Abschnitt abgeschlossen haben (die Dokumentation befindet sich [hier](https://huggingface.co/docs/datasets/loading)). Aber jetzt konzentrieren wir uns auf den MRPC-Datensatz! Dies ist einer der 10 Datensätze, aus denen sich das [GLUE-Benchmark](https://gluebenchmark.com/) zusammensetzt. Dies ist ein akademisches Benchmark, das verwendet wird, um die Performance von ML-Modellen in 10 verschiedenen Textklassifizierungsaufgaben zu messen. Die Bibliothek 🤗 Datasets bietet einen leichten Befehl zum Herunterladen und Caching eines Datensatzes aus dem Hub. Wir können den MRPC-Datensatz wie folgt herunterladen: + +⚠️ ** Warnung** Stelle sicher, dass `datasets` installiert ist, indem du `pip install datasets` ausführst. Dann lade den MRPC-Datensatz und drucke ihn aus, um zu sehen, was er enthält. + ```py from datasets import load_dataset @@ -235,7 +238,7 @@ tokenized_dataset = tokenizer( Das funktioniert gut, hat aber den Nachteil, dass ein Dictionary zurückgegeben wird (mit unseren Schlüsselwörtern `input_ids`, `attention_mask` und `token_type_ids` und Werten aus Listen von Listen). Es funktioniert auch nur, wenn du genügend RAM hast, um den gesamten Datensatz während der Tokenisierung zu im RAM zwischen zu speichern (während die Datensätze aus der Bibliothek 🤗 Datasets [Apache Arrow](https://arrow.apache.org/) Dateien sind, die auf der Festplatte gespeichert sind, sodass nur die gewünschten Samples im RAM geladen sind). -Um die Daten als Datensatz zu speichern, verwenden wir die Methode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Dies gewährt uns zusätzliche Flexibilität, wenn wir zusätzliche Vorverarbeitung als nur die Tokenisierung benötigen. Die `map()`-Methode funktioniert, indem sie eine Funktion auf jedes Element des Datensatzes anwendet, also definieren wir eine Funktion, die unsere Inputs tokenisiert: +Um die Daten als Datensatz zu speichern, verwenden wir die Methode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Dies gewährt uns zusätzliche Flexibilität, wenn wir zusätzliche Vorverarbeitung als nur die Tokenisierung benötigen. Die `map()`-Methode funktioniert, indem sie eine Funktion auf jedes Element des Datensatzes anwendet, also definieren wir eine Funktion, die unsere Inputs tokenisiert: ```py def tokenize_function(example): diff --git a/chapters/de/chapter4/2.mdx b/chapters/de/chapter4/2.mdx index 0b20fe1d9..a445d6f60 100644 --- a/chapters/de/chapter4/2.mdx +++ b/chapters/de/chapter4/2.mdx @@ -65,7 +65,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = CamembertForMaskedLM.from_pretrained("camembert-base") ``` -Dennoch empfehlen wir, dass man die [`Auto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) stattdessen benutzt, da diese architekturunabhängig sind. Das vorherige Code-Beispiel gilt nur für Checkpoints, die in die CamemBERT Architektur zu laden sind, aber mit den `Auto*` Klassen kann man Checkpoints ziemlich einfach tauschen: +Dennoch empfehlen wir, dass man die [`Auto*` classes](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) stattdessen benutzt, da diese architekturunabhängig sind. Das vorherige Code-Beispiel gilt nur für Checkpoints, die in die CamemBERT Architektur zu laden sind, aber mit den `Auto*` Klassen kann man Checkpoints ziemlich einfach tauschen: ```py from transformers import AutoTokenizer, AutoModelForMaskedLM @@ -81,7 +81,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = TFCamembertForMaskedLM.from_pretrained("camembert-base") ``` -Hier empfehlen wir auch, dass man stattdessen die [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) benutzt, da diese architekturunabhängig sind. Das vorherige Code-Beispiel gilt nur für Checkpoints, die in die CamemBERT Architektur zu laden sind, aber mit den `TFAuto*` Klassen kann man Checkpoints einfach tauschen: +Hier empfehlen wir auch, dass man stattdessen die [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) benutzt, da diese architekturunabhängig sind. Das vorherige Code-Beispiel gilt nur für Checkpoints, die in die CamemBERT Architektur zu laden sind, aber mit den `TFAuto*` Klassen kann man Checkpoints einfach tauschen: ```py from transformers import AutoTokenizer, TFAutoModelForMaskedLM diff --git a/chapters/de/chapter4/4.mdx b/chapters/de/chapter4/4.mdx new file mode 100644 index 000000000..1689f5ade --- /dev/null +++ b/chapters/de/chapter4/4.mdx @@ -0,0 +1,91 @@ +# Erstellung einer Modellkarte ("model card") + + + +Die Modellkarte (Steckbrief) ist eine Datei, die wahrscheinlich genauso wichtig wie das Modell und der Tokenizer in dem Modell-Repository ist. +Da liegt die zentrale Definition vom Modell und sie trägt dazu bei, dass andere Menschen der Community das wiederverwenden und die Ergebnisse reproduzieren können – also das ist die Basis, auf der Andere ihre Artifakte bauen können. + +Die Dokumentation zum Modell- Training und Evaluierung hilft anderen Nutzer:innen zu verstehen, was sie vom Modell erwarten sollten. +Ausreichende Information zu der Vor- und Nachaufarbeitung der Daten dient auch dazu, dass man die Einschränkungen, Biases und den Kontext identifizieren kann, wann das Modell nützlich ist und wann nicht. + +Deswegen ist die Erstellung einer klar definierten Modellkarte ein sehr wichtiger Schritt. Hier geben wir ein Paar Hinweise, die dir dabei helfen könnten. Die Modellkarte wird durch eine *README.md* Datei (eine Markdown Datei) kreiert, die du schonmal gesehen hast. + +Das Konzept von Modellkarte ("model card") stammt aus einer Forschungsrichtung bei Google, die zuerst in dem Paper ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) von Margaret Mitchell et al erschien. Vieles von dem, was hier steht, basiert auf dem Paper und wir empfehlen dir, das Paper zu lesen, um besser zu verstehen, warum Modellkarten so wichtig sind, wenn man Wert auf Reproduzierbarkeit, Wiederverwendbarkeit und Fairness legt. + +Eine Modellkarte fängt mit einer kurzen, große Übersicht davon, was das Modell kann plus einige Details in den folgenden Abschnitte: + +- Modell-Beschreibung +- Beabsichtigte Nutzung und Einschränkungen +- Modell-Bedienung +- Einschränkungen und Bias +- Trainingsdaten +- Trainingsverfahren +- Evaluierungsergebnisse + +Lass uns anschauen, was genau in jedem Abschnitt stehen sollte. + +### Modell-Beschreibung + +Die Modellbeschreibung enthält grundlegende Details zum Modell. Dazu gehören die Architektur, die Version, ob es in einem Paper vorgestellt wurde, ob eine Originalimplementierung verfügbar ist, der Autor und allgemeine Informationen über das Modell. Eventuelle Urheberrechte sind hier anzugeben. In diesem Abschnitt können auch allgemeine Informationen zu Trainingsverfahren, Parametern und wichtigen Haftungsausschlüssen erwähnt werden. + +### Verwendungszweck und Einschränkungen + +Hier beschreibst du die angedachten Anwendungsfälle fürs Modell, einschließlich der Sprachen, Felder und Domänen, in denen es angewendet werden kann. In diesem Abschnitt der Modellkarte können auch Bereiche dokumentiert werden, die bekanntermaßen außerhalb des Anwendungsbereichs des Modells liegen oder in denen die Leistung wahrscheinlich nicht optimal ist. + +### Modell-Bedienung + +Dieser Abschnitt sollte einige Beispiele für die Verwendung des Modells enthalten. Dies kann die Verwendung der Funktion `pipeline()`, die Verwendung der Modell- und Tokenizer-Klassen und jeden anderen Code zeigen, der deiner Meinung nach hilfreich sein könnte. + + +### Trainingsdaten + +In diesem Teil sollte angegeben werden, auf welchen Datensatz bzw. Datensätze das Modell trainiert wurde. Eine kurze Beschreibung des Datensatzes/der Datensätze ist ebenfalls willkommen. + +### Trainingsverfahren + +In diesem Abschnitt solltest du alle relevanten Aspekte des Modelltrainingsverfahren beschreiben, die für die Reproduzierbarkeit nützlich sind. Dazu gehören alle Vor- und Nachbearbeitungen, die an den Daten durchgeführt wurden, sowie Details wie die Anzahl der Trainingsepochene, Batch-Größe, die Lernrate usw. + +### Variablen und Metriken + +Hier solltest du die Bewertungsmetriken beschreiben und die verschiedenen Faktoren, die du dabei mit berücksichtigst. Durch die Angabe, welche Metrik(en) verwendet wurden, für welchen Datensatz und welche Datensatzaufteilung, kannst du die Leistung deines Modells leicht mit der anderer Modelle vergleichen. Diese sollten durch die vorherigen Abschnitte informiert werden, wie z. B. die beabsichtigten Benutzer und Anwendungsfälle. + +### Evaluierungsergebnisse + +Abschließend gibst du an, wie gut das Modell mit dem Bewertungsdatensatz abschneidet. Wenn das Modell einen Entscheidungsschwellenwert verwendet, gib entweder den in der Bewertung verwendeten Entscheidungsschwellenwert an oder mach Angaben zur Bewertung bei verschiedenen Schwellenwerten für die beabsichtigten Verwendungszwecke. + +## Beispiel + +Im Folgenden findest du einige Beispiele von guten Modellkarten: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + + +Mehr Beispiele von verschiedene Organisationen/Firmen sind hier verfügbar [here](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Hinweis + +Modellkarten sind bei der Veröffentlichung von Modellen nicht erforderlich und du musst bei der Erstellung nicht alle oben beschriebenen Abschnitte einbeziehen. Allerdings kann eine explizite Dokumentation des Modells künftigen Nutzern nur nützen, daher empfehlen wir dir, so viele Abschnitte wie möglich nach bestem Wissen und Gewissen auszufüllen. + +## Modellkarte-Metadaten + +Wenn du den Hugging Face Hub ein wenig erkundet hast, solltest du gesehen haben, dass einige Modelle zu bestimmten Kategorien gehören: Du kannst sie nach Aufgaben, Sprachen, Bibliotheken und mehr filtern. Die Kategorien, zu denen ein Modell gehört, werden anhand der Metadaten identifiziert, die du im Kopf der Modellkarte hinzufügst. + +Zum Beispiel sieh dir dieses an [`camembert-base` model card](https://huggingface.co/camembert-base/blob/main/README.md). Du solltest folgende Zeilen auf der Modellkarte sehen: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Diese Metadaten werden vom Hugging Face Hub analysiert, der dieses Modell dann als französisches Modell mit einer MIT-Lizenz identifiziert, das auf dem Oscar-Datensatz trainiert wurde. + +Die vollständige [Modellkarte](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) ermöglicht die Angabe von Sprachen, Lizenzen, Tags, Datensätzen, Metriken sowie den Bewertungsergebnissen, die das Modell wann erhalten hat Ausbildung. diff --git a/chapters/de/chapter4/5.mdx b/chapters/de/chapter4/5.mdx new file mode 100644 index 000000000..65eac0fa9 --- /dev/null +++ b/chapters/de/chapter4/5.mdx @@ -0,0 +1,12 @@ +# Teil 1 abgeschlossen! + + + +Dies ist das Ende des ersten Teils des Kurses! Teil 2 wird am 15. November mit einem großen Community-Event veröffentlicht, weitere Informationen findest du [hier](https://huggingface.co/blog/course-launch-event). + +Du solltest nun in der Lage sein, ein vorab trainiertes Modell für ein Textklassifizierungsproblem (einzelne Sätze oder Satzpaare) zu optimieren und das Ergebnis in den Model Hub hochzuladen. Um sicherzustellen, dass du diesen ersten Abschnitt beherrschst, solltest du genau das an einem Problem verwenden, das dich interessiert (und nicht unbedingt auf Englisch, wenn Sie eine andere Sprache sprechen)! Hilfe findest du in den [Hugging Face-Foren](https://discuss.huggingface.co/) und du kannst dein Projekt in [diesem Thema](https://discuss.huggingface.co/t/share-your-projects) teilen /6803), sobald du damit fertig bist. + +Wir freuen uns darauf, zu sehen, was du alles damit baust! diff --git a/chapters/de/chapter4/6.mdx b/chapters/de/chapter4/6.mdx new file mode 100644 index 000000000..cc87f7cb3 --- /dev/null +++ b/chapters/de/chapter4/6.mdx @@ -0,0 +1,231 @@ + + + + +# Quiz am Ende des Kapitels + + + +Lass uns testen, was du im vorheringen Kapitel gelernt hast! + +### 1. Auf welche Modelle sind die Hub-Modelle beschränkt? + + + +### 2. Wie kannst du die Modelle auf dem Hub verwalten? + +git-lfs benutzen.", + correct: true + } + ]} +/> + +### 3. Was kannst du mit der Hugging Face Hub-Weboberfläche tun? + + + +### 4. Was ist eine Modellkarte? + + + +### 5. Welche dieser Objekte der 🤗 Transformers-Bibliothek können mit `push_to_hub()` direkt auf dem Hub geteilt werden? + +{#if fw === 'pt'} +push_to_hub, und wenn su sie verwendest, werden alle Tokenizer-Dateien (Vokabular, Architektur des Tokenizers usw.) in ein bestimmtes Repo verschoben. Aber das ist nicht die einzig richtige Antwort!", + correct: true + }, + { + text: "Eine Modell-Konfiguration", + explain: "Richtig! Alle Modellkonfigurationen verfügen über die Methode push_to_hub, und wenn Sie sie verwenden, werden sie an ein bestimmtes Repo gepusht. Was kannst du sonst noch teilen?", + correct: true + }, + { + text: "Ein Model", + explain: "Richtig! Alle Modelle verfügen über die Methode push_to_hub, und wenn du sie verwendest, werden sie und ihre Konfigurationsdateien in ein bestimmtes Repo gepusht. Das ist jedoch nicht alles, was du teilen kannst.", + correct: true + }, + { + text: "Ein Trainer", + explain: "Das ist richtig – der Trainer implementiert auch die Methode push_to_hub und lädt mit dieser Methode das Modell, seine Konfiguration, den Tokenizer und einen Modellkartenentwurf auf einen gegebenen Server hoch repo. Versuch es auch mit einer anderen Antwort!", + correct: true + } + ]} +/> +{:else} +push_to_hub method, and using it will push all the tokenizer files (vocabulary, architecture of the tokenizer, etc.) to a given repo. That's not the only right answer, though!", + explain: "Richtig! Alle Tokenizer verfügen über die Methode push_to_hub, und wenn du sie verwendest, werden alle Tokenizer-Dateien (Vokabular, Architektur des Tokenizers usw.) in ein bestimmtes Repo verschoben. Das ist aber nicht die einzige richtige Antwort!", + correct: true + }, + { + text: "Eine Modell-Konfiguration", + explain: "Right! All model configurations have the push_to_hub method, and using it will push them to a given repo. What else can you share?", + explain: "Richtig! Alle Modellkonfigurationen verfügen über die Methode push_to_hub, und wenn du sie verwendest, werden sie an ein bestimmtes Repo gepusht. Was kannst du sonst noch teilen?", + correct: true + }, + { + text: "Ein Modell", + explain: "Richtig! Alle Modelle verfügen über die Methode push_to_hub, und wenn du sie verwendest, werden sie und ihre Konfigurationsdateien in ein bestimmtes Repo gepusht. Das ist jedoch nicht alles, was du teilen kannst.", + correct: true + }, + { + text: "Alles oben mit einem speziellen `Callback`", + explain: "Das ist richtig – der PushToHubCallback sendet während des Trainings regelmäßig alle diese Objekte an ein Repo.", + correct: true + } + ]} +/> +{/if} + +### 6. Was ist der erste Schritt bei Verwendung der Methode `push_to_hub()` oder der CLI-Tools? + + + +### 7. Du verwendest ein Modell und einen Tokenizer – wie kannst du diese auf den Hub hochladen? + +huggingface_hub-Dienstprogramm einschließt.", + explain: "Modelle und Tokenizer profitieren bereits von den Dienstprogrammen huggingface_hub: kein zusätzlicher Wrapping erforderlich!" + }, + { + text: "Indem du sie auf der Festplatte speicherst und transformers-cli upload-model aufrufst", + explain: "Der Befehl upload-model existiert nicht." + } + ]} +/> + +### 8. Welche Git-Operationen kann man mit der Klasse „Repository“ ausführen? + +git_commit() Methode da.", + correct: true + }, + { + text: "Ein Pull", + explain: "Das ist der Zweck der git_pull() Methode.", + correct: true + }, + { + text: "Ein Push", + explain: "Die Methode git_push() macht das.", + correct: true + }, + { + text: "Ein Merge", + explain: "Nein, die Operation wird mit dieser API nie möglich sein." + } + ]} +/> diff --git a/chapters/en/chapter0/1.mdx b/chapters/en/chapter0/1.mdx index 0f8bac262..40e21bf91 100644 --- a/chapters/en/chapter0/1.mdx +++ b/chapters/en/chapter0/1.mdx @@ -86,7 +86,7 @@ You can jump in and out of your virtual environment with the `activate` and `dea source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` You can make sure that the environment is activated by running the `which python` command: if it points to the virtual environment, then you have successfully activated it! diff --git a/chapters/en/chapter1/10.mdx b/chapters/en/chapter1/10.mdx index cb0ca145c..1e14a5c95 100644 --- a/chapters/en/chapter1/10.mdx +++ b/chapters/en/chapter1/10.mdx @@ -241,7 +241,7 @@ result = classifier("This is a course about the Transformers library") choices={[ { text: "The model is a fine-tuned version of a pretrained model and it picked up its bias from it.", - explain: "When applying Transfer Learning, the bias in the pretrained model used perspires in the fine-tuned model.", + explain: "When applying Transfer Learning, the bias in the pretrained model used persists in the fine-tuned model.", correct: true }, { diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index 4ff8b0d00..a31638e9e 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -68,7 +68,7 @@ There are three main steps involved when you pass some text to a pipeline: 3. The predictions of the model are post-processed, so you can make sense of them. -Some of the currently [available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html) are: +Some of the currently [available pipelines](https://huggingface.co/transformers/main_classes/pipelines) are: - `feature-extraction` (get the vector representation of a text) - `fill-mask` diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 80f692852..a44b4a1b1 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -97,7 +97,7 @@ By the way, you can evaluate the carbon footprint of your models' training throu This pretraining is usually done on very large amounts of data. Therefore, it requires a very large corpus of data, and training can take up to several weeks. -*Fine-tuning*, on the other hand, is the training done **after** a model has been pretrained. To perform fine-tuning, you first acquire a pretrained language model, then perform additional training with a dataset specific to your task. Wait -- why not simply train directly for the final task? There are a couple of reasons: +*Fine-tuning*, on the other hand, is the training done **after** a model has been pretrained. To perform fine-tuning, you first acquire a pretrained language model, then perform additional training with a dataset specific to your task. Wait -- why not simply train the model for your final use case from the start (**scratch**)? There are a couple of reasons: * The pretrained model was already trained on a dataset that has some similarities with the fine-tuning dataset. The fine-tuning process is thus able to take advantage of knowledge acquired by the initial model during pretraining (for instance, with NLP problems, the pretrained model will have some kind of statistical understanding of the language you are using for your task). * Since the pretrained model was already trained on lots of data, the fine-tuning requires way less data to get decent results. @@ -144,7 +144,7 @@ We will dive into those architectures independently in later sections. A key feature of Transformer models is that they are built with special layers called *attention layers*. In fact, the title of the paper introducing the Transformer architecture was ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)! We will explore the details of attention layers later in the course; for now, all you need to know is that this layer will tell the model to pay specific attention to certain words in the sentence you passed it (and more or less ignore the others) when dealing with the representation of each word. -To put this into context, consider the task of translating text from English to French. Given the input "You like this course", a translation model will need to also attend to the adjacent word "You" to get the proper translation for the word "like", because in French the verb "like" is conjugated differently depending on the subject. The rest of the sentence, however, is not useful for the translation of that word. In the same vein, when translating "this" the model will also need to pay attention to the word "course", because "this" translates differently depending on whether the associated noun is masculine or feminine. Again, the other words in the sentence will not matter for the translation of "this". With more complex sentences (and more complex grammar rules), the model would need to pay special attention to words that might appear farther away in the sentence to properly translate each word. +To put this into context, consider the task of translating text from English to French. Given the input "You like this course", a translation model will need to also attend to the adjacent word "You" to get the proper translation for the word "like", because in French the verb "like" is conjugated differently depending on the subject. The rest of the sentence, however, is not useful for the translation of that word. In the same vein, when translating "this" the model will also need to pay attention to the word "course", because "this" translates differently depending on whether the associated noun is masculine or feminine. Again, the other words in the sentence will not matter for the translation of "course". With more complex sentences (and more complex grammar rules), the model would need to pay special attention to words that might appear farther away in the sentence to properly translate each word. The same concept applies to any task associated with natural language: a word by itself has a meaning, but that meaning is deeply affected by the context, which can be any other word (or words) before or after the word being studied. diff --git a/chapters/en/chapter1/5.mdx b/chapters/en/chapter1/5.mdx index 477dd128f..89694ee83 100644 --- a/chapters/en/chapter1/5.mdx +++ b/chapters/en/chapter1/5.mdx @@ -15,8 +15,8 @@ Encoder models are best suited for tasks requiring an understanding of the full Representatives of this family of models include: -- [ALBERT](https://huggingface.co/transformers/model_doc/albert.html) -- [BERT](https://huggingface.co/transformers/model_doc/bert.html) -- [DistilBERT](https://huggingface.co/transformers/model_doc/distilbert.html) -- [ELECTRA](https://huggingface.co/transformers/model_doc/electra.html) -- [RoBERTa](https://huggingface.co/transformers/model_doc/roberta.html) +- [ALBERT](https://huggingface.co/docs/transformers/model_doc/albert) +- [BERT](https://huggingface.co/docs/transformers/model_doc/bert) +- [DistilBERT](https://huggingface.co/docs/transformers/model_doc/distilbert) +- [ELECTRA](https://huggingface.co/docs/transformers/model_doc/electra) +- [RoBERTa](https://huggingface.co/docs/transformers/model_doc/roberta) diff --git a/chapters/en/chapter1/6.mdx b/chapters/en/chapter1/6.mdx index 396d79bd3..b0f4ba09c 100644 --- a/chapters/en/chapter1/6.mdx +++ b/chapters/en/chapter1/6.mdx @@ -15,7 +15,7 @@ These models are best suited for tasks involving text generation. Representatives of this family of models include: -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl) - [GPT](https://huggingface.co/docs/transformers/model_doc/openai-gpt) -- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl) diff --git a/chapters/en/chapter1/7.mdx b/chapters/en/chapter1/7.mdx index 23599e26f..e39c5ca8e 100644 --- a/chapters/en/chapter1/7.mdx +++ b/chapters/en/chapter1/7.mdx @@ -15,7 +15,7 @@ Sequence-to-sequence models are best suited for tasks revolving around generatin Representatives of this family of models include: -- [BART](https://huggingface.co/transformers/model_doc/bart.html) -- [mBART](https://huggingface.co/transformers/model_doc/mbart.html) -- [Marian](https://huggingface.co/transformers/model_doc/marian.html) -- [T5](https://huggingface.co/transformers/model_doc/t5.html) +- [BART](https://huggingface.co/transformers/model_doc/bart) +- [mBART](https://huggingface.co/transformers/model_doc/mbart) +- [Marian](https://huggingface.co/transformers/model_doc/marian) +- [T5](https://huggingface.co/transformers/model_doc/t5) diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 81d496fef..33060505b 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -85,7 +85,7 @@ InvalidArgumentError: Input to reshape is a tensor with 14 values, but the reque ``` {/if} -Oh no! Why did this fail? "We followed the steps from the pipeline in section 2. +Oh no! Why did this fail? We followed the steps from the pipeline in section 2. The problem is that we sent a single sequence to the model, whereas 🤗 Transformers models expect multiple sentences by default. Here we tried to do everything the tokenizer did behind the scenes when we applied it to a `sequence`. But if you look closely, you'll see that the tokenizer didn't just convert the list of input IDs into a tensor, it added a dimension on top of it: @@ -329,7 +329,7 @@ With Transformer models, there is a limit to the lengths of the sequences we can - Use a model with a longer supported sequence length. - Truncate your sequences. -Models have different supported sequence lengths, and some specialize in handling very long sequences. [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) is one example, and another is [LED](https://huggingface.co/transformers/model_doc/led.html). If you're working on a task that requires very long sequences, we recommend you take a look at those models. +Models have different supported sequence lengths, and some specialize in handling very long sequences. [Longformer](https://huggingface.co/docs/transformers/model_doc/longformer) is one example, and another is [LED](https://huggingface.co/docs/transformers/model_doc/led). If you're working on a task that requires very long sequences, we recommend you take a look at those models. Otherwise, we recommend you truncate your sequences by specifying the `max_sequence_length` parameter: diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index 22852fb0c..5ac395c8e 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -84,10 +84,14 @@ In this section we will use as an example the MRPC (Microsoft Research Paraphras {/if} -The Hub doesn't just contain models; it also has multiple datasets in lots of different languages. You can browse the datasets [here](https://huggingface.co/datasets), and we recommend you try to load and process a new dataset once you have gone through this section (see the general documentation [here](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). But for now, let's focus on the MRPC dataset! This is one of the 10 datasets composing the [GLUE benchmark](https://gluebenchmark.com/), which is an academic benchmark that is used to measure the performance of ML models across 10 different text classification tasks. +The Hub doesn't just contain models; it also has multiple datasets in lots of different languages. You can browse the datasets [here](https://huggingface.co/datasets), and we recommend you try to load and process a new dataset once you have gone through this section (see the general documentation [here](https://huggingface.co/docs/datasets/loading)). But for now, let's focus on the MRPC dataset! This is one of the 10 datasets composing the [GLUE benchmark](https://gluebenchmark.com/), which is an academic benchmark that is used to measure the performance of ML models across 10 different text classification tasks. The 🤗 Datasets library provides a very simple command to download and cache a dataset on the Hub. We can download the MRPC dataset like this: + +⚠️ **Warning** Make sure that `datasets` is installed by running `pip install datasets`. Then, load the MRPC dataset and print it to see what it contains. + + ```py from datasets import load_dataset @@ -235,7 +239,7 @@ tokenized_dataset = tokenizer( This works well, but it has the disadvantage of returning a dictionary (with our keys, `input_ids`, `attention_mask`, and `token_type_ids`, and values that are lists of lists). It will also only work if you have enough RAM to store your whole dataset during the tokenization (whereas the datasets from the 🤗 Datasets library are [Apache Arrow](https://arrow.apache.org/) files stored on the disk, so you only keep the samples you ask for loaded in memory). -To keep the data as a dataset, we will use the [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) method. This also allows us some extra flexibility, if we need more preprocessing done than just tokenization. The `map()` method works by applying a function on each element of the dataset, so let's define a function that tokenizes our inputs: +To keep the data as a dataset, we will use the [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) method. This also allows us some extra flexibility, if we need more preprocessing done than just tokenization. The `map()` method works by applying a function on each element of the dataset, so let's define a function that tokenizes our inputs: ```py def tokenize_function(example): diff --git a/chapters/en/chapter3/6.mdx b/chapters/en/chapter3/6.mdx index 8749edef8..89d131b58 100644 --- a/chapters/en/chapter3/6.mdx +++ b/chapters/en/chapter3/6.mdx @@ -211,7 +211,7 @@ Test what you learned in this chapter! explain: "This is what we did with Trainer, not the 🤗 Accelerate library. Try again!" }, { - text: "It makes our training loops work on distributed strategies", + text: "It makes our training loops work on distributed strategies.", explain: "Correct! With 🤗 Accelerate, your training loops will work for multiple GPUs and TPUs.", correct: true }, diff --git a/chapters/en/chapter4/2.mdx b/chapters/en/chapter4/2.mdx index 8b110ec7b..0bd50669b 100644 --- a/chapters/en/chapter4/2.mdx +++ b/chapters/en/chapter4/2.mdx @@ -65,7 +65,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = CamembertForMaskedLM.from_pretrained("camembert-base") ``` -However, we recommend using the [`Auto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) instead, as these are by design architecture-agnostic. While the previous code sample limits users to checkpoints loadable in the CamemBERT architecture, using the `Auto*` classes makes switching checkpoints simple: +However, we recommend using the [`Auto*` classes](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) instead, as these are by design architecture-agnostic. While the previous code sample limits users to checkpoints loadable in the CamemBERT architecture, using the `Auto*` classes makes switching checkpoints simple: ```py from transformers import AutoTokenizer, AutoModelForMaskedLM @@ -81,7 +81,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = TFCamembertForMaskedLM.from_pretrained("camembert-base") ``` -However, we recommend using the [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) instead, as these are by design architecture-agnostic. While the previous code sample limits users to checkpoints loadable in the CamemBERT architecture, using the `TFAuto*` classes makes switching checkpoints simple: +However, we recommend using the [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) instead, as these are by design architecture-agnostic. While the previous code sample limits users to checkpoints loadable in the CamemBERT architecture, using the `TFAuto*` classes makes switching checkpoints simple: ```py from transformers import AutoTokenizer, TFAutoModelForMaskedLM diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index 6b27777f4..9de3fb1d8 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -178,7 +178,7 @@ Click on the "Files and versions" tab, and you should see the files visible in t -As you've seen, the `push_to_hub()` method accepts several arguments, making it possible to upload to a specific repository or organization namespace, or to use a different API token. We recommend you take a look at the method specification available directly in the [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html) to get an idea of what is possible. +As you've seen, the `push_to_hub()` method accepts several arguments, making it possible to upload to a specific repository or organization namespace, or to use a different API token. We recommend you take a look at the method specification available directly in the [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing) to get an idea of what is possible. The `push_to_hub()` method is backed by the [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) Python package, which offers a direct API to the Hugging Face Hub. It's integrated within 🤗 Transformers and several other machine learning libraries, like [`allenlp`](https://github.com/allenai/allennlp). Although we focus on the 🤗 Transformers integration in this chapter, integrating it into your own code or library is simple. diff --git a/chapters/en/chapter5/2.mdx b/chapters/en/chapter5/2.mdx index d3aea7b87..acf417bba 100644 --- a/chapters/en/chapter5/2.mdx +++ b/chapters/en/chapter5/2.mdx @@ -128,7 +128,7 @@ This is exactly what we wanted. Now, we can apply various preprocessing techniqu -The `data_files` argument of the `load_dataset()` function is quite flexible and can be either a single file path, a list of file paths, or a dictionary that maps split names to file paths. You can also glob files that match a specified pattern according to the rules used by the Unix shell (e.g., you can glob all the JSON files in a directory as a single split by setting `data_files="*.json"`). See the 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) for more details. +The `data_files` argument of the `load_dataset()` function is quite flexible and can be either a single file path, a list of file paths, or a dictionary that maps split names to file paths. You can also glob files that match a specified pattern according to the rules used by the Unix shell (e.g., you can glob all the JSON files in a directory as a single split by setting `data_files="*.json"`). See the 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) for more details. @@ -160,7 +160,7 @@ This returns the same `DatasetDict` object obtained above, but saves us the step -✏️ **Try it out!** Pick another dataset hosted on GitHub or the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) and try loading it both locally and remotely using the techniques introduced above. For bonus points, try loading a dataset that’s stored in a CSV or text format (see the [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) for more information on these formats). +✏️ **Try it out!** Pick another dataset hosted on GitHub or the [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) and try loading it both locally and remotely using the techniques introduced above. For bonus points, try loading a dataset that’s stored in a CSV or text format (see the [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) for more information on these formats). diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index 4a3ddc7b5..9e6e738bc 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -238,7 +238,7 @@ As you can see, this has removed around 15% of the reviews from our original tra -✏️ **Try it out!** Use the `Dataset.sort()` function to inspect the reviews with the largest numbers of words. See the [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) to see which argument you need to use sort the reviews by length in descending order. +✏️ **Try it out!** Use the `Dataset.sort()` function to inspect the reviews with the largest numbers of words. See the [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) to see which argument you need to use sort the reviews by length in descending order. @@ -385,7 +385,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -Oh no! That didn't work! Why not? Looking at the error message will give us a clue: there is a mismatch in the lengths of one of the columns, one being of length 1,463 and the other of length 1,000. If you've looked at the `Dataset.map()` [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), you may recall that it's the number of samples passed to the function that we are mapping; here those 1,000 examples gave 1,463 new features, resulting in a shape error. +Oh no! That didn't work! Why not? Looking at the error message will give us a clue: there is a mismatch in the lengths of one of the columns, one being of length 1,463 and the other of length 1,000. If you've looked at the `Dataset.map()` [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map), you may recall that it's the number of samples passed to the function that we are mapping; here those 1,000 examples gave 1,463 new features, resulting in a shape error. The problem is that we're trying to mix two different datasets of different sizes: the `drug_dataset` columns will have a certain number of examples (the 1,000 in our error), but the `tokenized_dataset` we are building will have more (the 1,463 in the error message; it is more than 1,000 because we are tokenizing long reviews into more than one example by using `return_overflowing_tokens=True`). That doesn't work for a `Dataset`, so we need to either remove the columns from the old dataset or make them the same size as they are in the new dataset. We can do the former with the `remove_columns` argument: diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index 332e171c3..8f424d99f 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -46,7 +46,7 @@ We can see that there are 15,518,009 rows and 2 columns in our dataset -- that's -✎ By default, 🤗 Datasets will decompress the files needed to load a dataset. If you want to preserve hard drive space, you can pass `DownloadConfig(delete_extracted=True)` to the `download_config` argument of `load_dataset()`. See the [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) for more details. +✎ By default, 🤗 Datasets will decompress the files needed to load a dataset. If you want to preserve hard drive space, you can pass `DownloadConfig(delete_extracted=True)` to the `download_config` argument of `load_dataset()`. See the [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) for more details. diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index 10e43fe5e..5688ea04a 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -365,7 +365,7 @@ Cool, we've pushed our dataset to the Hub and it's available for others to use! -💡 You can also upload a dataset to the Hugging Face Hub directly from the terminal by using `huggingface-cli` and a bit of Git magic. See the [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) for details on how to do this. +💡 You can also upload a dataset to the Hugging Face Hub directly from the terminal by using `huggingface-cli` and a bit of Git magic. See the [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) for details on how to do this. diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index 3da60cc96..418abbbb6 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -178,7 +178,7 @@ Okay, this has given us a few thousand comments to work with! -✏️ **Try it out!** See if you can use `Dataset.map()` to explode the `comments` column of `issues_dataset` _without_ resorting to the use of Pandas. This is a little tricky; you might find the ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) section of the 🤗 Datasets documentation useful for this task. +✏️ **Try it out!** See if you can use `Dataset.map()` to explode the `comments` column of `issues_dataset` _without_ resorting to the use of Pandas. This is a little tricky; you might find the ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) section of the 🤗 Datasets documentation useful for this task. diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index 291518720..e877653ef 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -97,7 +97,7 @@ Let's take the example we used during training, with the three merge rules learn ("h", "ug") -> "hug" ``` -The word `"bug"` will be tokenized as `["b", "ug"]`. `"mug"`, however, will be tokenized as `["[UNK]", "ug"]` since the letter `"m"` was not in the base vocabulary. Likewise, the word `"thug"` will be tokenized as `["[UNK]", "hug"]`: the letter `"t"` is not in the base vocabulary, and applying the merge rules results first in `"u"` and `"g"` being merged and then `"hu"` and `"g"` being merged. +The word `"bug"` will be tokenized as `["b", "ug"]`. `"mug"`, however, will be tokenized as `["[UNK]", "ug"]` since the letter `"m"` was not in the base vocabulary. Likewise, the word `"thug"` will be tokenized as `["[UNK]", "hug"]`: the letter `"t"` is not in the base vocabulary, and applying the merge rules results first in `"u"` and `"g"` being merged and then `"h"` and `"ug"` being merged. diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index 505cf7588..b0067ecc5 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -58,7 +58,7 @@ So, the sum of all frequencies is 210, and the probability of the subword `"ug"` -✏️ **Now your turn!** Write the code to compute the the frequencies above and double-check that the results shown are correct, as well as the total sum. +✏️ **Now your turn!** Write the code to compute the frequencies above and double-check that the results shown are correct, as well as the total sum. diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 7caee98ed..38086edcf 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -27,14 +27,14 @@ The 🤗 Tokenizers library has been built to provide several options for each o More precisely, the library is built around a central `Tokenizer` class with the building blocks regrouped in submodules: -- `normalizers` contains all the possible types of `Normalizer` you can use (complete list [here](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)). -- `pre_tokenizers` contains all the possible types of `PreTokenizer` you can use (complete list [here](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)). -- `models` contains the various types of `Model` you can use, like `BPE`, `WordPiece`, and `Unigram` (complete list [here](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)). -- `trainers` contains all the different types of `Trainer` you can use to train your model on a corpus (one per type of model; complete list [here](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)). -- `post_processors` contains the various types of `PostProcessor` you can use (complete list [here](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)). -- `decoders` contains the various types of `Decoder` you can use to decode the outputs of tokenization (complete list [here](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)). - -You can find the whole list of building blocks [here](https://huggingface.co/docs/tokenizers/python/latest/components.html). +- `normalizers` contains all the possible types of `Normalizer` you can use (complete list [here](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` contains all the possible types of `PreTokenizer` you can use (complete list [here](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` contains the various types of `Model` you can use, like `BPE`, `WordPiece`, and `Unigram` (complete list [here](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` contains all the different types of `Trainer` you can use to train your model on a corpus (one per type of model; complete list [here](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` contains the various types of `PostProcessor` you can use (complete list [here](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` contains the various types of `Decoder` you can use to decode the outputs of tokenization (complete list [here](https://huggingface.co/docs/tokenizers/components#decoders)). + +You can find the whole list of building blocks [here](https://huggingface.co/docs/tokenizers/components). ## Acquiring a corpus[[acquiring-a-corpus]] diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 9a9d1b8ff..de3da9a1f 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -35,7 +35,7 @@ This process of fine-tuning a pretrained language model on in-domain data is usu By the end of this section you'll have a [masked language model](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) on the Hub that can autocomplete sentences as shown below: - + Let's dive in! @@ -347,7 +347,7 @@ print(f"'>>> Concatenated reviews length: {total_length}'") '>>> Concatenated reviews length: 951' ``` -Great, the total length checks out -- so now let's split the concatenated reviews into chunks of the size given by `block_size`. To do so, we iterate over the features in `concatenated_examples` and use a list comprehension to create slices of each feature. The result is a dictionary of chunks for each feature: +Great, the total length checks out -- so now let's split the concatenated reviews into chunks of the size given by `chunk_size`. To do so, we iterate over the features in `concatenated_examples` and use a list comprehension to create slices of each feature. The result is a dictionary of chunks for each feature: ```python chunks = { @@ -1035,7 +1035,7 @@ Neat -- our model has clearly adapted its weights to predict words that are more -This wraps up our first experiment with training a language model. In [section 6](/course/en/chapter7/section6) you'll learn how to train an auto-regressive model like GPT-2 from scratch; head over there if you'd like to see how you can pretrain your very own Transformer model! +This wraps up our first experiment with training a language model. In [section 6](/course/en/chapter7/6) you'll learn how to train an auto-regressive model like GPT-2 from scratch; head over there if you'd like to see how you can pretrain your very own Transformer model! diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index bc957c37b..cdae18d5b 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -114,7 +114,7 @@ split_datasets["train"][1]["translation"] 'fr': 'Par défaut, développer les fils de discussion'} ``` -We get a dictionary with two sentences in the pair of languages we requested. One particularity of this dataset full of technical computer science terms is that they are all fully translated in French. However, French engineers are often lazy and leave most computer science-specific words in English when they talk. Here, for instance, the word "threads" might well appear in a French sentence, especially in a technical conversation; but in this dataset it has been translated into the more correct "fils de discussion." The pretrained model we use, which has been pretrained on a larger corpus of French and English sentences, takes the easier option of leaving the word as is: +We get a dictionary with two sentences in the pair of languages we requested. One particularity of this dataset full of technical computer science terms is that they are all fully translated in French. However, French engineers leave most computer science-specific words in English when they talk. Here, for instance, the word "threads" might well appear in a French sentence, especially in a technical conversation; but in this dataset it has been translated into the more correct "fils de discussion." The pretrained model we use, which has been pretrained on a larger corpus of French and English sentences, takes the easier option of leaving the word as is: ```py from transformers import pipeline diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 7a498a863..44551f15d 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -383,13 +383,13 @@ Now we can use the `prepare_tf_dataset()` method to convert our datasets to Tens ```python tf_train_dataset = model.prepare_tf_dataset( - tokenized_dataset["train"], + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) tf_eval_dataset = model.prepare_tf_dataset( - tokenized_dataset["valid"], + tokenized_datasets["valid"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -726,9 +726,9 @@ Let's start with the dataloaders. We only need to set the dataset's format to `" ```py from torch.utils.data.dataloader import DataLoader -tokenized_dataset.set_format("torch") -train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True) -eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32) +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader(tokenized_datasets["train"], batch_size=32, shuffle=True) +eval_dataloader = DataLoader(tokenized_datasets["valid"], batch_size=32) ``` Next, we group the parameters so that the optimizer knows which ones will get an additional weight decay. Usually, all bias and LayerNorm weights terms are exempt from this; here's how we can do this: @@ -870,7 +870,6 @@ for epoch in range(num_train_epochs): if step % 100 == 0: accelerator.print( { - "lr": get_lr(), "samples": step * samples_per_step, "steps": completed_steps, "loss/train": loss.item() * gradient_accumulation_steps, @@ -912,4 +911,4 @@ And that's it -- you now have your own custom training loop for causal language -{/if} \ No newline at end of file +{/if} diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index b32056e33..4e1797c7a 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -23,19 +23,13 @@ import gradio as gr title = "GPT-J-6B" description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below." article = "

GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model

" -examples = [ - ["The tower is 324 metres (1,063 ft) tall,"], - ["The Moon's orbit around Earth has"], - ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], -] + gr.Interface.load( "huggingface/EleutherAI/gpt-j-6B", inputs=gr.Textbox(lines=5, label="Input Text"), title=title, description=description, article=article, - examples=examples, - enable_queue=True, ).launch() ``` diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index 3c74d8452..fce4f80fb 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -231,6 +231,6 @@ with gr.Blocks() as block: block.launch() ``` - + We just explored all the core concepts of `Blocks`! Just like with `Interfaces`, you can create cool demos that can be shared by using `share=True` in the `launch()` method or deployed on [Hugging Face Spaces](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/en/events/3.mdx b/chapters/en/events/3.mdx index b0770d38d..2f7e69063 100644 --- a/chapters/en/events/3.mdx +++ b/chapters/en/events/3.mdx @@ -6,4 +6,4 @@ You can find all the demos that the community created under the [`Gradio-Blocks` **Natural language to SQL** - + diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 80ab8accd..cf10f9b60 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -39,6 +39,7 @@ title: ¡Haz completado el uso básico! - local: chapter2/8 title: Quiz de final de capítulo + quiz: 2 - title: 3. Ajuste (fine-tuning) de un modelo preentrenado sections: @@ -46,8 +47,17 @@ title: Introducción - local: chapter3/2 title: Procesamiento de los datos + - local: chapter3/3 + title: Ajuste de un modelo con la API Trainer + - local: chapter3/3_tf + title: Ajuste de un modelo con Keras - local: chapter3/4 title: Entrenamiento completo + - local: chapter3/5 + title: Ajuste de modelos, ¡hecho! + - local: chapter3/6 + title: Quiz de final de capítulo + quiz: 3 - title: 5. La librería 🤗 Datasets sections: @@ -66,9 +76,36 @@ - local: chapter5/7 title: 🤗 Datasets, ¡listo! - local: chapter5/8 - title: Quiz + title: Quiz de final de capítulo quiz: 5 + +- title: 6. La librería 🤗 Tokenizers + sections: + - local: chapter6/1 + title: Introducción + - local: chapter6/2 + title: Entrenar un nuevo tokenizador a partir de uno existente + - local: chapter6/3 + title: Los poderes especiales de los Tokenizadores Rápidos (Fast tokenizers) + - local: chapter6/3b + title: Tokenizadores Rápidos en un Pipeline de Question-Answering + - local: chapter6/4 + title: Normalización y pre-tokenización + - local: chapter6/5 + title: Tokenización por Codificación Byte-Pair + - local: chapter6/6 + title: Tokenización WordPiece + - local: chapter6/7 + title: Tokenización Unigram + - local: chapter6/8 + title: Construir un tokenizador, bloque por bloque + - local: chapter6/9 + title: Tokenizadores, listo! + - local: chapter6/10 + title: Quiz de final de capítulo + quiz: 1 + - title: 8. ¿Cómo solicitar ayuda? sections: - local: chapter8/1 diff --git a/chapters/es/chapter0/1.mdx b/chapters/es/chapter0/1.mdx index 41a65887b..cf5aab097 100644 --- a/chapters/es/chapter0/1.mdx +++ b/chapters/es/chapter0/1.mdx @@ -2,7 +2,7 @@ Bienvenido al curso de Hugging Face. Esta introducción te guiará en la configuración de un entorno de trabajo. Si acabas de empezar el curso, te recomendamos que primero eches un vistazo al [Capítulo 1](/course/chapter1), y luego vuelvas y configures tu entorno para poder probar el código por ti mismo. -Todas las bibliotecas que usaremos en este curso están disponibles como paquetes de Python, así que aquí te mostraremos cómo configurar un entorno de Python e instalar las bibliotecas específicas que necesitarás. +Todas las librerías que usaremos en este curso están disponibles como paquetes de Python, así que aquí te mostraremos cómo configurar un entorno de Python e instalar las librerías específicas que necesitarás. Cubriremos dos formas de configurar tu entorno de trabajo, utilizando un cuaderno Colab o un entorno virtual Python. Siéntete libre de elegir la que más te convenga. Para los principiantes, recomendamos encarecidamente que comiencen utilizando un cuaderno Colab. @@ -38,7 +38,7 @@ import transformers A gif showing the result of the two commands above: installation and import
-Esto instala una versión muy ligera de 🤗 Transformers. En particular, no se instalan frameworks específicos de deep learning (como PyTorch o TensorFlow). Dado que vamos a utilizar un montón de características diferentes de la biblioteca, se recomienda instalar la versión de desarrollo, que viene con todas las dependencias necesarias para casi cualquier caso de uso imaginable: +Esto instala una versión muy ligera de 🤗 Transformers. En particular, no se instalan frameworks específicos de deep learning (como PyTorch o TensorFlow). Dado que vamos a utilizar un montón de características diferentes de la librería, se recomienda instalar la versión de desarrollo, que viene con todas las dependencias necesarias para casi cualquier caso de uso imaginable: ``` !pip install transformers[sentencepiece] @@ -82,11 +82,11 @@ ls -a Puedes entrar y salir de tu entorno virtual con los scripts `activate` y `deactivate`: ``` -# Activate the virtual environment +# Activa el entorno virtual source .env/bin/activate -# Deactivate the virtual environment -source .env/bin/deactivate +# Desactiva el entorno virtual +deactivate ``` Puedes asegurarte de que el entorno está activado ejecutando el comando `which python`: si apunta al entorno virtual, entonces lo has activado con éxito. diff --git a/chapters/es/chapter1/10.mdx b/chapters/es/chapter1/10.mdx index 6749eeee5..6bad89a0d 100644 --- a/chapters/es/chapter1/10.mdx +++ b/chapters/es/chapter1/10.mdx @@ -80,7 +80,7 @@ result = filler("...") }, { text: "This man has been waiting for you.", - explain: "Incorrecto. Esrte pipeline llena palabras ocultas, por lo que necesita un mask token en algún lugar." + explain: "Incorrecto. Este pipeline llena palabras ocultas, por lo que necesita un mask token en algún lugar." } ]} /> @@ -154,8 +154,6 @@ result = classifier("This is a course about the Transformers library") ### 7. Selecciona la oración que describe mejor los términos "modelo", "arquitectura" y "pesos". -Select the sentence that best describes the terms "model," "architecture," and "weights." - ### 11. ¿Cuál puede ser una posible fuente del sesgo observado en un modelo? -What possible source can the bias observed in a model have? - diff --git a/chapters/es/chapter1/4.mdx b/chapters/es/chapter1/4.mdx index 7d6b958b0..05bce22bc 100644 --- a/chapters/es/chapter1/4.mdx +++ b/chapters/es/chapter1/4.mdx @@ -16,7 +16,7 @@ Estos son algunos hitos en la (corta) historia de los Transformadores: -La [arquitectura de los Transformadores](https://arxiv.org/abs/1706.03762) fue presentada por primera vez en junio de 2017. El trabajo original se enfocaba en tareas de traducción. A esto le siguó la introducción de numerosos modelos influyentes, que incluyen: +La [arquitectura de los Transformadores](https://arxiv.org/abs/1706.03762) fue presentada por primera vez en junio de 2017. El trabajo original se enfocaba en tareas de traducción. A esto le siguió la introducción de numerosos modelos influyentes, que incluyen: - **Junio de 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), el primer modelo de Transformadores preentrenados, que fue usado para ajustar varias tareas de PLN y obtuvo resultados de vanguardia @@ -50,7 +50,7 @@ Un ejemplo de una tarea es predecir la palabra siguiente en una oración con bas -Otro ejemplo es el *modelado de leguaje oculto*, en el que el modelo predice una palabra oculta en la oración. +Otro ejemplo es el *modelado de lenguaje oculto*, en el que el modelo predice una palabra oculta en la oración.
Example of masked language modeling in which a masked word from a sentence is predicted. @@ -84,7 +84,7 @@ Esta es la razón por la que compartir modelos de lenguaje es fundamental: compa -El *preentrenamiento* es el acto de entrenar un modelo desde cero: los pesos se inicializan de manera aleatória y el entrenamiento empieza sin un conocimiento previo. +El *preentrenamiento* es el acto de entrenar un modelo desde cero: los pesos se inicializan de manera aleatoria y el entrenamiento empieza sin un conocimiento previo.
The pretraining of a language model is costly in both time and money. @@ -121,7 +121,7 @@ En esta sección, revisaremos la arquitectura general del Transformador. No te p El modelo está compuesto por dos bloques: * **Codificador (izquierda)**: El codificador recibe una entrada y construye una representación de ésta (sus características). Esto significa que el modelo está optimizado para conseguir un entendimiento a partir de la entrada. -* **Decodificador (derecha)**: El decodificador usa la representacón del codificador (características) junto con otras entradas para generar una secuencia objetivo. Esto significa que el modelo está optimizado para generar salidas. +* **Decodificador (derecha)**: El decodificador usa la representación del codificador (características) junto con otras entradas para generar una secuencia objetivo. Esto significa que el modelo está optimizado para generar salidas.
Architecture of a Transformers models @@ -148,9 +148,9 @@ Ahora que tienes una idea de qué son las capas de atención, echemos un vistazo ## La arquitectura original -La arquitectura del Transformador fue diseñada originalmente para traducción. Durante el entrenamiento, el codificador recibe entradas (oraciones) en un idioma dado, mientras que el decodificador recibe las mismas oraciones en el idioma objetivo. En el codificador, las capas de atención pueden usar todas las palabras en una oración (dado que, como vimos, la traducción de una palabra dada puede ser dependiente de lo que está antes y después en la oración). Por su parte, el decodificador trabaja de manera secuencial y sólo le puede prestar atención a las palabras en la oración que ya ha traducido (es decir, sólo las palabras antes de que la palabra se ha generado). Por ejemplo, cuando hemos predecido las primeras tres palabras del objetivo de traducción se las damos al decodificador, que luego usa todas las entradas del codificador para intentar predecir la cuarta palabra. +La arquitectura del Transformador fue diseñada originalmente para traducción. Durante el entrenamiento, el codificador recibe entradas (oraciones) en un idioma dado, mientras que el decodificador recibe las mismas oraciones en el idioma objetivo. En el codificador, las capas de atención pueden usar todas las palabras en una oración (dado que, como vimos, la traducción de una palabra dada puede ser dependiente de lo que está antes y después en la oración). Por su parte, el decodificador trabaja de manera secuencial y sólo le puede prestar atención a las palabras en la oración que ya ha traducido (es decir, sólo las palabras antes de que la palabra se ha generado). Por ejemplo, cuando hemos predicho las primeras tres palabras del objetivo de traducción se las damos al decodificador, que luego usa todas las entradas del codificador para intentar predecir la cuarta palabra. -Para acelerar el entrenamiento (cuando el modelo tiene acceso a las oraciones objetivo), al decodificador se le alimenta el objetivo completo, pero no puede usar palabras futuras (si tuviera acceso a la palabra en la posición 2 cuando trata de predecir la palabra en la posición 2, ¡el problema no sería muy dificil!). Por ejemplo, al intentar predecir la cuarta palabra, la capa de atención sólo tendría acceso a las palabras en las posiciones 1 a 3. +Para acelerar el entrenamiento (cuando el modelo tiene acceso a las oraciones objetivo), al decodificador se le alimenta el objetivo completo, pero no puede usar palabras futuras (si tuviera acceso a la palabra en la posición 2 cuando trata de predecir la palabra en la posición 2, ¡el problema no sería muy difícil!). Por ejemplo, al intentar predecir la cuarta palabra, la capa de atención sólo tendría acceso a las palabras en las posiciones 1 a 3. La arquitectura original del Transformador se veía así, con el codificador a la izquierda y el decodificador a la derecha: @@ -159,13 +159,13 @@ La arquitectura original del Transformador se veía así, con el codificador a l
-Observa que la primera capa de atención en un bloque de decodificador presta atención a todas las entradas (pasadas) al decodificador, mientras que la segunda capa de atención usa la salida del codificador. De esta manera puede acceder a toda la oración de entrada para predecir de mejor manera la palabra actual. Esto es muy útil dado que diferentes idiomas pueden tener reglas gramaticales que ponen las palabras en órden distinto o algún contexto que se provee después puede ser útil para determinar la mejor traducción de una palabra dada. +Observa que la primera capa de atención en un bloque de decodificador presta atención a todas las entradas (pasadas) al decodificador, mientras que la segunda capa de atención usa la salida del codificador. De esta manera puede acceder a toda la oración de entrada para predecir de mejor manera la palabra actual. Esto es muy útil dado que diferentes idiomas pueden tener reglas gramaticales que ponen las palabras en orden distinto o algún contexto que se provee después puede ser útil para determinar la mejor traducción de una palabra dada. La *máscara de atención* también se puede usar en el codificador/decodificador para evitar que el modelo preste atención a algunas palabras especiales --por ejemplo, la palabra especial de relleno que hace que todas las entradas sean de la misma longitud cuando se agrupan oraciones. ## Arquitecturas vs. puntos de control -A medida que estudiemos a profundidad los Transformadores, verás menciones a *arquitecturas*, *puntos de control* (*checkpoints*) y *modelos*. Estos términos tienen significados ligeramentes diferentes: +A medida que estudiemos a profundidad los Transformadores, verás menciones a *arquitecturas*, *puntos de control* (*checkpoints*) y *modelos*. Estos términos tienen significados ligeramente diferentes: * **Arquitecturas**: Este es el esqueleto del modelo -- la definición de cada capa y cada operación que sucede al interior del modelo. * **Puntos de control**: Estos son los pesos que serán cargados en una arquitectura dada. diff --git a/chapters/es/chapter1/5.mdx b/chapters/es/chapter1/5.mdx index b06cb7b75..c39f00b73 100644 --- a/chapters/es/chapter1/5.mdx +++ b/chapters/es/chapter1/5.mdx @@ -9,7 +9,7 @@ Los modelos de codificadores usan únicamente el codificador del Transformador. En cada etapa, las capas de atención pueden acceder a todas las palabras de la oración inicial. Estos modelos se caracterizan generalmente por tener atención "bidireccional" y se suelen llamar modelos *auto-encoding*. -El preentrenamiento de estos modelos generalmente gira en torno a corromper de alguna manera una oración dada (por ejemplo, ocultando aleatóriamente palabras en ella) y pidiéndole al modelo que encuentre o reconstruya la oración inicial. +El preentrenamiento de estos modelos generalmente gira en torno a corromper de alguna manera una oración dada (por ejemplo, ocultando aleatoriamente palabras en ella) y pidiéndole al modelo que encuentre o reconstruya la oración inicial. Los modelos de codificadores son más adecuados para tareas que requieren un entendimiento de la oración completa, como la clasificación de oraciones, reconocimiento de entidades nombradas (y más generalmente clasificación de palabras) y respuesta extractiva a preguntas. diff --git a/chapters/es/chapter1/6.mdx b/chapters/es/chapter1/6.mdx index 3cc3f9afb..653531431 100644 --- a/chapters/es/chapter1/6.mdx +++ b/chapters/es/chapter1/6.mdx @@ -7,7 +7,7 @@ -Los modelos de decodificadores usan únicamente el decodificador del Transformador. En cada etapa, para una palabra dada las capas de atención pueden acceder sólamente a las palabras que se ubican antes en la oración. Estos modelos se suelen llamar modelos *auto-regressive*. +Los modelos de decodificadores usan únicamente el decodificador del Transformador. En cada etapa, para una palabra dada las capas de atención pueden acceder solamente a las palabras que se ubican antes en la oración. Estos modelos se suelen llamar modelos *auto-regressive*. El preentrenamiento de los modelos de decodificadores generalmente gira en torno a la predicción de la siguiente palabra en la oración. diff --git a/chapters/es/chapter1/7.mdx b/chapters/es/chapter1/7.mdx index 0d48876a1..265781853 100644 --- a/chapters/es/chapter1/7.mdx +++ b/chapters/es/chapter1/7.mdx @@ -2,9 +2,9 @@ -Los modelos codificador/decodificador (tambén llamados *modelos secuencia a secuencia*) usan ambas partes de la arquitectura del Transformador. En cada etapa, las capas de atención del codificador pueden acceder a todas las palabras de la sencuencia inicial, mientras que las capas de atención del decodificador sólo pueden acceder a las palabras que se ubican antes de una palabra dada en el texto de entrada. +Los modelos codificador/decodificador (también llamados *modelos secuencia a secuencia*) usan ambas partes de la arquitectura del Transformador. En cada etapa, las capas de atención del codificador pueden acceder a todas las palabras de la secuencia inicial, mientras que las capas de atención del decodificador sólo pueden acceder a las palabras que se ubican antes de una palabra dada en el texto de entrada. -El preentrenamiento de estos modelos se puede hacer usando los objetivos de los modelos de codificadores o decodificadores, pero usualmente implican algo más complejo. Por ejemplo, [T5](https://huggingface.co/t5-base) está preentrenado al reemplazar segmentos aleatórios de texto (que pueden contener varias palabras) con una palabra especial que las oculta, y el objetivo es predecir el texto que esta palabra reemplaza. +El preentrenamiento de estos modelos se puede hacer usando los objetivos de los modelos de codificadores o decodificadores, pero usualmente implican algo más complejo. Por ejemplo, [T5](https://huggingface.co/t5-base) está preentrenado al reemplazar segmentos aleatorios de texto (que pueden contener varias palabras) con una palabra especial que las oculta, y el objetivo es predecir el texto que esta palabra reemplaza. Los modelos secuencia a secuencia son más adecuados para tareas relacionadas con la generación de nuevas oraciones dependiendo de una entrada dada, como resumir, traducir o responder generativamente preguntas. diff --git a/chapters/es/chapter1/8.mdx b/chapters/es/chapter1/8.mdx index 818575337..607e95510 100644 --- a/chapters/es/chapter1/8.mdx +++ b/chapters/es/chapter1/8.mdx @@ -29,4 +29,4 @@ print([r["token_str"] for r in result]) Cuando se le pide llenar la palabra faltante en estas dos oraciones, el modelo devuelve solo una respuesta agnóstica de género (*waiter/waitress*). Las otras son ocupaciones que se suelen asociar con un género específico -- y si, prostituta es una de las primeras 5 posibilidades que el modelo asocia con "mujer" y "trabajo". Esto sucede a pesar de que BERT es uno de los pocos modelos de Transformadores que no se construyeron basados en datos *raspados* de todo el internet, pero usando datos aparentemente neutrales (está entrenado con los conjuntos de datos de [Wikipedia en Inglés](https://huggingface.co/datasets/wikipedia) y [BookCorpus](https://huggingface.co/datasets/bookcorpus)). -Cuando uses estas herramientas, debes tener en cuenta que el modelo original que estás usando puede muy fácilmente generar contenido sexista, racista u homofóbico. Ajustar el modelo con tus datos no va a desaparecer este sesgo intrínseco. +Cuando uses estas herramientas, debes tener en cuenta que el modelo original que estás usando puede muy fácilmente generar contenido sexista, racista u homófobo. Ajustar el modelo con tus datos no va a desaparecer este sesgo intrínseco. diff --git a/chapters/es/chapter2/4.mdx b/chapters/es/chapter2/4.mdx index b20685498..e2b954efb 100644 --- a/chapters/es/chapter2/4.mdx +++ b/chapters/es/chapter2/4.mdx @@ -27,11 +27,12 @@ Los tokenizadores son uno de los componentes fundamentales del pipeline en NLP. Sirven para traducir texto en datos que los modelos puedan procesar; es decir, de texto a valores numéricos. En esta sección veremos en qué se fundamenta todo el proceso de tokenizado. En las tareas de NLP, los datos generalmente ingresan como texto crudo. Por ejemplo: + ``` Jim Henson era un titiritero ``` -Sin embargo, necesitamos una forma de convertir el texto crudo a valores numéricos para los modelos. Eso es precisamente lo que hacen los tokenizadores, y existe una variedad de formas en que puede hacerse. El objetivo final es obetener valores que sean cortos pero muy significativos para el modelo. +Sin embargo, necesitamos una forma de convertir el texto crudo a valores numéricos para los modelos. Eso es precisamente lo que hacen los tokenizadores, y existe una variedad de formas en que puede hacerse. El objetivo final es obtener valores que sean cortos pero muy significativos para el modelo. Veamos algunos algoritmos de tokenización, e intentemos atacar algunas preguntas que puedas tener. @@ -40,12 +41,13 @@ Veamos algunos algoritmos de tokenización, e intentemos atacar algunas pregunta El primer tokenizador que nos ocurre es el _word-based_ (_basado-en-palabras_). Es generalmente sencillo, con pocas normas, y generalmente da buenos resultados. Por ejemplo, en la imagen a continuación separamos el texto en palabras y buscamos una representación numérica. +
Un ejemplo de tokenizador _word-based_.
-Existen varias formas de separar el texto. Por ejempĺo, podríamos usar los espacios para tokenizar usando Python y la función `split()`. +Existen varias formas de separar el texto. Por ejemplo, podríamos usar los espacios para tokenizar usando Python y la función `split()`. ```py tokenized_text = "Jim Henson era un titiritero".split() @@ -55,6 +57,7 @@ print(tokenized_text) ```python out ['Jim', 'Henson', 'era', 'un', 'titiritero'] ``` + También hay variaciones de tokenizadores de palabras que tienen reglas adicionales para la puntuación. Con este tipo de tokenizador, podemos acabar con unos "vocabularios" bastante grandes, donde un vocabulario se define por el número total de tokens independientes que tenemos en nuestro corpus. A cada palabra se le asigna un ID, empezando por 0 y subiendo hasta el tamaño del vocabulario. El modelo utiliza estos ID para identificar cada palabra. @@ -69,13 +72,12 @@ Una forma de reducir la cantidad de tokens desconocidos es ir un poco más allá -Character-based tokenizers split the text into characters, rather than words. This has two primary benefits: -Un tokenizador _character-based_ separa el texto en caracteres, y no en palabras. Conllevando dos beneficios principales: +Un tokenizador _character-based_ separa el texto en caracteres, y no en palabras. Esto conlleva dos beneficios principales: - Obtenemos un vocabulario mucho más corto. -- Habrá muchos menos tokens por fuera del vocabulatio conocido. +- Habrá muchos menos tokens por fuera del vocabulario conocido. -No obstante, pueden surgir incovenientes por los espacios en blanco y signos de puntuación. +No obstante, pueden surgir inconvenientes por los espacios en blanco y signos de puntuación.
Ejemplo de tokenizador basado en palabras. @@ -105,7 +107,7 @@ Este es un ejemplo que muestra cómo un algoritmo de tokenización de subpalabra Estas subpalabras terminan aportando mucho significado semántico: por ejemplo, en el ejemplo anterior, "tokenización" se dividió en "token" y "ización", dos tokens que tienen un significado semántico y a la vez son eficientes en cuanto al espacio (sólo se necesitan dos tokens para representar una palabra larga). Esto nos permite tener una cobertura relativamente buena con vocabularios pequeños y casi sin tokens desconocidos. -Este enfoque es especialmente útil en algunos idimas como el turco, donde se pueden formar palabras complejas (casi) arbitrariamente largas encadenando subpalabras. +Este enfoque es especialmente útil en algunos idiomas como el turco, donde se pueden formar palabras complejas (casi) arbitrariamente largas encadenando subpalabras. ### Y más! @@ -113,7 +115,7 @@ Como es lógico, existen muchas más técnicas. Por nombrar algunas: - Byte-level BPE (a nivel de bytes), como usa GPT-2 - WordPiece, usado por BERT -- SentencePiece or Unigram (pedazo de sentencia o unigrama), como se usa en los modelos multilengua +- SentencePiece or Unigram (pedazo de sentencia o unigrama), como se usa en los modelos multilingües A este punto, deberías tener conocimientos suficientes sobre el funcionamiento de los tokenizadores para empezar a utilizar la API. @@ -130,10 +132,10 @@ tokenizer = BertTokenizer.from_pretrained("bert-base-cased") ``` {#if fw === 'pt'} -Al igual que `AutoModel`, la clase `AutoTokenizer` tomará la clase de tokenizador adecuada en la biblioteca basada en el nombre del punto de control, y se puede utilizar directamente con cualquier punto de control: +Al igual que `AutoModel`, la clase `AutoTokenizer` tomará la clase de tokenizador adecuada en la librería basada en el nombre del punto de control, y se puede utilizar directamente con cualquier punto de control: {:else} -Al igual que `TFAutoModel`, la clase `AutoTokenizer` tomará la clase de tokenizador adecuada en la biblioteca basada en el nombre del punto de control, y se puede utilizar directamente con cualquier punto de control: +Al igual que `TFAutoModel`, la clase `AutoTokenizer` tomará la clase de tokenizador adecuada en la librería basada en el nombre del punto de control, y se puede utilizar directamente con cualquier punto de control: {/if} @@ -154,6 +156,7 @@ tokenizer("Using a Transformer network is simple") 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` + Guardar un tokenizador es idéntico a guardar un modelo: ```py @@ -200,6 +203,7 @@ Este tokenizador es un tokenizador de subpalabras: divide las palabras hasta obt ### De tokens a IDs de entrada La conversión a IDs de entrada se hace con el método del tokenizador `convert_tokens_to_ids()`: + ```py ids = tokenizer.convert_tokens_to_ids(tokens) @@ -230,6 +234,7 @@ print(decoded_string) ```python out 'Using a Transformer network is simple' ``` + Notemos que el método `decode` no sólo convierte los índices de nuevo en tokens, sino que también agrupa los tokens que formaban parte de las mismas palabras para producir una frase legible. Este comportamiento será extremadamente útil cuando utilicemos modelos que predigan texto nuevo (ya sea texto generado a partir de una indicación, o para problemas de secuencia a secuencia como la traducción o el resumen). -A estas alturas deberías entender las operaciones atómicas que un tokenizador puede manejar: tokenización, conversión a IDs, y conversión de IDs de vuelta a una cadena. Sin embargo, sólo hemos rozado la punta del iceberg. En la siguiente sección, llevaremos nuestro enfoque a sus límites y echaremos un vistazo a cómo superarlos. \ No newline at end of file +A estas alturas deberías entender las operaciones atómicas que un tokenizador puede manejar: tokenización, conversión a IDs, y conversión de IDs de vuelta a una cadena. Sin embargo, sólo hemos rozado la punta del iceberg. En la siguiente sección, llevaremos nuestro enfoque a sus límites y echaremos un vistazo a cómo superarlos. diff --git a/chapters/es/chapter2/6.mdx b/chapters/es/chapter2/6.mdx index 443ee3ee2..489b35977 100644 --- a/chapters/es/chapter2/6.mdx +++ b/chapters/es/chapter2/6.mdx @@ -37,7 +37,7 @@ sequence = "I've been waiting for a HuggingFace course my whole life." model_inputs = tokenizer(sequence) ``` -Aquí la varibale `model_inputs` contiene todo lo necesario para que un modelo opere bien. Para DistilBERT, que incluye los IDs de entrada también como la máscara de atención. Otros modelos que aceptan entradas adicionales también tendrán las salidas del objeto `tokenizer`. +Aquí la variable `model_inputs` contiene todo lo necesario para que un modelo opere bien. Para DistilBERT, que incluye los IDs de entrada también como la máscara de atención. Otros modelos que aceptan entradas adicionales también tendrán las salidas del objeto `tokenizer`. Como veremos en los ejemplos de abajo, este método es muy poderoso. Primero, puede tokenizar una sola secuencia: @@ -58,14 +58,14 @@ model_inputs = tokenizer(sequences) Puede rellenar de acuerdo a varios objetivos: ```py -# Will pad the sequences up to the maximum sequence length +# Rellenar las secuencias hasta la mayor longitud de secuencia model_inputs = tokenizer(sequences, padding="longest") -# Will pad the sequences up to the model max length -# (512 for BERT or DistilBERT) +# Rellenar las secuencias hasta la máxima longitud del modelo +# (512 para BERT o DistilBERT) model_inputs = tokenizer(sequences, padding="max_length") -# Will pad the sequences up to the specified max length +# Rellenar las secuencias hasta la máxima longitud especificada model_inputs = tokenizer(sequences, padding="max_length", max_length=8) ``` @@ -74,26 +74,26 @@ También puede truncar secuencias: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] -# Will truncate the sequences that are longer than the model max length -# (512 for BERT or DistilBERT) +# Truncar las secuencias más largas que la máxima longitud del modelo +# (512 para BERT o DistilBERT) model_inputs = tokenizer(sequences, truncation=True) -# Will truncate the sequences that are longer than the specified max length +# Truncar las secuencias más largas que la longitud especificada model_inputs = tokenizer(sequences, max_length=8, truncation=True) ``` -El objeto `tokenizer` puede manejar la conversión a tensores de frameworks específicos, los cuales pueden ser enviados directametne al modelo. Por ejemplo, en el siguiente código de ejemplo estamos solicitando al tokenizer que regrese los tensores de los distintos frameworks — `"pt"` regresa tensores de PyTorch, `"tf"` regresa tensores de TensorFlow, y `"np"` regresa arreglos de NumPy: +El objeto `tokenizer` puede manejar la conversión a tensores de frameworks específicos, los cuales pueden ser enviados directamente al modelo. Por ejemplo, en el siguiente código de ejemplo estamos solicitando al tokenizer que regrese los tensores de los distintos frameworks — `"pt"` regresa tensores de PyTorch, `"tf"` regresa tensores de TensorFlow, y `"np"` regresa arreglos de NumPy: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] -# Returns PyTorch tensors +# Devuelve tensores PyTorch model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") -# Returns TensorFlow tensors +# Devuelve tensores TensorFlow model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") -# Returns NumPy arrays +# Devuelve arrays Numpy model_inputs = tokenizer(sequences, padding=True, return_tensors="np") ``` @@ -129,13 +129,14 @@ print(tokenizer.decode(ids)) "i've been waiting for a huggingface course my whole life." ``` -El tokenizador agregó la palabra especial `[CLS]` al principio y la palabra especial `[SEP]` al final. Esto se debe a que el modelo fue preentrenado con esos, así para obtener los mismos resultados por inferencia necesitamos agregarlos también. Nota que algunos modelos no agregan palabras especiales, o agregan unas distintas; los modelos también pueden agregar estas palabras especiales sólo al principio, o sólo al final. En cualquier caso, el tokenizador sabe cuáles son las esperadas y se encargará de ello por tí. +El tokenizador agregó la palabra especial `[CLS]` al principio y la palabra especial `[SEP]` al final. Esto se debe a que el modelo fue preentrenado con esos, así para obtener los mismos resultados por inferencia necesitamos agregarlos también. Nota que algunos modelos no agregan palabras especiales, o agregan unas distintas; los modelos también pueden agregar estas palabras especiales sólo al principio, o sólo al final. En cualquier caso, el tokenizador sabe cuáles son las esperadas y se encargará de ello por tí. -## Conclusión: Del tokenizador al moelo +## Conclusión: Del tokenizador al modelo Ahora que hemos visto todos los pasos individuales que el objeto `tokenizer` usa cuando se aplica a textos, veamos una última vez cómo maneja varias secuencias (¡relleno!), secuencias muy largas (¡truncado!), y múltiples tipos de tensores con su API principal: {#if fw === 'pt'} + ```py import torch from transformers import AutoTokenizer, AutoModelForSequenceClassification @@ -148,7 +149,9 @@ sequences = ["I've been waiting for a HuggingFace course my whole life.", "So ha tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") output = model(**tokens) ``` + {:else} + ```py import tensorflow as tf from transformers import AutoTokenizer, TFAutoModelForSequenceClassification @@ -161,4 +164,5 @@ sequences = ["I've been waiting for a HuggingFace course my whole life.", "So ha tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") output = model(**tokens) ``` + {/if} diff --git a/chapters/es/chapter2/7.mdx b/chapters/es/chapter2/7.mdx index 6d7c470c3..d8a0ac53f 100644 --- a/chapters/es/chapter2/7.mdx +++ b/chapters/es/chapter2/7.mdx @@ -1,4 +1,4 @@ -# ¡Haz completado el uso básico! +# ¡Has completado el uso básico! Model Hub para encontrar el mejor punto de control para tu tarea!" + explain: "Incorrecto; aunque algunos puntos de control y modelos son capaces de manejar varios lenguajes, no hay herramientas integradas para la selección automática de punto de control de acuerdo al lenguaje. ¡Deberías dirigirte a Model Hub para encontrar el mejor punto de control para tu tarea!" } ]} /> @@ -140,7 +140,7 @@ }, { text: "Un modelo que detecta automáticamente el lenguaje usado por sus entradas para cargar los pesos correctos", - explain: "Incorrecto; auqneu algunos puntos de control y modelos son capaced de manejar varios lenguajes, no hay herramientas integradas para la selección automática de punto de control de acuerdo al lenguaje. ¡Deberías dirigirte a Model Hub para encontrar el mejor punto de control para tu tarea!" + explain: "Incorrecto; aunque algunos puntos de control y modelos son capaces de manejar varios lenguajes, no hay herramientas integradas para la selección automática de punto de control de acuerdo al lenguaje. ¡Deberías dirigirte a Model Hub para encontrar el mejor punto de control para tu tarea!" } ]} /> @@ -173,7 +173,7 @@ ]} /> -### 7. ¿Cuál es el punto de aplicar una funcion SoftMax a las salidas logits por un modelo de clasificación de secuencias? +### 7. ¿Cuál es el punto de aplicar una función SoftMax a las salidas logits por un modelo de clasificación de secuencias? -En el [Capítulo 2](/course/chapter2) exploramos como usar los tokenizadores y modelos preentrenados para realizar predicciones. Pero qué tal si deseas ajustar un modelo preentrenado con tu propio conjunto de datos ? +En el [Capítulo 2](/course/chapter2) exploramos cómo usar los tokenizadores y modelos preentrenados para realizar predicciones. Pero, ¿qué pasa si deseas ajustar un modelo preentrenado con tu propio conjunto de datos? {#if fw === 'pt'} -* Como preparar un conjunto de datos grande desde el Hub. -* Como usar la API de alto nivel del entrenador para ajustar un modelo. -* Como usar un bucle personalizado de entrenamiento. -* Como aprovechar la Accelerate library 🤗 para fácilmente ejecutar el bucle personalizado de entrenamiento en cualquier configuración distribuida. + +* Cómo preparar un conjunto de datos grande desde el Hub. +* Cómo usar la API de alto nivel del entrenador para ajustar un modelo. +* Cómo usar un bucle personalizado de entrenamiento. +* Cómo aprovechar la librería 🤗 Accelerate para fácilmente ejecutar el bucle personalizado de entrenamiento en cualquier configuración distribuida. {:else} -* Como preparar un conjunto de datos grande desde el Hub. -* Como usar Keras para ajustar un modelo. -* Como usar Keras para obtener predicciones. -* Como usar una métrica personalizada. + +* Cómo preparar un conjunto de datos grande desde el Hub. +* Cómo usar Keras para ajustar un modelo. +* Cómo usar Keras para obtener predicciones. +* Cómo usar una métrica personalizada. {/if} diff --git a/chapters/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index eded26291..088bbfe03 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -47,6 +47,7 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` + {:else} Continuando con el ejemplo del [capítulo anterior](/course/chapter2), aquí mostraremos como podríamos entrenar un clasificador de oraciones/sentencias en TensorFlow: @@ -55,7 +56,7 @@ import tensorflow as tf import numpy as np from transformers import AutoTokenizer, TFAutoModelForSequenceClassification -# Same as before +# Igual que antes checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) @@ -65,11 +66,12 @@ sequences = [ ] batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) -# This is new +# Esto es nuevo model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` + {/if} Por supuesto, entrenando el modelo con solo dos oraciones no va a producir muy buenos resultados. Para obtener mejores resultados, debes preparar un conjunto de datos más grande. @@ -79,14 +81,19 @@ En esta sección usaremos como ejemplo el conjunto de datos MRPC (Cuerpo de par ### Cargando un conjunto de datos desde el Hub {#if fw === 'pt'} + {:else} {/if} -El Hub no solo contiene modelos; sino que también tiene múltiples conjunto de datos en diferentes idiomas. Puedes explorar los conjuntos de datos [aquí](https://huggingface.co/datasets), y recomendamos que trates de cargar y procesar un nuevo conjunto de datos una vez que hayas revisado esta sección (mira la documentación general [aquí](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Por ahora, enfoquémonos en el conjunto de datos MRPC! Este es uno de los 10 conjuntos de datos que comprende el [punto de referencia GLUE](https://gluebenchmark.com/), el cual es un punto de referencia académico que se usa para medir el desempeño de modelos ML sobre 10 tareas de clasificación de texto. +El Hub no solo contiene modelos; sino que también tiene múltiples conjunto de datos en diferentes idiomas. Puedes explorar los conjuntos de datos [aquí](https://huggingface.co/datasets), y recomendamos que trates de cargar y procesar un nuevo conjunto de datos una vez que hayas revisado esta sección (mira la documentación general [aquí](https://huggingface.co/docs/datasets/loading)). Por ahora, enfoquémonos en el conjunto de datos MRPC! Este es uno de los 10 conjuntos de datos que comprende el [punto de referencia GLUE](https://gluebenchmark.com/), el cual es un punto de referencia académico que se usa para medir el desempeño de modelos ML sobre 10 tareas de clasificación de texto. + +La librería 🤗 Datasets provee un comando muy simple para descargar y memorizar un conjunto de datos en el Hub. Podemos descargar el conjunto de datos de la siguiente manera: -La Libreria Datasets 🤗 provee un comando muy simple para descargar y memorizar un conjunto de datos en el Hub. Podemos descargar el conjunto de datos de la siguiente manera: + +⚠️ **Advertencia** Asegúrate de que `datasets` esté instalado ejecutando `pip install datasets`. Luego, carga el conjunto de datos MRPC y imprímelo para ver qué contiene. + ```py from datasets import load_dataset @@ -147,13 +154,14 @@ Internamente, `label` es del tipo de dato `ClassLabel`, y la asociación de valo -✏️ **Inténtalo!** Mira el elemento 15 del conjunto de datos de entrenamiento y el elemento 87 del conjunto de datos de validación. Cuáles son sus etiquetas? +✏️ **¡Inténtalo!** Mira el elemento 15 del conjunto de datos de entrenamiento y el elemento 87 del conjunto de datos de validación. Cuáles son sus etiquetas? ### Preprocesando un conjunto de datos {#if fw === 'pt'} + {:else} @@ -179,7 +187,7 @@ inputs ``` ```python out -{ +{ 'input_ids': [101, 2023, 2003, 1996, 2034, 6251, 1012, 102, 2023, 2003, 1996, 2117, 2028, 1012, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] @@ -190,8 +198,7 @@ Nosotros consideramos las llaves `input_ids` y `attention_mask` en el [Capítulo -✏️ **Inténtalo!** Toma el elemento 15 del conjunto de datos de entrenamiento y tokeniza las dos oraciones independientemente y como un par. Cuál es la diferencia entre los dos resultados? - +✏️ **¡Inténtalo!** Toma el elemento 15 del conjunto de datos de entrenamiento y tokeniza las dos oraciones independientemente y como un par. Cuál es la diferencia entre los dos resultados? @@ -216,15 +223,15 @@ De esta manera vemos que el modelo espera las entradas de la siguiente forma `[C Como puedes observar, las partes de la entrada que corresponden a `[CLS] sentence1 [SEP]` todas tienen un tipo de token ID `0`, mientras que las otras partes que corresponden a `sentence2 [SEP]`, todas tienen tipo ID `1`. -Nótese que si seleccionas un punto de control diferente, no necesariamente tendrás el `token_type_ids` en tus entradas tonenizadas (por ejemplo, ellas no aparecen si usas un modelo DistilBERT). Estas aparecen cuando el modelo sabe que hacer con ellas, porque las ha visto durante su etapa de preentrenamiento. +Nótese que si seleccionas un punto de control diferente, no necesariamente tendrás el `token_type_ids` en tus entradas tokenizadas (por ejemplo, ellas no aparecen si usas un modelo DistilBERT). Estas aparecen cuando el modelo sabe que hacer con ellas, porque las ha visto durante su etapa de preentrenamiento. -Aquí, BERT esta preentrenado con tokens de tipo ID, y además del objetivo de modelado de lenguaje oculto que mencionamos en el [Capítulo 1](/course/chapter1), también tiene el objetivo llamado _predicción de la siguiente oración_. El objectivo con esta tarea es modelar la relación entre pares de oraciones. +Aquí, BERT está preentrenado con tokens de tipo ID, y además del objetivo de modelado de lenguaje oculto que mencionamos en el [Capítulo 1](/course/chapter1), también tiene el objetivo llamado _predicción de la siguiente oración_. El objetivo con esta tarea es modelar la relación entre pares de oraciones. -Para predecir la siguiente oración, el modelo recibe pares de oraciones (con tokens ocultados aleatoriamente) y se le pide que prediga si la segunda secuencia sigue a la primera. Para que la tarea no sea tan simple, la mitad de las veces las oraciones estan seguidas en el texto original de donde se obtuvieron, y la otra mitad las oraciones vienen de dos documentos distintos. +Para predecir la siguiente oración, el modelo recibe pares de oraciones (con tokens ocultados aleatoriamente) y se le pide que prediga si la segunda secuencia sigue a la primera. Para que la tarea no sea tan simple, la mitad de las veces las oraciones están seguidas en el texto original de donde se obtuvieron, y la otra mitad las oraciones vienen de dos documentos distintos. -En general, no debes preocuparte si los `token_type_ids` estan o no en las entradas tokenizadas: con tal que uses el mismo punto de control para el tokenizador y el modelo, todo estará bien porque el tokenizador sabe que pasarle a su modelo. +En general, no debes preocuparte si los `token_type_ids` están o no en las entradas tokenizadas: con tal de que uses el mismo punto de control para el tokenizador y el modelo, todo estará bien porque el tokenizador sabe qué pasarle a su modelo. -Ahora que hemos visto como nuestro tokenizador puede trabajar con un par de oraciones, podemos usarlo para tokenizar todo el conjunto de datos: como en el [capítulo anterior](/course/chapter2), podemos darle al tokenizador una lista de pares de oraciones, dándole la lista de las primeras oraciones, y luego la lista de las segundas oraciones. Esto también es compatible con las opciones de relleno y truncamiento que vimos en el [Capítulo 2](/course/chapter2). Por lo tanto, una manera de preprocessar el conjunto de datos de entrenamiento sería: +Ahora que hemos visto como nuestro tokenizador puede trabajar con un par de oraciones, podemos usarlo para tokenizar todo el conjunto de datos: como en el [capítulo anterior](/course/es/chapter2), podemos darle al tokenizador una lista de pares de oraciones, dándole la lista de las primeras oraciones, y luego la lista de las segundas oraciones. Esto también es compatible con las opciones de relleno y truncamiento que vimos en el [Capítulo 2](/course/chapter2). Por lo tanto, una manera de preprocesar el conjunto de datos de entrenamiento sería: ```py tokenized_dataset = tokenizer( @@ -235,18 +242,18 @@ tokenized_dataset = tokenizer( ) ``` -Esto funciona bien, pero tiene la desventaja de que devuelve un diccionario (con nuestras llaves, `input_ids`, `attention_mask`, and `token_type_ids`, y valores que son listas de listas). Además va a trabajar solo si tienes suficiente memoria principal para almacenar todo el conjunto de datos durante la tokenización (mientras que los conjuntos de datos de la librería Datasets 🤗 son archivos [Apache Arrow](https://arrow.apache.org/) almacenados en disco, y así solo mantienes en memoria las muestras que necesitas). +Esto funciona bien, pero tiene la desventaja de que devuelve un diccionario (con nuestras llaves, `input_ids`, `attention_mask`, and `token_type_ids`, y valores que son listas de listas). Además va a trabajar solo si tienes suficiente memoria principal para almacenar todo el conjunto de datos durante la tokenización (mientras que los conjuntos de datos de la librería 🤗 Datasets son archivos [Apache Arrow](https://arrow.apache.org/) almacenados en disco, y así solo mantienes en memoria las muestras que necesitas). -Para mantener los datos como un conjunto de datos, usaremos el método [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Este también nos ofrece una flexibilidad adicional en caso de que necesitemos preprocesamiento mas allá de la tokenización. El método `map()` trabaja aplicando una función sobre cada elemento del conjunto de datos, así que definamos una función para tokenizar nuestras entradas: +Para mantener los datos como un conjunto de datos, usaremos el método [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Este también nos ofrece una flexibilidad adicional en caso de que necesitemos preprocesamiento mas allá de la tokenización. El método `map()` trabaja aplicando una función sobre cada elemento del conjunto de datos, así que definamos una función para tokenizar nuestras entradas: ```py def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -Esta función recibe un diccionario (como los elementos de nuestro conjunto de datos) y devuelve un nuevo diccionario con las llaves `input_ids`, `attention_mask`, y `token_type_ids`. Nótese que también funciona si el diccionario `example` contiene múltiples elementos (cada llave con una lista de oraciones) debido a que el `tokenizador` funciona con listas de pares de oraciones, como se vio anteriormente. Esto nos va a permitir usar la opción `batched=True` en nuestra llamada a `map()`, lo que acelera la tokenización significativamente. El `tokenizador` es respaldado por un tokenizador escrito en Rust que viene de la libreria [Tokenizadores 🤗](https://github.com/huggingface/tokenizers). Este tokenizador puede ser muy rápido, pero solo si le da muchas entradas al mismo tiempo. +Esta función recibe un diccionario (como los elementos de nuestro conjunto de datos) y devuelve un nuevo diccionario con las llaves `input_ids`, `attention_mask`, y `token_type_ids`. Nótese que también funciona si el diccionario `example` contiene múltiples elementos (cada llave con una lista de oraciones) debido a que el `tokenizador` funciona con listas de pares de oraciones, como se vio anteriormente. Esto nos va a permitir usar la opción `batched=True` en nuestra llamada a `map()`, lo que acelera la tokenización significativamente. El `tokenizador` es respaldado por un tokenizador escrito en Rust que viene de la librería [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Este tokenizador puede ser muy rápido, pero solo si le da muchas entradas al mismo tiempo. -Nótese que por ahora hemos dejado el argumento `padding` fuera de nuestra función de tokenización. Esto es porque rellenar todos los elementos hasta su máxima longitud no es eficiente: es mejor rellenar los elememtos cuando se esta construyendo el lote, debido a que solo debemos rellenar hasta la máxima longitud en el lote, pero no en todo el conjunto de datos. Esto puede ahorrar mucho tiempo y poder de processamiento cuando las entradas tienen longitudes variables. +Nótese que por ahora hemos dejado el argumento `padding` fuera de nuestra función de tokenización. Esto es porque rellenar todos los elementos hasta su máxima longitud no es eficiente: es mejor rellenar los elementos cuando se esta construyendo el lote, debido a que solo debemos rellenar hasta la máxima longitud en el lote, pero no en todo el conjunto de datos. Esto puede ahorrar mucho tiempo y poder de procesamiento cuando las entradas tienen longitudes variables. Aquí se muestra como se aplica la función de tokenización a todo el conjunto de datos en un solo paso. Estamos usando `batched=True` en nuestra llamada a `map` para que la función sea aplicada a múltiples elementos de nuestro conjunto de datos al mismo tiempo, y no a cada elemento por separado. Esto permite un preprocesamiento más rápido. @@ -255,7 +262,7 @@ tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -La manera en que la libreria 🤗 aplica este procesamiento es a través de campos añadidos al conjunto de datos, uno por cada diccionario devuelto por la función de preprocesamiento. +La manera en que la librería 🤗 Datasets aplica este procesamiento es a través de campos añadidos al conjunto de datos, uno por cada diccionario devuelto por la función de preprocesamiento. ```python out DatasetDict({ @@ -274,7 +281,7 @@ DatasetDict({ }) ``` -Hasta puedes usar multiprocesamiento cuando aplicas la función de preprocesamiento con `map()` pasando el argumento `num_proc`. Nosotros no usamos esta opción porque los Tokenizadores de la libreria 🤗 usa múltiples hilos de procesamiento para tokenizar rápidamente nuestros elementos, pero sino estas usando un tokenizador rápido respaldado por esta libreria, esta opción puede acelerar tu preprocesamiento. +Hasta puedes usar multiprocesamiento cuando aplicas la función de preprocesamiento con `map()` pasando el argumento `num_proc`. Nosotros no usamos esta opción porque los tokenizadores de la librería 🤗 Tokenizers usa múltiples hilos de procesamiento para tokenizar rápidamente nuestros elementos, pero sino estas usando un tokenizador rápido respaldado por esta librería, esta opción puede acelerar tu preprocesamiento. Nuestra función `tokenize_function` devuelve un diccionario con las llaves `input_ids`, `attention_mask`, y `token_type_ids`, así que esos tres campos son adicionados a todas las divisiones de nuestro conjunto de datos. Nótese que pudimos haber cambiado los campos existentes si nuestra función de preprocesamiento hubiese devuelto un valor nuevo para cualquiera de las llaves en el conjunto de datos al que le aplicamos `map()`. @@ -293,20 +300,24 @@ La función responsable de juntar los elementos dentro de un lote es llamada *fu {/if} -Para poner esto en práctica, tenemos que definir una función de cotejo que aplique la cantidad correcta de relleno a los elementos del conjunto de datos que queremos agrupar. Afortundamente, la libreria Transformers de 🤗 nos provee esta función mediante `DataCollatorWithPadding`. Esta recibe un tokenizador cuando la creas (para saber cual token de relleno se debe usar, y si el modelo espera el relleno a la izquierda o la derecha en las entradas) y hace todo lo que necesitas: +Para poner esto en práctica, tenemos que definir una función de cotejo que aplique la cantidad correcta de relleno a los elementos del conjunto de datos que queremos agrupar. Afortunadamente, la librería 🤗 Transformers nos provee esta función mediante `DataCollatorWithPadding`. Esta recibe un tokenizador cuando la creas (para saber cual token de relleno se debe usar, y si el modelo espera el relleno a la izquierda o la derecha en las entradas) y hace todo lo que necesitas: {#if fw === 'pt'} + ```py from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` + {:else} + ```py from transformers import DataCollatorWithPadding data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") ``` + {/if} Para probar este nuevo juguete, tomemos algunos elementos de nuestro conjunto de datos de entrenamiento para agruparlos. Aquí, removemos las columnas `idx`, `sentence1`, and `sentence2` ya que éstas no se necesitan y contienen cadenas (y no podemos crear tensores con cadenas), miremos las longitudes de cada elemento en el lote. @@ -346,20 +357,19 @@ batch = data_collator(samples) 'labels': torch.Size([8])} ``` -Luce bién! Ahora que hemos convertido el texto crudo a lotes que nuestro modelo puede aceptar, estamos listos para ajustarlo! +¡Luce bien! Ahora que hemos convertido el texto crudo a lotes que nuestro modelo puede aceptar, estamos listos para ajustarlo! {/if} -✏️ **Inténtalo!** Reproduce el preprocesamiento en el conjunto de datos GLUE SST-2. Es un poco diferente ya que esta compuesto de oraciones individuales en lugar de pares, pero el resto de lo que hicimos deberia ser igual. Para un reto mayor, intenta escribir una función de preprocesamiento que trabaje con cualquiera de las tareas GLUE. +✏️ **¡Inténtalo!** Reproduce el preprocesamiento en el conjunto de datos GLUE SST-2. Es un poco diferente ya que esta compuesto de oraciones individuales en lugar de pares, pero el resto de lo que hicimos debería ser igual. Para un reto mayor, intenta escribir una función de preprocesamiento que trabaje con cualquiera de las tareas GLUE. {#if fw === 'tf'} -Ahora que tenemos nuestro conjunto de datos y el cotejador de datos, necesitamos juntarlos. Nosotros podriamos cargar lotes de datos y cotejarlos, pero eso sería mucho trabajo, y probablemente no muy eficiente. En cambio, existe un método que ofrece una solución eficiente para este problema: `to_tf_dataset()`. Este envuelve un `tf.data.Dataset` alrededor de tu conjunto de datos, con una función opcional de cotejo. `tf.data.Dataset` es un formato nativo de TensorFlow que Keras puede usar con el `model.fit()`, así este método convierte inmediatamente un conjunto de datos 🤗 a un formato que viene listo para entrenamiento. Veámoslo en acción con nuestro conjunto de datos. - +Ahora que tenemos nuestro conjunto de datos y el cotejador de datos, necesitamos juntarlos. Nosotros podríamos cargar lotes de datos y cotejarlos, pero eso sería mucho trabajo, y probablemente no muy eficiente. En cambio, existe un método que ofrece una solución eficiente para este problema: `to_tf_dataset()`. Este envuelve un `tf.data.Dataset` alrededor de tu conjunto de datos, con una función opcional de cotejo. `tf.data.Dataset` es un formato nativo de TensorFlow que Keras puede usar con el `model.fit()`, así este método convierte inmediatamente un conjunto de datos 🤗 a un formato que viene listo para entrenamiento. Veámoslo en acción con nuestro conjunto de datos. ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -379,6 +389,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -Y eso es todo! Ahora podemos usar esos conjuntos de datos en nuestra próxima clase, donde el entrenamiento será mas sencillo después de todo el trabajo de preprocesamiento de datos. +¡Y eso es todo! Ahora podemos usar esos conjuntos de datos en nuestra próxima clase, donde el entrenamiento será mas sencillo después de todo el trabajo de preprocesamiento de datos. {/if} diff --git a/chapters/es/chapter3/3.mdx b/chapters/es/chapter3/3.mdx new file mode 100644 index 000000000..83bacfffd --- /dev/null +++ b/chapters/es/chapter3/3.mdx @@ -0,0 +1,180 @@ + + +# Ajuste de un modelo con la API Trainer + + + + + +🤗 Transformers incluye una clase `Trainer` para ayudarte a ajustar cualquiera de los modelos preentrenados proporcionados en tu dataset. Una vez que hayas hecho todo el trabajo de preprocesamiento de datos de la última sección, sólo te quedan unos pocos pasos para definir el `Trainer`. La parte más difícil será preparar el entorno para ejecutar `Trainer.train()`, ya que se ejecutará muy lentamente en una CPU. Si no tienes una GPU preparada, puedes acceder a GPUs o TPUs gratuitas en [Google Colab](https://colab.research.google.com/). + +Los siguientes ejemplos de código suponen que ya has ejecutado los ejemplos de la sección anterior. Aquí tienes un breve resumen de lo que necesitas: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +### Entrenamiento + +El primer paso antes de que podamos definir nuestro `Trainer` es definir una clase `TrainingArguments` que contendrá todos los hiperparámetros que el `Trainer` utilizará para el entrenamiento y la evaluación del modelo. El único argumento que tienes que proporcionar es el directorio donde se guardarán tanto el modelo entrenado como los puntos de control (checkpoints). Para los demás parámetros puedes dejar los valores por defecto, deberían funcionar bastante bien para un ajuste básico. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Si quieres subir automáticamente tu modelo al Hub durante el entrenamiento, incluye `push_to_hub=True` en `TrainingArguments`. Aprenderemos más sobre esto en el [Capítulo 4](/course/chapter4/3). + + + +El segundo paso es definir nuestro modelo. Como en el [capítulo anterior](/course/chapter2), utilizaremos la clase `AutoModelForSequenceClassification`, con dos etiquetas: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Observarás que, a diferencia del [Capítulo 2](/course/chapter2), aparece una advertencia después de instanciar este modelo preentrenado. Esto se debe a que BERT no ha sido preentrenado para la clasificación de pares de frases, por lo que la cabeza del modelo preentrenado se ha eliminado y en su lugar se ha añadido una nueva cabeza adecuada para la clasificación de secuencias. Las advertencias indican que algunos pesos no se han utilizado (los correspondientes a la cabeza de preentrenamiento eliminada) y que otros se han inicializado aleatoriamente (los correspondientes a la nueva cabeza). La advertencia concluye animándote a entrenar el modelo, que es exactamente lo que vamos a hacer ahora. + +Una vez que tenemos nuestro modelo, podemos definir un `Trainer` pasándole todos los objetos construidos hasta ahora: el `model`, los `training_args`, los datasets de entrenamiento y validación, nuestro `data_collator`, y nuestro `tokenizer`: + +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Ten en cuenta que cuando pasas el `tokenizer` como hicimos aquí, el `data_collator` por defecto utilizado por el `Trainer` será un `DataCollatorWithPadding` como definimos anteriormente, por lo que puedes omitir la línea `data_collator=data_collator`. De todas formas, era importante mostrarte esta parte del proceso en la sección 2. + +Para ajustar el modelo en nuestro dataset, sólo tenemos que llamar al método `train()` de nuestro `Trainer`: + +```py +trainer.train() +``` + +Esto iniciará el ajuste (que debería tardar un par de minutos en una GPU) e informará de la training loss cada 500 pasos. Sin embargo, no te dirá lo bien (o mal) que está rindiendo tu modelo. Esto se debe a que: + +1. No le hemos dicho al `Trainer` que evalúe el modelo durante el entrenamiento especificando un valor para `evaluation_strategy`: `steps` (evaluar cada `eval_steps`) o `epoch` (evaluar al final de cada época). +2. No hemos proporcionado al `Trainer` una función `compute_metrics()` para calcular una métrica durante dicha evaluación (de lo contrario, la evaluación sólo habría impreso la pérdida, que no es un número muy intuitivo). + +### Evaluación + +Veamos cómo podemos construir una buena función `compute_metrics()` para utilizarla la próxima vez que entrenemos. La función debe tomar un objeto `EvalPrediction` (que es una tupla nombrada con un campo `predictions` y un campo `label_ids`) y devolverá un diccionario que asigna cadenas a flotantes (las cadenas son los nombres de las métricas devueltas, y los flotantes sus valores). Para obtener algunas predicciones de nuestro modelo, podemos utilizar el comando `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +La salida del método `predict()` es otra tupla con tres campos: `predictions`, `label_ids`, y `metrics`. El campo `metrics` sólo contendrá la pérdida en el dataset proporcionado, así como algunas métricas de tiempo (cuánto se tardó en predecir, en total y de media). Una vez que completemos nuestra función `compute_metrics()` y la pasemos al `Trainer`, ese campo también contendrá las métricas devueltas por `compute_metrics()`. + +Como puedes ver, `predictions` es una matriz bidimensional con forma 408 x 2 (408 es el número de elementos del dataset que hemos utilizado). Esos son los logits de cada elemento del dataset que proporcionamos a `predict()` (como viste en el [capítulo anterior](/curso/capítulo2), todos los modelos Transformer devuelven logits). Para convertirlos en predicciones que podamos comparar con nuestras etiquetas, necesitamos tomar el índice con el valor máximo en el segundo eje: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Ahora podemos comparar esas predicciones `preds` con las etiquetas. Para construir nuestra función `compute_metric()`, nos basaremos en las métricas de la librería 🤗 [Evaluate](https://github.com/huggingface/evaluate/). Podemos cargar las métricas asociadas al dataset MRPC tan fácilmente como cargamos el dataset, esta vez con la función `evaluate.load()`. El objeto devuelto tiene un método `compute()` que podemos utilizar para calcular de la métrica: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Los resultados exactos que obtengas pueden variar, ya que la inicialización aleatoria de la cabeza del modelo podría cambiar las métricas obtenidas. Aquí, podemos ver que nuestro modelo tiene una precisión del 85,78% en el conjunto de validación y una puntuación F1 de 89,97. Estas son las dos métricas utilizadas para evaluar los resultados en el dataset MRPC para la prueba GLUE. La tabla del [paper de BERT](https://arxiv.org/pdf/1810.04805.pdf) recoge una puntuación F1 de 88,9 para el modelo base. Se trataba del modelo "uncased" (el texto se reescribe en minúsculas antes de la tokenización), mientras que nosotros hemos utilizado el modelo "cased" (el texto se tokeniza sin reescribir), lo que explica el mejor resultado. + +Juntándolo todo obtenemos nuestra función `compute_metrics()`: + +```py +def compute_metrics(eval_preds): + metric = evaluate.load("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +Y para ver cómo se utiliza para informar de las métricas al final de cada época, así es como definimos un nuevo `Trainer` con nuestra función `compute_metrics()`: + +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +Ten en cuenta que hemos creado un nuevo `TrainingArguments` con su `evaluation_strategy` configurado como `"epoch"` y un nuevo modelo. De lo contrario sólo estaríamos continuando el entrenamiento del modelo que ya habíamos entrenado. Para lanzar una nueva ejecución de entrenamiento, ejecutamos: + +```py +trainer.train() +``` + +Esta vez, nos informará de la pérdida de validación y las métricas al final de cada época, además de la pérdida de entrenamiento. De nuevo, la puntuación exacta de precisión/F1 que alcances puede ser un poco diferente de la que encontramos nosotros, debido a la inicialización aleatoria del modelo, pero debería estar en el mismo rango. + +El `Trainer` funciona en múltiples GPUs o TPUs y proporciona muchas opciones, como el entrenamiento de precisión mixta (usa `fp16 = True` en tus argumentos de entrenamiento). Repasaremos todo lo que ofrece en el capítulo 10. + +Con esto concluye la introducción al ajuste utilizando la API de `Trainer`. En el [Capítulo 7](/course/chapter7) se dará un ejemplo de cómo hacer esto para las tareas más comunes de PLN, pero ahora veamos cómo hacer lo mismo en PyTorch puro. + + + +✏️ **¡Inténtalo!** Ajusta un modelo sobre el dataset GLUE SST-2 utilizando el procesamiento de datos que has implementado en la sección 2. + + diff --git a/chapters/es/chapter3/3_tf.mdx b/chapters/es/chapter3/3_tf.mdx new file mode 100644 index 000000000..34f5dcb69 --- /dev/null +++ b/chapters/es/chapter3/3_tf.mdx @@ -0,0 +1,203 @@ + + +# Ajuste de un modelo con Keras + + + +Una vez que hayas realizado todo el trabajo de preprocesamiento de datos de la última sección, sólo te quedan unos pocos pasos para entrenar el modelo. Sin embargo, ten en cuenta que el comando `model.fit()` se ejecutará muy lentamente en una CPU. Si no dispones de una GPU, puedes acceder a GPUs o TPUs gratuitas en [Google Colab](https://colab.research.google.com/). + +Los siguientes ejemplos de código suponen que ya has ejecutado los ejemplos de la sección anterior. Aquí tienes un breve resumen de lo que necesitas: + +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding +import numpy as np + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") + +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +### Entrenamiento + +Los modelos TensorFlow importados de 🤗 Transformers ya son modelos Keras. A continuación, una breve introducción a Keras. + + + +Eso significa que, una vez que tenemos nuestros datos, se requiere muy poco trabajo para empezar a entrenar con ellos. + + + +Como en el [capítulo anterior](/course/chapter2), utilizaremos la clase `TFAutoModelForSequenceClassification`, con dos etiquetas: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Observarás que, a diferencia del [Capítulo 2](/course/es/chapter2), aparece una advertencia después de instanciar este modelo preentrenado. Esto se debe a que BERT no ha sido preentrenado para la clasificación de pares de frases, por lo que la cabeza del modelo preentrenado se ha eliminado y en su lugar se ha añadido una nueva cabeza adecuada para la clasificación de secuencias. Las advertencias indican que algunos pesos no se han utilizado (los correspondientes a la cabeza de preentrenamiento eliminada) y que otros se han inicializado aleatoriamente (los correspondientes a la nueva cabeza). La advertencia concluye animándote a entrenar el modelo, que es exactamente lo que vamos a hacer ahora. + +Para afinar el modelo en nuestro dataset, sólo tenemos que compilar nuestro modelo con `compile()` y luego pasar nuestros datos al método `fit()`. Esto iniciará el proceso de ajuste (que debería tardar un par de minutos en una GPU) e informará de la pérdida de entrenamiento a medida que avanza, además de la pérdida de validación al final de cada época. + + + +Ten en cuenta que los modelos 🤗 Transformers tienen una característica especial que la mayoría de los modelos Keras no tienen - pueden usar automáticamente una pérdida apropiada que calculan internamente. Usarán esta pérdida por defecto si no estableces un argumento de pérdida en `compile()`. Tea en cuenta que para utilizar la pérdida interna tendrás que pasar las etiquetas como parte de la entrada, en vez de como una etiqueta separada como es habitual en los modelos Keras. Veremos ejemplos de esto en la Parte 2 del curso, donde definir la función de pérdida correcta puede ser complicado. Para la clasificación de secuencias, sin embargo, una función de pérdida estándar de Keras funciona bien, así que eso es lo que usaremos aquí. + + + +```py +from tensorflow.keras.losses import SparseCategoricalCrossentropy + +model.compile( + optimizer="adam", + loss=SparseCategoricalCrossentropy(from_logits=True), + metrics=["accuracy"], +) +model.fit( + tf_train_dataset, + validation_data=tf_validation_dataset, +) +``` + + + +Ten en cuenta un fallo muy común aquí: por poder, _puedes_ pasar simplemente el nombre de la función de pérdida como una cadena a Keras, pero por defecto Keras asumirá que ya has aplicado una función softmax a tus salidas. Sin embargo, muchos modelos devuelven los valores justo antes de que se aplique la función softmax, también conocidos como _logits_. Tenemos que decirle a la función de pérdida que eso es lo que hace nuestro modelo, y la única manera de hacerlo es llamándola directamente, en lugar de pasar su nombre con una cadena. + + + +### Mejorar el rendimiento del entrenamiento + + + +Si ejecutas el código anterior seguro que funciona, pero comprobarás que la pérdida sólo disminuye lenta o esporádicamente. La causa principal es la _tasa de aprendizaje_ (learning rate en inglés). Al igual que con la pérdida, cuando pasamos a Keras el nombre de un optimizador como una cadena, Keras inicializa ese optimizador con valores por defecto para todos los parámetros, incluyendo la tasa de aprendizaje. Sin embargo, por experiencia sabemos que los transformadores se benefician de una tasa de aprendizaje mucho menor que la predeterminada para Adam, que es 1e-3, también escrito +como 10 a la potencia de -3, o 0,001. 5e-5 (0,00005), que es unas veinte veces menor, es un punto de partida mucho mejor. + +Además de reducir la tasa de aprendizaje, tenemos un segundo truco en la manga: podemos reducir lentamente la tasa de aprendizaje a lo largo del entrenamiento. En la literatura, a veces se habla de _decrecimiento_ o _reducción_ de la tasa de aprendizaje. En Keras, la mejor manera de hacer esto es utilizar un _programador de tasa de aprendizaje_. Una buena opción es `PolynomialDecay` que, a pesar del nombre, con la configuración por defecto simplemente hace que la tasa de aprendizaje decaiga decae linealmente desde el valor inicial hasta el valor final durante el transcurso del entrenamiento, que es exactamente lo que queremos. Con el fin de utilizar un programador correctamente, necesitamos decirle cuánto tiempo va a durar el entrenamiento. Especificamos esto a continuación como el número de pasos de entrenamiento: `num_train_steps`. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 + +# El número de pasos de entrenamiento es el número de muestras del conjunto de datos +# dividido por el tamaño del lote y multiplicado por el número total de épocas. +# Ten en cuenta que el conjunto de datos tf_train_dataset es un conjunto de datos +# tf.data.Dataset por lotes, no el conjunto de datos original de Hugging Face +# por lo que su len() ya es num_samples // batch_size. + +num_train_steps = len(tf_train_dataset) * num_epochs +lr_scheduler = PolynomialDecay( + initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps +) +from tensorflow.keras.optimizers import Adam + +opt = Adam(learning_rate=lr_scheduler) +``` + + + +La librería 🤗 Transformers también tiene una función `create_optimizer()` que creará un optimizador `AdamW` con descenso de tasa de aprendizaje. Verás en detalle este útil atajo en próximas secciones del curso. + + + +Ahora tenemos nuestro nuevo optimizador, y podemos intentar entrenar con él. En primer lugar, vamos a recargar el modelo, para restablecer los cambios en los pesos del entrenamiento que acabamos de hacer, y luego podemos compilarlo con el nuevo optimizador: + +```py +import tensorflow as tf + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) +model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +``` + +Ahora, ajustamos de nuevo: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Si quieres subir automáticamente tu modelo a Hub durante el entrenamiento, puedes pasar un `PushToHubCallback` en el método `model.fit()`. Aprenderemos más sobre esto en el [Capítulo 4](/course/es/chapter4/3) + + + +### Predicciones del Modelo[[model-predictions]] + + + +Entrenar y ver cómo disminuye la pérdida está muy bien, pero ¿qué pasa si queremos obtener la salida del modelo entrenado, ya sea para calcular algunas métricas o para utilizar el modelo en producción? Para ello, podemos utilizar el método `predict()`. Este devuelve los _logits_ de la cabeza de salida del modelo, uno por clase. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Podemos convertir estos logits en predicciones de clases del modelo utilizando `argmax` para encontrar el logit más alto, que corresponde a la clase más probable: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Ahora, ¡utilicemos esos `preds` (predicciones) para calcular métricas! Podemos cargar las métricas asociadas al conjunto de datos MRPC tan fácilmente como cargamos el conjunto de datos, esta vez con la función `evaluate.load()`. El objeto devuelto tiene un método `compute()` que podemos utilizar para calcular las métricas: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Los resultados exactos que obtengas pueden variar, ya que la inicialización aleatoria de la cabeza del modelo podría cambiar los valores resultantes de las métricas. Aquí, podemos ver que nuestro modelo tiene una precisión del 85,78% en el conjunto de validación y una puntuación F1 de 89,97. Estas son las dos métricas utilizadas para evaluar los resultados del conjunto de datos MRPC del benchmark GLUE. La tabla del [paper de BERT](https://arxiv.org/pdf/1810.04805.pdf) muestra una puntuación F1 de 88,9 para el modelo base. Se trataba del modelo `uncased` ("no encasillado"), mientras que nosotros utilizamos el modelo `cased` ("encasillado"), lo que explica el mejor resultado. + +Con esto concluye la introducción al ajuste de modelos utilizando la API de Keras. En el [Capítulo 7](/course/es/chapter7) se dará un ejemplo de cómo hacer esto para las tareas de PLN más comunes. Si quieres perfeccionar tus habilidades con la API Keras, intenta ajustar un modelo con el conjunto de datos GLUE SST-2, utilizando el procesamiento de datos que hiciste en la sección 2. diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx index 8d4e84e8d..054a25639 100644 --- a/chapters/es/chapter3/4.mdx +++ b/chapters/es/chapter3/4.mdx @@ -30,7 +30,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ### Prepárate para el entrenamiento -Antes de escribir nuestro bucle de entrenamiento, necesitaremos definir algunos objetos. Los primeros son los dataloaders que usaremos para iterar sobre lotes. Pero antes de que podamos definir esos dataloaders, necesitamos aplicar un poquito de preprocesamiento a nuestro `tokenized_datasets`, para encargarnos de algunas cosas que el `Trainer` hizo por nosotros de manera automática. Específicamente, necesitamos: +Antes de escribir nuestro bucle de entrenamiento, necesitaremos definir algunos objetos. Los primeros son los `dataloaders` (literalmente, "cargadores de datos") que usaremos para iterar sobre lotes. Pero antes de que podamos definir esos `dataloaders`, necesitamos aplicar un poquito de preprocesamiento a nuestro `tokenized_datasets`, para encargarnos de algunas cosas que el `Trainer` hizo por nosotros de manera automática. Específicamente, necesitamos: - Remover las columnas correspondientes a valores que el model no espera (como las columnas `sentence1` y `sentence2`). - Renombrar la columna `label` con `labels` (porque el modelo espera el argumento llamado `labels`). @@ -51,7 +51,7 @@ Ahora podemos verificar que el resultado solo tiene columnas que nuestro modelo ["attention_mask", "input_ids", "labels", "token_type_ids"] ``` -Ahora que esto esta hecho, es fácil definir nuestros dataloaders: +Ahora que esto esta hecho, es fácil definir nuestros `dataloaders`: ```py from torch.utils.data import DataLoader @@ -100,9 +100,9 @@ print(outputs.loss, outputs.logits.shape) tensor(0.5441, grad_fn=) torch.Size([8, 2]) ``` -Todos los modelos Transformers 🤗 van a retornar la pérdida cuando se pasan los `labels`, y también obtenemos los logits (dos por cada entrada en nuestro lote, asi que es un tensor de tamaño 8 x 2). +Todos los modelos 🤗 Transformers van a retornar la pérdida cuando se pasan los `labels`, y también obtenemos los logits (dos por cada entrada en nuestro lote, asi que es un tensor de tamaño 8 x 2). -Estamos casi listos para escribir nuestro bucle de entrenamiento! Nos están faltando dos cosas: un optimizador y un programador de la rata de aprendizaje. Ya que estamos tratando de replicar a mano lo que el `Trainer` estaba haciendo, usaremos los mismos valores por defecto. El optimizador usado por el `Trainer` es `AdamW`, que es el mismo que Adam, pero con un cambio para la regularización de decremento de los pesos (ver ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) por Ilya Loshchilov y Frank Hutter): +Estamos casi listos para escribir nuestro bucle de entrenamiento! Nos están faltando dos cosas: un optimizador y un programador de la tasa de aprendizaje. Ya que estamos tratando de replicar a mano lo que el `Trainer` estaba haciendo, usaremos los mismos valores por defecto. El optimizador usado por el `Trainer` es `AdamW`, que es el mismo que Adam, pero con un cambio para la regularización de decremento de los pesos (ver ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) por Ilya Loshchilov y Frank Hutter): ```py from transformers import AdamW @@ -110,7 +110,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -Finalmente, el programador por defecto de la rata de aprendizaje es un decremento lineal desde al valor máximo (5e-5) hasta 0. Para definirlo apropiadamente, necesitamos saber el número de pasos de entrenamiento que vamos a tener, el cual viene dado por el número de épocas que deseamos correr multiplicado por el número de lotes de entrenamiento (que es el largo de nuestro dataloader de entrenamiento). El `Trainer` usa tres épocas por defecto, asi que usaremos eso: +Finalmente, el programador por defecto de la tasa de aprendizaje es un decremento lineal desde al valor máximo (5e-5) hasta 0. Para definirlo apropiadamente, necesitamos saber el número de pasos de entrenamiento que vamos a tener, el cual viene dado por el número de épocas que deseamos correr multiplicado por el número de lotes de entrenamiento (que es el largo de nuestro dataloader de entrenamiento). El `Trainer` usa tres épocas por defecto, asi que usaremos eso: ```py from transformers import get_scheduler @@ -146,7 +146,7 @@ device device(type='cuda') ``` -Ya estamos listos para entrenar! Para tener una idea de cuando el entrenamiento va a terminar, adicionamos una barra de progreso sobre el número de pasos de entrenamiento, usando la libreria `tqdm`: +¡Ya está todo listo para entrenar! Para tener una idea de cuándo va a terminar el entrenamiento, adicionamos una barra de progreso sobre el número de pasos de entrenamiento, usando la librería `tqdm`: ```py from tqdm.auto import tqdm @@ -171,7 +171,7 @@ Puedes ver que la parte central del bucle de entrenamiento luce bastante como el ### El bucle de evaluación -Como lo hicimos anteriormente, usaremos una métrica ofrecida por la libreria 🤗 Evaluate. Ya hemos visto el método `metric.compute()`, pero de hecho las métricas se pueden acumular sobre los lotes a medida que avanzamos en el bucle de predicción con el método `add_batch()`. Una vez que hemos acumulado todos los lotes, podemos obtener el resultado final con `metric.compute()`. Aquí se muestra como se puede implementar en un bucle de evaluación: +Como lo hicimos anteriormente, usaremos una métrica ofrecida por la librería 🤗 Evaluate. Ya hemos visto el método `metric.compute()`, pero de hecho las métricas se pueden acumular sobre los lotes a medida que avanzamos en el bucle de predicción con el método `add_batch()`. Una vez que hemos acumulado todos los lotes, podemos obtener el resultado final con `metric.compute()`. Aquí se muestra cómo se puede implementar en un bucle de evaluación: ```py import evaluate @@ -206,7 +206,7 @@ De nuevo, tus resultados serán un tanto diferente debido a la inicialización a -El bucle de entrenamiento que definimos anteriormente trabaja bien en un solo CPU o GPU. Pero usando la libreria [Accelerate 🤗](https://github.com/huggingface/accelerate), con solo pocos ajustes podemos habilitar el entrenamiento distribuido en múltiples GPUs o CPUs. Comenzando con la creación de los dataloaders de entrenamiento y validación, aquí se muestra como luce nuestro bucle de entrenamiento: +El bucle de entrenamiento que definimos anteriormente trabaja bien en una sola CPU o GPU. Pero usando la librería [Accelerate 🤗](https://github.com/huggingface/accelerate), con solo pocos ajustes podemos habilitar el entrenamiento distribuido en múltiples GPUs o CPUs. Comenzando con la creación de los dataloaders de entrenamiento y validación, aquí se muestra como luce nuestro bucle de entrenamiento: ```py from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler @@ -242,7 +242,7 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -Y aqui están los cambios: +Y aquí están los cambios: ```diff + from accelerate import Accelerator @@ -286,15 +286,17 @@ Y aqui están los cambios: progress_bar.update(1) ``` -La primera línea a agregarse es la línea del import. La segunda línea crea un objeto `Accelerator` que revisa el ambiente e inicializa la configuración distribuida apropiada. La libreria Accelerate 🤗 se encarga de asignarte el dispositivo, para que puedas remover las líneas que ponen el modelo en el dispositivo (o si prefieres, cámbialas para usar el `accelerator.device` en lugar de `device`). +La primera línea a agregarse es la línea del `import`. La segunda línea crea un objeto `Accelerator` que revisa el ambiente e inicializa la configuración distribuida apropiada. La librería 🤗 Accelerate se encarga de asignarte el dispositivo, para que puedas remover las líneas que ponen el modelo en el dispositivo (o si prefieres, cámbialas para usar el `accelerator.device` en lugar de `device`). -Ahora la mayor parte del trabajo se hace en la línea que envia los dataloaders, el modelo y el optimizador al `accelerator.prepare()`. Este va a envolver esos objetos en el contenedor apropiado para asegurarse que tu entrenamiento distribuido funcione como se espera. Los cambios que quedan son remover la línea que coloca el lote en el `device` (de nuevo, si deseas dejarlo asi bastaría con cambiarlo para que use el `accelerator.device`) y reemplazar `loss.backward()` con `accelerator.backward(loss)`. +Ahora la mayor parte del trabajo se hace en la línea que envía los `dataloaders`, el modelo y el optimizador al `accelerator.prepare()`. Este va a envolver esos objetos en el contenedor apropiado para asegurarse que tu entrenamiento distribuido funcione como se espera. Los cambios que quedan son remover la línea que coloca el lote en el `device` (de nuevo, si deseas dejarlo así bastaría con cambiarlo para que use el `accelerator.device`) y reemplazar `loss.backward()` con `accelerator.backward(loss)`. -⚠️ Para obtener el beneficio de la aceleración ofrecida por los TPUs de la nube, recomendamos rellenar las muestras hasta una longitud fija con los argumentos `padding="max_length"` y `max_length` del tokenizador. + ⚠️ Para obtener el beneficio de la aceleración ofrecida por los TPUs de la + nube, recomendamos rellenar las muestras hasta una longitud fija con los + argumentos `padding="max_length"` y `max_length` del tokenizador. -Si deseas copiarlo y pegarlo para probar, así es como luce el bucle completo de entrenamiento con Accelerate 🤗: +Si deseas copiarlo y pegarlo para probar, así es como luce el bucle completo de entrenamiento con 🤗 Accelerate: ```py from accelerate import Accelerator @@ -334,6 +336,7 @@ for epoch in range(num_epochs): ``` Colocando esto en un script `train.py` permitirá que el mismo sea ejecutable en cualquier configuración distribuida. Para probarlo en tu configuración distribuida, ejecuta el siguiente comando: + ```bash accelerate config ``` @@ -354,4 +357,4 @@ from accelerate import notebook_launcher notebook_launcher(training_function) ``` -Puedes encontrar más ejemplos en el [repositorio Accelerate 🤗](https://github.com/huggingface/accelerate/tree/main/examples). +Puedes encontrar más ejemplos en el [repositorio 🤗 Accelerate](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/es/chapter3/5.mdx b/chapters/es/chapter3/5.mdx new file mode 100644 index 000000000..0fba0735b --- /dev/null +++ b/chapters/es/chapter3/5.mdx @@ -0,0 +1,24 @@ + + +# Ajuste de modelos, ¡hecho! + + + +¡Qué divertido! En los dos primeros capítulos aprendiste sobre modelos y tokenizadores, y ahora sabes cómo ajustarlos a tus propios datos. Para recapitular, en este capítulo: + +{#if fw === 'pt'} + +- Aprendiste sobre los conjuntos de datos del [Hub](https://huggingface.co/datasets) +- Aprendiste a cargar y preprocesar conjuntos de datos, incluyendo el uso de padding dinámico y los "collators" +- Implementaste tu propio ajuste (fine-tuning) y cómo evaluar un modelo +- Implementaste un bucle de entrenamiento de bajo nivel +- Utilizaste 🤗 Accelerate para adaptar fácilmente tu bucle de entrenamiento para que funcione en múltiples GPUs o TPUs + +{:else} + +- Aprendiste sobre los conjuntos de datos en [Hub](https://huggingface.co/datasets) +- Aprendiste a cargar y preprocesar conjuntos de datos +- Aprendiste a ajustar (fine-tuning) y evaluar un modelo con Keras +- Implementaste una métrica personalizada + +{/if} diff --git a/chapters/es/chapter3/6.mdx b/chapters/es/chapter3/6.mdx new file mode 100644 index 000000000..5e5a1ba9f --- /dev/null +++ b/chapters/es/chapter3/6.mdx @@ -0,0 +1,333 @@ + + + + +# Quiz de final de capítulo + + + +A ver qué has aprendido en este capítulo: + +### 1. El dataset `emotion` contiene mensajes de Twitter etiquetados con emociones. Búscalo en el [Hub](https://huggingface.co/datasets), y lee la tarjeta del dataset. ¿Cuál de estas no es una de sus emociones básicas? + + + +### 2. Busca el dataset `ar_sarcasm` en el [Hub](https://huggingface.co/datasets). ¿Con qué tarea es compatible? + +tarjeta del dataset.", + }, + { + text: "Reconocimiento de entidades nombradas", + explain: + "No es correcto, echa otro vistazo a la tarjeta del dataset.", + }, + { + text: "Responder preguntas", + explain: + "No es correcto, echa otro vistazo a la tarjeta del dataset.", + }, + ]} +/> + +### 3. ¿Cómo se procesan un par de frases según el modelo BERT? + +[SEP] para separar las dos frases, ¡pero falta algo más!", + }, + { + text: "[CLS] tokens_frase_1 tokens_frase_2", + explain: + "Se necesita un token especial [CLS] al principio, ¡pero falta algo más!", + }, + { + text: "[CLS] tokens_frase_1 [SEP] tokens_frase_2 [SEP]", + explain: "¡Correcto!", + correct: true, + }, + { + text: "[CLS] tokens_frase_1 [SEP] tokens_frase_2", + explain: + "Se necesita un token especial [CLS] al principio y un token especial [SEP] para separar las dos frases, ¡pero falta algo más!", + }, + ]} +/> + +{#if fw === 'pt'} + +### 4. ¿Cuáles son las ventajas del método `Dataset.map()`? + + + +### 5. ¿Qué significa padding dinámico? + + + +### 6. ¿Cuál es el objetivo de la función "collate"? + +DataCollatorWithPadding en especial.', + }, + { + text: "Combina todas las muestras del conjunto de datos en un lote.", + explain: + '¡Correcto! Puedes pasar una función "collate" como argumento a un DataLoader. Nosotros usamos la función DataCollatorWithPadding, que rellena todos los elementos de un lote para que tengan la misma longitud.', + correct: true, + }, + { + text: "Preprocesa todo el conjunto de datos.", + explain: + 'Eso sería una función de preprocesamiento, no una función "collate".', + }, + { + text: "Trunca las secuencias del conjunto de datos.", + explain: + 'Una función "collate" está relacionada con el procesamiento de lotes individuales, no del conjunto de datos completo. Si quieres truncar, puedes utilizar el argumento truncate del tokenizer.', + }, + ]} +/> + +### 7. ¿Qué ocurre cuando instancias una de las clases `AutoModelForXxx` con un modelo del lenguaje preentrenado (como `bert-base-uncased`) que corresponde a una tarea distinta de aquella para la que fue entrenado? + +AutoModelForSequenceClassification con bert-base-uncased, recibimos una advertencia al instanciar el modelo. La cabeza preentrenada no se puede utilizar para la tarea de clasificación de secuencias, por lo que es eliminada y se instancia una nueva cabeza con pesos aleatorios.", + correct: true, + }, + { + text: "La cabeza del modelo preentrenado es eliminada.", + explain: "Se necesita hacer algo más, inténtalo de nuevo.", + }, + { + text: "Nada, ya que el modelo se puede seguir ajustando para la otra tarea.", + explain: + "La cabeza del modelo preentrenado no fue entrenada para resolver esta tarea, ¡así que deberíamos eliminarla!", + }, + ]} +/> + +### 8. ¿Para qué sirve `TrainingArguments`? + +Trainer.", + explain: "¡Correcto!", + correct: true, + }, + { + text: "Especifica el tamaño del modelo.", + explain: + "El tamaño del modelo viene definido por la configuración del modelo, no por la clase TrainingArguments.", + }, + { + text: "Solo contiene los hiperparámetros utilizados para la evaluación.", + explain: + "En el ejemplo especificamos dónde se guardarán el modelo y sus checkpoints. ¡Inténtalo de nuevo!", + }, + { + text: "Solo contiene los hiperparámetros utilizados para el entrenamiento.", + explain: + "En el ejemplo también utilizamos evaluation_strategy, que afecta a la evaluación. ¡Inténtalo de nuevo!", + }, + ]} +/> + +### 9. ¿Por qué deberías utilizar la librería 🤗 Accelerate? + +Trainer, no con la librería 🤗 Accelerate. ¡Vuelve a intentarlo!", + }, + { + text: "Hace que nuestros bucles de entrenamiento funcionen con estrategias distribuidas.", + explain: + "¡Correcto! Con 🤗 Accelerate, tus bucles de entrenamiento funcionarán para múltiples GPUs y TPUs.", + correct: true, + }, + { + text: "Ofrece más funciones de optimización.", + explain: + "No, la librería 🤗 Accelerate no proporciona ninguna función de optimización.", + }, + ]} +/> + +{:else} + +### 4. ¿Qué ocurre cuando instancias una de las clases `TFAutoModelForXxx` con un modelo del lenguaje preentrenado (como `bert-base-uncased`) que corresponde a una tarea distinta de aquella para la que fue entrenado? + +TFAutoModelForSequenceClassification con bert-base-uncased, recibimos una advertencia al instanciar el modelo. La cabeza preentrenada no se puede utilizar para la tarea de clasificación de secuencias, por lo que es eliminada y se instancia una nueva cabeza con pesos aleatorios.", + correct: true, + }, + { + text: "La cabeza del modelo preentrenado es eliminada", + explain: "Se necesita hacer algo más, inténtalo de nuevo.", + }, + { + text: "Nada, ya que el modelo se puede seguir ajustando para la otra tarea.", + explain: + "La cabeza del modelo preentrenado no fue entrenada para resolver esta tarea, ¡así que deberíamos eliminarla!", + }, + ]} +/> + +### 5. Los modelos TensorFlow de `transformers` ya son modelos Keras. ¿Qué ventajas ofrece esto? + +TPUStrategy, incluyendo la inicialización del modelo.", + }, + { + text: "Puede aprovechar los métodos existentes, como compile(), fit() y predict().", + explain: + "¡Correcto! Una vez que tienes los datos, entrenar el modelo requiere muy poco esfuerzo.", + correct: true, + }, + { + text: "Tienes la oportunidad de aprender Keras a la vez que transformadores.", + explain: "Correcto, pero estamos buscando otra respuesta :)", + correct: true, + }, + { + text: "Puede calcular fácilmente las métricas relacionadas con el dataset.", + explain: + "Keras nos ayuda con el entrenamiento y la evaluación del modelo, no con el cálculo de métricas relacionadas con el dataset.", + }, + ]} +/> + +### 6. ¿Cómo puedes definir tu propia métrica personalizada? + +tf.keras.metrics.Metric.", + explain: "¡Genial!", + correct: true, + }, + { + text: "Utilizando la API funcional de Keras.", + explain: "¡Inténtalo de nuevo!", + }, + { + text: "Utilizando una función cuya firma sea metric_fn(y_true, y_pred).", + explain: "¡Correcto!", + correct: true, + }, + { + text: "Buscándolo en Google.", + explain: + "Esta no es la respuesta que estamos buscando, pero te podría ayudar a encontrarla.", + correct: true, + }, + ]} +/> + +{/if} diff --git a/chapters/es/chapter5/1.mdx b/chapters/es/chapter5/1.mdx index 31a9a4c07..07db42118 100644 --- a/chapters/es/chapter5/1.mdx +++ b/chapters/es/chapter5/1.mdx @@ -5,7 +5,7 @@ classNames="absolute z-10 right-0 top-0" /> -En el [Capítulo 3](/course/chapter3) tuviste tu primer acercamento a la librería 🤗 Datasets y viste que existían 3 pasos principales para ajustar un modelo: +En el [Capítulo 3](/course/chapter3) tuviste tu primer acercamiento a la librería 🤗 Datasets y viste que existían 3 pasos principales para ajustar un modelo: 1. Cargar un conjunto de datos del Hub de Hugging Face. 2. Preprocesar los datos con `Dataset.map()`. diff --git a/chapters/es/chapter5/2.mdx b/chapters/es/chapter5/2.mdx index a239ddf53..e68820743 100644 --- a/chapters/es/chapter5/2.mdx +++ b/chapters/es/chapter5/2.mdx @@ -50,7 +50,7 @@ De este modo, podemos ver que los archivos comprimidos son reemplazados por los -✎ Si te preguntas por qué hay un caracter de signo de admiración (`!`) en los comandos de shell, esto es porque los estamos ejecutando desde un cuaderno de Jupyter. Si quieres descargar y descomprimir el archivo directamente desde la terminal, elimina el signo de admiración. +✎ Si te preguntas por qué hay un carácter de signo de admiración (`!`) en los comandos de shell, esto es porque los estamos ejecutando desde un cuaderno de Jupyter. Si quieres descargar y descomprimir el archivo directamente desde la terminal, elimina el signo de admiración. @@ -129,7 +129,7 @@ Esto es exactamente lo que queríamos. Ahora podemos aplicar varias técnicas de -El argumento `data_files` de la función `load_dataset()` es muy flexible. Puede ser una única ruta de archivo, una lista de rutas o un diccionario que mapee los nombres de los conjuntos a las rutas de archivo. También puedes buscar archivos que cumplan con cierto patrón específico de acuerdo con las reglas usadas por el shell de Unix (e.g., puedes buscar todos los archivos JSON en una carpeta al definir `datafiles="*.json"`). Revisa la [documentación](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) para más detalles. +El argumento `data_files` de la función `load_dataset()` es muy flexible. Puede ser una única ruta de archivo, una lista de rutas o un diccionario que mapee los nombres de los conjuntos a las rutas de archivo. También puedes buscar archivos que cumplan con cierto patrón específico de acuerdo con las reglas usadas por el shell de Unix (e.g., puedes buscar todos los archivos JSON en una carpeta al definir `datafiles="*.json"`). Revisa la [documentación](https://huggingface.co/docs/datasets/loading#local-and-remote-files) para más detalles. @@ -161,6 +161,6 @@ Esto devuelve el mismo objeto `DatasetDict` que obtuvimos antes, pero nos ahorra -✏️ **¡Inténtalo!** Escoge otro dataset alojado en GitHub o en el [Repositorio de Machine Learning de UCI](https://archive.ics.uci.edu/ml/index.php) e intenta cargarlo local y remotamente usando las técnicas descritas con anterioridad. Para puntos extra, intenta cargar un dataset que esté guardado en un formato CSV o de texto (revisa la [documentación](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) pata tener más información sobre estos formatos). +✏️ **¡Inténtalo!** Escoge otro dataset alojado en GitHub o en el [Repositorio de Machine Learning de UCI](https://archive.ics.uci.edu/ml/index.php) e intenta cargarlo local y remotamente usando las técnicas descritas con anterioridad. Para puntos extra, intenta cargar un dataset que esté guardado en un formato CSV o de texto (revisa la [documentación](https://huggingface.co/docs/datasets/loading#local-and-remote-files) pata tener más información sobre estos formatos). diff --git a/chapters/es/chapter5/3.mdx b/chapters/es/chapter5/3.mdx index 241916c9d..daae902ea 100644 --- a/chapters/es/chapter5/3.mdx +++ b/chapters/es/chapter5/3.mdx @@ -11,7 +11,7 @@ La mayor parte del tiempo tus datos no estarán perfectamente listos para entren -## Subdivdiendo nuestros datos +## Subdividiendo nuestros datos De manera similar a Pandas, 🤗 Datasets incluye varias funciones para manipular el contenido de los objetos `Dataset` y `DatasetDict`. Ya vimos el método `Dataset.map()` en el [Capítulo 3](/course/chapter3) y en esta sección vamos a explorar otras funciones que tenemos a nuestra disposición. @@ -30,7 +30,7 @@ Dado que TSV es una variación de CSV en la que se usan tabulaciones en vez de c from datasets import load_dataset data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t es el caracter para tabulaciones en Python +# \t es el carácter para tabulaciones en Python drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") ``` @@ -54,7 +54,7 @@ drug_sample[:3] 'usefulCount': [36, 13, 128]} ``` -Puedes ver que hemos fijado la semilla en `Dataset.shuffle()` por motivos de reproducibilidad. `Dataset.select()` espera un interable de índices, así que incluimos `range(1000)` para tomar los primeros 1.000 ejemplos del conjunto de datos aleatorizado. Ya podemos ver algunos detalles para esta muestra: +Puedes ver que hemos fijado la semilla en `Dataset.shuffle()` por motivos de reproducibilidad. `Dataset.select()` espera un iterable de índices, así que incluimos `range(1000)` para tomar los primeros 1.000 ejemplos del conjunto de datos aleatorizado. Ya podemos ver algunos detalles para esta muestra: * La columna `Unnamed: 0` se ve sospechosamente como un ID anonimizado para cada paciente. * La columna `condition` incluye una mezcla de niveles en mayúscula y minúscula. @@ -215,7 +215,7 @@ drug_dataset["train"].sort("review_length")[:3] 'review_length': [1, 1, 1]} ``` -Como lo discutimos anteriormente, algunas reseñas incluyen una sola palabra, que si bien puede ser útil para el análisis de sentimientos, no sería tan informativa si quisieramos predecir la condición. +Como lo discutimos anteriormente, algunas reseñas incluyen una sola palabra, que si bien puede ser útil para el análisis de sentimientos, no sería tan informativa si quisiéramos predecir la condición. @@ -238,7 +238,7 @@ Como puedes ver, esto ha eliminado alrededor del 15% de las reseñas de nuestros -✏️ **¡Inténtalo!** Usa la función `Dataset.sort()` para inspeccionar las reseñas con el mayor número de palabras. Revisa la [documentación](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) para ver cuál argumento necesitas para ordenar las reseñas de mayor a menor. +✏️ **¡Inténtalo!** Usa la función `Dataset.sort()` para inspeccionar las reseñas con el mayor número de palabras. Revisa la [documentación](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) para ver cuál argumento necesitas para ordenar las reseñas de mayor a menor. @@ -312,7 +312,7 @@ Opciones | Tokenizador rápido | Tokenizador lento Esto significa que usar un tokenizador rápido con la opción `batched=True` es 30 veces más rápido que su contraparte lenta sin usar lotes. ¡Realmente impresionante! Esta es la razón principal por la que los tokenizadores rápidos son la opción por defecto al usar `AutoTokenizer` (y por qué se denominan "rápidos"). Estos logran tal rapidez gracias a que el código de los tokenizadores corre en Rust, que es un lenguaje que facilita la ejecución del código en paralelo. -La paralelización también es la razón para el incremento de 6x en la velocidad del tokenizador al ejecutarse por lotes: No puedes ejecutar una única operacón de tokenización en paralelo, pero cuando quieres tokenizar muchos textos al mismo tiempo puedes dividir la ejecución en diferentes procesos, cada uno responsable de sus propios textos. +La paralelización también es la razón para el incremento de 6x en la velocidad del tokenizador al ejecutarse por lotes: No puedes ejecutar una única operación de tokenización en paralelo, pero cuando quieres tokenizar muchos textos al mismo tiempo puedes dividir la ejecución en diferentes procesos, cada uno responsable de sus propios textos. `Dataset.map()` también tiene algunas capacidades de paralelización. Dado que no funcionan con Rust, no van a hacer que un tokenizador lento alcance el rendimiento de uno rápido, pero aún así pueden ser útiles (especialmente si estás usando un tokenizador que no tiene una versión rápida). Para habilitar el multiprocesamiento, usa el argumento `num_proc` y especifica el número de procesos para usar en `Dataset.map()`: @@ -329,7 +329,7 @@ tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_p También puedes medir el tiempo para determinar el número de procesos que vas a usar. En nuestro caso, usar 8 procesos produjo la mayor ganancia de velocidad. Aquí están algunos de los números que obtuvimos con y sin multiprocesamiento: -Opciones | Tokenizador rápido | Rokenizador lento +Opciones | Tokenizador rápido | Tokenizador lento :--------------:|:--------------:|:-------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s @@ -348,7 +348,7 @@ Que toda esta funcionalidad está incluida en un método es algo impresionante e -💡 Un _ejemplo_ en Machine Learning se suele definir como el conjunto de _features_ que le damos al modelo. En algunos contextos estos features serán el conjuto de columnas en un `Dataset`, mientras que en otros se pueden extraer mútiples features de un solo ejemplo que pertenecen a una columna –como aquí y en tareas de responder preguntas-. +💡 Un _ejemplo_ en Machine Learning se suele definir como el conjunto de _features_ que le damos al modelo. En algunos contextos estos features serán el conjunto de columnas en un `Dataset`, mientras que en otros se pueden extraer múltiples features de un solo ejemplo que pertenecen a una columna –como aquí y en tareas de responder preguntas-. @@ -385,7 +385,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -¿Por qué no funcionó? El mensaje de error nos da una pista: hay un desajuste en las longitudes de una de las columnas, siendo una de logitud 1.463 y otra de longitud 1.000. Si has revisado la [documentación de `Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), te habrás dado cuenta que estamos mapeando el número de muestras que le pasamos a la función: en este caso los 1.000 ejemplos nos devuelven 1.463 features, arrojando un error. +¿Por qué no funcionó? El mensaje de error nos da una pista: hay un desajuste en las longitudes de una de las columnas, siendo una de longitud 1.463 y otra de longitud 1.000. Si has revisado la [documentación de `Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map), te habrás dado cuenta que estamos mapeando el número de muestras que le pasamos a la función: en este caso los 1.000 ejemplos nos devuelven 1.463 features, arrojando un error. El problema es que estamos tratando de mezclar dos datasets de tamaños diferentes: las columnas de `drug_dataset` tendrán un cierto número de ejemplos (los 1.000 en el error), pero el `tokenized_dataset` que estamos construyendo tendrá más (los 1.463 en el mensaje de error). Esto no funciona para un `Dataset`, así que tenemos que eliminar las columnas del anterior dataset o volverlas del mismo tamaño del nuevo. Podemos hacer la primera operación con el argumento `remove_columns`: diff --git a/chapters/es/chapter5/4.mdx b/chapters/es/chapter5/4.mdx index 344fb0545..326a2a609 100644 --- a/chapters/es/chapter5/4.mdx +++ b/chapters/es/chapter5/4.mdx @@ -7,7 +7,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter5/section4.ipynb"}, ]} /> -Hoy en día es común que tengas que trabajar con dataset de varios GB, especialmente si planeas pre-entrenar un transformador como BERT o GPT-2 desde ceros. En estos casos, _solamente cargar_ los datos puede ser un desafío. Por ejemplo, el corpus de WebText utilizado para preentrenar GPT-2 consiste de más de 8 millones de documentos y 40 GB de texto. ¡Cargarlo en la RAM de tu computador portatil le va a causar un paro cardiaco! +Hoy en día es común que tengas que trabajar con dataset de varios GB, especialmente si planeas pre-entrenar un transformador como BERT o GPT-2 desde ceros. En estos casos, _solamente cargar_ los datos puede ser un desafío. Por ejemplo, el corpus de WebText utilizado para preentrenar GPT-2 consiste de más de 8 millones de documentos y 40 GB de texto. ¡Cargarlo en la RAM de tu computador portátil le va a causar un paro cardíaco! Afortunadamente, 🤗 Datasets está diseñado para superar estas limitaciones: te libera de problemas de manejo de memoria al tratar los datasets como archivos _proyectados en memoria_ (_memory-mapped_) y de límites de almacenamiento al hacer _streaming_ de las entradas en un corpus. @@ -45,7 +45,7 @@ Como podemos ver, hay 15.518.009 filas y dos columnas en el dataset, ¡un montó -✎ Por defecto, 🤗 Datasets va a descomprimir los archivos necesarios para cargar un dataset. Si quieres ahorrar espacio de almacenamiento, puedes usar `DownloadConfig(delete_extracted=True)` al argumento `download_config` de `load_dataset()`. Revisa la [documentación](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) para más detalles. +✎ Por defecto, 🤗 Datasets va a descomprimir los archivos necesarios para cargar un dataset. Si quieres ahorrar espacio de almacenamiento, puedes usar `DownloadConfig(delete_extracted=True)` al argumento `download_config` de `load_dataset()`. Revisa la [documentación](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) para más detalles. @@ -128,7 +128,7 @@ print( 'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' ``` -Aquí usamos el módulo `timeit` de Python para medir el tiempo de ejecución que se toma `code_snippet`. Tipicamemente, puedes iterar a lo largo de un dataset a una velocidad de unas cuantas décimas de un GB por segundo. Esto funciona muy bien para la gran mayoría de aplicaciones, pero algunas veces tendrás que trabajar con un dataset que es tan grande para incluso almacenarse en el disco de tu computador. Por ejemplo, si quisieramos descargar el _Pile_ completo ¡necesitaríamos 825 GB de almacenamiento libre! Para trabajar con esos casos, 🤗 Datasets puede trabajar haciendo _streaming_, lo que permite la descarga y acceso a los elementos sobre la marcha, sin necesidad de descargar todo el dataset. Veamos cómo funciona: +Aquí usamos el módulo `timeit` de Python para medir el tiempo de ejecución que se toma `code_snippet`. Típicamemente, puedes iterar a lo largo de un dataset a una velocidad de unas cuantas décimas de un GB por segundo. Esto funciona muy bien para la gran mayoría de aplicaciones, pero algunas veces tendrás que trabajar con un dataset que es tan grande para incluso almacenarse en el disco de tu computador. Por ejemplo, si quisieramos descargar el _Pile_ completo ¡necesitaríamos 825 GB de almacenamiento libre! Para trabajar con esos casos, 🤗 Datasets puede trabajar haciendo _streaming_, lo que permite la descarga y acceso a los elementos sobre la marcha, sin necesidad de descargar todo el dataset. Veamos cómo funciona: @@ -173,7 +173,7 @@ next(iter(tokenized_dataset)) -💡 Para acelerar la tokenización con _streaming_ puedes definir `batched=True`, como lo vimos en la sección anterior. Esto va a procesar los ejemplos lote por lote. Recuerda que el tamaño por defecto de los lotes es 1.000 y puede ser expecificado con el argumento `batch_size`. +💡 Para acelerar la tokenización con _streaming_ puedes definir `batched=True`, como lo vimos en la sección anterior. Esto va a procesar los ejemplos lote por lote. Recuerda que el tamaño por defecto de los lotes es 1.000 y puede ser especificado con el argumento `batch_size`. @@ -189,7 +189,7 @@ next(iter(shuffled_dataset)) 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} ``` -En este ejemplo, seleccionamos un ejemplo aleatório de los primeros 10.000 ejemplos en el buffer. Apenas se accede a un ejemplo, su lugar en el buffer se llena con el siguiente ejemplo en el corpus (i.e., el ejemplo número 10.001). También peudes seleccionar elementos de un dataset _streamed_ usando las funciones `IterableDataset.take()` y `IterableDataset.skip()`, que funcionan de manera similar a `Dataset.select()`. Por ejemplo, para seleccionar los 5 primeros ejemplos en el dataset de abstracts de PubMed podemos hacer lo siguiente: +En este ejemplo, seleccionamos un ejemplo aleatorio de los primeros 10.000 ejemplos en el buffer. Apenas se accede a un ejemplo, su lugar en el buffer se llena con el siguiente ejemplo en el corpus (i.e., el ejemplo número 10.001). También puedes seleccionar elementos de un dataset _streamed_ usando las funciones `IterableDataset.take()` y `IterableDataset.skip()`, que funcionan de manera similar a `Dataset.select()`. Por ejemplo, para seleccionar los 5 primeros ejemplos en el dataset de abstracts de PubMed podemos hacer lo siguiente: ```py dataset_head = pubmed_dataset_streamed.take(5) @@ -209,12 +209,12 @@ list(dataset_head) 'text': 'Oxygen supply in rural africa: a personal experience ...'}] ``` -También podemos usar la función `IterableDataset.skip()` para crear conjuntos de entrenamiento y validación de un dataset ordenado aleatóriamente así: +También podemos usar la función `IterableDataset.skip()` para crear conjuntos de entrenamiento y validación de un dataset ordenado aleatoriamente así: ```py -# Skip the first 1,000 examples and include the rest in the training set +# Salta las primeras 1000 muestras e incluye el resto en el conjunto de entrenamiento train_dataset = shuffled_dataset.skip(1000) -# Take the first 1,000 examples for the validation set +# Toma las primeras 1000 muestras para el conjunto de validación validation_dataset = shuffled_dataset.take(1000) ``` @@ -283,4 +283,3 @@ next(iter(pile_dataset["train"])) Ya tienes todas las herramientas para cargar y procesar datasets de todas las formas y tamaños, pero a menos que seas muy afortunado, llegará un punto en tu camino de PLN en el que tendrás que crear el dataset tu mismo para resolver tu problema particular. De esto hablaremos en la siguiente sección. - diff --git a/chapters/es/chapter5/5.mdx b/chapters/es/chapter5/5.mdx index 113138fa5..21419d4e5 100644 --- a/chapters/es/chapter5/5.mdx +++ b/chapters/es/chapter5/5.mdx @@ -13,7 +13,7 @@ Algunas veces el dataset que necesitas para crear una aplicación de procesamien * Entrenar un _clasificador de etiquetas múltiples_ que pueda etiquetar issues con metadados basado en la descripción del issue (e.g., "bug", "mejora" o "pregunta") * Crear un motor de búsqueda semántica para encontrar qué issues coinciden con la pregunta del usuario -En esta sección nos vamos a enfocar en la creación del corpus y en la siguiente vamos a abordar la aplicación de búsqueda semántica. Para que esto sea un meta-proyecto, vamos a usar los issues de Github asociados con un proyecto popular de código abierto: 🤗 Datasets! Veamos cómo obtener los datos y explorar la información contenida en estos issues. +En esta sección nos vamos a enfocar en la creación del corpus y en la siguiente vamos a abordar la aplicación de búsqueda semántica. Para que esto sea un meta-proyecto, vamos a usar los issues de GitHub asociados con un proyecto popular de código abierto: 🤗 Datasets! Veamos cómo obtener los datos y explorar la información contenida en estos issues. ## Obteniendo los datos @@ -255,7 +255,7 @@ Como se muestra en la siguiente captura de pantalla, los comentarios asociados c Comments associated with an issue about 🤗 Datasets.
-El API REST de GitHub tiene un [endpoint `Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) que devuelve todos los comentarios asociados con un número de issue. Probémos este endpoint para ver qué devuelve: +El API REST de GitHub tiene un [endpoint `Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) que devuelve todos los comentarios asociados con un número de issue. Probemos este endpoint para ver qué devuelve: ```py issue_number = 2792 @@ -378,7 +378,7 @@ En este ejemplo, hemos creado un repositorio vacío para el dataset llamado `git -✏️ **¡Inténtalo!** Usa tu nombre de usuario de Hugging Face Hub para obtener un token y crear un repositorio vacío llamado `girhub-issues`. Recuerda **nunca guardar tus credenciales** en Colab o cualquier otro repositorio, ya que esta información puede ser aprovechada por terceros. +✏️ **¡Inténtalo!** Usa tu nombre de usuario de Hugging Face Hub para obtener un token y crear un repositorio vacío llamado `github-issues`. Recuerda **nunca guardar tus credenciales** en Colab o cualquier otro repositorio, ya que esta información puede ser aprovechada por terceros. @@ -427,7 +427,7 @@ Dataset({ -💡 También puedes subir un dataset al Hub de Hugging Face directamente desde la terminal usando `huggingface-cli` y un poco de Git. Revisa la [guía de 🤗 Datasets](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) para más detalles sobre cómo hacerlo. +💡 También puedes subir un dataset al Hub de Hugging Face directamente desde la terminal usando `huggingface-cli` y un poco de Git. Revisa la [guía de 🤗 Datasets](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) para más detalles sobre cómo hacerlo. @@ -445,7 +445,7 @@ En el Hub de Hugging Face, esta información se almacena en el archivo *README.m 2. Lee la [guía de 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sobre cómo crear tarjetas informativas y usarlas como plantilla. -Puedes crear el archivo *README.md* drectamente desde el Hub y puedes encontrar una plantilla de tarjeta en el repositorio `lewtun/github-issues`. Así se ve una tarjeta de dataset diligenciada: +Puedes crear el archivo *README.md* directamente desde el Hub y puedes encontrar una plantilla de tarjeta en el repositorio `lewtun/github-issues`. Así se ve una tarjeta de dataset diligenciada:
A dataset card. @@ -457,7 +457,7 @@ Puedes crear el archivo *README.md* drectamente desde el Hub y puedes encontrar -¡Eso es todo! Hemos visto que crear un buen dataset requiere de mucho esfuerzo de tu parte, pero afortunadamente subirlo y compartirlo con la comunidad no. En la siguiente sección usaremos nuestro nuevo dataset para crear un motor de búsqueda semántica con 🤗 Datasets que pueda emparejar pregunras con los issues y comentarios más relevantes. +¡Eso es todo! Hemos visto que crear un buen dataset requiere de mucho esfuerzo de tu parte, pero afortunadamente subirlo y compartirlo con la comunidad no. En la siguiente sección usaremos nuestro nuevo dataset para crear un motor de búsqueda semántica con 🤗 Datasets que pueda emparejar preguntas con los issues y comentarios más relevantes. diff --git a/chapters/es/chapter5/6.mdx b/chapters/es/chapter5/6.mdx index 2c4cbd13f..d92a1333c 100644 --- a/chapters/es/chapter5/6.mdx +++ b/chapters/es/chapter5/6.mdx @@ -22,7 +22,7 @@ {/if} -En la [sección 5](/course/chapter5/5) creamos un dataset de issues y comentarios del repositorio de Github de 🤗 Datasets. En esta sección usaremos esta información para construir un motor de búsqueda que nos ayude a responder nuestras preguntas más apremiantes sobre la librería. +En la [sección 5](/course/chapter5/5) creamos un dataset de issues y comentarios del repositorio de GitHub de 🤗 Datasets. En esta sección usaremos esta información para construir un motor de búsqueda que nos ayude a responder nuestras preguntas más apremiantes sobre la librería. @@ -189,7 +189,7 @@ Dataset({ -✏️ **¡Inténtalo!** Prueba si puedes usar la función `Dataset.map()` para "explotar" la columna `comments` en `issues_dataset` _sin_ necesidad de usar Pandas. Esto es un poco complejo; te recomendamos revisar la sección de ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentación de 🤗 Datasets para completar esta tarea. +✏️ **¡Inténtalo!** Prueba si puedes usar la función `Dataset.map()` para "explotar" la columna `comments` en `issues_dataset` _sin_ necesidad de usar Pandas. Esto es un poco complejo; te recomendamos revisar la sección de ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) de la documentación de 🤗 Datasets para completar esta tarea. @@ -266,7 +266,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) ``` -Ten en cuenta que hemos definido `from_pt=True` como un argumento del método `from_pretrained()`. Esto es porque el punto de control `multi-qa-mpnet-base-dot-v1` sólo tiene pesos de PyTorch, asi que usar `from_pt=True` los va a covertir automáticamente al formato TensorFlow. Como puedes ver, ¡es múy fácil cambiar entre frameworks usando 🤗 Transformers! +Ten en cuenta que hemos definido `from_pt=True` como un argumento del método `from_pretrained()`. Esto es porque el punto de control `multi-qa-mpnet-base-dot-v1` sólo tiene pesos de PyTorch, asi que usar `from_pt=True` los va a convertir automáticamente al formato TensorFlow. Como puedes ver, ¡es múy fácil cambiar entre frameworks usando 🤗 Transformers! {/if} diff --git a/chapters/es/chapter5/8.mdx b/chapters/es/chapter5/8.mdx index 4718d8faf..99f5e0ffd 100644 --- a/chapters/es/chapter5/8.mdx +++ b/chapters/es/chapter5/8.mdx @@ -1,6 +1,6 @@ -# Quiz +# Quiz de final de capítulo data_files de la función load_dataset() psara cargar archivos remotos.", + explain: "¡Correcto! Puedes pasar URL al argumento data_files de la función load_dataset() para cargar archivos remotos.", correct: true }, ]} @@ -41,7 +41,7 @@ from datasets import load_dataset dataset = load_dataset("glue", "mrpc", split="train") ``` -¿Cuál de los sigientes comandos a a producir una muestra aleatoria de 50 elementos de `dataset`? +¿Cuál de los siguientes comandos a a producir una muestra aleatoria de 50 elementos de `dataset`? pets_dataset.filter(lambda x['name'].startswith('L'))", - explain: "Esto es incorrecrto. Una función lambda toma la forma general lambda *arguments* : *expression*, así que tienes que definir los argumentos en este caso." + explain: "Esto es incorrecto. Una función lambda toma la forma general lambda *arguments* : *expression*, así que tienes que definir los argumentos en este caso." }, { - text: "Crear una funcióin como def filter_names(x): return x['name'].startswith('L') y ejecutar pets_dataset.filter(filter_names).", + text: "Crear una función como def filter_names(x): return x['name'].startswith('L') y ejecutar pets_dataset.filter(filter_names).", explain: "¡Correcto! Justo como con Dataset.map(), puedes pasar funciones explícitas a Dataset.filter(). Esto es útil cuando tienes una lógica compleja que no es adecuada para una función lambda. ¿Cuál de las otras soluciones podría funcionar?", correct: true } @@ -150,7 +150,7 @@ dataset[0] ]} /> -### 7. ¿Cuáles son los principales beneficiones de crear una tarjeta para un dataset? +### 7. ¿Cuáles son los principales beneficios de crear una tarjeta para un dataset? + +En el [Capítulo 3](/course/chapter3), revisamos como hacer fine-tuning a un modelo para una tarea dada. Cuando hacemos eso, usamos el mismo tokenizador con el que el modelo fue entrenado -- pero, ¿Qué hacemos cuando queremos entrenar un modelo desde cero? En estos casos, usar un tokenizador que fue entrenado en un corpus con otro dominio u otro lenguaje típicamente no es lo más óptimo. Por ejemplo un tokenizador que es entrenado en un corpus en Inglés tendrá un desempeño pobre en un corpus de textos en Japonés porque el uso de los espacios y de la puntuación es muy diferente entre los dos lenguajes. + + +En este capítulo, aprenderás como entrenar un tokenizador completamente nuevo en un corpus, para que luego pueda ser usado para pre-entrenar un modelo de lenguaje. Todo esto será hecho con la ayuda de la librería [🤗 Tokenizers](https://github.com/huggingface/tokenizers), la cual provee tokenizadores rápidos (_fast tokenizers_) en la librería [🤗 Transformers](https://github.com/huggingface/transformers). Miraremos de cerca todas las características que la provee la librería, y explorar cómo los tokenizadores rápidos (fast tokenizers) difieren de las versiones "lentas". + +Los temas a cubrir incluyen: + +* Cómo entrenar un tokenizador nuevo similar a los usados por un checkpoint dado en un nuevo corpus de texto. +* Las características especiales de los tokenizador rápidos ("fast tokenizers"). +* Las diferencias entre los tres principales algoritmos de tokenización usados en PLN hoy. +* Como construir un tokenizador desde cero con la librería 🤗 Tokenizers y entrenarlo en datos. + +Las técnicas presentadas en este capítulo te prepararán para la sección en el [Capítulo 7](/course/chapter7/6) donde estudiaremos cómo crear un modelo de lenguaje para Código Fuente en Python. Comenzaremos en primer lugar revisando qué significa "entrenar" un tokenizador. \ No newline at end of file diff --git a/chapters/es/chapter6/10.mdx b/chapters/es/chapter6/10.mdx new file mode 100644 index 000000000..0b9f89193 --- /dev/null +++ b/chapters/es/chapter6/10.mdx @@ -0,0 +1,283 @@ + + +# Quiz de Final de Capítulo[[end-of-chapter-quiz]] + + + +Probemos lo que aprendimos en este capítulo! + +### 1. Cuando debería entrenar un nuevo tokenizador? + + + +### 2. Cuál es la ventaja de usar un generador de listas de textos comparado con una lista de listas de textos al usar `train_new_from_iterator()`? + +train_new_from_iterator() acepta.", + explain: "Una lista de listas de textos es un tipo particular de generador de listas de textos, por lo que el método aceptará esto también. Intenta de nuevo!" + }, + { + text: "Evitarás cargar todo el conjunto de datos en memoria de una sóla vez.", + explain: "Correcto! Cada lote de textos será liberado de la memoria al ir iterando, y la ganancia será especialmente visible si usas la librería 🤗 Datasets para almacenar tus textos.", + correct: true + }, + { + text: "Esto permite que la librería 🤗 Tokenizers library use multiprocesamiento.", + explain: "No, usará multiprocesamiento en ambos casos." + }, + { + text: "El tokenizador que entrenarás generará mejores textos.", + explain: "El tokenizador no genera texto -- estás confundiéndolo con un modelo de lenguaje?" + } + ]} +/> + +### 3. Cuáles son las ventajas de utilizar un tokenizador "rápido"? + + + +### 4. Como hace el pipeline `token-classification` para manejar entidades que se extienden a varios tokens? + + + +### 5. Cómo hace el pipeline de `question-answering` para manejar contextos largos? + + + +### 6. Qué es la normalización? + + + +### 7. Qué es la pre-tokenización para un tokenizador de subpalabra? + + + +### 8. Selecciona las afirmaciones que aplican para el modelo de tokenización BPE. + + + +### 9. Selecciona las afirmaciones que aplican para el modelo de tokenizacion WordPiece. + + + +### 10. Selecciona las afirmaciones que aplican para el modelo de tokenización Unigram. + + diff --git a/chapters/es/chapter6/2.mdx b/chapters/es/chapter6/2.mdx new file mode 100644 index 000000000..d4e629374 --- /dev/null +++ b/chapters/es/chapter6/2.mdx @@ -0,0 +1,250 @@ +# Entrenar un nuevo tokenizador a partir de uno existente[[training-a-new-tokenizer-from-an-old-one]] + + + +Si un modelo de lenguaje no está disponible en el lenguaje en el que estás interesado, o si el corpus es muy diferente del lenguaje original en el que el modelo de lenguaje fue entrenado, es muy probable que quieras reentrenar el modelo desde cero utilizando un tokenizador adaptado a tus datos. Eso requerirá entrenar un tokenizador nuevo en tu conjunto de datos. Pero, ¿Qué significa eso exactamente? Cuando revisamos los tokenizadores por primera vez en el [Capítulo 2](/course/chapter2), vimos que la mayoría de los modelos basados en Transformers usan un algoritmo de _tokenización basado en subpalabras_. Para identificar qué subpalabras son de interés y ocurren más frecuentemente en el corpus deseado, el tokenizador necesita mirar de manera profunda todo el texto en el corpus -- un proceso al que llamamos *entrenamiento*. Las reglas exactas que gobiernan este entrenamiento dependen en el tipo de tokenizador usado, y revisaremos los 3 algoritmos principales más tarde en el capítulo. + + + + + + +⚠️ ¡Entrenar un tokenizador no es lo mismo que entrenar un modelo! Entrenar un modelo utiliza `stochastic gradient descent` para minimizar la pérdida (`loss`) en cada lote (`batch`). Es un proceso aleatorio por naturaleza (lo que signifiva que hay que fijar semillas para poder obterner los mismos resultados cuando se realiza el mismo entrenamiento dos veces). Entrenar un tokenizador es un proceso estadístico que intenta identificar cuales son las mejores subpalabras para un corpus dado, y las reglas exactas para elegir estas subpalabras dependen del algoritmo de tokenización. Es un proceso deterministico, lo que significa que siempre se obtienen los mismos resultados al entrenar el mismo algoritmo en el mismo corpus. + + + +## Ensamblando un Corpus[[assembling-a-corpus]] + +Hay una API muy simple en 🤗 Transformers que se puede usar para entrenar un nuevo tokenizador con las mismas características que uno existente: `AutoTokenizer.train_new_from_iterator()`. Para verlo en acción, digamos que queremos entrenar GPT-2 desde cero, pero en lenguaje distinto al Inglés. Nuestra primera tarea será reunir muchos datos en ese lenguaje en un corpus de entrenamiento. Para proveer ejemplos que todos serán capaces de entender no usaremos un lenguaje como el Ruso o el Chino, sino uno versión del inglés más especializado: Código en Python. + +La librería [🤗 Datasets](https://github.com/huggingface/datasets) nos puede ayudar a ensamblar un corpus de código fuente en Python. Usaremos la típica función `load_dataset()` para descargar y cachear el conjunto de datos [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Este conjunto de datos fue creado para el [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) y contiene millones de funciones de librerías open source en GitHub en varios lenguajes de programación. Aquí cargaremos la parte del conjunto de datos que está en Python: + +```py +from datasets import load_dataset + +# Esto puede tomar varios minutos para cargarse, así que ¡Agarra un té o un café mientras esperas! +raw_datasets = load_dataset("code_search_net", "python") +``` +Podemos echar un vistazo a la porción de entrenamiento para ver a qué columnas tenemos acceso: + +```py +raw_datasets["train"] +``` + +```python out +Dataset({ + features: ['repository_name', 'func_path_in_repository', 'func_name', 'whole_func_string', 'language', + 'func_code_string', 'func_code_tokens', 'func_documentation_string', 'func_documentation_tokens', 'split_name', + 'func_code_url' + ], + num_rows: 412178 +}) +``` + +Podemos ver que el conjunto de datos separa los docstrings del código y sugiere una tokenización de ambos. Acá, sólo utilizaremos la columna `whole_func_string` para entrenar nuestro tokenizador. Podemos mirar un ejemplo de estas funciones utilizando algún índice en la porción de "train". + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` +lo cual debería imprimir lo siguiente: + +```out +def handle_simple_responses( + self, timeout_ms=None, info_cb=DEFAULT_MESSAGE_CALLBACK): + """Accepts normal responses from the device. + + Args: + timeout_ms: Timeout in milliseconds to wait for each response. + info_cb: Optional callback for text sent from the bootloader. + + Returns: + OKAY packet's message. + """ + return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) +``` + +Lo primero que necesitamos hacer es transformar el dataset en un _iterador_ de listas de textos -- por ejemplo, una lista de listas de textos. utilizar listas de textos permitirá que nuestro tokenizador vaya más rápido (entrenar en batches de textos en vez de procesar textos de manera individual uno por uno), y debería ser un iterador si queremos evitar tener cargar todo en memoria de una sola vez. Si tu corpus es gigante, querrás tomar ventaja del hecho que 🤗 Datasets no carga todo en RAM sino que almacena los elementos del conjunto de datos en disco. +Hacer lo siguiente debería crear una lista de listas de 1000 textos cada una, pero cargando todo en memoria: + +```py +# Don't uncomment the following line unless your dataset is small! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Al usar un generador de Python, podemos evitar que Python cargue todo en memoria hasta que sea realmente necesario. Para crear dicho generador, solo necesitas reemplazar los corchetes con paréntesis: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` +Esta línea de código no trae ningún elemento del conjunto de datos; sólo crea un objeto que se puede usar en Python con un ciclo `for`. Los textos sólo serán cargados cuando los necesites (es decir, cuando estás un paso del ciclo `for` que los requiera), y sólo 1000 textos a la vez serán cargados. De eso forma no agotarás toda tu memoria incluso si procesas un conjunto de datos gigante. + +El problema con un objeto generador es que sólo se puede usar una vez. Entonces en vea que el siguiente código nos entregue una lista de los primeros 10 dígitos dos veces: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` +Nos lo entrega una vez, y luego una lista vacía: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +Es por eso que definimos una función que retorne un generador: + +```py +def get_training_corpus(): + return ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) + ) + + +training_corpus = get_training_corpus() +``` +También puedes definir un generador dentro de un ciclo `for`utilizando el comando `yield`: + +```py +def get_training_corpus(): + dataset = raw_datasets["train"] + for start_idx in range(0, len(dataset), 1000): + samples = dataset[start_idx : start_idx + 1000] + yield samples["whole_func_string"] +``` + +el cual producirá el mismo generador anterior, pero también permitiendo usar lógicas más complejas de las que se puede hacer en un `list comprehension`. + +## Entrenar un nuevo Tokenizador[[training-a-new-tokenizer]] + +Ahora que tenemos nuestro corpus en la forma de un iterador de lotes de textos, estamos listos para entrenar un nuevo tokenizador. Para hacer esto, primero tenemos que cargar el tokenizador que queremos utilizar con nuestro modelo (en este caso, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` +Aunque vamos a entrenar un nuevo tokenizador, es una buena idea hacer esto para evitar comenzar de cero completamente. De esta manera, no tendremos que especificar nada acerca del algoritmo de tokenización o de los tokens especiales que queremos usar; nuestro tokenizador será exactamente el mismo que GPT-2, y lo único que cambiará será el vocabulario, el cuál será determinado por el entrenamiento en nuestro corpus. + +Primero, echemos un vistazo a cómo este tokenizador tratará una función de ejemplo: + +```py +example = '''def add_numbers(a, b): + """Add the two numbers `a` and `b`.""" + return a + b''' + +tokens = old_tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'n', 'umbers', '(', 'a', ',', 'Ġb', '):', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', + 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Este tokenizador tiene algunos símbolos especiales como `Ġ` y `Ċ`, lo cual denota espacios y nuevas líneas (saltos de líneas) respectivamente. Como podemos ver, esto no es muy eficiente: el tokenizador retorna tokens individuales para cada espacio, cuando debería agrupar los niveles de indentación (dado que tener grupos de cuatro u ocho espacios va a ser muy común en el uso de código). Además separa el nombre de la función de manera un poco extraña al no estar acostumbrado a ver palabras separadas con el caracter `_`. + +Entrenemos nuestro nuevo tokenizador y veamos si resuelve nuestros problemas. Para esto usaremos el método `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` +Este comando puede tomar tiempo si tu corpus es muy largo, pero para este conjunto de datos de 1.6 GB de textos es muy rápido (1 minuto 16 segundos en un AMD Ryzen 9 3900X CPU con 12 núcleos). + +Nota que `AutoTokenizer.train_new_from_iterator()` sólo funciona si el tokenizador que estás usando es un tokenizador rápido (_fast tokenizer_). Cómo verás en la siguiente sección, la librería 🤗 Transformers contiene 2 tipos de tokenizadores: algunos están escritos puramente en Python y otros (los rápidos) están respaldados por la librería 🤗 Tokenizers, los cuales están escritos en lenguaje de programación [Rust](https://www.rust-lang.org). Python es el lenguaje mayormente usado en ciencia de datos y aplicaciones de deep learning, pero cuando algo necesita ser paralelizado para ser rápido, tiene que ser escrito en otro lenguaje. Por ejemplo, las multiplicaciones matriciales que están en el corazón de los cómputos de un modelo están escritos en CUDA, una librería optimizada en C para GPUs. del computation are written in CUDA, an optimized C library for GPUs. + +Entrenar un nuevo tokenizador en Python puro sería insoportablemente lento, razón pr la cual desarrollamos la librería 🤗 Tokenizers. Notar que de la misma manera que no tuviste que aprender el lenguaje CUDA para ser capaz de ejecutar tu modelo en un barch de inputs en una GPU, no necesitarás aprender Rust para usar los tokenizadores rápidos (_fast tokenizers_). La librería 🤗 Tokenizers provee bindings en Python para muchos métodos que internamente llaman trozos de código en Rust; por ejemplo, para paralelizar el entrenamiento de un nuevo tokenizador o, como vimos en el [Capítulo 3](/course/chapter3), la tokenización de un batch de inputs. + +La mayoría de los modelos Transformers tienen un tokenizador rápido (_Fast Tokenizer_) disponible (hay algunas excepciones que se pueden revisar [acá](https://huggingface.co/transformers/#supported-frameworks)), y la API `AutoTokenizer` siempre seleccionar un tokenizador rápido para ti en caso de estar disponible. En la siguiente sección echaremos un vistazo a algunas de las características especiales que tienen los tokenizadores rápidos, los cuales serán realmente útiles para tareas como clasificación de tokens y question answering. Antes de sumergirnos en eso, probemos nuestro tokenizador recién entrenado en nuestro ejemplo previo: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Acá nuevamente vemos los símbolos especiales `Ġ` y `Ċ` que denotan espacios y nuevas líneas (saltos de líneas), pero también podemos ver que nuestro tokenizador aprendió algunos tokens que son altamente específicos para el corpus de funciones en Python: por ejemplo, está el token `ĊĠĠĠ` que representa una indentación y un token `Ġ"""` que representan la triple comilla para comenzar un docstring. El tokenizador también divide correctamente los nombres de funciones usando `_`. Esta es una representación más compacta ya que utilizar un tokenizador común y corriente en inglés en el mismo ejemplo nos dara una oración más larga: + + + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` +Echemos un vistazo al siguiente ejemplo: + +```python +example = """class LinearLayer(): + def __init__(self, input_size, output_size): + self.weight = torch.randn(input_size, output_size) + self.bias = torch.zeros(output_size) + + def __call__(self, x): + return x @ self.weights + self.bias + """ +tokenizer.tokenize(example) +``` + +```python out +['class', 'ĠLinear', 'Layer', '():', 'ĊĠĠĠ', 'Ġdef', 'Ġ__', 'init', '__(', 'self', ',', 'Ġinput', '_', 'size', ',', + 'Ġoutput', '_', 'size', '):', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'weight', 'Ġ=', 'Ġtorch', '.', 'randn', '(', 'input', '_', + 'size', ',', 'Ġoutput', '_', 'size', ')', 'ĊĠĠĠĠĠĠĠ', 'Ġself', '.', 'bias', 'Ġ=', 'Ġtorch', '.', 'zeros', '(', + 'output', '_', 'size', ')', 'ĊĊĠĠĠ', 'Ġdef', 'Ġ__', 'call', '__(', 'self', ',', 'Ġx', '):', 'ĊĠĠĠĠĠĠĠ', + 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] +``` + +En adición al token correspondiente a la indentación, también podemos ver un token para la doble indentación: `ĊĠĠĠĠĠĠĠ`. Palabras espaciales del lenguaje Python como `class`, `init`, `call`, `self`, and `return` son tokenizadas como un sólo token y podemos ver que además de dividir en `_` y `.`, el tokenizador correctamente divide incluso en nombres que usan camel-case: `LinearLayer` es tokenizado como `["ĠLinear", "Layer"]`. + +## Guardar el Tokenizador[[saving-the-tokenizer]] + + +Para asegurarnos que podemos usar el tokenizador más tarde, necesitamos guardar nuestro nuevo tokenizador. Al igual que los modelos, esto se hace con el método `save_pretrained()`. + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Esto creará una nueva carpeta llamada *code-search-net-tokenizer*, la cual contendrá todos los archivos que el tokenizador necesita para ser cargado. Si quieres compartir el tokenizador con tus colegas y amigos, puedes subirlo al Hub logeando en tu cuenta. Si estás trabajando en notebooks, hay una función conveniente para ayudarte a hacer esto: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Esto mostrará un widget donde puedes ingresar tus credenciales de Hugging Face. En caso de no estar usando un notebook, puedes escribir la siguiente línea en tu terminal: + +```bash +huggingface-cli login +``` + +Una vez logueado puedes enviar tu tokenizador al Hub ejecutando el siguiente comando:: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` +Esto creará un nuevo repositorio en tu namespace con el nombre `code-search-net-tokenizer`, conteniendo el archivo del tokenizador. Luego puedes cargar tu tokenizador desde donde quieras utilizando método `from_pretrained()`. + +```py +# Replace "huggingface-course" below with your actual namespace to use your own tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` +Ya estás listo para entrenar un modelo de lenguaje desde cero y hacer fine-tuning en la tarea que desees. Llegaremos a eso en el [Capítulo 7](/course/chapter7), pero primero en el resto del capítulo miraremos más de cerca los tokenizadores rápidos (_Fast Tokenizers_) y explorar en detalle lo que pasa en realidad pasa cuando llamamos al método `train_new_from_iterator()`. diff --git a/chapters/es/chapter6/3.mdx b/chapters/es/chapter6/3.mdx new file mode 100644 index 000000000..65392631a --- /dev/null +++ b/chapters/es/chapter6/3.mdx @@ -0,0 +1,478 @@ + + +# Los poderes especiales de los Tokenizadores Rápidos (Fast tokenizers)[[fast-tokenizers-special-powers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + + + +En esta sección miraremos más de cerca las capacidades de los tokenizadores en 🤗 Transformers. Hasta ahora sólo los hemos utilizado para tokenizar las entradas o decodificar los IDs en texto, pero los tokenizadores -- especialmente los que están respaldados en la librería 🤗 Tokenizers -- pueden hacer mucho más. Para ilustrar estas características adicionales, exploraremos cómo reproducir los resultados de los pipelines de `clasificación de tokens` (al que llamamos ner) y `question-answering` que nos encontramos en el [Capítulo 1](/course/chapter1). + + + +En la siguiente discusión, a menudo haremos la diferencia entre un tokenizador "lento" y uno "rápido". Los tokenizadores lentos son aquellos escritos en Python dentro de la librería Transformers, mientras que las versiones provistas por la librería 🤗 Tokenizers, son los que están escritos en Rust. Si recuerdas la tabla del [Capítulo 5](/course/chapter5/3) en la que se reportaron cuanto tomó a un tokenizador rápido y uno lento tokenizar el Drug Review Dataset, ya deberías tener una idea de por qué los llamamos rápidos y lentos: + + +| | Tokenizador Rápido | Tokenizador Lento +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Al tokenizar una sóla oración, no siempre verás una diferencia de velocidad entre la versión lenta y la rápida del mismo tokenizador. De hecho, las versión rápida podría incluso ser más lenta! Es sólo cuando se tokenizan montones de textos en paralelos al mismo tiempo que serás capaz de ver claramente la diferencia. + + + +## Codificación en Lotes (Batch Encoding)[[batch-encoding]] + + + +La salida de un tokenizador no siempre un simple diccionario; lo que se obtiene en realidad es un objeto especial `BatchEncoding`. Es una subclase de un diccionario (razón por la cual pudimos indexar el resultado sin ningún problema anteriormente), pero con métodos adicionales que son mayormente usados por los tokenizadores rápidos (_Fast Tokenizers_). + +Además de sus capacidad en paralelización, la funcionalidad clave de un tokenizador rápido es que siempre llevan registro de la porción de texto de la cual los tokens finales provienen -- una característica llamada *offset mapping*. Esto permite la capacidad de mapear cada palabra con el token generado o mapear cada caracter del texto original con el token respectivo y viceversa. + +Echemos un vistazo a un ejemplo: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +encoding = tokenizer(example) +print(type(encoding)) +``` +Como se mencionó previamente, obtenemos un objeto de tipo `BatchEncoding` como salida del tokenizador: + +```python out + +``` + +Dado que la clase `AutoTokenizer` escoge un tokenizador rápido por defecto, podemos usar los métodos adicionales que este objeto `BatchEncoding` provee. Tenemos dos manera de chequear si el tokenizador es rápido o lento. Podemos chequear el atributo `is_fast` del tokenizador: + +```python +tokenizer.is_fast +``` + +```python out +True +``` +o chequear el mismo atributo de nuestro `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Veamos lo que un tokenizador rápido nos permite hacer. Primero podemos acceder a los tokens sin tener que convertir los IDs a tokens: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +En este caso el token con índice 5 is `##yl`, el cual es parte de la palabra "Sylvain" en la oración original. Podemos también utilizar el método `word_ids()` para obtener el índice de la palabra de la que cada token proviene: + +```py +encoding.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] +``` + +Podemos ver que los tokens especiales del tokenizador `[CLS]` y `[SEP]` están mapeados a `None`, y que cada token está mapeado a la palabra de la cual se origina. Esto es especialmente útil para determinar si el token está al inicio de la palabra o si dos tokens están en la misma palabra. POdríamos confiar en el prefijo `[CLS]` and `[SEP]` para eso, pero eso sólo funciona para tokenizadores tipo BERT; este método funciona para cualquier tipo de tokenizador mientras sea de tipo rápido. En el próximo capítulo, veremos como podemos usar esta capacidad para aplicar etiquetas para cada palabra de manera apropiada en tareas como Reconocimiento de Entidades (Named Entity Recognition NER), y etiquetado de partes de discurso (part-of-speech POS tagging). También podemos usarlo para enmascarar todos los tokens que provienen de la misma palabra en masked language modeling (una técnica llamada _whole word masking_). + + + +La noción de qué es una palabra es complicada. Por ejemplo "I'll" (la contracción de "I will" en inglés) ¿cuenta como una o dos palabras? De hecho depende del tokenizador y la operación de pretokenización que aplica. Algunos tokenizadores sólo separan en espacios, por lo que considerarán esto como una sóla palabra. Otros utilizan puntuación por sobre los espacios, por lo que lo considerarán como dos palabras. + +✏️ **Inténtalo!** Crea un tokenizador a partir de los checkpoints `bert-base-cased` y `roberta-base` y tokeniza con ellos "81s". ¿Qué observas? Cuál son los IDs de la palabra? + + + +De manera similar está el método `sentence_ids()` que podemos utilizar para mapear un token a la oración de la cuál proviene (aunque en este caso el `token_type_ids` retornado por el tokenizador puede darnos la misma información). + +Finalmente, podemos mapear cualquier palabra o token a los caracteres originales del texto, y viceversa, utilizando los métodos `word_to_chars()` o `token_to_chars()` y los métodos `char_to_word()` o `char_to_token()`. Por ejemplo el método `word_ids()` nos dijo que `##yl` es parte de la palabra con índice 3, pero qué palabra es en la oración? Podemos averiguarlo así: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Como mencionamos previamente, todo esto funciona gracias al hecho de que los tokenizadores rápidos llevan registro de la porción de texto del que cada token proviene en una lista de *offsets*. Para ilustrar sus usos, a continuación mostraremos como replicar los resultados del pipeline de `clasificación de tokens` de manera manual. + + + +✏️ **Inténtalo!** Crea tu propio texto de ejemplo y ve si puedes entender qué tokens están asociados con el ID de palabra, y también cómo extraer los caracteres para una palabra. Como bonus, intenta usar dos oraciones como entrada/input y ve si los IDs de oraciones te hacen sentido. + + + +## Dentro del Pipeline de `clasificación de tokens`[[inside-the-token-classification-pipeline]] + +En el [Capítulo 1](/course/chapter1) tuvimos nuestra primera probada aplicando NER -- donde la tarea es identificar qué partes del texto corresponden a entidades como personas, locaciones, u organizaciones -- con la función `pipeline()` de la librería 🤗 Transformers. Luego en el [Capítulo 2](/course/chapter2), vimos como un pipeline agrupa las tres etapas necesarias para obtener predicciones desde un texto crudo: tokenización, pasar los inputs a través del modelo, y post-procesamiento. Las primeras dos etapas en el pipeline de `clasificación de tokens` son las mismas que en otros pipelines, pero el post-procesamiento es un poco más complejo -- ×+/¡veamos cómo! + + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obteniendo los resultados base con el pipeline[[getting-the-base-results-with-the-pipeline]] + +Primero, agarremos un pipeline de clasificación de tokens para poder tener resultados que podemos comparar manualmente. El usado por defecto es [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); el que realiza NER en oraciones: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +El modelo indentificó apropiadamente cada token generado por "Sylvain" como una persona, cada token generado por "Hugging Face" como una organización y el token "Brooklyn" como una locación. Podemos pedirle también al pipeline que agrupe los tokens que corresponden a la misma identidad: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification", aggregation_strategy="simple") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +La estrategia de agregación (`aggregation_strategy`) elegida cambiará los puntajes calculados para cada entidad agrupada. Con `"simple"` el puntaje es la media los puntajes de cada token en la entidad dada: por ejemplo, el puntaje de "Sylvain" es la media de los puntajes que vimos en el ejemplo previo para los tokens `S`, `##yl`, `##va`, y `##in`. Otras estrategias disponibles son: + +- `"first"`, donde el puntaje de cada entidad es el puntaje del primer token de la entidad (para el caso de "Sylvain" sería 0.9923828, el puntaje del token `S`) +- `"max"`, donde el puntaje de cada entidad es el puntaje máximo de los tokens en esa entidad (para el caso de "Hugging Face" sería 0.98879766, el puntaje de "Face") +- `"average"`, donde el puntaje de cada entidad es el promedio de los puntajes de las palabras que componen la entidad (para el caso de "Sylvain" no habría diferencia con la estrategia "simple", pero "Hugging Face" tendría un puntaje de 0.9819, el promedio de los puntajes para "Hugging", 0.975, y "Face", 0.98879) + +Ahora veamos como obtener estos resultados sin utilizar la función `pipeline()`! + +### De los inputs a las predicciones[[from-inputs-to-predictions]] + +{#if fw === 'pt'} + +Primero necesitamos tokenizar nuestro input y pasarlo a través del modelo. Esto es exactamente lo que se hace en el [Capítulo 2](/course/chapter2); instanciamos el tokenizador y el modelo usando las clases `AutoXxx` y luego los usamos en nuestro ejemplo: + + +```py +from transformers import AutoTokenizer, AutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="pt") +outputs = model(**inputs) +``` + +Dado que estamos usando acá `AutoModelForTokenClassification`, obtenemos un conjunto de logits para cada token en la secuencia de entrada: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +Primero necesitamos tokenizar nuestro input y pasarlo por nuestro modelo. Esto es exactamente lo que se hace en el [Capítulo 2](/course/chapter2); instanciamos el tokenizador y el modelo usando las clases `TFAutoXxx` y luego los usamos en nuestro ejemplo: + +```py +from transformers import AutoTokenizer, TFAutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="tf") +outputs = model(**inputs) +``` +Dado que estamos usando acá `TFAutoModelForTokenClassification`, obtenemos un conjunto de logits para cada token en la secuencia de entrada: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Tenemos un lote de 1 secuencia con 19 tokens y el modelo tiene 9 etiquetas diferentes, por lo que la salida del modelo tiene dimensiones 1 x 19 x 9. Al igual que el pipeline de clasificación de texto, usamos la función softmax para convertir esos logits en probabilidades, y tomamos el argmax para obtener las predicciones (notar que podemos tomar el argmax de los logits directamente porque el softmax no cambia el orden): + +{#if fw === 'pt'} + +```py +import torch + +probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() +predictions = outputs.logits.argmax(dim=-1)[0].tolist() +print(predictions) +``` + +{:else} + +```py +import tensorflow as tf + +probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] +probabilities = probabilities.numpy().tolist() +predictions = tf.math.argmax(outputs.logits, axis=-1)[0] +predictions = predictions.numpy().tolist() +print(predictions) +``` + +{/if} + +```python out +[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] +``` + +El atributo `model.config.id2label` contiene el mapeo de los índices con las etiquetas para que podemos hacer sentido de las predicciones: + +```py +model.config.id2label +``` + +```python out +{0: 'O', + 1: 'B-MISC', + 2: 'I-MISC', + 3: 'B-PER', + 4: 'I-PER', + 5: 'B-ORG', + 6: 'I-ORG', + 7: 'B-LOC', + 8: 'I-LOC'} +``` + +Como vimos antes, hay 9 etiquetas: `0` es la etiqueta para los tokens que no tienen ningúna entidad (proviene del inglés "outside"), y luego tenemos dos etiquetas para cada tipo de entidad (misceláneo, persona, organización, y locación). La etiqueta `B-XXX` indica que el token is el inicio de la entidad `XXX` y la etiqueta `I-XXX` indica que el token está dentro de la entidad `XXX`. For ejemplo, en el ejemplo actual esperaríamos que nuestro modelo clasificará el token `S` como `B-PER` (inicio de la entidad persona), y los tokens `##yl`, `##va` y `##in` como `I-PER` (dentro de la entidad persona). + +Podrías pensar que el modelo está equivocado en este caso ya que entregó la etiqueta `I-PER` a los 4 tokens, pero eso no es completamente cierto. En realidad hay 4 formatos par esas etiquetas `B-` y `I-`: *I0B1* y *I0B2*. El formato I0B2 (abajo en rosado), es el que presentamos, mientras que en el formato I0B1 (en azul), las etiquetas de comenzando con `B-` son sólo utilizadas para separar dos entidades adyacentes del mismo tipo. Al modelo que estamos usando se le hizo fine-tune en un conjunto de datos utilizando ese formato, lo cual explica por qué asigna la etiqueta `I-PER` al token `S`. + +
+IOB1 vs IOB2 format + +
+ +Con este mapa, estamos listos para reproducir (de manera casi completa) los resultados del primer pipeline -- basta con tomar los puntajes y etiquetas de cada token que no fue clasificado como `0`: + +```py +results = [] +tokens = inputs.tokens() + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + results.append( + {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] +``` + +Esto es muy similar a lo que teníamos antes, con una excepción: el pipeline también nos dió información acerca del `inicio` y el `final` de cada entidad en la oración original. Aquí es donde nuestro mapeo de offsets entrarán en juego. Para obtener los offsets, sólo tenemos que fijar la opción `return_offsets_mapping=True` cuando apliquemos el tokenizador a nuestros inputs: + +```py +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +inputs_with_offsets["offset_mapping"] +``` + +```python out +[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), + (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] +``` + +Cada tupla es la porción de texto correspondiente a cada token, donde `(0, 0)` está reservado para los tokens especiales. Vimos antes que el token con índice 5 is `##yl`, el cual tiene como offsets `(12, 14)`, Si tomamos los trozos correspondientes en nuestro ejemplo: + + +```py +example[12:14] +``` + +obtenemos la porción apropiada sin los `##`: + + +```python out +yl +``` + +Usando esto, ahora podemos completar los resultados previos: + +```py +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + start, end = offsets[idx] + results.append( + { + "entity": label, + "score": probabilities[idx][pred], + "word": tokens[idx], + "start": start, + "end": end, + } + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Esto es lo mismo que obtuvimos en el primer pipeline! + +### Agrupando Entidades[[grouping-entities]] + +Usar los offsets para determinar las llaves de inicio y fin para cada entidad is útil, pero esa información no es estrictamente necesaria. Cuando queremos agrupar las entidades, sin embargo, los offsets nos ahorarán un montón de código engorroso. Por ejemplo, si queremos agrupar los tokens `Hu`, `##gging`, y `Face`, podemos hacer reglas especiales que digan que los dos primeros se tienen que unir eliminando los `##`, y `Face` debería añadirse con el espacio ya que no comienza con `##` -- pero eso sólo funcionaría para este tipo particular de tokenizador. Tendríamos que escribir otro grupo de reglas para un tokenizador tipo SentencePiece (trozo de oración) tipo Byte-Pair-Encoding (codificación por par de bytes) (los que se discutirán más adelante en este capítulo). + +Con estos offsets, todo ese código hecho a medida no se necesita: basta tomar la porción del texto original que comienza con el primer token y termina con el último token. En el caso de los tokens `Hu`, `##gging`, and `Face`, deberíamos empezar en el character 33 (el inicio de `Hu`) y termianr antes del caracter 45 (al final de `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Para escribir el código encargado del post-procesamiento de las prediciones que agrupan entidades, agruparemos la entidades que son consecutivas y etiquetadas con `I-XXX`, excepto la primera, la cual puedes estar etiquetada como `B-XXX` o `I-XXX` (por lo que, dejamos de agrupar una entidad cuando nos encontramos un `0`, un nuevo tipo de entidad, o un `B-XXX` que nos dice que una entidad del mismo tipo está empezando): + +```py +import numpy as np + +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +idx = 0 +while idx < len(predictions): + pred = predictions[idx] + label = model.config.id2label[pred] + if label != "O": + # Remove the B- or I- + label = label[2:] + start, _ = offsets[idx] + + # Toma todos los tokens etiquetados con la etiqueta I + all_scores = [] + while ( + idx < len(predictions) + and model.config.id2label[predictions[idx]] == f"I-{label}" + ): + all_scores.append(probabilities[idx][pred]) + _, end = offsets[idx] + idx += 1 + + # El puntaje es la media de todos los puntajes de los tokens en la entidad agrupada + score = np.mean(all_scores).item() + word = example[start:end] + results.append( + { + "entity_group": label, + "score": score, + "word": word, + "start": start, + "end": end, + } + ) + idx += 1 + +print(results) +``` + +Y obtenemos los mismos resultados de nuestro segundo pipeline! + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Otro ejemplo de una tarea donde estos offsets son extremadamente útiles es question answering. Sumergirnos en ese pipeline, lo cual haremos en la siguiente sección, también nos permitirá echar un vistazo a una última característica de los tokenizadores en la librería 🤗 Transformers: lidiar con tokens desbordados (overflowing tokens) cuando truncamos una entrada/input a un largo dado. diff --git a/chapters/es/chapter6/3b.mdx b/chapters/es/chapter6/3b.mdx new file mode 100644 index 000000000..df6d509fd --- /dev/null +++ b/chapters/es/chapter6/3b.mdx @@ -0,0 +1,643 @@ + + +# Tokenizadores Rápidos en un Pipeline de Question-Answering[[fast-tokenizers-in-the-qa-pipeline]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Ahora nos sumergiremos en el pipeline de `question-answering` (preguntas y respuestas) y veremos como hacer uso de los offsets para tomar la respuesta de la pregunta desde el contexto, un poco como lo que hicimos para las entidades agrupadas en la sección previa. Luego veremos como lidiar con contextos muy largos que terminan siendo truncados. Puedes saltar esta sección si no estás interesado en la tarea de pregunta y respuesta (_question answering_). + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Usando el pipeline de `question-answering`[[using-the-question-answering-pipeline]] + +Como vimos en el [Capítulo 1](/course/chapter1), podemos usar el pipeline de `question-answering` para obtener la respuesta a una pregunta de la siguiente manera: + +```py +from transformers import pipeline + +question_answerer = pipeline("question-answering") +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch, and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question = "Which deep learning libraries back 🤗 Transformers?" +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +A diferencia de otros pipelines, los cuales no pueden truncar y dividir textos que son más largos que el largo máximo aceptado por el modelo (y por lo tanto perder información al final de un documento), este pipeline puede lidiar con contextos muy largos y retornará una respuesta a la pregunta incluso si está al final. + +```py +long_context = """ +🤗 Transformers: State of the Art NLP + +🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internals are exposed as consistently as possible. + - Model files can be used independently of the library for quick experiments. + +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +¡Veamos cómo hace todo esto! + +## Usando un modelo para question answering[[using-a-model-for-question-answering]] + +Como para cualquier otro pipeline, empezamos tokenizando nuestro input y lo envíamos a través del modelo. El punto de control (`checkpoint`) usado por defecto para el pipeline de `question-answering` es [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (el "squad" en el nombre viene del conjunto de datos en el cual se le hizo fine-tune; hablaremos más acerca del conjunto de datos SQuAD en el [Capítulo 7](/course/chapter7/7)) + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="pt") +outputs = model(**inputs) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="tf") +outputs = model(**inputs) +``` + +{/if} + +Notar que tokenizamos nuestra y el contexto como un par, con la pregunta primero. + +
+An example of tokenization of question and context + +
+ +Los modelos para question answering funcionan de manera un poco distinta de los modelos que hemos visto hasta ahora. Usando la imagen de arriba como ejemplo, el modelo ha sido entrenado para predecir el índice de los tokens al inicio de la respuesta (en este caso el 21) y el índice del token donde la respuesta termina (en este caso el 24). Esto porque estos modelos no retornar un tensor de logits sino dos: uno para los logits correspondientes al token de inicio de la respuesta, y uno para los logits correspondientes al token de término de la respuesta. Dado que en este caso tenemos un input conteniendo 66 tokens, obtenemos: + +```py +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([1, 66]) torch.Size([1, 66]) +``` + +{:else} + +```python out +(1, 66) (1, 66) +``` + +{/if} + +Para convertir estos logits en probabilidades, aplicaremos la función softmax -- pero antes de eso, necesitamos asegurarnos que enmascaramos los índices que no son parte del contexto. Nuestro input es `[CLS] pregunta [SEP] contexto [SEP]`, por lo que necesitamos enmascarar los tokens de la pregunta como también el token `[SEP]`. Mantredemos el token `[CLS]`, ya que algunos modelos lo usan para indicar que la respuesta no está en el contexto. + +Dado que aplicaremos una softmax después, sólo necesitamos reemplazar los logits que queremos enmascarar con un número negativo muy grande. En este caso, usamos el `-10000`: + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = torch.tensor(mask)[None] + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +import tensorflow as tf + +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +mask = tf.constant(mask)[None] + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Ahora que tenemos enmascarados los logits de manera apropiada correspondientes a los tokens que no queremos predecir. Podemos aplicar la softmax: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() +``` + +{/if} + +En esta punto, podemos tomar el argmax de las probabilidades de inicio y fin -- pero podríamos terminar con un índice de inicio que es mayot que índice de término, por lo que necesitamos tomar unas pocas precauciones más. Calcularemos la probabilidad de cada posible `start_index` and `end_index` (índice de inicio y final respectivamente) donde `start_index <= end_index`, luego tomamos la tupla `(start_index, end_index)` con la probabilidad más alta. + +Asumiendo que los eventos "La respuesta comienzda en `start_index`" y "La respuesta termina en `end_index`" son independientes, la probabilidad de que la respuesta inicie en `start_index` y termine en `end_index` es: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Así que para calcular todos los puntajes, necesitamos calcular todos los productos \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) donde `start_index <= end_index`. + +Primero calculemos todos los posibles productos: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Luego enmascararemos los valores donde `start_index > end_index` reemplazándolos como 0 (las otras probabilidades son todos números positivos). La función `torch.triu()` retorna la parte triangular superior de el tensor 2D pasado como argumento, por lo que hará el enmascaramiento por nosotros. + +```py +scores = torch.triu(scores) +``` + +{:else} + + +Luego enmascararemos los valores donde `start_index > end_index` reemplazándolos como 0 (las otras probabilidades son todos números positivos). La función `np.triu()` retorna la parte triangular superior de el tensor 2D pasado como argumento, por lo que hará el enmascaramiento por nosotros. + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Ahora basta con obtener el índice el máximo. Dado que Pytorch retornará el índice en el tensor aplanado, necesitamos usar las operaciones división entera `//` y módulo `%` para obtener el `start_index` y el `end_index`: + +```py +max_index = scores.argmax().item() +start_index = max_index // scores.shape[1] +end_index = max_index % scores.shape[1] +print(scores[start_index, end_index]) +``` + +No estamos listos aún, pero al menos ya tenemos el puntaje correcto para la respuesta (puedes chequear esto comparándolo con el primer resultado en la sección previa): + +```python out +0.97773 +``` + + + +✏️ **Inténtalo!** Calcula los índices de inicio y término para las cinco respuestas más probables. + + + +Tenemos el `start_index` y el `end_index` de la respuesta en términos de tokens, así que ahora sólo necesitamos convertirlos en los índices de caracteres en el contexto. Aquí es donde los offsets serán sumamente útiles. Podemos tomarlos y usarlos como lo hicimos en la tarea de clasificación de tokens: + +```py +inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) +offsets = inputs_with_offsets["offset_mapping"] + +start_char, _ = offsets[start_index] +_, end_char = offsets[end_index] +answer = context[start_char:end_char] +``` + +Ahora sólo tenemos que dar formato a todo para tener nuestros resultados: + +```py +result = { + "answer": answer, + "start": start_char, + "end": end_char, + "score": scores[start_index, end_index], +} +print(result) +``` + +```python out +{'answer': 'Jax, PyTorch and TensorFlow', + 'start': 78, + 'end': 105, + 'score': 0.97773} +``` + +Genial! Obtuvimos lo mismo que en nuestro primer ejemplo! + + + +✏️ **Inténtalo!** Usaremos los mejores puntajes calculados anteriormente para mostrar las cinco respuestas más probables. Para revisar nuestros resultados regresa al primer pipeline y agrega `top_k=5` al llamarlo. + + + +## Manejando contextos largos[[handling-long-contexts]] + +Si tratamos de tokenizar la pregunta en un contexto largo que usamos en el ejemplo previamente, tendremos un número de tokens que es más alto que el largo máximo usado en el pipeline de `question-answering` (que es 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Entonces, necesitaremos truncar nuestras entradas/inputs al largo máximo. Hay varias maneras de hacer esto, pero no queremos truncar la pregunta, sólo el contexto. Dado que el contexto es la segunda oración, usaremos la estrategia de truncamiento `"only_second"`. El problema que aparece es que la respuesta a la pregunta podría no estar en el contexto truncado. En este caso, por ejemplo, elegimos una pregunta donde la respuesta está hacia el final del contexto, y cuando truncamos la respuesta no está presente: + +```py +inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") +print(tokenizer.decode(inputs["input_ids"])) +``` + +```python out +""" +[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP + +[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internal [SEP] +""" +``` + +Esto significa que el modelo le costará bastante elegir la respuesta correcta. Para corregir eso, el pipeline de `question-answering` permite separar el contexto en trozos pequeños, especificando el largo máximo. Para asegurarnos que no separemos el contexto exactamente en un lugar incorrecto donde podríamos encontrar la respuesta, también incluye algunos traslapes (overlaps) entre los trozos. + +Podemos hacer que el tokenizador (rápido o lento) haga esto por nosotros agregando `return_overflowing_tokens=True`, y podemos especificar el traslape (overlap) que queremos con el argumento `stride`. Acá un ejemplo, usando una oración corta: + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +inputs = tokenizer( + sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] This sentence is not [SEP]' +'[CLS] is not too long [SEP]' +'[CLS] too long but we [SEP]' +'[CLS] but we are going [SEP]' +'[CLS] are going to split [SEP]' +'[CLS] to split it anyway [SEP]' +'[CLS] it anyway. [SEP]' +``` + +Como podemos ver, la oración ha sido dividida en trozos de tal manera que cada entrada en `inputs["input_ids"] tiene a lo más 6 tokens (tendríamos que agregar relleno (`padding`) en el último trozo para tener el mismo largo que los otros) y hay traslape (overlap) de 2 tokens entre cada uno de los trozos. + +Miremos de cerca el resultado de la tokenización: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Como se esperaba, obtenemos los IDs de entrada y una máscara de atención (attention mask). La última clave, `overflow_to_sample_mapping`, es un mapa que nos dice a qué oraciones corresponde cada resultado -- en este caso tenemos 7 resultados, todos provenientes de la (única) oración que le pasamos al tokenizador: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +Esto es más útil cuando tokenizamos varias oraciones juntas. Por ejemplo así: + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + "This sentence is shorter but will still get split.", +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +obtenemos: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +lo que significa que la primera oración está dividida en 7 trozos igual que antes, y los siguientes 4 trozos vienen de la segunda oración. + +Ahora volvamos a nuestro contexto largo. Por defecto el pipeline de `question-answering` usa un largo máximo de 384, como mencionamos antes, y un stride de 128, lo que corresponde a la manera en la que al modelo se le hizo fine-tuning (puedes ajustar esos parámetros pasando los argumentos `max_seq_len` y `stride` al llamar el pipeline). Por lo tanto, usaremos esos parámetros al tokenizar. También agregaremos relleno (`padding`) (para tener muestras del mismo largo, para que podamos construir los tensores) como también pedir los offsets: + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Esos `inputs` contendrán los IDs de entrada y las máscaras de atención (attention masks) que el modelo espera, así como los offsets y el `overflow_to_sample_mapping` que hablamos antes. Dado que esos dos no son parámetros usados por el modelo, los sacaremos de los `inputs` (y no guardaremos el mapa, ya que no es útil acá) antes de convertirlo en un tensor: + +{#if fw === 'pt'} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("pt") +print(inputs["input_ids"].shape) +``` + +```python out +torch.Size([2, 384]) +``` + +{:else} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("tf") +print(inputs["input_ids"].shape) +``` + +```python out +(2, 384) +``` + +{/if} + +Nuestro contexto largo fue dividido en dos, lo que significa que después de pasar por nuestro modelo, tendremos 2 sets de logits de inicio y término: + +```py +outputs = model(**inputs) + +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 384]) torch.Size([2, 384]) +``` + +{:else} + +```python out +(2, 384) (2, 384) +``` + +{/if} + +Al igual que antes, primero enmascaramos los tokens que no son parte del contexto antes de aplicar softmax. También enmascaramos todos los tokens de de relleno (`padding`) (de acuerdo a la máscara de atención (attention masks)): + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +sequence_ids = inputs.sequence_ids() +# Mask everything apart from the tokens of the context +mask = [i != 1 for i in sequence_ids] +# Unmask the [CLS] token +mask[0] = False +# Mask all the [PAD] tokens +mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Luego podemos usar la función softmax para convertir nuestros logits en probabilidades: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() +``` + +{/if} + +El siguiente paso es similar a lo que hicimos para el contexto pequeño, pero lo repetimos para cada uno de nuestros dos trozos. Le atribuímos un puntaje a todas las posibles respuestas, para luego tomar la respuesta con el mejor puntaje: + +{#if fw === 'pt'} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = torch.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{:else} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = np.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{/if} + +```python out +[(0, 18, 0.33867), (173, 184, 0.97149)] +``` + +Estos dos candidatos corresponden a las mejores respuestas que el modelo fue capaz de encontrar en cada trozo. El modelo está mucho más confiado de que la respuesta correcta está en la segunda parte (¡lo que es una buena señal!). Ahora sólo tenemos que mapear dichos tokens a los caracteres en el contexto (sólo necesitamos mapear la segunda para obtener nuestra respuesta, pero es interesante ver que el modelo ha elegido en el primer trozo). + + + +✏️ **Inténtalo!** Adapta el código de arriba pra retornar los puntajes de las 5 respuestas más probables (en total, no por trozo). + + + +Los `offsets` que tomamos antes es en realidad una lista de offsets, con una lista por trozo de texto: + +```py +for candidate, offset in zip(candidates, offsets): + start_token, end_token, score = candidate + start_char, _ = offset[start_token] + _, end_char = offset[end_token] + answer = long_context[start_char:end_char] + result = {"answer": answer, "start": start_char, "end": end_char, "score": score} + print(result) +``` + +```python out +{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} +{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} +``` + +Si ignoramos el primer resultado, obtenemos el mismo resultado que nuestro pipeline para el contexto largo -- bien! + + + +✏️ **Inténtalo!** Usa los mejores puntajes que calculaste antes para mostrar las 5 respuestas más probables. Para revisar tus resultados, regresa al primer pipeline y agrega `top_k=5` al llamarlo. + + + +Esto concluye nuestra profundización en las capacidades de los tokenizadores. Pondremos todo esto en práctica de nuevo en el siguiente capítulo, cuando te mostremos cómo hacer fine-tuning a un modelo en una variedad de tareas comunes de PLN. diff --git a/chapters/es/chapter6/4.mdx b/chapters/es/chapter6/4.mdx new file mode 100644 index 000000000..c020896f7 --- /dev/null +++ b/chapters/es/chapter6/4.mdx @@ -0,0 +1,123 @@ +# Normalización y pre-tokenización[[normalization-and-pre-tokenization]] + + + +Antes de sumergirnos más profundamente en los tres algoritmos más comunes de tokenización usados con los modelos transformers (Byte-Pair Encoding [BPE], WordPiece, and Unigram), primero miraremos el preprocesamiento que cada tokenizador aplica al texto. Acá una descripción general de los pasos en el pipeline de tokenización: + +
+The tokenization pipeline. + +
+ +Antes de dividir un texto en subtokens (de acuerdo a su modelo), el tokenizador realiza dos pasos: _normalización_ y _pre-tokenización_. + +## Normalización[[normalization]] + + + +El paso de normalización involucra una limpieza general, como la remoción de espacios en blanco innecesario, transformar a minúsculas, y/o remoción de acentos. Si estás familiarizado con [Normalización Unicode](http://www.unicode.org/reports/tr15/) (como NFC o NFKC), esto es algo que el tokenizador también puede aplicar. + +Los tokenizadores de la librería 🤗 Transformers tienen un atributo llamado `backend_tokenizer` que provee acceso al tokenizador subyacente de la librería 🤗 Tokenizers: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +El atributo `normalizer` del objeto `tokenizer` tiene un método `normalize_str()` que puede puedes usar para ver cómo la normalización se realiza: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +En este ejemplo, dado que elegimos el punto de control (checkpoint) `bert-base-uncased`, la normalización aplicó transformación a minúsculas y remoción de acentos. + + + +✏️ **Inténtalo!** Carga un tokenizador desde el punto de control (checkpoint)`bert-base-cased` y pásale el mismo ejemplo. Cuáles son las principales diferencias que puedes ver entre las versiones cased y uncased de los tokenizadores? + + + +## Pre-tokenización[[pre-tokenization]] + + + +Como veremos en las siguientes secciones, un tokenizador no puede ser entrenado en un texto tal como viene así nada más. En vez de eso, primero necesitamos separar los textos en entidades más pequeñas, como palabras. Ahí es donde el paso de pre-tokenización entra en juego. Como vimos en el [Capítulo 2](/course/chapter2), un tokenizador basado en palabras (word-based) puede dividir el texto en palabras separando en espacios en blanco y puntuación. Esas palabras serán las fronteras de los subtokens que el tokenizador aprende durante su entrenamiento. + +Para ver qué tan rápido un tokenizador rápido (fast tokenizer) realiza la pre-tokenización, podemos usar el método `pre_tokenize_str()` del atributo `pre_tokenizer` del objeto `tokenizer`: + +```py +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] +``` + +Notar como el tokenizador ya lleva registro de los offsets, el cual nos entrega el mapeo de offsets que usamos en la sección anterior. Acá el tokenizador ignora los dos espacios y los reemplaza con uno sólo, pero el offset salta entre `are` y `you` para tomar eso en cuenta. + +Dado que estamos usando un tokenizador BERT, la pre-tokenización involucra separar en espacios en blanco y puntuación. Otros tokenizadores pueden tener distintas reglas para esta etapa. Por ejemplo, si usamor el tokenizador de GPT-2: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +dividirá en espacios en blanco y puntuación también, pero mantendrá los espacios y los reemplazará con el símbolo `Ġ`, permitiendo recobrar los espacios originales en el caso de decodificar los tokens: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +También notar que a diferencia del tokenizador BERT, este tokenizador no ignora los espacios dobles. + +Para el último ejemplo, tenemos que mirar el tokenizador T5, el cuál está basado en el algoritmo SentencePiece: + +```py +tokenizer = AutoTokenizer.from_pretrained("t5-small") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] +``` + +Al igual que el tokenizador GPT-2, este mantiene los espacios y los reemplaza con un token específico (`_`), pero el tokenizador T5 sólo divide en espacios en blanco, no en puntuación. También notar que agrego un espacio por defecto al inicio de la oración (antes de `Hello`) e ignoró el doble espacio entre `are` y `you`. + +Ahora que hemos visto un poco de cómo los diferentes tokenizadores procesan texto, podemos empezar a explorar los algoritmos subyacentes propiamente tal. Comenzaremos con una mirada rápida al ampliamente aplicable SentencePiece; luego, a lo largo de las 3 secciones siguientes examinaremos cómo los tres principales algoritmos usados para el trabajo de tokenización por subpalabra (subword tokenization). + +## SentencePiece[[sentencepiece]] + +[SentencePiece](https://github.com/google/sentencepiece) es un algoritmo para el preprocesamiento de texto que puedes usar con cualquiera de los modelos que veremos en las siguientes tres secciones. Éste considere el texto como una secuencia de caractéres Unicode, y reemplaza los especios con un caracter especial, `_`. Usado en conjunto con el algoritmo Unigram (ver [Sección 7](/course/chapter7/7)), ni siquiera requiere un paso de pre-tokenización, lo cual es muy útil para lenguajes donde el caracter de espacio no es usado (como el Chino o el Japonés). + +La otra característica principal de SentencePiece es la *tokenización reversible* (tokenización reversible): dado que no hay tratamiento especial de los espacios, decodificar los tokens se hace simplemente concatenandolos y reemplazando los `_`s con espacios -- esto resulta en el texto normalizado. Como vimos antes, el tokenizador BERT remueve los espacios repetidos, por lo que su tokenización no es reversible. + +## Descripción General del Algoritmo[[algorithm-overview]] + +En las siguientes secciones, profundizaremos en los tres principales algoritmos de tokenización por subpalabra (subword tokenization): BPE (usado por GPT-2 y otros), WordPiece (usado por ejemplo por BERT), y Unigram (usado por T5 y otros). Antes de comenzar, aquí una rápida descripción general de cómo funciona cada uno de ellos. No dudes en regresar a esta tabla luego de leer cada una de las siguientes secciones si no te hace sentido aún. + + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Entrenamiento | Comienza a partir de un pequeño vocabulario y aprende reglas para fusionar tokens | Comienza a partir de un pequeño vocabulario y aprende reglas para fusionar tokens | Comienza de un gran vocabulario y aprende reglas para remover tokens +Etapa de Entrenamiento | Fusiona los tokens correspondiente a los pares más comunes | Fusiona los tokens correspondientes al par con el mejor puntaje basado en la frecuencia del par, privilegiando pares donde cada token individual es menos frecuente | Remueve todos los tokens en el vocabulario que minimizarán la función de pérdida (loss) calculado en el corpus completo. +Aprende | Reglas de fusión y un vocabulario | Sólo un vocabulario | Un vocabulario con puntaje para cada token +Codificación | Separa una palabra en caracteres y aplica las fusiones aprendidas durante el entrenamiento | Encuentra la subpalabra más larga comenzando del inicio que está en el vocabulario, luego hace lo mismo para el resto de las palabras | Encuentra la separación en tokens más probable, usando los puntajes aprendidos durante el entrenamiento + +Ahora profundicemos en BPE! \ No newline at end of file diff --git a/chapters/es/chapter6/5.mdx b/chapters/es/chapter6/5.mdx new file mode 100644 index 000000000..c8bd82898 --- /dev/null +++ b/chapters/es/chapter6/5.mdx @@ -0,0 +1,360 @@ +# Tokenización por Codificación Byte-Pair[[byte-pair-encoding-tokenization]] + + + +La codificación por pares de byte (Byte-Pair Encoding (BPE)) fue inicialmente desarrollado como un algoritmo para comprimir textos, y luego fue usado por OpenAI para la tokenización al momento de pre-entrenar el modelo GPT. Es usado por un montón de modelos Transformers, incluyendo GPT, GPT-2, RoBERTa, BART, y DeBERTa. + + + + + +💡 Esta sección cubre BPE en produndidad, yendo tan lejos como para mostrar una implementación completa. Puedes saltarte hasta el final si sólo quieres una descripción general del algoritmo de tokenización. + + + +## Algoritmo de Entrenamiento[[training-algorithm]] + +El entrenamiento de BPE comienza calculando el conjunto de palabras únicas usada en el corpus (después de completar las etapas de normalización y pre-tokenización), para luego contruir el vocabulario tomando todos los símbolos usados para escribir esas palabras. Como un ejemplo muy simple, digamos que nuestros corpus usa estas cinco palabras: + + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +El vocabulario vase entonces será `["b", "g", "h", "n", "p", "s", "u"]`. Para casos reales, el vocabulario base contendrá todos los caracteres ASCII, al menos, y probablemente algunos caracteres Unicode también. Si un ejemplo que estás tokenizando usa un caracter que no está en el corpus de entrenamiento, ese caracter será convertido al token "desconocido". Esa es una razón por la cual muchos modelos de NLP son muy malos analizando contenido con emojis. + + + +Los tokenizadores de GPT-2 y RoBERTa (que son bastante similares) tienen una manera bien inteligente de lidiar con esto: ellos no miran a las palabras como si estuvieran escritas con caracteres Unicode, sino con bytes. De esa manera el vocabulario base tiene un tamaño pequeño (256), pero cada caracter que te puedas imaginar estará incluido y no terminará convertido en el token "desconocido". Este truco se llama *byte-level BPE*. + + + +Luego de obtener el vocabulario base, agregamos nuevos tokens hasta que el tamaño deseado del vocabulario se alcance por medio de aprender *fusiones* (merges), las cuales son reglas para fusionar dos elementos del vocabulario existente en uno nuevo. Por lo que al inicio de estas fusiones crearemos tokens con dos caracteres, y luego, a medida que el entrenamiento avance, subpalabras más largas. + +En cualquier etapa durante el entrenamiento del tokenizador, el algoritmo BPE buscará pos los pares más frecuentes de los tokens existentes (por "par", acá nos referimos a dos tokens consecutivos en una palabra). El par más frecuente es el que será fusionado, y enjuagamos y repetimos para la siguiente etapa. + +Volviedo a nuestro ejemplo previo, asumamos que las palabras tenían las siguientes frecuencias: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +lo que significa que `"hug"` estuvo presente 10 veces en el corpus, `"pug"` 5 veces, `"pun"` 12 veces, `"bun"` 4 veces, and `"hugs"` 5 veces. Empezamos el entrenamiento separando cada palabra en caracteres (los que formaron nuestro vocabulario inicial) para que podamos ver cada palabra como una lista de tokens: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Luego miramos los pares. El par `("h", "u")` está presente en las palabras `"hug"` y `"hugs"`, 15 veces en el total del corpus. No es el par más frecuente: ese honor le corresponde a `("u", "g")`, el cual está presente en `"hug"`, `"pug"`, y `"hugs"`, para un gran total de 20 veces en el vocabulario. + +Por lo tanto, la primera regla de fusión aprendida por el tokenizador es `("u", "g") -> "ug"`, lo que significa que `"ug"` será agregado al vocabulario, y el par debería ser fusionado en todas las palabras del corpus. Al final de esta etapa, el vocabulario se ve así: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +``` + +Ahora tenemos algunos pares que resultan en un token más largo de dos caracteres: por ejemplo el par `("h", "ug")` (presente 15 veces en el corpus). Sin embargo, el par más frecuente en este punto is `("u", "n")`, presente 16 veces en el corpus, por lo que la segunda regla de fusión aprendida es `("u", "n") -> "un"`. Agregando esto y fusionando todas las ocurrencias existentes nos lleva a: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +``` + +Ahora el par más frecuente es `("h", "ug")`, por lo que aprendemos que la regla de fusión es `("h", "ug") -> "hug"`, lo cual nos da tuestro primer token de tres letras. Luego de la fusión el corpus se ve así: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +``` + +Y continuamos así hasta que alcancemos el tamaño deseado del vocabulario. + + + +✏️ **Ahora es tu turno!** Cuál crees que será la siguiente regla de fusión? + + + +## Algoritmo de Tokenización[[tokenization-algorithm]] + +La tokenización sigue el proceso de entrenamiento de cerca, en el sentido que nuevos inputs son tokenizados aplicando los siguientes pasos: + +1. Normalización +2. Pre-tokenización +3. Separar las palabras en caracteres individuales +4. Aplicar las reglas de fusión aprendidas en orden en dichas separaciones. + +Tomemos el ejemplo que usamos durante el entrenamiento, con las tres reglas de fusión aprendidas: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` +La palabra `"bug"` será tokenizada como `["b", "ug"]`. En cambio, `"mug"`, será tokenizado como `["[UNK]", "ug"]` dado que la letra `"m"` no fue parte del vocabulario base. De la misma manera, la palabra `"thug"` será tokenizada como `["[UNK]", "hug"]`: la letra `"t"` no está en el vocabulario base, y aplicando las reglas de fusión resulta primero la fusión de `"u"` y `"g"` y luego de `"hu"` and `"g"`. + + + +✏️ **Ahora es tu turno!** ¿Cómo crees será tokenizada la palabra `"unhug"`? + + + +## Implementando BPE[[implementing-bpe]] + +Ahora echemos un vistazo a una implementación el algoritmo BPE. Esta no será una versión optimizada que puedes usar en corpus grande; sólo queremos mostrar el código para que puedas entender el algoritmo un poquito mejor. + +Primero necesitamos un corpus, así que creemos uno simple con algunas oraciones: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +A continuación, necesitamos pre-tokenizar el corpus en palabras. Dado que estamos replicando un tokenizador BPE (como GPT-2), usaremos el tokenizdor `gpt2` para la pre-tokenización: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Luego calculamos las frecuencias de cada palabra en el corpues mientras hacemos la pre-tokenización: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) + +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +print(word_freqs) +``` + +```python out +defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, + 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, + 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, + 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) +``` + +El siguiente paso es calcualar el vocabulario base, formado por todos los caracteres usados en el corpus: + +```python +alphabet = [] + +for word in word_freqs.keys(): + for letter in word: + if letter not in alphabet: + alphabet.append(letter) +alphabet.sort() + +print(alphabet) +``` + +```python out +[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', + 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] +``` + +También agregamos el token especial usado por el modelo al inicio de ese vocabulario. En el caso de GPT-2, el único token especial es `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Ahora necesitamos separar cada palabra en caracteres individuales, para poder comenzar el entrenamiento: + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Ahora estamos listos para el entrenamiento, escribamos una función que calcule la frecuencia de cada par. Necesitaremos usar esto en cada paso del entrenamiento: + +```python +def compute_pair_freqs(splits): + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + pair_freqs[pair] += freq + return pair_freqs +``` + +Ahora miremos una parte de ese diccionario después de las separaciones iniciales: + +```python +pair_freqs = compute_pair_freqs(splits) + +for i, key in enumerate(pair_freqs.keys()): + print(f"{key}: {pair_freqs[key]}") + if i >= 5: + break +``` + +```python out +('T', 'h'): 3 +('h', 'i'): 3 +('i', 's'): 5 +('Ġ', 'i'): 2 +('Ġ', 't'): 7 +('t', 'h'): 3 +``` + +Ahora, encontrar el par más frecuenta sólo toma un rápido ciclo: + +```python +best_pair = "" +max_freq = None + +for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + +print(best_pair, max_freq) +``` + +```python out +('Ġ', 't') 7 +``` + +Por lo que la primera fusión a aprender es `('Ġ', 't') -> 'Ġt'`, y luego agregamos `'Ġt'` al vocabulario: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Para continuar, necesitamos aplicar la fusión en nuestro diccionario de divisiones (`splits` dictionary). Escribamos otra función para esto: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + split = split[:i] + [a + b] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Y podemos echar un vistazo al resultado de nuestra primera fusión: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Ahora tenemos todo lo que necesitamos para iterar hasta que aprendamos todas las fusiones que queramos. Apuntemos a un tamaño de vocabulario de 50: + +```python +vocab_size = 50 + +while len(vocab) < vocab_size: + pair_freqs = compute_pair_freqs(splits) + best_pair = "" + max_freq = None + for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + splits = merge_pair(*best_pair, splits) + merges[best_pair] = best_pair[0] + best_pair[1] + vocab.append(best_pair[0] + best_pair[1]) +``` + +Como resultado, hemos aprendido 19 reglas de fusión (el vocabulario inicial tenía un tamaño de 31 -- 30 caracteres del alfabeto, más el token especial): + +```py +print(merges) +``` + +```python out +{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', + ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', + ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', + ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} +``` + +And the vocabulary is composed of the special token, the initial alphabet, and all the results of the merges: + +```py +print(vocab) +``` + +```python out +['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', + 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', + 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] +``` + + + +💡 Usar `train_new_from_iterator()` en el mismo corpus no resultará en exactament el mismo vocabulario. Esto es porque cuando hay una elección del par más frecuente, seleccionamos el primero encontrado, mientras que la librería 🤗 Tokenizers selecciona el primero basado en sus IDs internos. + + + +Para tokenizar un nuevo texto lo pre-tokenizamos, lo separamos, luego aplicamos todas las reglas de fusión aprendidas: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + splits = [[l for l in word] for word in pre_tokenized_text] + for pair, merge in merges.items(): + for idx, split in enumerate(splits): + i = 0 + while i < len(split) - 1: + if split[i] == pair[0] and split[i + 1] == pair[1]: + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[idx] = split + + return sum(splits, []) +``` + +Podemos intentar esto con cualquier texto compuesto de de caracteres del alfabeto: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Nuestra implementación arrojará un error si hay un caracter desconocido dado que no hicimos nada para manejarlos. GPT-2 en realidad no tiene un token desconocido (es imposible obtener un caracter desconocido cuando se usa byte-level BPE), pero esto podría ocurrir acá porque no incluímos todos los posibles bytes en el vocabulario inicial. Este aspectode BPE va más allá del alcance de está sección, por lo que dejaremos los detalles fuera. + + + +Eso es todo para el algoritmo BPE! A continuación echaremos un vistazo a WordPiece. \ No newline at end of file diff --git a/chapters/es/chapter6/6.mdx b/chapters/es/chapter6/6.mdx new file mode 100644 index 000000000..bb5dd62e5 --- /dev/null +++ b/chapters/es/chapter6/6.mdx @@ -0,0 +1,373 @@ +# Tokenización WordPiece[[wordpiece-tokenization]] + + + +WordPiece es el algoritmo de tokenización que Google desarrolló para pre-entrenar BERT. Ha sido reutilizado un varios modelos Transformers basados en BERT, tales como DistilBERT, MobileBERT, Funnel Transformers, y MPNET. Es muy similar a BPE en términos del entrenamiento, pero la tokenización se hace de distinta manera. + + + + + +💡 Esta sección cubre WordPiece en profundidad, yendo tan lejos como para mostrar una implementación completa. Puedes saltarte hasta el final si sólo quieres una descripción general del algoritmo de tokenización. + + + +## Algoritmo de Entrenamiento[[training-algorithm]] + + + +⚠️ Google nunca liberó el código (open-sourced) su implementación del algoritmo de entrenamiento de WordPiece, por tanto lo que sigue es nuestra mejor suposición badado en la literatura publicada. Puede no ser 100% preciso. + + + +Al igual que BPE, WordPiece comienza a partir de un pequeño vocabulario incluyendo los tokens especiales utilizados por el modelo y el alfabeto inicial. Dado que identifica subpalabras (subwords) agregando un prefijo (como `##` para el caso de BERT), cada palabra está inicialmente separada agregando dicho prefijo a todos los caracteres dentro de la palabra. Por lo que por ejemplo la palabra `"word"` queda separada así: + +``` +w ##o ##r ##d +``` +Por lo tanto, el alfabeto inicial contiene todos los caracteres presentes al comienzo de una palabra y los caracteres presente dentro de una palabra precedida por el prefijo de WordPiece. + +Luego, de nuevo al igual que BPE, WordPiece aprende reglas de fusión. La principal diferencia es la forma que el par fusionado es seleccionado. Envex de seleccionar el par más frecuente, WordPiece calcula un puntaje para cada par, utilizando la siguiente formula: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +Dividiendo por la frecuencia del par por el producto de las frecuencias de cada una de sus partes, el algoritmo prioriza la fusión de pares donde las partes individuales son menos frecuentes en el vocabulario. Por ejemplo, no fusionará necesariamente `("un", "##able")` incluso si ese par ocurre de manera muy frecuente en el vocabulario, porque los dos pares `"un"` y `"##able"` muy probablemente aparecerán en un montón de otras palabras y tendrán una alta frecuencia. En contraste con un par como `("hu", "##gging")` los cuales son probablemente menos frecuentes individualmente. + +Miremos el mismo vocabulario que usamos en el ejemplo de entrenamiento de BPE: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Las separaciones acá serán: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +por lo que el vocabulario inicial será `["b", "h", "p", "##g", "##n", "##s", "##u"]` (si nos olvidamos de los tokens especiales por ahora). El par más frecuente es `("##u", "##g")` (presente 20 veces), pero la frecuencia individual de `"##u"` es muy alta, por lo que el puntaje no es el más alto (es 1 / 36). Todos los pares con `"##u"` en realidad tienen el mismo puntaje (1 / 36), por lo que el mejor puntaje va para el par `("##g", "##s")` -- el único sin `"##u"` -- 1 / 20, y la primera fusión aprendida es `("##g", "##s") -> ("##gs")`. + +Notar que cuando fusionamos, removemos el `##` entre los dos tokens, por que agregamos `"##gs"` al vocabulario y aplicamos la fusión en las palabras del corpus: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +``` + +En este punto, `"##u"` está en todos los posibles pares, por lo que todos terminan con el mismo puntaje. Digamos que en este caso, el primer par se fusiona, `("h", "##u") -> "hu"`. Esto nos lleva a: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +Luego el siguiente mejor puntaje está compartido por `("hu", "##g")` y `("hu", "##gs")` (con 1/15, comparado con 1/21 para todos los otros pares), por lo que el primer par con el puntaje más alto se fusiona: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +y continuamos como esto hasta que alcancemos el tamaño de vocabulario deseado. + + + +✏️ **Ahora es tu turno!** Cuál será la siguiente regla de fusioń? + + + +## Algoritmo de Tokenización[[tokenization-algorithm]] + +La tokenización difiere en WordPiece y BPE en que WordPiece sólo guarda el vocabulario final, no las reglas de fusión aprendidas. Comenzando a partir de la palabra a tokenizar, WordPiece encuentra la subpalabra más larga que está en el vocabulario, luego la separa. Por ejemplo, su usamos el vocabulario aprendido en el ejemplo anterior, para la palabra `"hugs"` la subpalabra más larga comenzando desde el inicio que está dentro del vocabulario es `"hug"`, por lo que separamos ahí y obtenemos `["hug", "##s"]`. Luego continuamos con `"##s"`, el cuál está en el vocabulario, por lo que la tokenización de `"hugs"` es `["hug", "##s"]`. + +Con BPE, habríamos aplicado las fusiones aprendidas en orden y tokenizado esto como `["hu", "##gs"]`, por lo que la codificación es diferente. + +Como otro ejemplo, veamos como la palabra `"bugs"` sería tokenizado. `"b"` es la subpalabra más larga comenzando del inicio de la palabra que está en el vocabulario, por lo que separamos ahí y obtenemos `["b", "##ugs"]`. Luego `"##u"` es la subpalabra más larga somenzando desde el inicio de `"##ugs"` que está en el vocabulario, por lo que separamos ahí y obtenemos `["b", "##u, "##gs"]`. Finalmente, `"##gs"` está en el vocabulario, por lo que esta última lista es la tokenización de `"bugs"`. + +Cuando la tokenización llega a la etapa donde ya no es posible encontrar una subpalabra en el vocabulario, la palabra entera es tokenizada como desconocida -- Por ejemplo, `"mug"` sería tokenizada como `["[UNK]"]`, al igual que `"bum"` (incluso si podemos comenzar con `"b"` y `"##u"`, `"##m"` no está en el vocabulario, y la tokenización resultante será sólo `["[UNK]"]`, y no `["b", "##u", "[UNK]"]`). Este es otra diferencia con respecto a BPE, el cual sólo clasificaría los caracteres individuales que no están en el vocabulario como desconocido. + + + +✏️ **Ahora es tu turno!** ¿Cómo se tokenizaría la palabra `"pugs"`? + + + +## Implementando WordPiece[[implementing-wordpiece]] + +Ahora echemos un vistazo a una implementación del algoritmo WordPiece. Al igual que BPE, este es sólo pedagócico y no podrás aplicar esto en corpus grande. + +Usaremos el mismo corpus que en el ejemplo de BPE: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Primero, necesitamos pre-tokenizar el corpus en palabras. Dado que estamos replicando el tokenizador WordPiece (como BERT), usaremos el tokenizador `bert-base-cased` para la pre-tokenización: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Luego calculamos las frecuencias de cada palabra en el corpus mientras hacemos la pre-tokenización: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +```python out +defaultdict( + int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, + 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, + ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, + 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) +``` + +Como vimos antes, el alfabeto es el único conjunto compuesto de todas las primeras letras de las palabras, y todas las otras letras que aparecen con el prefijo `##`: + +```python +alphabet = [] +for word in word_freqs.keys(): + if word[0] not in alphabet: + alphabet.append(word[0]) + for letter in word[1:]: + if f"##{letter}" not in alphabet: + alphabet.append(f"##{letter}") + +alphabet.sort() +alphabet + +print(alphabet) +``` + +```python out +['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', + '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', + 'w', 'y'] +``` + +También agregamos los tokens especiales usados por el modelo al inicio de ese vocabulario. En el caso de BERT, es la lista `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +A continuación necesitamos separar cada palabra, con todas las letras que no tienen el prefijo `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Ahora que estamos listos para el entrenamiento, escribamos una función que calcule el puntaje para cada par. Usaremos esto en cada etapa del entrenamiento: + +```python +def compute_pair_scores(splits): + letter_freqs = defaultdict(int) + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + letter_freqs[split[0]] += freq + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + letter_freqs[split[i]] += freq + pair_freqs[pair] += freq + letter_freqs[split[-1]] += freq + + scores = { + pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) + for pair, freq in pair_freqs.items() + } + return scores +``` + +Echemos un vistazo a parte de este diccionario luego de las separaciones iniciales: + +```python +pair_scores = compute_pair_scores(splits) +for i, key in enumerate(pair_scores.keys()): + print(f"{key}: {pair_scores[key]}") + if i >= 5: + break +``` + +```python out +('T', '##h'): 0.125 +('##h', '##i'): 0.03409090909090909 +('##i', '##s'): 0.02727272727272727 +('i', '##s'): 0.1 +('t', '##h'): 0.03571428571428571 +('##h', '##e'): 0.011904761904761904 +``` + +Ahora, encontrar el par con el mejor puntaje sólo toma un rápido ciclo: + +```python +best_pair = "" +max_score = None +for pair, score in pair_scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + +print(best_pair, max_score) +``` + +```python out +('a', '##b') 0.2 +``` + +Por lo que la primera fusión a aprender es `('a', '##b') -> 'ab'`, y agregamos `'ab'` al vocabulario: + +```python +vocab.append("ab") +``` + +Para continuar, necesitamos aplicar esa fusión en nuestro diccionario de separaciones (`splits` dictionary). Escribamos otra función para esto: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + merge = a + b[2:] if b.startswith("##") else a + b + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +Y podemos mirar el resultado de la primera fusión: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Ahora tenemos todos los que necesitamos para iterar hasta haber aprendido todas las fusiones que queramos. Apuntemos a un tamaño de vocabulario de 70: + +```python +vocab_size = 70 +while len(vocab) < vocab_size: + scores = compute_pair_scores(splits) + best_pair, max_score = "", None + for pair, score in scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + splits = merge_pair(*best_pair, splits) + new_token = ( + best_pair[0] + best_pair[1][2:] + if best_pair[1].startswith("##") + else best_pair[0] + best_pair[1] + ) + vocab.append(new_token) +``` + +Luego podemos ver el vocabulario generado: + +```py +print(vocab) +``` + +```python out +['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', + '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', + 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +Como podemos ver, comparado con BPE, este tokenizador aprende partes de palabras como tokens un poco más rápido. + + + +💡 Usar `train_new_from_iterator()` en el mismo corpus no resultará en exactamente el mismo vocabulario. Esto porque la librería 🤗 Tokenizers no implementa WordPiece para el entrenamiento (dado que no estamos completamente seguros de su funcionamiento interno), en vez de eso utiliza BPE. + + + +Para tokenizar un nuevo texto, lo pre-tokenizamos, lo separamos, y luego aplicamos el algoritmo de tokenización para cada palabra. Es decir, miramos la subpalabra más grande comenzando al inicio de la primera palabra y la separamos, luego repetimos el proceso en la segunda parte, y así pará el resto de dicha palabra y de las siguientes palabras en el texto: + +```python +def encode_word(word): + tokens = [] + while len(word) > 0: + i = len(word) + while i > 0 and word[:i] not in vocab: + i -= 1 + if i == 0: + return ["[UNK]"] + tokens.append(word[:i]) + word = word[i:] + if len(word) > 0: + word = f"##{word}" + return tokens +``` + +Probémoslo en una palabra que esté en el vocabulario, y en otra que no esté: + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Ahora, escribamos una función que tokenize un texto: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + encoded_words = [encode_word(word) for word in pre_tokenized_text] + return sum(encoded_words, []) +``` + +Podemos probar en cualquier texto: + +```python +tokenize("This is the Hugging Face course!") +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +Eso es todo para el algoritmo WordPiece! Ahora echemos un visto a Unigram. diff --git a/chapters/es/chapter6/7.mdx b/chapters/es/chapter6/7.mdx new file mode 100644 index 000000000..325e86c7c --- /dev/null +++ b/chapters/es/chapter6/7.mdx @@ -0,0 +1,382 @@ +# Tokenización Unigram[[unigram-tokenization]] + + + +El algoritmo de Unigram es a menudo utilizado en SetencePiece, el cual es el algoritmo de tokenización usado por modelos como AlBERT, T5, mBART, Big Bird y XLNet. + + + + + +💡 Esta sección cubre Unigram en profundidad, yendo tan lejos como para mostrar una implementación completa. Puedes saltarte hasta el final si sólo quieres una descripción general del algoritmo de tokenización. + + + +## Algoritmo de Entrenamiento[[training-algorithm]] + +Comparado con BPE y WordPiece, Unigram funciona en la otra dirección: comienza desde un gran vocabulario y remueve tokens hasta que alcanza el tamaño deseado del vocabulario.. Hay varias opciones para construir el vocabulario base: podemos tomar los substrings más comunes en palabras pre-tokenizadas, por ejemplo, o aplicar BPE en el corpus inicial con un tamaño de vocabulario grande. + +En cada paso del entrenamiento, el algoritmo de Unigram calcula la pérdida (`loss`)sobre el corpus dado el vocabulario actual. Entonces para cada símbolo en el vocabulario, el algoritmo calcula cuánto incremetaría el la pérdida (`loss`) total si el símbolo se remueve, y busca por los símbolos que lo incrementarían lo menos posible. Esos símbolos tienen un efecto más bajo en la pérdida sobre el corpus, por lo que en un sentido son "menos necesarios" y son los mejores candidatos para ser removidos. + +Esto es una operación bastante costosa, por lo que no removemos un sólo símbolo asociato con el incremento en la pérdida (`loss`) más baja, sino que \\(p\\) (\\(p\\) es un parámetro que puedes controlar, usualmente 10 o 20) porciento de los símbolos asociados con el incremento más bajo de la pérdida. Este proceso es repetido hasta que el vocabulario ha alcanzado el tamaño deseado. + +Nota que nunca removemos los caracteres base, para asegurarnos que cada palabra pueda ser tokenizada. + +Hora, esto es todavía un poco vago: la parte principal del algoritmo es calcular una pérdida (`loss`) sobre el corpus, y ver como cambia cuando removemos algunos tokens desde el vocabulario, pero no hemos explicado como hacer esto aún. Este paso se basa en el algoritmo de tokenización de un modelo Unigram, por lo que profundizaremos en esto a continuación. + +Usaremos el corpus de los ejemplos previos: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +y para este ejemplo, tomaremos todos los substrings strictos para el vocabulario inicial. + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algoritmo de Tokenización[[tokenization-algorithm]] + +Un modelo Unigram es un tipo de modelo de lenguaje que considera cada token como independiente de los tokens antes que él. Es el modelo de lenguaje más simple, en el sentido de que la probabilidad de que el token X dado el contexto previo es sólo la probabilidad del token X. Por lo que, si usamos un modelo de Lenguaje Unigram para generar texto, siempre predeciríamos el token más común. + +La probabilidad de un token dado es su frecuencia (el número de veces en el cual lo encontramos) en el corpus original, dividido por la suma de todas las frecuencias de todos los tokens en el vocabulario (para asegurarnos que las probabilidad sumen 1). Por ejemplo, `"ug"` está presente en `"hug"`, `"pug"`, y `"hugs"`, por lo que tiene una frecuencia de 20 en nuestro corpus. + +Acá están las frecuencias de todas las posibles subpalabras en el vocabulario: + +``` +("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) +("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) +``` + +Por lo que, la suma de todas las frecuencias es 210, y la probabilidad de la subpalabra `"ug"` es por lo tanto 20/210. + + + +✏️ **Ahora es tu turno!** Escribe el código para calcular las frecuencias de arriba y chequea que los resultados mostrados son correctos, como también la suma total. + + + +Ahora, para tokenizar una palabra dada, miramos todas las posibles segmentaciones en tokens y calculamos la probabilidad de cada uno de acuerdo al modelo Unigram. Dado que todos los tokens se consideran como independientes, esta probabilidad es sólo el producto de la probabilidad de cada token. Por ejemplo, la tokenización `["p", "u", "g"]` de `"pug"` tiene como probabilidad: + +$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ + +Comparativamente, la tokenización `["pu", "g"]` tiene como probabilidad: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +por lo que es un poco más probable. En general, las tokenizaciones con el menor número de tokens posibles tendrán la probabilidad más alta (debido a la división por 210 repetida para cada token), lo cual corresponde a lo que queremos intuitivamente: separar una palabra en el menor número de tokens posibles. + +La tokenización de una palabra con el modelo Unigram es entonces la tokenización con la probabilidad más alta. Acá están las probabilidades para el ejemplo de `"pug"` que obtendríamos para cada posible segmentación: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Por lo que, `"pug"` sería tokenizado como `["p", "ug"]` o `["pu", "g"]`, dependiendo de cual de esas segmentaciones e encuentre primero (notar que en un corpus grande, casos equivalentes como este serán raros). + +En este caso, fue fácil encontrar todas las posibles segmentaciones y calcular sus probabilidades, pero en general, va a ser un poco más difícil. Hay un algoritmo clásico usado para esto, llamado el *Algoritmo de Viterbi* (*Viterbi algorithm*). Esencialmente, podemos construir un grafo para detectar las posibles segmentaciones de una palabra dada diciendo que existe una rama que va desde el caracter _a_ hasta el caracter _b_ si la subpalabra de _a_ hasta _b_ está en el vocabulario, y se atribuye a esa rama la probabilidad de la subpalabra. + +Para encontrar el camino en dicho grafo que va a tener el mejor puntaje el Algoritmo de Viterbi determina, por cada posición en la palabra, la segmentacion con el mejor puntaje que termina en esa posición. Dado que vamos desde el inicio al final, el mejor puntaje puede ser encontrado iterando a través de todas las subpalabras que terminan en la posición actual y luego usando el mejor puntaje de tokenización desde la posición en que esta palabra comienza. Luego sólo tenemos que desenrollar el camino tomado para llegar al final. + +Echemos un vistazo a un ejemplo usando nuestro vocabulario y la palabra `"unhug"`. Para cada posición, las subpalabras con el mejor puntaje terminando ahí son las siguientes: + +``` +Character 0 (u): "u" (score 0.171429) +Character 1 (n): "un" (score 0.076191) +Character 2 (h): "un" "h" (score 0.005442) +Character 3 (u): "un" "hu" (score 0.005442) +Character 4 (g): "un" "hug" (score 0.005442) +``` + +Por lo tanto, `"unhug"` se tokenizaría como `["un", "hug"]`. + + + +✏️ **Ahora es tu turno!** Determina la tokenización de la palabra `"huggun"`, y su puntaje + + + + +## De vuelta al entrenamiento[[back-to-training]] + +Ahora que hemos visto cómo funciona la tokenización, podemos ir un poco más profundo en la pérdida (`loss`) usada durante el entrenamiento. En cualquier etapa, esta pérdida (`loss`) es calculada tokenizando cualquier palabra en el corpus, usando el vocabulario actual y el modelo Unigram determinado por las frecuencias de cada token en el corpus (como se vió antes). + +Cada palabra en el corpus tiene un puntaje, y la pérdida (`loss`) es la log verosimilitud negativa (negative log likelihood) de estos puntajes -- es decir, la suma por todas las palabras en el corpus de todos los `-log(P(word))`. + +Volvamos a nuestro ejemplo con el siguiente corpus: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +La tokenización de cada palabra con sus respectivos puntajes es: + +``` +"hug": ["hug"] (score 0.071428) +"pug": ["pu", "g"] (score 0.007710) +"pun": ["pu", "n"] (score 0.006168) +"bun": ["bu", "n"] (score 0.001451) +"hugs": ["hug", "s"] (score 0.001701) +``` + +Por lo que la loss es: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Ahora necesitamos calcular cómo remover cada token afecta a la pérdida (`loss`). Esto es bastante tedioso, por lo que lo haremos sólo para dos tokens avá y nos ahorraremos el proceso entero para cuando tengamos código que nos ayude. En este (muy) particular caso, teníamos dos tokenizaciones equivalentes de todas las palabras: como vimos antes, por ejemplo, `"pug"` podría ser tokenizado como `["p", "ug"]` con el mismo puntaje. Por lo tanto, removiendo el token `"pu"` del vocabulario nos dará la misma pérdida. + +Por otro lado, remover, `"hug"` hará nuestra pérdida peor, porque la tokenización de `"hug"` y `"hugs"` se convertirá en: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Estos cambios causarán que la pérdida aumenta en: + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Por lo tanto, el token `"pu"` será probablemente removido del vocabulario, pero `"hug"`. + +## Implementando Unigram[[implementing-unigram]] + +Ahora, implementemos todo lo que hemos visto hasta ahora en código. Al igual que BPE y WordPiece, esta es una implementación no tan eficiente del algoritmo Unigram (de hecho, todo lo contrario), pero debería ayudar a entenderla un poco mejor. + +Usaremos el mismo corpus que antes como nuestro ejemplo: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Esta vez, usaremos `xlnet-base-cased` como nuestro modelo: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Al igual que BPE y WordPiece, comenzamos contando el número de ocurrencias para cada palabra en el corpus: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +Luego, necesitamos inicializar nuestro vocabulario a algo más grande que el tamaño de vocabulario que querremos al final. Tenemos que incluir, todos los caracteres básicos (de otra manera no seremos capaces de tokenizar cada palabra), pero para los substrings más grandes mantendremos sólos los más comunes, de manera que los ordenemos por frecuencia: + +```python +char_freqs = defaultdict(int) +subwords_freqs = defaultdict(int) +for word, freq in word_freqs.items(): + for i in range(len(word)): + char_freqs[word[i]] += freq + # Loop through the subwords of length at least 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Sort subwords by frequency +sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) +sorted_subwords[:10] +``` + +```python out +[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] +``` + +Agrupamos los caracteres con las mejores subpalabras para llegar a un vocabulario inicial de 300: + +```python +token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] +token_freqs = {token: freq for token, freq in token_freqs} +``` + + + +💡 SentencePiece usa un algoritmo más eficiente llamado Enhanced Suffix Array (ESA) para crear el vocabulario inicial. + + + +A continuación, calculamos la suma de todas las frecuencias, para convertir las frecuencias en probabilidades. Para nuestro modelo, almacenaremos los logaritmos de las probabilidades, porque es numericamente más estable sumar logaritmos que multiplicar números pequeños, y esto simplificará el cálculo de la pérdida (`loss`) del modelo: + +```python +from math import log + +total_sum = sum([freq for token, freq in token_freqs.items()]) +model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Ahora, la función es la que tokeniza palabras usando el algoritmo de Viterbi. Como vimos antes, el algoritmo calcula la mejor segmentación de cada substring de la palabra, la cual almacenará en una variable llamada `best_segmentations`. Almacenaremos un diccionario por posición en la palabra (desde 0 hasta su largo total), con dos claves: el índice de inicio del último token en la mejor segmentación, y el puntaje de la mejor segmentación. Con el índice del inicio del último token, seremos capaces de recuperar la segmentación total una vez que la lista esté completamente poblada. + +Poblar la lista se hace con dos ciclos: el ciclo principal recorre cada posición de inicio, y el segundo loop, prueba todos los substrings comenaando en esa posición. Si el substring está en el vocabulario, tenemos una nueva segmentación de la palabra hasta esa posición final, la cual comparamos con lo que está en `best_segmentations`. + +Una vez que el ciclo principal se termina, empezamos desde el final y saltamos de una posición de inicio hasta la siguiente, guardando los tokens a medida que avanzamos, hasta alcanzar el inicio de la palabra: + +```python +def encode_word(word, model): + best_segmentations = [{"start": 0, "score": 1}] + [ + {"start": None, "score": None} for _ in range(len(word)) + ] + for start_idx in range(len(word)): + # This should be properly filled by the previous steps of the loop + best_score_at_start = best_segmentations[start_idx]["score"] + for end_idx in range(start_idx + 1, len(word) + 1): + token = word[start_idx:end_idx] + if token in model and best_score_at_start is not None: + score = model[token] + best_score_at_start + # If we have found a better segmentation ending at end_idx, we update + if ( + best_segmentations[end_idx]["score"] is None + or best_segmentations[end_idx]["score"] > score + ): + best_segmentations[end_idx] = {"start": start_idx, "score": score} + + segmentation = best_segmentations[-1] + if segmentation["score"] is None: + # We did not find a tokenization of the word -> unknown + return [""], None + + score = segmentation["score"] + start = segmentation["start"] + end = len(word) + tokens = [] + while start != 0: + tokens.insert(0, word[start:end]) + next_start = best_segmentations[start]["start"] + end = start + start = next_start + tokens.insert(0, word[start:end]) + return tokens, score +``` + +Ya podemos probar nuestro modelo inicial en algunas palabras: + +```python +print(encode_word("Hopefully", model)) +print(encode_word("This", model)) +``` + +```python out +(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) +(['This'], 6.288267030694535) +``` + +Ahora es fácil calcular la pérdida (`loss`) del modelo en el corpus! + +```python +def compute_loss(model): + loss = 0 + for word, freq in word_freqs.items(): + _, word_loss = encode_word(word, model) + loss += freq * word_loss + return loss +``` + +Podemos chequear que funciona en el modelo que tenemos: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Calcular los puntajes para cada token no es tan difícil tampoco; sólo tenemos que calcular la pérdida para los modelos obtenidos al eliminar cada token: + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # We always keep tokens of length 1 + if len(token) == 1: + continue + model_without_token = copy.deepcopy(model) + _ = model_without_token.pop(token) + scores[token] = compute_loss(model_without_token) - model_loss + return scores +``` + +Podemos probarlo en token dado: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Dado que `"ll"` se usa en la tokenización de `"Hopefully"`, y removerlo nos hará probablemente usar el token `"l"` dos veces, esperamos que tendrá una pérdida positiva. `"his"` es sólo usado dentro de la palabra `"This"`, lo cuál es tokenizado como sí mismo, por lo que esperamos que tenga pérdida cero. Acá están los resultados: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Este acercamiento es muy ineficiente, por lo que SentencePiece usa una aproximación de la pérdida del modelo sin el token X: en vez de comenzar desde cero, sólo reemplaza el token X por su segmentación en el vocabulario que queda. De esta manera, todos los puntajes se pueden calcular de una sóla vez al mismo tiempo que la pérdida del modelo. + + + +Con todo esto en su lugar, lo último que necesitamos hacer es agregar los tokens especiales usados por el modelo al vocabulario, e iterar hasta haber podado suficientes tokens de nuestro vocabulario hasta alcanzar el tamaño deseado: + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Remove percent_to_remove tokens with the lowest scores. + for i in range(int(len(model) * percent_to_remove)): + _ = token_freqs.pop(sorted_scores[i][0]) + + total_sum = sum([freq for token, freq in token_freqs.items()]) + model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Luego, para tokenizar algo de texto, sólo necesitamos aplicar la pre-tokenización y luego usar nuestra función `encode_word()`: + +```python +def tokenize(text, model): + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in words_with_offsets] + encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] + return sum(encoded_words, []) + + +tokenize("This is the Hugging Face course.", model) +``` + +```python out +['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] +``` + +Eso es todo para Unigram! Ojalá a esta altura te sientas como un experto en todos los aspectos de los tokenizadores. En la siguiente sección, ahondaremos en las unidades básicas de la librería 🤗 Tokenizers, y te mostraremos cómo puedes usarlo para construir tu propio tokenizador. \ No newline at end of file diff --git a/chapters/es/chapter6/8.mdx b/chapters/es/chapter6/8.mdx new file mode 100644 index 000000000..0754b82d0 --- /dev/null +++ b/chapters/es/chapter6/8.mdx @@ -0,0 +1,566 @@ +# Construir un tokenizador, bloque por bloque[[building-a-tokenizer-block-by-block]] + + + +Como hemos visto en las secciones previas, la tokenización está compuesta de varias etapas: + +- Normalización (cualquier limpieza del texto que se considere necesaria, tales como remover espacios o acentos, normalización Unicode, etc.) +- Pre-tokenización (separar la entrada en palabras) +- Pasar las entradas (inputs) por el modelo (usar las palabras pre-tokenizadas para producir una secuencia de tokens) +- Post-procesamiento (agregar tokens especiales del tokenizador, generando la máscara de atención (attention mask) y los IDs de tipo de token) + +Como recordatorio, acá hay otro vistazo al proceso en totalidad: + +
+The tokenization pipeline. + +
+ +La librería 🤗 Tokenizers ha sido construida para proveer varias opciones para cada una de esas etapas, las cuales se pueden mezclar y combinar. En esta sección veremos cómo podemos construir un tokenizador desde cero, opuesto al entrenamiento de un nuevo tokenizador a partir de uno existente como hicimos en la [Sección 2](/course/chapter6/2). Después de esto, serás capaz de construir cualquier tipo de tokenizador que puedas imaginar! + + + +De manera más precisa, la librería está construida a partir de una clase central `Tokenizer` con las unidades más básica reagrupadas en susbmódulos: + +- `normalizers` contiene todos los posibles tipos de `Normalizer` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` contiene todos los posibles tipos de `PreTokenizer` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` contiene los distintos tipos de `Model` que puedes usar, como `BPE`, `WordPiece`, and `Unigram` (la lista completa [aquí](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` contiene todos los distintos tipos de `Trainer` que puedes usar para entrenar tu modelo en un corpus (uno por cada tipo de modelo; la lista completa [aquí](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` contiene varios tipos de `PostProcessor` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` contiene varios tipos de `Decoder` que puedes usar para decodificar las salidas de la tokenización (la lista completa [aquí](https://huggingface.co/docs/tokenizers/components#decoders)). + +Puedes encontrar la lista completas de las unidades más básicas [aquí](https://huggingface.co/docs/tokenizers/components). + +## Adquirir un corpus[[acquiring-a-corpus]] + +Para entrenar nuestro nuevo tokenizador, usaremos un pequeño corpus de texto (para que los ejemplos se ejecuten rápido). Los pasos para adquirir el corpus son similares a los que tomamos al [beginning of this chapter](/course/chapter6/2), pero esta vez usaremos el conjunto de datos [WikiText-2](https://huggingface.co/datasets/wikitext): + +```python +from datasets import load_dataset + +dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") + + +def get_training_corpus(): + for i in range(0, len(dataset), 1000): + yield dataset[i : i + 1000]["text"] +``` + +La función `get_training_corpus()` es un generador que entregará lotes de 1.000 textos, los cuales usaremos para entrenar el tokenizador. + +🤗 Tokenizers puedes también ser entrenada en archivos de textos directamente. Así es como podemos generar un archivo de texto conteniendo todos los textos/entradas de WikiText-2 que podemos usar localmente: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +A continuación mostraremos como construir tu propios propios tokenizadores BERT, GPT-2 y XLNet, bloque por bloque. Esto nos dará un ejemplo de cada una de los tres principales algoritmos de tokenización: WordPiece, BPE y Unigram. Empecemos con BERT! + +## Construyendo un tokenizador WordPiece desde cero[[building-a-wordpiece-tokenizer-from-scratch]] + +Para construir un tokenizador con la librería 🤗 Tokenizers, empezamos instanciando un objeto `Tokenizer` con un `model`, luego fijamos sus atributos `normalizer`, `pre_tokenizer`, `post_processor`, y `decoder` a los valores que queremos. + +Para este ejemplo, crearemos un `Tokenizer` con modelo WordPiece: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Tenemos que especificar el `unk_token` para que el modelo sepa que retornar si encuentra caracteres que no ha visto antes. Otros argumentos que podemos fijar acá incluyen el `vocab` de nuestro modelo (vamos a entrenar el modelo, por lo que no necesitamos fijar esto) y `max_input_chars_per_word`, el cual especifica el largo máximo para cada palabra (palabras más largas que el valor pasado se serpararán). + +El primer paso de la tokenización es la normalizacion, así que empecemos con eso. Dado que BERT es ampliamente usado, hay un `BertNormalizer` con opciones clásicas que podemos fijar para BERT: `lowercase` (transformar a minúsculas) y `strip_accents` (eliminar acentos); `clean_text` para remover todos los caracteres de control y reemplazar espacios repetidos en uno solo; y `handle_chinese_chars` el cual coloca espacios alrededor de los caracteres en Chino. Para replicar el tokenizador `bert-base-uncased`, basta con fijar este normalizador: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Sin embargo, en términos generales, cuando se construye un nuevo tokenizador no tendrás acceso a tan útil normalizador ya implementado en la librería 🤗 Tokenizers -- por lo que veamos como crear el normalizador BERT a mano. La librería provee un normalizador `Lowercase` y un normalizador `StripAccents`, y puedes componer varios normalizadores usando un `Sequence` (secuencia): + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +También estamos usando un normalizador Unicode `NFD`, ya que de otra manera el normalizador `StripAccents` no reconocerá apropiadamente los caracteres acentuados y por lo tanto, no los eliminará. + +Como hemos visto antes, podemos usar el método `normalize_str()` del `normalizer` para chequear los efectos que tiene en un texto dado: + +```python +# print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Para ir más allá** Si pruebas las dos versiones de los normalizadores previos en un string conteniendo un caracter unicode `u"\u0085"` +de seguro notarás que los dos normalizadores no son exactamente equivalentes. +Para no sobre-complicar demasiado la version con `normalizers.Sequence`, no hemos incluido los reemplazos usando Expresiones Regulares (Regex) que el `BertNormalizer` requiere cuando el argumento `clean_text` se fija como `True` - lo cual es el comportamiento por defecto. Pero no te preocupes, es posible obtener la misma normalización sin usar el útil `BertNormalizer` agregando dos `normalizers.Replace` a la secuencia de normalizadores. + + + +A continuación está la etapa de pre-tokenización. De nuevo, hay un `BertPreTokenizer` pre-hecho que podemos usar: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +O podemos constuirlo desde cero: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Nota que el pre-tokenizador `Whitespace` separa en espacios en blando y todos los caracteres que no son letras, dígitos o el guión bajo/guión al piso (_), por lo que técnicamente separa en espacios en blanco y puntuación: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Si sólo quieres separar en espacios en blanco, deberías usar el pre-tokenizador `WhitespaceSplit`: + +```python +pre_tokenizer = pre_tokenizers.WhitespaceSplit() +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] +``` + +Al igual que con los normalizadores, puedes un `Sequence` para componer varios pre-tokenizadores: + +```python +pre_tokenizer = pre_tokenizers.Sequence( + [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] +) +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +El siguiente paso en el pipeline de tokenización es pasar las entradas a través del modelo. Ya especificamos nuestro modelo en la inicialización, pero todavía necesitamos entrenarlo, lo cual requerirá un `WordPieceTrainer`. El aspecto principal a recordar cuando se instancia un entrenador (trainer) en 🤗 Tokenizers es que necesitas pasarle todos los tokens especiales que tiene la intención de usar -- de otra manera no los agregará al vocabulario, dado que que no están en el corpus de entrenamiento: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +Al igual que especificar `vocab_size` y `special_tokens`, podemos fijar `min_frequency` (el número de veces que un token debe aparecer para ser incluido en el vocabulario) o cambiar `continuing_subword_prefix` (si queremos usar algo diferente a `##`). + +Para entrenar nuestro modelo usando el iterador que definimos antes, tenemos que ejecutar el siguiente comando: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +También podemos usar archivos de texto para entrenar nuestro tokenizador, lo cual se vería así (reinicializamos el modelo con un `WordPiece` vacío de antemano): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +En ambos casos, podemos probar el tokenizador en un texto llamando al método `encode: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +# print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +El `encoding` (codificación) obtenido es un objeto `Encoding`, el cual contiene todas las salidas necesarias del tokenizador y sus distintos atributos: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, y `overflowing`. + +El último paso en el pipeline de tokenización es el post-procesamiento. Necesitamos agregar el token `[CLS]` al inicio y el token `[SEP]` al final (o después de cada oración, si tenemos un par de oraciones). Usaremos un `TemplateProcessor` para esto, pero primero necesitamos conocer los IDs de los tokens `[CLS]` y `[SEP]` en el vocabulario: + +```python +cls_token_id = tokenizer.token_to_id("[CLS]") +sep_token_id = tokenizer.token_to_id("[SEP]") +# print(cls_token_id, sep_token_id) +``` + +```python out +(2, 3) +``` + +Para escribir la plantilla (template) para un `TemplateProcessor`, tenemos que especificar como tratar una sóla oración y un par de oraciones. Para ambos, escribimos los tokens especiales que queremos usar; la primera oración se representa por `$A`, mientras que la segunda oración (si se está codificando un par) se representa por `$B`. Para cada uno de estos (tokens especiales y oraciones), también especificamos el ID del tipo de token correspondiente después de un dos puntos (:). + +La clásica plantilla para BERT se define como sigue: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single=f"[CLS]:0 $A:0 [SEP]:0", + pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], +) +``` + +Nota que necesitamos pasar los IDs de los tokens especiales, para que el tokenizador pueda convertirlos apropiadamente a sus IDs. + +Una vez que se agrega esto, volviendo a nuestro ejemplo anterior nos dará: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +# print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Y en un par de oraciones, obtenemos el resultado apropiado: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") +# print(encoding.tokens) +# print(encoding.type_ids) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Ya casi finalizamos de construir este tokenizador desde cero -- el último paso es incluir un decodificador: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Probemoslo en nuestro `encoding` previo: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Genial! Ahora podemos guardar nuestro tokenizador en un archivo JSON así: + +```python +tokenizer.save("tokenizer.json") +``` + +Podemos cargar ese archivo en un objeto `Tokenizer` con el método `from_file()`: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Para usar este tokenizador en 🤗 Transformers, tenemos que envolverlo en un `PreTrainedTokenizerFast`. Podemos usar una clase generica o, si nuestro tokenizador corresponde un modelo existente, usar esa clase (en este caso, `BertTokenizerFast`). Si aplicas esta lección para construir un tokenizador nuevo de paquete, tendrás que usar la primera opción. + +Para envolver el tokenizador en un `PreTrainedTokenizerFast`, podemos pasar el tokenizador que construimos como un `tokenizer_object` o pasar el archivo del tokenizador que guardarmos como `tokenizer_file`. El aspecto clave a recordar es que tenemos que manualmente fijar los tokens especiales, dado que la clase no puede inferir del objeto `tokenizer` qué token es el el token de enmascaramiento (mask token), el token `[CLS]`, etc.: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Si estás usando una clase de tokenizador específico (como `BertTokenizerFast`), sólo necesitarás especificar los tokens especiales diferentes a los que están por defecto (en este caso, ninguno): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Luego puedes usar este tokenizador como cualquier otro tokenizador de 🤗 Transformers. Puedes guardarlo con el método `save_pretrained()`, o subirlo al Hub con el método `push_to_hub()`. + +Ahora que hemos visto como construir el tokenizador WordPiece, hagamos lo mismo para un tokenizador BPE. Iremos un poco más rápido dato que conoces todos los pasos, y sólo destacaremos las diferencias. + +## Construyendo un tokenizador BPE desde cero[[building-a-bpe-tokenizer-from-scratch]] + +Ahora construyamos un tokenizador GPT-2. Al igual que el tokenizador BERT, empezamos inicializando un `Tokenizer` con un modelo BPE: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +También al igual que BERT, podríamos inicializar este modelo con un vocabulario si tuviéramos uno (necesitaríamos pasar el `vocab` y `merges`, en este caso), pero dado que entrenaremos desde cero, no necesitaremos hacer eso. Tampoco necesitamos especificar `unk_token` porque GPT-2 utiliza un byte-level BPE, que no lo requiere. + +GPT-2 no usa un normalizador, por lo que nos saltamos este paso y vamos directo a la pre-tokenización: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +La opción que agregada acá `ByteLevel` es para no agregar un espacio al inicio de una oración (el cuál es el valor por defecto). Podemos echar un vistazo a la pre-tokenización de un texto de ejemplo como antes: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") +``` + +```python out +[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), + ('tokenization', (15, 27)), ('!', (27, 28))] +``` + +A continuación está el modelo, el cual necesita entrenamiento. Para GPT-2, el único token especial es el token de final de texto (end-of-text): + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Al igual que con el `WordPieceTrainer`, junto con `vocab_size` y `special_tokens`, podemos especificar el `min_frequency` si queremos, o si tenemos un sufijo de fín de palabra (end-of-word suffix) (como ``), podemos fijarlo con `end_of_word_suffix`. + +Este tokenizador también se puede entrenar en archivos de textos: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Echemos un vistazo a la tokenización de un texto de muestra: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +# print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Aplicaremos el post-procesamiento byte-level para el tokenizador GPT-2 como sigue: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +La opción `trim_offsets = False` indica al post-procesador que deberíamos dejar los offsets de los tokens que comiencen con 'Ġ' sin modificar: De esta manera el inicio de los offsets apuntarán al espacio antes de la palabra, no el primer caracter de la palabra (dado que el espacio es técnicamente parte del token). Miremos el resultado con el texto que acabamos de codificar, donde `'Ġtest'` el token en el índice 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Finalmente, agregamos un decodificador byte-level: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +y podemos chequear si funciona de manera apropiada: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Genial! Ahora que estamos listos, podemos guardar el tokenizador como antes, y envolverlo en un `PreTrainedTokenizerFast` o `GPT2TokenizerFast` si queremos usarlo en 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +o: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Como en el último ejemplo, mostraremos cómo construir un tokenizador Unigram desde cero. + +## Construyendo un tokenizador Unigran desde cero[[building-a-unigram-tokenizer-from-scratch]] + +Construyamos un tokenizador XLNet. Al igual que los tokenizadores previos, empezamos inicializando un `Tokenizer` con un modelo Unigram: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +De nuevo, podríamos inicializar este modelo con un vocabulario si tuvieramos uno. + +Para la normalización, XLNet utiliza unos pocos reemplazos (los cuales vienen de SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Esto reemplaza `` y '' con " y cualquier secuencia de dos o más espacios con un espacio simple, además remueve los acentos en el texto a tokenizar. + +El pre-tokenizador a usar para cualquier tokenizador SentencePiece es `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Podemos echar un vistazo a la pre-tokenización de un texto de ejemplo como antes: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") +``` + +```python out +[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] +``` + +A continuación está el modelo, el cuál necesita entrenamiento. XLNet tiene varios tokens especiales: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Un argumento muy importante a no olvidar para el `UnigramTrainer` es el `unk_token`. También podemos pasarle otros argumentos específicos al algoritmo Unigram, tales como el `shrinking_factor` para cada paso donde removemos tokens (su valor por defecto es 0.75) o el `max_piece_length` para especificar el largo máximo de un token dado (su valor por defecto es 16). + +Este tokenizador también se puede entrenar en archivos de texto: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Ahora miremos la tokenización de un texto de muestra: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +# print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Una peculiariodad de XLNet es que coloca el token `` al final de la oración, con un ID de tipo de 2 (para distinguirlo de los otros tokens). Como el resultado el resultado se rellena a la izquierda (left padding). Podemos lidiar con todos los tokens especiales y el token de ID de tipo con una plantilla, al igual que BERT, pero primero tenemos que obtener los IDs de los tokens `` y ``: + +```python +cls_token_id = tokenizer.token_to_id("") +sep_token_id = tokenizer.token_to_id("") +# print(cls_token_id, sep_token_id) +``` + +```python out +0 1 +``` + +La plantilla se ve así: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single="$A:0 :0 :2", + pair="$A:0 :0 $B:1 :1 :2", + special_tokens=[("", sep_token_id), ("", cls_token_id)], +) +``` + +Y podemos probar si funciona codificando un par de oraciones: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") +# print(encoding.tokens) +# print(encoding.type_ids) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', + '▁of', '▁sentence', 's', '!', '', ''] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] +``` + +Finalmente, agregamos el decodificador `Metaspace`: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +y estamos listos con este tokenizador! Podemos guardar el tokenizador como antes, y envolverlo en un `PreTrainedTokenizerFast` o `XLNetTokenizerFast` si queremos usarlo en 🤗 Transformers. Una cosa a notar al usar `PreTrainedTokenizerFast` es que además de los tokens especiales, necesitamos decirle a la librería 🤗 Transformers que rellene a la izquierda (agregar left padding): + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="", + eos_token="", + unk_token="", + pad_token="", + cls_token="", + sep_token="", + mask_token="", + padding_side="left", +) +``` + +O de manera alternativa: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Ahora que has visto como varias de nuestras unidades más básicas se usan para construir tokenizadores existentes, deberías ser capaz de escribir cualquier tokenizador que quieras con la librería 🤗 Tokenizers y ser capaz de usarlo en la librería 🤗 Transformers. \ No newline at end of file diff --git a/chapters/es/chapter6/9.mdx b/chapters/es/chapter6/9.mdx new file mode 100644 index 000000000..37efd29b3 --- /dev/null +++ b/chapters/es/chapter6/9.mdx @@ -0,0 +1,16 @@ +# Tokenizadores, listo![[tokenizers-check]] + + + +Gran trabajo terminando este capítulo! + +Luego de esta profundizacion en los tokenizadores, deberías: + +- Ser capaz de entrenar un nuevo tokenizador usando un existente como plantilla +- Entender como usar los offsets para mapear las posiciones de los tokens a sus trozos de texto original +- Conocer las diferencias entre BPE, WordPiece y Unigram +- Ser capaz de mezclar y combinar los bloques provistos por la librería 🤗 Tokenizers para construir tu propio tokenizador +- Ser capaz de usar el tokenizador dentro de la librería 🤗 Transformers. diff --git a/chapters/es/glossary/1.mdx b/chapters/es/glossary/1.mdx index 562cf2fe9..153b35f6a 100644 --- a/chapters/es/glossary/1.mdx +++ b/chapters/es/glossary/1.mdx @@ -36,8 +36,8 @@ | Incompatibility | Incompatibilidad | | Inference | Inferencia | | Key (in a dictionary) | Llave | -| Learning rate | Rata de aprendizaje | -| Library | Libreria | +| Learning rate | Tasa de aprendizaje | +| Library | Librería | | Linux | Linux | | Load | Cargar | | Loss | Pérdida | @@ -53,14 +53,14 @@ | Padding | Relleno | | Parameter | Parámetro | | Python | Python | -| Pytorch | Pytorch | +| PyTorch | PyTorch | | Samples | Muestras | | Save | Guardar | | Scheduler | Programador | | Script | Script | | Self-Contained | Auto-contenido | | Setup | Instalación | -| TensorFlow | Tensorflow | +| TensorFlow | TensorFlow | | Terminal | Terminal | | Tokenizer | Tokenizador | | Train | Entrenar | @@ -90,10 +90,10 @@ Please refer to [TRANSLATING.txt](/chapters/es/TRANSLATING.txt) for a translatio - Refer and contribute to the glossary frequently to stay on top of the latest choices we make. This minimizes the amount of editing that is required. Add new terms alphabetically sorted. -- In certain cases is better to accept an Enlish word. Check for the correct usage of terms in computer science and commonly used terms in other publications. +- In certain cases is better to accept an English word. Check for the correct usage of terms in computer science and commonly used terms in other publications. - Don't translate industry-accepted acronyms. e.g. TPU or GPU. - If translating a technical word, keep the choice of Spanish translation consistent. This does not apply for non-technical choices, as in those cases variety actually helps keep the text engaging. -- Be exact when choosing equivalents for technical words. Package is Paquete. Library is Libreria. Don't mix and match. +- Be exact when choosing equivalents for technical words. Package is Paquete. Library is Librería. Don't mix and match. diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index afe4a516f..5b7f5151f 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -19,6 +19,16 @@ - local: chapter2/3 title: مدل‌ها +- title: ۳- کوک کردن یک مدل از پیش تعلیم دیده + sections: + - local: chapter3/1 + title: مقدمه + - local: chapter3/2 + title: پردازش داده + - local: chapter3/3 + title: کوک کردن مدل‌ها با استفاده از Trainer API یا کِراس + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها sections: - local: chapter4/1 @@ -26,13 +36,6 @@ - local: chapter4/2 title: بکارگیری مدل‌های از پیش تعلیم دیده -- title: 3. کوک کردن یک مدل از پیش تعلیم دیده - sections: - - local: chapter3/1 - title: مقدمه - - local: chapter3/2 - title: پردازش داده - - title: واژه‌نامه sections: - local: glossary/1 diff --git a/chapters/fa/chapter0/1.mdx b/chapters/fa/chapter0/1.mdx index d46295940..9f9cd475f 100644 --- a/chapters/fa/chapter0/1.mdx +++ b/chapters/fa/chapter0/1.mdx @@ -115,7 +115,7 @@ source .env/bin/activate \# Deactivate the virtual environment - source .env/bin/deactivate + deactivate ```
diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d553572ad..9b50e29e3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -99,12 +99,15 @@ model.train_on_batch(batch, labels) {/if} -هاب تنها شامل مدل‌ها نمی‌باشد؛ بلکه شامل دیتاسِت‌های متعدد در بسیاری از زبان‌های مختلف می‌باشد. شما می‌توانید دیتاسِت‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک دیتاسِت جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی دیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. +هاب تنها شامل مدل‌ها نمی‌باشد؛ بلکه شامل دیتاسِت‌های متعدد در بسیاری از زبان‌های مختلف می‌باشد. شما می‌توانید دیتاسِت‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک دیتاسِت جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading) مشاهده کنید). اما اجازه بدهید اکنون روی دیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. کتابخانه دیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و انبار کردن یک دیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دیتاسِت MRPC را به روش زیر دانلود کنیم:
+ +⚠️ **هشدار** مطمئن شوید که `datasets` نصب شده است. برای اطمینان، دستور `pip install datasets` را اجرا کنید. سپس، مجموعه داده MRPC را بارگذاری کنید و آن را چاپ کنید تا ببینید چه چیزی در آن وجود دارد. + ```py from datasets import load_dataset @@ -300,7 +303,7 @@ tokenized_dataset = tokenizer( این روش به خوبی کار می‌کند، اما مشکل‌اش این است که دیکشنری (از کلیدهای ما شامل، `input_ids`, `attention_mask` و `token_type_ids` و مقادیر آنها که لیست‌هایی از لیست‌ها هستند) برمی‌گرداند. همچنین این روش فقط زمانی کار می‌کند که حافظه موقت کافی جهت ذخیره‌سازی کل دیتاسِت در حین توکِن کردن داشته باشید (در حالی که دیتاسِت‌های موجود در کتابخانه `Datatasets` از هاگینگ‌فِیس فایل‌هایی از نوع [Apache Arrow](https://arrow.apache.org/) هستند که روی دیسک ذخیره شده‌اند، بنابراین شما فقط نمونه‌هایی را که جهت ذخیره در حافظه درخواست کرده‌اید نگه‌ می‌دارید). -به منظور نگه داشتن داده به صورت یک دیتاسِت، از تابع [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌ بر توکِن کردن نیاز داشته باشیم این روش انعطاف‌پذیری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند: +به منظور نگه داشتن داده به صورت یک دیتاسِت، از تابع [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) استفاده می‌کنیم. چنانچه به پیش‌پردازش‌های بیشتری علاوه‌ بر توکِن کردن نیاز داشته باشیم این روش انعطاف‌پذیری لازم را به ما می‌دهد. تابع `map()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند:
@@ -498,4 +501,4 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( [^1]: Microsoft Research Paraphrase Corpus -
\ No newline at end of file +
diff --git a/chapters/fa/chapter3/3.mdx b/chapters/fa/chapter3/3.mdx new file mode 100644 index 000000000..aa64dc157 --- /dev/null +++ b/chapters/fa/chapter3/3.mdx @@ -0,0 +1,221 @@ + + +
+ +# کوک کردن مدل‌ها با استفاده از API `Trainer` + + + + + +ترنسفورمرهای هاگینگ‌فِیس کلاسی به نام `Trainer` دارند که برای کمک به کوک کردن هر مدل از پیش تعلیم دیده‌ای که روی داده شما ارائه می‌دهد به کار می‌رود. به محض اینکه همه کارهای پیش‌پردازش داده در بخش آخر را انجام دادید، فقط چند مرحله باقی‌مانده تا تعریف `Trainer` دارید. سخت ترین قسمت، احتمالا آماده‌سازی محیط جهت اجراي `Trainer.train()` می‌باشد، چرا که این تابع روی CPU بسیار کند اجرا می‌شود. اگر GPU ندارید، می‌توانید از GPU یا TPUهای مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. + +نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: + + +
+ +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) +``` + +
+ +### تعلیم + +قبل از این که بتوانیم `Trainer` مان را تعریف کنیم اولین مرحله تعریف کلاس `TrainingArguments` می‌باشد که شامل همه پارامترهای سطح بالایی است که `Trainer` برای `Training` و `Evaluation` استفاده خواهد کرد. تنها آرگومانی که شما باید ارائه کنید آدرسی است که مدل تعلیم دیده به همراه نقاط تعلیم در آن ذخیره خواهند شد. بقیه پارامترها را می‌توانید به حالت پیش‌فرض رها کنید، که برای کوک کردن پایه به خوبی کار خواهد کرد. + + +
+ +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + +
+ + + +💡 اگر مایلید مدل‌تان را به صورت خودکار در حین تعلیم در هاب بارگذاری کنید، پارامتر `push_to_hub=True` را در `TrainingArguments` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این باره بیشتر خواهیم آموخت. + + + +مرحله دوم تعریف مدل‌مان می‌باشد. مانند [فصل قبل](/course/chapter2)، از کلاس `AutoModelForSequenceClassification` با دو برچسب کلاس استفاده خواهیم کرد: + +
+ +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +
+ +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. + +به محض اینکه مدل‌مان مشخص شد می‌توانیم `Trainer` را با ارسال همه اشیائی که تا کنون ساخته شده‌اند - `model`، `training_args`، دیتاسِت‌های `training` و `validation`، `data_collator` و `tokenizer` به داخل آن تعریف کنیم: + +
+ +```py +from transformers import Trainer + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +
+ +توجه داشته باشید زمانی که `tokenizer` را ارسال می‌کنید، مثل کاری که ما در اینجا انجام دادیم، `data_collator` پیش‌فرض مورد استفاده `Trainer`، همانطور که قبلا تعریف کردیم، `DataCollatorWithPadding` خواهد بود، در تنیجه شما می‌توانید خط `data_collator=data_collator` را در این فراخوانی نادیده بگیرید. این هنوز مهم بود که این بخش از پردازش را در بخش ۲ به شما نشان دهیم! + +برای کوک کردن مدل روی دیتاسِت‌مان ما فقط باید تابع `train()` از `Trainer`مان را صدا بزنیم: + +
+ +```py +trainer.train() +``` + +
+ +این کار، کوک کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و هزینه تعلیم را هر ۵۰۰ مرحله یک‌بار گزارش می‌کند. با این حال به شما نمی‌گوید که مدل‌تان چقدر خوب (یا بد) عمل می‌کند. این به این خاطر است که: + +۱. ما به `Trainer` نگفتیم که در حین تعلیم کیفیت مدل را اندازه‌گیری کند. کاری که می‌توانستیم با مقداردهی پارامتر `evaluation_strategy` به `"steps"` (برای ارزیابی در هر `eval_steps`) یا به `"epoch"` (برای ارزیابی در انتهای هر epoch) انجام دهیم. + +۲. ما تابع `compute_metrics()` را برای `Trainer` فراهم نکردیم تا بتواند معیارها را در حین اصطلاحا ارزیابی محاسبه کند (که در غیر این صورت، ارزیابی فقط هزینه را چاپ می‌کند که عدد چندان گویایی هم نیست) . + +### ارزیابی + +اجازه دهید ببینیم چگونه می‌توانیم تابع `compute_metrics()` مفیدی بسازیم و در تعلیم بعدی از آن استفاده کنیم. تابع باید یک شیء `EvalPrediction` دریافت کند (که تاپلی است شامل فیلدهای `predictions` و `label_ids`) و یک دیکشنری باز گرداند که رشته‌های متنی را به اعداد حقیقی تبدیل می‌کند (رشته‌های متنی نام معیارهای بازگردانده شونده و اعداد حقیقی مقادیر آن‌ها می باشند). برای استخراج چند پیش‌بینی‌ از مدل‌مان، می‌توانیم از دستور `Trainer.predict()` استفاده کنیم: + +
+ +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +
+ +خروجی تابع `predict()` تاپل نام گذاری شده دیگری شامل سه فیلد: `predictions`، `label_ids` و `metrics` می‌باشد. فیلد `metrics` فقط شامل هزینه داده عبور کرده و برخی معیارهای زمان (پیش‌بینی‌، در مجموع و به طور میانگین، چقدر طول کشیده) می‌باشد. به محض این که تابع `compute_metrics()` را کامل کرده و آن را به `Trainer` ارسال کنیم، آن فیلد متریک‌های بازگشتی از `compute_metrics()` را نیز در بر خواهد داشت. + +همانطور که می‌بینید، `predictions` آرایه‌ای دو بعدی است با شکل ۴۰۸ x ۲ (که ۴۰۸ تعداد عناصر در دیتاسِت مورد استفاده‌ ما می‌باشد). این ها logits مربوط به هریک از عناصر دیتاسِتی هستند که ما به تابع `predict()` ارسال کردیم (همانطور که در [فصل قبل](/course/chapter2) دیدید، همه مدل‌های ترَنسفورمِر logits را باز می‌گردانند). برای تبدیل logits به پیش‌بینی‌‌هایی که بتوانیم با برچسب‌هایمان مقایسه کنیم، نیاز داریم اندیس مقدار بیشینه روی بعد دوم را برداریم: + +
+ +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +
+ +اکنون می‌توانیم `preds` را با برچسب‌ها مقایسه کنیم. برای ساختن تابع `compute_metric()`، به متریک‌های کتابخانه داده‌های هاگینگ‌فِیس تکیه خواهیم کرد. ما می‌توانیم متریک‌های وابسته به دیتاسِت MRPC را به راحتی خود دیتاسِت، اما این بار با استفاده از تابع `load_metric()`، بارگذاری کنیم. شیء بازگردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه متریک از آن استفاده کنیم: + +
+ +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +
+ +از آنجایی که مقداردهی تصادفی اولیه مدل می‌تواند متریک‌های نهایی را تغییر دهد، نتایج دقیقی که شما بدست می‌آورید ممکن است متفاوت باشد. در اینجا می‌توانیم ببینیم که مدل ما `accuracy` معادل ۸۵.۷۸٪ و `F1 Score` معادل ۸۹.۹۷٪ روی مجموعه `validation` بدست می‌آورد. آنها دو متریک برای ارزیابی نتایج محک GLUE روی دیتاسِت MRPC هستند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، برای مدل پایه، `F1 Score` معادل ۸۸.۹ را گزارش می‌کند. توجه داشته باشید که آن مدل `uncased` بود، حال آن که در اینجا ما از مدل `cased` استفاده می‌کنیم، که دستیابی به نتایج بهتر را توضیح می‌دهد. + +اکنون با قرار دادن همه چیز کنارهم تابع `compute_metrics()` را بدست خواهیم آورد: + +
+ +```py +def compute_metrics(eval_preds): + metric = load_metric("glue", "mrpc") + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + return metric.compute(predictions=predictions, references=labels) +``` + +
+ +و در اینجا نشان می‌دهیم که چگونه یک `Trainer` جدید با استفاده از تابع `compute_metrics()` تعریف می‌کنیم، تا بتوانیم عملکرد آن را در حین گزارش متریک‌ها در پایان هر epoch مشاهده کنیم: + +
+ +```py +training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + +trainer = Trainer( + model, + training_args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + tokenizer=tokenizer, + compute_metrics=compute_metrics, +) +``` + +
+ +توجه داشته باشید که ما مدلی جدید و `TrainingArguments` جدیدی که `evaluation_strategy` آن `"epoch"` است می‌سازیم - در غیر این صورت فقط تعلیم مدلی که از پیش تعلیم دیده بود را ادامه می‌دادیم. برای راه‌اندازی دور جدید تعلیم، دستور زیر را اجرا می‌کنیم: + +
+ +``` +trainer.train() +``` + +
+ +این بار هزینه validation و متریک‌ها را در پایان هر epoch و در بالای هزینه تعلیم گزارش می‌کنیم. دوباره، به خاطر مقدار دهی تصادفی اولیه لایه سر مدل، مقادیر دقیق accuracy/F1 score که شما بدست می‌آورید ممکن است کمی متفاوت از آنچه ما بدست آورده‌ایم باشد، اما این مقادیر باید در محدوده تخمینی یکسانی باشند. + +به صورت پیش فرض، `Trainer` روی چندین GPU یا TPU کار خواهد کرد و گزینه‌های فراوانی، مثل تعلیم mixed-precision (از مقدار `fp16 = True` در آرگومان‌های تعلیم استفاده کنید) فراهم می‌کند. در فصل ۱۰ همه حالت‌هایی که پشتیبانی می‌کند را مرور خواهیم کرد. + +این پایان مقدمه‌ای بر کوک کردن با استفاده از `Trainer` API می‌باشد. در [فصل ۷](/course/chapter7) مثالی برای نشان دادن چگونگی انجام این کار برای معمول‌ترین مسئله‌های NLP ارائه خواهیم کرد، اما اکنون اجازه دهید ببینیم چگونه همین کار را صرفا با استفاده از PyTorch انجام دهیم. + + + +✏️ **اتحان کنید!** با استفاده از پردازش داده‌ای که در بخش ۲ انجام دادید، مدلی را روی دیتاسِت GLUE SST-2 کوک کنید. + + + +
\ No newline at end of file diff --git a/chapters/fa/chapter3/3_tf.mdx b/chapters/fa/chapter3/3_tf.mdx new file mode 100644 index 000000000..fb49d492f --- /dev/null +++ b/chapters/fa/chapter3/3_tf.mdx @@ -0,0 +1,240 @@ + + +
+ +# کوک کردن مدل‌ها با استفاده از کِراس + + + +زمانی که همه کارهای پیش‌پردازش در بخش قبل را انجام دادید، فقط چند مرحله با‌قی‌مانده تا تعلیم مدل دارید. با این حال، توجه داشته باشید که دستور `model.fit()` روی CPU بسیار آهسته اجرا خواهد شد. اگر GPU ندارید، می‌توانید از GPU یا TPU مجانی روی [گوگل کولَب](https://colab.research.google.com/) استفاده کنید. + +نمونه کدهای زیر فرض می‌کنند که شما مثال‌های بخش قبل را از پیش اجرا کرده‌اید. این یک خلاصه کوتاه است جهت یادآوری آنچه نیاز دارید: + +
+ +```py +from datasets import load_dataset +from transformers import AutoTokenizer, DataCollatorWithPadding +import numpy as np + +raw_datasets = load_dataset("glue", "mrpc") +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + + +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) + + +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf") + +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) + +tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "token_type_ids"], + label_cols=["labels"], + shuffle=False, + collate_fn=data_collator, + batch_size=8, +) +``` + +
+ + +### تعلیم + +مدل‌های تِنسورفِلو که از ترَنسفورمِرهای هاگینگ‌فِیس وارد شده‌اند از پیش مدل‌های کِراس هستند. این هم مقدمه‌ای کوتاه به کِراس. + + + +این به این معنی است که به محض اینکه داده‌مان را در اختیار بگیریم، کار بسیار کمی لازم است تا تعلیم را روی آن شروع کنیم. + + + +مانند [فصل قبل](/course/chapter2)، ما از کلاس `TFAutoModelForSequenceClassification` با دو برچسب دسته استفاده خواهیم کرد: + +
+ +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +
+ +شما متوجه خواهید شد که برخلاف [فصل ۲](/course/chapter2)، بعد از ساختن این مدل از پیش‌ تعلیم دیده یک هشدار دریافت می‌کنید. این به این خاطر است که BERT برای دسته‌بندی دو جمله‌ها از پیش‌ تعلیم ندیده است، بنابراین لایه سَر مدل از پیش‌ تعلیم دیده حذف شده و یک لایه سَر مناسب جهت دسته بندی رشته‌‌‌ها به جای آن قرار گرفته است. هشدارها نشان می‌دهند که برخی از وزن‌های مدل استفاده نشده‌اند (آنهایی که مربوط به لایه‌ سَر حذف شده مدل از پیش تعلیم دیده هستند) و برخی دیگر به صورت تصادفی مقدار‌ دهی شده‌‌اند (آنهایی که مربوط به لایه‌ سَر جدید هستند). در نتیجه این امر شما را تشویق به تعلیم مدل می‌کند، که دقیقا همان کاری است که می‌خواهیم اکنون انجام دهیم. + +برای کوک‌ کردن مدل روی دِیتاسِت‌مان، ما فقط باید مدل را `compile()` کنیم و سپس داده‌مان را به تابع `fit()` ارسال کنیم. این کار فرایند کوک‌ کردن را شروع می‌کند (که باید چند دقیقه روی GPU طول بکشد) و در همین حین هزینه `training` و هزینه `validation` را در انتهای هر epoch گزارش می‌دهد. + + + +توجه داشته باشید که مدل‌های ترَنسفورمِر هاگینگ‌فِیس قابلیت ویژه‌ای دارند که بسیاری از مدل‌های کِراس ندارند - آنها می‌توانند به صورت خودکار از یک تابع هزینه مناسب که به صورت داخلی محاسبه می‌کنند استفاده کنند. در صورتی که شما آرگومانی برای تابع هزینه در زمان `compile()` تعیین نکنید آنها از این تابع هزینه به صورت پیش‌فرض استفاده خواهند کرد. توجه داشته باشید که جهت استفاده از تابع هزینه داخلی شما نیاز خواهید داشت برچسب دسته‌های خودتان را به عنوان بخشی از ورودی، نه به صورت یک برچسب دسته مجزا که روش معمول استفاده از برچسب دسته‌ها در مدل‌های کِراس می‌باشد، ارسال کنید. شما مثال‌هایی از این را در بخش ۲ این درس خواهید دید، جایی که تعیین تابع هزینه‌ی درست می‌تواند تا اندازه‌ای پیچیده باشد. به هر حال، برای دسته‌بندی رشته‌‌‌ها، یک تابع هزینه استانداد کِراس به خوبی کار می‌کند، چیزی که ما در اینجا استفاده خواهیم کرد. + + + +
+ +```py +from tensorflow.keras.losses import SparseCategoricalCrossentropy + +model.compile( + optimizer="adam", + loss=SparseCategoricalCrossentropy(from_logits=True), + metrics=["accuracy"], +) +model.fit( + tf_train_dataset, + validation_data=tf_validation_dataset, +) +``` + +
+ + + +در اینجا توجه شما را به یک مسئله عام جلب می‌کنیم - شما *می‌توانید* فقط نام تابع هزینه را به صورت یک متغیر متنی برای کِراس ارسال کنید، اما کِراس به صورت پیش‌فرض فکر می‌کند شما یک لایه softmax از پیش به خروجی‌تان اعمال کرده‌اید. با این حال، بسیاری از مدل‌ها مقادیر را درست قبل از اینکه softmax به آنها اعمال شود به خروجی می‌دهند، که همچنین به عنوان *logits* شناخته می‌شوند. ما نیاز داریم که به تابع هزینه بگوییم، این کاری است که مدل‌مان انجام می‌دهد و تنها راه گفتن آن این است که به جای ارسال نام تابع هزینه به صورت متغیر متنی، آن را به صورت مستقیم صدا بزنیم. + + + +### بهبود کارایی تعلیم + + + +اگر کد بالا را امتحان کنید، قطعا اجرا خواهد شد، اما متوجه خواهید شد که هزینه بسیار آهسته یا به صورت گاه و بیگاه کاهش می‌یابد. علت اصلی این امر *نرخ یادگیری* می‌باشد. مانند تابع هزینه، وقتی که ما نام بهینه‌ساز را به صورت یک متغیر متنی به کِراس ارسال می‌کنیم، کِراس همه پارامترهای آن، شامل نرخ یادگیری، را با مقادیر پیش‌فرض مقداردهی اولیه می‌کند. به تجربه طولانی، ما می‌دانیم که مدل‌های ترَنسفورمِر از نرخ‌های یادگیری بسیار کوچک‌تر بهره بیشتری می‌برند تا مقدار پیش‌فرض برای بهینه‌ساز Adam، که ۱e-۳ می‌باشد و به صورت‌ ۱۰ به توان یا ۰،۰۰۱ نیز نوشته می‌شود. + +علاوه بر کم کردن یکباره نرخ یادگیری، ترفند دیگری نیز در آستین داریم: ما می‌توانیم نرخ یادگیری را به آهستگی در طول دوره تعلیم کاهش دهیم. گاها خواهید دید که از این روش در متون مشابه با عنوان نرخ یادگیری *محو شونده* یا *بازپُختی* یاد می‌شود. بهترین روش برای انجام این کار در کِراس استفاده از زمان‌بند نرخ یادگیری است. یک زمان‌بند خوب برای استفاده، زمان‌بند `PolynomialDecay` می‌باشد - این زمان‌بند برخلاف نامش نرخ یادگیری را در حالت پیش‌فرض به صورت خطی از مقدار اولیه تا مقدار نهایی در طول دوره تعلیم کاهش می‌دهد که دقیقا همان چیزی است که ما می‌خواهیم. به منظور استفاده درست از زمان‌بند ما نیاز داریم که به آن بگویم طول زمان تعلیم چقدر خواهد بود. در زیر ما آن را به عنوان `num_train_steps` محاسبه می‌کنیم. + +
+ +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# not the original Hugging Face Dataset, so its len() is already num_samples // batch_size. +num_train_steps = len(tf_train_dataset) * num_epochs +lr_scheduler = PolynomialDecay( + initial_learning_rate=5e-5, end_learning_rate=0.0, decay_steps=num_train_steps +) +from tensorflow.keras.optimizers import Adam + +opt = Adam(learning_rate=lr_scheduler) +``` + +
+ + + +کتابخانه ترنسفورمرهای هاگینگ‌فِیس همچنین یک تابع `create_optimizer()` دارد که بهینه‌سازی از نوع `AdamW`، دارای میزان کاهش نرخ یادگیری می‌سازد. این یک میان‌بر مناسب است که آن‌ را با جزئیات در بخش‌های بعدی این آموزش خواهید دید. + + + +اکنون بهینه‌ساز کاملا جدیدمان را در اختیار داریم و می‌توانیم آن را تعلیم دهیم. ابتدا، اجازه دهید مدل را مجددا بارگذاری کنیم تا تغییرات ایجاد شده بر وزنها که در تعلیم قبلی اعمال شده‌اند را به حالت اولیه بازگردانیم، سپس می‌توانیم مدل را با بهینه ساز جدید تدوین کنیم: + +
+ +```py +import tensorflow as tf + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) +model.compile(optimizer=opt, loss=loss, metrics=["accuracy"]) +``` + +
+ +حالا دوباره مدل را فیت می‌کنیم: + +
+ +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + +
+ + + + +💡 اگر مایلید مدلتان را در حین تعلیم به صورت خودکار در هاب بارگذاری کنید، می‌توانید پارامتر `PushToHubCallback` را در تابع `model.fit()` ارسال کنید. در [فصل ۴](/course/chapter4/3) در این مورد بیشتر خواهیم آموخت. + + + +### پیش‌بینی‌های مدل + + + +تعلیم و تماشای پایین رفتن هزینه خیلی خوب است، اما اگر واقعا بخواهیم از مدل تعلیم دیده‌مان، چه برای محاسبه برخی معیار‌ها و چه برای استفاده در خط تولید، خروجی دریافت کنیم باید چه کار کنیم؟ برای این منظور می‌توانیم از تابع `predict()` استفاده کنیم. این کار به ازای هر کلاس یک *logits* از لایه‌ سَر خروجی مدل باز می‌گرداند. + + +
+ +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +
+ +سپس می‌توانیم `logits` را با استفاده از `argmax` برای یافتن بزرگ‌ترین `logit`، که نماینده محتمل‌ترین دسته می‌باشد، به پیش‌بینی‌های دسته مدل تبدیل کنیم: + +
+ +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +
+ +
+ +```python out +(408, 2) (408,) +``` + +
+ +اکنون، اجازه دهید از `preds` برای محاسبه برخی معیارها استفاده کنیم! ما می‌توانیم معیارهای مرتبط با دیتاسِت MRPC را، به همان آسانی که دیتاسِت را بارگذاری کردیم، بارگذاری کنیم اما این بار با استفاده از تابع `load_metric()`. شیء باز گردانده شده تابعی به نام `compute()` دارد که می‌توانیم برای محاسبه معیارها از آن استفاده کنیم: + +
+ +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) +``` + +
+ +
+ +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +
+ +از آنجایی که مقداردهی اولیه تصادفی در لایه‌ سَر مدل ممکن است مقادیر معیارهای حاصل را تغییر دهد، نتایج دریافتی شما می‌توانند متفاوت باشند. در اینجا می‌بینیم که مدل ما دقتی معادل ۸۵.۷۸٪ و F1 score معادل ۸۹.۹۷٪ روی مجموعه `validation` دارد. این‌‌ها دو معیاری هستند که جهت سنجش نتایج روی داده MRPC در محک GLUE به کار رفته‌اند. جدول نتایج در مقاله [BERT](https://arxiv.org/pdf/1810.04805.pdf)، F1 score برابر با ۸۸.۹ برای مدل پایه گزارش کرده است. توجه داشته باشید که آن مدل `uncased` بود در حالی که اکنون ما از مدل `cased` استفاده می‌کنیم، که نتایج بهتر را توجیح می‌کند. + +به این ترتیب مقدمه کوک کردن با استفاده از `API` کِراس به پایان می‌رسد. در فصل ۷ یک مثال از انجام این کار برای معمول‌ترین مسئله‌های `NLP` ارائه خواهد شد. اگر مایلید مهارت‌های خود را روی `API` کِراس تقویت کنید، سعی کنید مدلی را روی مسئله `GLUE SST-2`، با استفاده از روش پردازش داده‌ که در بخش ۲ انجام دادید، کوک کنید. + + +
\ No newline at end of file diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index 47bfa9809..e47bd6044 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -86,7 +86,7 @@ Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `ac source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` Vous pouvez vous assurer que l'environnement est activé en exécutant la commande `which python` : si elle pointe vers l'environnement virtuel, alors vous l'avez activé avec succès ! diff --git a/chapters/fr/chapter2/1.mdx b/chapters/fr/chapter2/1.mdx index aa72cc82f..2fb2880e1 100644 --- a/chapters/fr/chapter2/1.mdx +++ b/chapters/fr/chapter2/1.mdx @@ -11,7 +11,7 @@ La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème - **La facilité d'utilisation** : en seulement deux lignes de code il est possible de télécharger, charger et utiliser un modèle de NLP à l'état de l'art pour faire de l'inférence, - **La flexibilité** : au fond, tous les modèles sont de simples classes PyTorch `nn.Module` ou TensorFlow `tf.keras.Model` et peuvent être manipulés comme n'importe quel autre modèle dans leurs *frameworks* d'apprentissage automatique respectifs, -- **La simplicité** : pratiquement aucune abstraction n'est faite dans la bibliothèque. Avoir tout dans un fichier est un concept central : la passe avant d'un modèle est entièrement définie dans un seul fichier afin que le code lui-même soit compréhensible et piratable. +- **La simplicité** : pratiquement aucune abstraction n'est faite dans la bibliothèque. Avoir tout dans un fichier est un concept central : la passe avant d'un modèle est entièrement définie dans un seul fichier afin que le code lui-même soit compréhensible et modifiable. Cette dernière caractéristique rend 🤗 *Transformers* très différent des autres bibliothèques d'apprentissage automatique. Les modèles ne sont pas construits sur des modules partagés entre plusieurs fichiers. Au lieu de cela, chaque modèle possède ses propres couches. diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index 1336799d4..b05f8b154 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -180,7 +180,7 @@ Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nou Cette architecture ne contient que le module de *transformer* de base : étant donné certaines entrées, il produit ce que nous appellerons des *états cachés*, également connus sous le nom de *caractéristiques*. Pour chaque entrée du modèle, nous récupérons un vecteur en grande dimension représentant la **compréhension contextuelle de cette entrée par le *transformer***. -Si cela ne fait pas sens, ne vous inquiétez pas. Nous expliquons tout plus tard. +Si cela n'a pas de sens, ne vous inquiétez pas. Nous expliquons tout plus tard. Bien que ces états cachés puissent être utiles en eux-mêmes, ils sont généralement les entrées d'une autre partie du modèle, connue sous le nom de *tête*. Dans le [chapitre 1](/course/fr/chapter1), les différentes tâches auraient pu être réalisées avec la même architecture mais en ayant chacune d'elles une tête différente. diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 6e36b38df..f0f74bd1c 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -92,10 +92,14 @@ Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC ( {/if} -Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes. +Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de données dans un tas de langues différentes. Vous pouvez explorer les jeux de données [ici](https://huggingface.co/datasets) et nous vous conseillons d'essayer de charger un nouveau jeu de données une fois que vous avez étudié cette section (voir la documentation générale [ici](https://huggingface.co/docs/datasets/loading)). Mais pour l'instant, concentrons-nous sur le jeu de données MRPC ! Il s'agit de l'un des 10 jeux de données qui constituent le [*benchmark* GLUE](https://gluebenchmark.com/) qui est un *benchmark* académique utilisé pour mesurer les performances des modèles d'apprentissage automatique sur 10 différentes tâches de classification de textes. La bibliothèque 🤗 *Datasets* propose une commande très simple pour télécharger et mettre en cache un jeu de données à partir du *Hub*. On peut télécharger le jeu de données MRPC comme ceci : + +⚠️ **Attention** Assurez-vous que `datasets` est installé en exécutant `pip install datasets`. Ensuite, chargez le jeu de données MRPC et imprimez-le pour voir ce qu'il contient. + + ```py from datasets import load_dataset @@ -246,7 +250,7 @@ tokenized_dataset = tokenizer( Cela fonctionne bien, mais a l'inconvénient de retourner un dictionnaire (avec nos clés, `input_ids`, `attention_mask`, et `token_type_ids`, et des valeurs qui sont des listes de listes). Cela ne fonctionnera également que si vous avez assez de RAM pour stocker l'ensemble de votre jeu de données pendant la tokenisation (alors que les jeux de données de la bibliothèque 🤗 *Datasets* sont des fichiers [Apache Arrow](https://arrow.apache.org/) stockés sur le disque, vous ne gardez donc en mémoire que les échantillons que vous demandez). -Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées : +Pour conserver les données sous forme de jeu de données, nous utiliserons la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Cela nous permet également une certaine flexibilité, si nous avons besoin d'un prétraitement plus poussé que la simple tokenisation. La méthode `map()` fonctionne en appliquant une fonction sur chaque élément de l'ensemble de données, donc définissons une fonction qui tokenise nos entrées : ```py def tokenize_function(example): diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index 86a218a7c..58640da8b 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -132,7 +132,7 @@ C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer divers -L'argument `data_files` de la fonction `load_dataset()` est assez flexible et peut être soit un chemin de fichier unique, une liste de chemins de fichiers, ou un dictionnaire qui fait correspondre les noms des échantillons aux chemins de fichiers. Vous pouvez également regrouper les fichiers correspondant à un motif spécifié selon les règles utilisées par le shell Unix. Par exemple, vous pouvez regrouper tous les fichiers JSON d'un répertoire en une seule division en définissant `data_files="*.json"`. Voir la [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) de 🤗 *Datasets* pour plus de détails. +L'argument `data_files` de la fonction `load_dataset()` est assez flexible et peut être soit un chemin de fichier unique, une liste de chemins de fichiers, ou un dictionnaire qui fait correspondre les noms des échantillons aux chemins de fichiers. Vous pouvez également regrouper les fichiers correspondant à un motif spécifié selon les règles utilisées par le shell Unix. Par exemple, vous pouvez regrouper tous les fichiers JSON d'un répertoire en une seule division en définissant `data_files="*.json"`. Voir la [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) de 🤗 *Datasets* pour plus de détails. @@ -164,6 +164,6 @@ Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de t -✏️ **Essayez !** Choisissez un autre jeu de données hébergé sur GitHub ou dans le [*UCI Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et essayez de le charger localement et à distance en utilisant les techniques présentées ci-dessus. Pour obtenir des points bonus, essayez de charger un jeu de données stocké au format CSV ou texte (voir la [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) pour plus d'informations sur ces formats). +✏️ **Essayez !** Choisissez un autre jeu de données hébergé sur GitHub ou dans le [*UCI Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et essayez de le charger localement et à distance en utilisant les techniques présentées ci-dessus. Pour obtenir des points bonus, essayez de charger un jeu de données stocké au format CSV ou texte (voir la [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files) pour plus d'informations sur ces formats). diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index 271396fcb..1c38a5fff 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -249,7 +249,7 @@ Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos je -✏️ **Essayez !** Utilisez la fonction `Dataset.sort()` pour inspecter les avis avec le plus grand nombre de mots. Consultez la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) pour voir quel argument vous devez utiliser pour trier les avis par longueur dans l'ordre décroissant. +✏️ **Essayez !** Utilisez la fonction `Dataset.sort()` pour inspecter les avis avec le plus grand nombre de mots. Consultez la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) pour voir quel argument vous devez utiliser pour trier les avis par longueur dans l'ordre décroissant. @@ -396,7 +396,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -Oh non ! Cela n'a pas fonctionné ! Pourquoi ? L'examen du message d'erreur nous donne un indice : il y a une incompatibilité dans les longueurs de l'une des colonnes. L'une étant de longueur 1 463 et l'autre de longueur 1 000. Si vous avez consulté la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) de `Dataset.map()`, vous vous souvenez peut-être qu'il s'agit du nombre d'échantillons passés à la fonction que nous mappons. Ici, ces 1 000 exemples ont donné 1 463 nouvelles caractéristiques, entraînant une erreur de forme. +Oh non ! Cela n'a pas fonctionné ! Pourquoi ? L'examen du message d'erreur nous donne un indice : il y a une incompatibilité dans les longueurs de l'une des colonnes. L'une étant de longueur 1 463 et l'autre de longueur 1 000. Si vous avez consulté la [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) de `Dataset.map()`, vous vous souvenez peut-être qu'il s'agit du nombre d'échantillons passés à la fonction que nous mappons. Ici, ces 1 000 exemples ont donné 1 463 nouvelles caractéristiques, entraînant une erreur de forme. Le problème est que nous essayons de mélanger deux jeux de données différents de tailles différentes : les colonnes `drug_dataset` auront un certain nombre d'exemples (les 1 000 dans notre erreur), mais le `tokenized_dataset` que nous construisons en aura plus (le 1 463 dans le message d'erreur). Cela ne fonctionne pas pour un `Dataset`, nous devons donc soit supprimer les colonnes de l'ancien jeu de données, soit leur donner la même taille que dans le nouveau jeu de données. Nous pouvons faire la première option avec l'argument `remove_columns` : diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 1fdc67418..2d3d62ba6 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -48,7 +48,7 @@ Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de do -✎ Par défaut, 🤗 *Datasets* décompresse les fichiers nécessaires pour charger un jeu de données. Si vous souhaitez conserver de l'espace sur le disque dur, vous pouvez passer `DownloadConfig(delete_extracted=True)` à l'argument `download_config` de `load_dataset()`. Voir la [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) pour plus de détails. +✎ Par défaut, 🤗 *Datasets* décompresse les fichiers nécessaires pour charger un jeu de données. Si vous souhaitez conserver de l'espace sur le disque dur, vous pouvez passer `DownloadConfig(delete_extracted=True)` à l'argument `download_config` de `load_dataset()`. Voir la [documentation](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) pour plus de détails. diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index af48c82b3..63a14bf36 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -431,7 +431,7 @@ Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponibl -💡 Vous pouvez également télécharger un jeu de données sur le *Hub* directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [guide de 🤗 *Datasets*](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) pour savoir comment procéder. +💡 Vous pouvez également télécharger un jeu de données sur le *Hub* directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [guide de 🤗 *Datasets*](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) pour savoir comment procéder. diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index 610af1078..19d2e1f5d 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -193,7 +193,7 @@ D'accord, cela nous a donné quelques milliers de commentaires avec lesquels tra -✏️ **Essayez !** Voyez si vous pouvez utiliser `Dataset.map()` pour exploser la colonne `comments` de `issues_dataset` _sans_ recourir à l'utilisation de Pandas. C'est un peu délicat. La section [« Batch mapping »](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 *Datasets* peut être utile pour cette tâche. +✏️ **Essayez !** Voyez si vous pouvez utiliser `Dataset.map()` pour exploser la colonne `comments` de `issues_dataset` _sans_ recourir à l'utilisation de Pandas. C'est un peu délicat. La section [« Batch mapping »](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) de la documentation 🤗 *Datasets* peut être utile pour cette tâche. diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx index 4846966e4..66a5c11db 100644 --- a/chapters/fr/chapter6/10.mdx +++ b/chapters/fr/chapter6/10.mdx @@ -228,7 +228,7 @@ Testons ce que vous avez appris dans ce chapitre ! explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, { - text: "WordPiece Les tokenizer apprennent les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + text: "Les tokenizer WordPiece apprennent les règles de fusion en fusionnant la paire de tokens la plus fréquente.", explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, { diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index 6e670483f..f523851ed 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -25,7 +25,7 @@ {/if} -Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire d'u ncontexte la réponse à la question posée. Nous verrons ensuite comment gérer les contextes très longs qui finissent par être tronqués. Vous pouvez sauter cette section si vous n'êtes pas intéressé par la tâche de réponse aux questions. +Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire d'un contexte la réponse à la question posée. Nous verrons ensuite comment gérer les contextes très longs qui finissent par être tronqués. Vous pouvez sauter cette section si vous n'êtes pas intéressé par la tâche de réponse aux questions. {#if fw === 'pt'} @@ -274,11 +274,11 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() A ce stade, nous pourrions prendre l'argmax des probabilités de début et de fin mais nous pourrions nous retrouver avec un indice de début supérieur à l'indice de fin. Nous devons donc prendre quelques précautions supplémentaires. Nous allons calculer les probabilités de chaque `start_index` et `end_index` possible où `start_index<=end_index`, puis nous prendrons le *tuple* `(start_index, end_index)` avec la plus grande probabilité. -En supposant que les événements « La réponse commence à `start_index` » et « La réponse se termine à `end_index` » sont indépendants, la probabilité que la réponse commence à `end_index` et se termine à `end_index` est : +En supposant que les événements « La réponse commence à `start_index` » et « La réponse se termine à `end_index` » sont indépendants, la probabilité que la réponse commence à `start_index` et se termine à `end_index` est : $$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ -Ainsi, pour calculer tous les scores, il suffit de calculer tous les produits \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) où `start_index <= end_index`. +Ainsi, pour calculer tous les scores, il suffit de calculer tous les produits $$\\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\)$$ où `start_index <= end_index`. Calculons d'abord tous les produits possibles : diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index 083a8fd8a..5c9108ba3 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -103,7 +103,7 @@ Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug -✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? +✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenisé ? diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index 2240cc023..ab970d817 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -25,7 +25,7 @@ Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part À chaque étape de l'entraînement, l'algorithme *Unigram* calcule une perte sur le corpus compte tenu du vocabulaire actuel. Ensuite, pour chaque symbole du vocabulaire, l'algorithme calcule de combien la perte globale augmenterait si le symbole était supprimé et recherche les symboles qui l'augmenteraient le moins. Ces symboles ont un effet moindre sur la perte globale du corpus, ils sont donc en quelque sorte « moins nécessaires » et sont les meilleurs candidats à la suppression. -Comme il s'agit d'une opération très coûteuse, nous ne nous contentons pas de supprimer le symbole unique associé à la plus faible augmentation de la perte mais le \\(p\\) pourcent des symboles associés à la plus faible augmentation de la perte. \(p\\) est un hyperparamètre que vous pouvez contrôler, valant généralement 10 ou 20. Ce processus est ensuite répété jusqu'à ce que le vocabulaire ait atteint la taille souhaitée. +Comme il s'agit d'une opération très coûteuse, nous ne nous contentons pas de supprimer le symbole unique associé à la plus faible augmentation de la perte mais le \\(p\\) pourcent des symboles associés à la plus faible augmentation de la perte. \\(p\\) est un hyperparamètre que vous pouvez contrôler, valant généralement 10 ou 20. Ce processus est ensuite répété jusqu'à ce que le vocabulaire ait atteint la taille souhaitée. Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index f8a740502..5bed366b9 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -30,14 +30,14 @@ La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs opt Plus précisément, la bibliothèque est construite autour d'une classe centrale `Tokenizer` avec les blocs de construction regroupés en sous-modules : -- `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)), -- `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)), -- `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)), -- `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)), -- `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)), -- `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)). - -Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/python/latest/components.html). +- `normalizers` contient tous les types de `Normalizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/normalizers)), +- `pre_tokenizers` contient tous les types de `PreTokenizer` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)), +- `models` contient les différents types de `Model` que vous pouvez utiliser, comme `BPE`, `WordPiece`, et `Unigram` (liste complète [ici](https://huggingface.co/docs/tokenizers/api/models)), +- `trainers` contient tous les différents types de `Trainer` que vous pouvez utiliser pour entraîner votre modèle sur un corpus (un par type de modèle ; liste complète [ici](https://huggingface.co/docs/tokenizers/api/trainers)), +- `post_processors` contient les différents types de `PostProcessor` que vous pouvez utiliser (liste complète [ici](https://huggingface.co/docs/tokenizers/api/post-processors)), +- `decoders` contient les différents types de `Decoder` que vous pouvez utiliser pour décoder les sorties de tokenization (liste complète [ici](https://huggingface.co/docs/tokenizers/components#decoders)). + +Vous pouvez trouver la liste complète des blocs de construction [ici](https://huggingface.co/docs/tokenizers/components). ## Acquisition d'un corpus diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 6f62da3f5..d778c8479 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -119,7 +119,7 @@ split_datasets["train"][1]["translation"] ``` Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues qui nous intéresse. -Une particularité de ce jeu de données rempli de termes techniques informatiques est qu'ils sont tous entièrement traduits en français. Cependant, les ingénieurs français sont souvent paresseux et laissent la plupart des mots spécifiques à l'informatique en anglais lorsqu'ils parlent. Ici, par exemple, le mot « *threads* » pourrait très bien apparaître dans une phrase française, surtout dans une conversation technique. Mais dans ce jeu de données, il a été traduit en « fils de discussion ». Le modèle pré-entraîné que nous utilisons (qui a été pré-entraîné sur un plus grand corpus de phrases françaises et anglaises) prend l'option de laisser le mot tel quel : +Une particularité de ce jeu de données rempli de termes techniques informatiques est qu'ils sont tous entièrement traduits en français. Cependant, les ingénieurs français laissent la plupart des mots spécifiques à l'informatique en anglais lorsqu'ils parlent. Ici, par exemple, le mot « *threads* » pourrait très bien apparaître dans une phrase française, surtout dans une conversation technique. Mais dans ce jeu de données, il a été traduit en « fils de discussion ». Le modèle pré-entraîné que nous utilisons (qui a été pré-entraîné sur un plus grand corpus de phrases françaises et anglaises) prend l'option de laisser le mot tel quel : ```py from transformers import pipeline diff --git a/chapters/gj/chapter0/1.mdx b/chapters/gj/chapter0/1.mdx index de32bfdb2..c62b588e1 100644 --- a/chapters/gj/chapter0/1.mdx +++ b/chapters/gj/chapter0/1.mdx @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` જો તમે verify કરવા માંગતા હોવ તો `which python` command run કરો. એ તમરા virtual environment ના ફોલ્ડર ને આઉટપુટ માં આપશે. આ એવું સાબિત કરે છે કે virtual environment સફળાપૂર્વક active છે.! diff --git a/chapters/he/chapter0/1.mdx b/chapters/he/chapter0/1.mdx index 9d9861223..ffc29378e 100644 --- a/chapters/he/chapter0/1.mdx +++ b/chapters/he/chapter0/1.mdx @@ -108,7 +108,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ```
diff --git a/chapters/hi/chapter0/1.mdx b/chapters/hi/chapter0/1.mdx index 6a9490ee0..9af4ab08d 100644 --- a/chapters/hi/chapter0/1.mdx +++ b/chapters/hi/chapter0/1.mdx @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` आप यह सुनिश्चित कर सकते हैं कि `which python` आदेश चलाकर कौन सा पर्यावरण सक्रिय है: यदि यह आभासी वातावरण की ओर इशारा करता है, तो आपने इसे सफलतापूर्वक सक्रिय कर दिया है! diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index eb45b962d..501c3dc5e 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -84,10 +84,14 @@ model.train_on_batch(batch, labels) {/if} -हब में केवल मॉडल ही नहीं हैं; इसमें कई अलग-अलग भाषाओं में कई डेटासेट भी हैं। आप [यहां](https://huggingface.co/datasets) डेटासेट ब्राउज़ कर सकते हैं, और हम अनुशंसा करते हैं कि आप इस अनुभाग को पढ़ने के बाद एक नए डेटासेट को लोड और संसाधित करने का प्रयास करें ([यहां](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) सामान्य दस्तावेज देखें)। लेकिन अभी के लिए, आइए MRPC डेटासेट पर ध्यान दें! यह [GLUE बेंचमार्क](https://gluebenchmark.com/) की रचना करने वाले 10 डेटासेट में से एक है, जो एक अकादमिक बेंचमार्क है जिसका उपयोग 10 अलग-अलग पाठ वर्गीकरण कार्यों में ML मॉडल के प्रदर्शन को मापने के लिए किया जाता है। +हब में केवल मॉडल ही नहीं हैं; इसमें कई अलग-अलग भाषाओं में कई डेटासेट भी हैं। आप [यहां](https://huggingface.co/datasets) डेटासेट ब्राउज़ कर सकते हैं, और हम अनुशंसा करते हैं कि आप इस अनुभाग को पढ़ने के बाद एक नए डेटासेट को लोड और संसाधित करने का प्रयास करें ([यहां](https://huggingface.co/docs/datasets/loading) सामान्य दस्तावेज देखें)। लेकिन अभी के लिए, आइए MRPC डेटासेट पर ध्यान दें! यह [GLUE बेंचमार्क](https://gluebenchmark.com/) की रचना करने वाले 10 डेटासेट में से एक है, जो एक अकादमिक बेंचमार्क है जिसका उपयोग 10 अलग-अलग पाठ वर्गीकरण कार्यों में ML मॉडल के प्रदर्शन को मापने के लिए किया जाता है। 🤗 डेटासेट लाइब्रेरी एक बहुत ही सरल कमांड प्रदान करती है हब पर डेटासेट को डाउनलोड और कैश करने के लिए। हम MRPC डेटासेट को इस तरह डाउनलोड कर सकते हैं: + +⚠️ **चेतावनी** सुनिश्चित करें कि `datasets` स्थापित है। इसके लिए `pip install datasets` चलाएँ। फिर, MRPC डेटासेट को लोड करें और देखें कि इसमें क्या है। + + ```py from datasets import load_dataset @@ -235,7 +239,7 @@ tokenized_dataset = tokenizer( यह अच्छी तरह से काम करता है, लेकिन इसमें एक शब्दकोश (साथ में हमारी कुंजी, `input_ids`, `attention_mask`, और `token_type_ids`, और मान जो सूचियों की सूचियां हैं) के लौटने का नुकसान है। यह केवल तभी काम करेगा जब आपके पास पर्याप्त RAM हो अपने पूरे डेटासेट को टोकननाइजेशन के दौरान स्टोर करने के लिए (जबकि 🤗 डेटासेट लाइब्रेरी के डेटासेट [अपाचे एरो](https://arrow.apache.org/) फाइलें हैं जो डिस्क पर संग्रहीत है, तो आप केवल उन सैम्पल्स को रखते हैं जिन्हें आप मेमोरी मे लोड करना चाहतें है)। -डेटा को डेटासेट के रूप में रखने के लिए, हम [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) पद्धति का उपयोग करेंगे। अगर हमें सिर्फ टोकननाइजेशन की तुलना में अधिक पूर्व प्रसंस्करण की आवश्यकता होती है, तो यह हमें कुछ अधिक लचीलेपन की भी अनुमति देता है। `map()` विधि डेटासेट के प्रत्येक तत्व पर एक फ़ंक्शन लागू करके काम करती है, तो चलिए एक फ़ंक्शन को परिभाषित करते हैं जो हमारे इनपुट को टोकननाइज़ करेगा : +डेटा को डेटासेट के रूप में रखने के लिए, हम [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) पद्धति का उपयोग करेंगे। अगर हमें सिर्फ टोकननाइजेशन की तुलना में अधिक पूर्व प्रसंस्करण की आवश्यकता होती है, तो यह हमें कुछ अधिक लचीलेपन की भी अनुमति देता है। `map()` विधि डेटासेट के प्रत्येक तत्व पर एक फ़ंक्शन लागू करके काम करती है, तो चलिए एक फ़ंक्शन को परिभाषित करते हैं जो हमारे इनपुट को टोकननाइज़ करेगा : ```py def tokenize_function(example): diff --git a/chapters/id/chapter0/1.mdx b/chapters/id/chapter0/1.mdx index 0f49326d9..b8efdcaa9 100644 --- a/chapters/id/chapter0/1.mdx +++ b/chapters/id/chapter0/1.mdx @@ -86,7 +86,7 @@ Instruksi dibawah adalah instruksi untuk mengaktifkan dan menonaktifkan _virtual source .env/bin/activate # Menonaktifkan virtual environment -source .env/bin/deactivate +deactivate ``` Anda bisa memastikan bahwa anda menggunakan Python versi _virtual environment_ dengan mengeksekusi `which python` di terminal: jika balasan terminal adalah Python di dalam folder *.env*, maka _virtual environment_ anda sudah aktif! diff --git a/chapters/it/chapter0/1.mdx b/chapters/it/chapter0/1.mdx index 844edc4f1..ac48cd479 100644 --- a/chapters/it/chapter0/1.mdx +++ b/chapters/it/chapter0/1.mdx @@ -86,7 +86,7 @@ Puoi entrare e uscire dall'ambiente virtuale utilizzando gli script `activate` e source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` Assicurati che l'ambiente sia configurato correttamente eseguendo il comando `which python`: se come risposta ottieni l'ambiente virtuale, significa che l'hai attivato bene! diff --git a/chapters/it/chapter3/2.mdx b/chapters/it/chapter3/2.mdx index 9de949110..c83c1601a 100644 --- a/chapters/it/chapter3/2.mdx +++ b/chapters/it/chapter3/2.mdx @@ -84,10 +84,14 @@ In questa sezione verrà usato come esempio il dataset MRPC (Microsoft Research {/if} -L'Hub non contiene solo modelli; contiene anche molti dataset in tante lingue diverse. I dataset possono essere esplorati [qui](https://huggingface.co/datasets), ed è consigliato tentare di caricare e processare un nuovo dataset dopo aver completato questa sezione (cfr. la [documentazione](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Per ora, focalizziamoci sul dataset MRPC! Questo è uno dei 10 dataset che fanno parte del [GLUE benchmark](https://gluebenchmark.com/), che è un benchmark accademico usato per misurare la performance di modelli ML su 10 compiti di classificazione del testo. +L'Hub non contiene solo modelli; contiene anche molti dataset in tante lingue diverse. I dataset possono essere esplorati [qui](https://huggingface.co/datasets), ed è consigliato tentare di caricare e processare un nuovo dataset dopo aver completato questa sezione (cfr. la [documentazione](https://huggingface.co/docs/datasets/loading). Per ora, focalizziamoci sul dataset MRPC! Questo è uno dei 10 dataset che fanno parte del [GLUE benchmark](https://gluebenchmark.com/), che è un benchmark accademico usato per misurare la performance di modelli ML su 10 compiti di classificazione del testo. La libreria 🤗 Datasets fornisce un comando molto semplice per scaricare e mettere nella cache un dataset sull'Hub. Il dataset MRPC può essere scaricato così: + +⚠️ **Attenzione** Assicurati che `datasets` sia installato eseguendo `pip install datasets`. Poi, carica il dataset MRPC e stampalo per vedere cosa contiene. + + ```py from datasets import load_dataset @@ -236,7 +240,7 @@ tokenized_dataset = tokenizer( Questo metodo funziona, ma ha lo svantaggio di restituire un dizionario (avente `input_ids`, `attention_mask`, e `token_type_ids` come chiavi, e delle liste di liste come valori). Oltretutto, questo metodo funziona solo se si ha a disposizione RAM sufficiente per contenere l'intero dataset durante la tokenizzazione (mentre i dataset dalla libreria 🤗 Datasets sono file [Apache Arrow](https://arrow.apache.org/) archiviati su disco, perciò in memoria vengono caricati solo i campioni richiesti). -Per tenere i dati come dataset, utilizzare il metodo [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Ciò permette anche della flessibilità extra, qualora fosse necessario del preprocessing aggiuntivo oltre alla tokenizzazione. Il metodo `map()` applica una funziona ad ogni elemento del dataset, perciò bisogna definire una funzione che tokenizzi gli input: +Per tenere i dati come dataset, utilizzare il metodo [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Ciò permette anche della flessibilità extra, qualora fosse necessario del preprocessing aggiuntivo oltre alla tokenizzazione. Il metodo `map()` applica una funziona ad ogni elemento del dataset, perciò bisogna definire una funzione che tokenizzi gli input: ```py def tokenize_function(example): diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx index 738710236..c3ead7ad6 100644 --- a/chapters/it/chapter5/2.mdx +++ b/chapters/it/chapter5/2.mdx @@ -128,7 +128,7 @@ Questo è proprio ciò che volevamo. Ora possiamo applicare diverse tecniche di -L'argomento `data_files` della funzione `load_dataset()` è molto flessibile, e può essere usato con un percorso file singolo, con una lista di percorsi file, o un dizionario che mappa i nomi delle sezioni ai percorsi file. È anche possibile usare comandi glob per recuperare tutti i file che soddisfano uno specifico pattern secondo le regole dello shell di Unix (ad esempio, è possibile recuperare tutti i file JSON presenti in una cartella usando il pattern `data_files="*.json"`). Consulta la [documentazione](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) 🤗 Datasets per maggiori informazioni. +L'argomento `data_files` della funzione `load_dataset()` è molto flessibile, e può essere usato con un percorso file singolo, con una lista di percorsi file, o un dizionario che mappa i nomi delle sezioni ai percorsi file. È anche possibile usare comandi glob per recuperare tutti i file che soddisfano uno specifico pattern secondo le regole dello shell di Unix (ad esempio, è possibile recuperare tutti i file JSON presenti in una cartella usando il pattern `data_files="*.json"`). Consulta la [documentazione](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets per maggiori informazioni. @@ -160,7 +160,7 @@ Questo codice restituisce lo stesso oggetto `DatasetDict` visto in precedenza, m -✏️ **Prova tu!** Scegli un altro dataset presente su GitHub o sulla [Repository di Machine Learning UCI](https://archive.ics.uci.edu/ml/index.php) e cerca di caricare sia in locale che in remoto usando le tecniche introdotte in precedenza. Per punti extra, prova a caricare un dataset archiviato in formato CSV o testuale (vedi la [documentazione](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) per ulteriori informazioni su questi formati). +✏️ **Prova tu!** Scegli un altro dataset presente su GitHub o sulla [Repository di Machine Learning UCI](https://archive.ics.uci.edu/ml/index.php) e cerca di caricare sia in locale che in remoto usando le tecniche introdotte in precedenza. Per punti extra, prova a caricare un dataset archiviato in formato CSV o testuale (vedi la [documentazione](https://huggingface.co/docs/datasets/loading#local-and-remote-files) per ulteriori informazioni su questi formati). diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx index 6b8bc7f8f..3eafa185e 100644 --- a/chapters/it/chapter5/3.mdx +++ b/chapters/it/chapter5/3.mdx @@ -241,7 +241,7 @@ Come puoi vedere, questo ha rimosso circa il 15% delle recensioni nelle sezioni -✏️ **Prova tu!** Usa la funzione `Dataset.sort()` per analizzare le revisioni con il maggior numero di parole. Controlla la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) per vedere quali argomenti bisogna usare per ordinare le recensioni in ordine decrescente di lunghezza. +✏️ **Prova tu!** Usa la funzione `Dataset.sort()` per analizzare le revisioni con il maggior numero di parole. Controlla la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) per vedere quali argomenti bisogna usare per ordinare le recensioni in ordine decrescente di lunghezza. @@ -386,7 +386,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -Oh no! Non ha funzionato! Perché? Il messaggio di errore ci dà un indizio: c'è una discordanza tra la lungheza di una delle colonne (una è lunga 1.463 e l'altra 1.000). Se hai guardato la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) di `Dataset.map()`, ricorderai che quello è il numero di campioni passati alla funzione map; qui quei 1.000 esempi danno 1.463 nuove feature, che risulta in un errore di shape. +Oh no! Non ha funzionato! Perché? Il messaggio di errore ci dà un indizio: c'è una discordanza tra la lungheza di una delle colonne (una è lunga 1.463 e l'altra 1.000). Se hai guardato la [documentazione](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) di `Dataset.map()`, ricorderai che quello è il numero di campioni passati alla funzione map; qui quei 1.000 esempi danno 1.463 nuove feature, che risulta in un errore di shape. Il problema è che stiamo cercando di mescolare due dataset diversi di grandezze diverse: le colonne del `drug_dataset` avranno un certo numero di esempi (il 1.000 del nostro errore), ma il `tokenized_dataset` che stiamo costruendo ne avrà di più (il 1.463 nel nostro messaggio di errore). Non va bene per un `Dataset`, per cui abbiamo bisogno o di rimuovere le colonne dal dataset vecchio, o renderle della stessa dimensione del nuovo dataset. La prima opzione può essere effettuata utilizzando l'argomento `remove_columns`: diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx index 57f15060c..ad2f6e191 100644 --- a/chapters/it/chapter5/4.mdx +++ b/chapters/it/chapter5/4.mdx @@ -47,7 +47,7 @@ Possiamo vedere che ci sono 15.518.009 righe e 2 colonne nel nostro dataset -- u -✎ Di base, 🤗 Datasets decomprimerà i file necessari a caricare un dataset. Se vuoi risparmiare sullo spazio dell'hard disk, puoi passare `DownloadConfig(delete_extracted_True)` all'argomento `download_config` di `load_dataset()`. Per maggiori dettagli leggi la [documentazione](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig). +✎ Di base, 🤗 Datasets decomprimerà i file necessari a caricare un dataset. Se vuoi risparmiare sullo spazio dell'hard disk, puoi passare `DownloadConfig(delete_extracted_True)` all'argomento `download_config` di `load_dataset()`. Per maggiori dettagli leggi la [documentazione](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig). diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx index 3a9c0f19f..a9246a463 100644 --- a/chapters/it/chapter5/5.mdx +++ b/chapters/it/chapter5/5.mdx @@ -430,7 +430,7 @@ Bene, abbiamo caricato il nostro dataset sull'Hub, e può essere utilizzato da t -💡 Puoi caricare un dataset nell'Hub di Hugging Face anche direttamente dal terminale usando `huggingface-cli` e un po' di magia Git. La [guida a 🤗 Datasets](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) spiega come farlo. +💡 Puoi caricare un dataset nell'Hub di Hugging Face anche direttamente dal terminale usando `huggingface-cli` e un po' di magia Git. La [guida a 🤗 Datasets](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) spiega come farlo. diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx index b1296144f..1bd0dca34 100644 --- a/chapters/it/chapter5/6.mdx +++ b/chapters/it/chapter5/6.mdx @@ -190,7 +190,7 @@ Perfetto, ora abbiamo qualche migliaio di commenti con cui lavorare! -✏️ **Prova tu!** Prova ad utilizzare `Dataset.map()` per far esplodere la colonna `commenti` di `issues_dataset` _senza_ utilizzare Pandas. È un po' difficile: potrebbe tornarti utile la sezione ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) della documentazione di 🤗 Datasets. +✏️ **Prova tu!** Prova ad utilizzare `Dataset.map()` per far esplodere la colonna `commenti` di `issues_dataset` _senza_ utilizzare Pandas. È un po' difficile: potrebbe tornarti utile la sezione ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) della documentazione di 🤗 Datasets. diff --git a/chapters/ja/chapter0/1.mdx b/chapters/ja/chapter0/1.mdx index fea5d0d10..83b678d0b 100644 --- a/chapters/ja/chapter0/1.mdx +++ b/chapters/ja/chapter0/1.mdx @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` 仮想環境が有効になっているかどうかは、`which python`というコマンドを実行することで確認することができます。もし以下のように仮想環境であることを示していれば、正常に有効化できています! diff --git a/chapters/ko/chapter0/1.mdx b/chapters/ko/chapter0/1.mdx index 0a8dd7735..90aa72de2 100644 --- a/chapters/ko/chapter0/1.mdx +++ b/chapters/ko/chapter0/1.mdx @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` 환경이 제대로 활성화 되었는지 `which python` 명령어를 실행하여 확인해 봅시다. 아래와 같이 가상 환경을 보여준다면 제대로 활성화가 것입니다! diff --git a/chapters/ko/chapter1/9.mdx b/chapters/ko/chapter1/9.mdx index 9fee845cc..1eff01f0d 100644 --- a/chapters/ko/chapter1/9.mdx +++ b/chapters/ko/chapter1/9.mdx @@ -13,4 +13,4 @@ | --- | --- | --- | | 인코더 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | 문장 분류, 개체명 인식, 추출 질의 응답 | | 디코더 | CTRL, GPT, GPT-2, Transformer XL | 텍스트 생성 | -| 인코더-디코더 | BART, T5, Marian, mBART | 오약, 번역, 생성 질의 응답 | \ No newline at end of file +| 인코더-디코더 | BART, T5, Marian, mBART | 요약, 번역, 생성 질의 응답 | diff --git a/chapters/ko/chapter2/2.mdx b/chapters/ko/chapter2/2.mdx index 3f246f672..7468ea435 100644 --- a/chapters/ko/chapter2/2.mdx +++ b/chapters/ko/chapter2/2.mdx @@ -60,7 +60,7 @@ classifier(
-각 단게에 대해 빠르게 살펴보겠습니다. +각 단계에 대해 빠르게 살펴보겠습니다. ## 토크나이저를 이용한 전처리[[preprocessing-with-a-tokenizer]] diff --git a/chapters/ko/chapter5/2.mdx b/chapters/ko/chapter5/2.mdx index 9f8c463f1..67ccff72a 100644 --- a/chapters/ko/chapter5/2.mdx +++ b/chapters/ko/chapter5/2.mdx @@ -128,7 +128,7 @@ DatasetDict({ -`load_dataset()` 함수의 `data_files` 인수는 매우 유연하여 하나의 파일 경로 (str), 파일 경로들의 리스트 (list) 또는 스플릿 이름을 각 파일 경로에 매핑하는 dictionary를 값으로 받을 수 있습니다. 또한 Unix 쉘에서 사용하는 규칙에 따라 지정된 패턴과 일치하는 파일들을 glob할 수도 있습니다. (예를 들어, `data_files="*.json"`와 같이 설정하면 한 폴더에 있는 모든 JSON 파일들을 하나의 스플릿으로 glob할 수 있습니다.) 자세한 내용은 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files)에서 확인할 수 있습니다. +`load_dataset()` 함수의 `data_files` 인수는 매우 유연하여 하나의 파일 경로 (str), 파일 경로들의 리스트 (list) 또는 스플릿 이름을 각 파일 경로에 매핑하는 dictionary를 값으로 받을 수 있습니다. 또한 Unix 쉘에서 사용하는 규칙에 따라 지정된 패턴과 일치하는 파일들을 glob할 수도 있습니다. (예를 들어, `data_files="*.json"`와 같이 설정하면 한 폴더에 있는 모든 JSON 파일들을 하나의 스플릿으로 glob할 수 있습니다.) 자세한 내용은 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files)에서 확인할 수 있습니다. diff --git a/chapters/pt/chapter0/1.mdx b/chapters/pt/chapter0/1.mdx index c0fd3cc96..1476cd56d 100644 --- a/chapters/pt/chapter0/1.mdx +++ b/chapters/pt/chapter0/1.mdx @@ -87,7 +87,7 @@ Você pode acessar e sair do seu ambiente virtual com os scripts `activate` e `d source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` Você pode ter certeza que seu ambiente virtual está ativado rodando o comando `which python`: se ele apontar para o ambiente virtual, então você conseguiu ativa-lo com sucesso! diff --git a/chapters/pt/chapter1/5.mdx b/chapters/pt/chapter1/5.mdx index f14bb0ac2..1b8846e0a 100644 --- a/chapters/pt/chapter1/5.mdx +++ b/chapters/pt/chapter1/5.mdx @@ -1,4 +1,4 @@ -# Modelos decodificadores +# Modelos codificadores -Os modelos de encoder (decodificadores) usam apenas o encoder de um modelo Transformer. Em cada estágio, as camadas de atenção podem acessar todas as palavras da frase inicial. Esses modelos geralmente são caracterizados como tendo atenção "bidirecional" e são frequentemente chamados de *modelos de codificação automática*. +Os modelos de encoder (codificadores) usam apenas o encoder de um modelo Transformer. Em cada estágio, as camadas de atenção podem acessar todas as palavras da frase inicial. Esses modelos geralmente são caracterizados como tendo atenção "bidirecional" e são frequentemente chamados de *modelos de codificação automática*. O pré-treinamento desses modelos geralmente gira em torno de corromper de alguma forma uma determinada frase (por exemplo, mascarando palavras aleatórias nela) e encarregando o modelo de encontrar ou reconstruir a frase inicial. diff --git a/chapters/pt/chapter5/2.mdx b/chapters/pt/chapter5/2.mdx index 053e35ae9..2d12ad4e0 100644 --- a/chapters/pt/chapter5/2.mdx +++ b/chapters/pt/chapter5/2.mdx @@ -130,7 +130,7 @@ Isto é exatamente o que queríamos. Agora, podemos aplicar várias técnicas de -O argumento `data_files` da função `load_dataset()` é bastante flexível e pode ser um único caminho de arquivo ou uma lista de caminhos de arquivo, ou um dicionário que mapeia nomes divididos para caminhos de arquivo. Você também pode incluir arquivos que correspondam a um padrão especificado de acordo com as regras utilizadas pela Unix shell (por exemplo, você pode adicionar todos os arquivos JSON em um diretório como uma única divisão, definindo `data_files="*.json"`). Consulte a [documentação](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) do 🤗 Datasets para obter mais detalhes. +O argumento `data_files` da função `load_dataset()` é bastante flexível e pode ser um único caminho de arquivo ou uma lista de caminhos de arquivo, ou um dicionário que mapeia nomes divididos para caminhos de arquivo. Você também pode incluir arquivos que correspondam a um padrão especificado de acordo com as regras utilizadas pela Unix shell (por exemplo, você pode adicionar todos os arquivos JSON em um diretório como uma única divisão, definindo `data_files="*.json"`). Consulte a [documentação](https://huggingface.co/docs/datasets/loading#local-and-remote-files) do 🤗 Datasets para obter mais detalhes. @@ -161,7 +161,7 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") Isto retorna o mesmo objeto `DatasetDict` obtido anteriormente, mas nos poupa o passo de baixar e descomprimir manualmente os arquivos _SQuAD_it-*.json.gz_. Isto envolve nas várias formas de carregar conjuntos de dados que não estão hospedados no Hugging Face Hub. Agora que temos um conjunto de dados para brincar, vamos sujar as mãos com várias técnicas de manipulação de dados! -✏️ **Tente fazer isso!** Escolha outro conjunto de dados hospedado no GitHub ou no [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) e tente carregá-lo tanto local como remotamente usando as técnicas introduzidas acima. Para pontos bônus, tente carregar um conjunto de dados que esteja armazenado em formato CSV ou texto (veja a [documentação](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) para mais informações sobre estes formatos). +✏️ **Tente fazer isso!** Escolha outro conjunto de dados hospedado no GitHub ou no [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) e tente carregá-lo tanto local como remotamente usando as técnicas introduzidas acima. Para pontos bônus, tente carregar um conjunto de dados que esteja armazenado em formato CSV ou texto (veja a [documentação](https://huggingface.co/docs/datasets/loading#local-and-remote-files) para mais informações sobre estes formatos). diff --git a/chapters/pt/chapter5/3.mdx b/chapters/pt/chapter5/3.mdx index 7d48c6149..ba734d249 100644 --- a/chapters/pt/chapter5/3.mdx +++ b/chapters/pt/chapter5/3.mdx @@ -238,7 +238,7 @@ Como você pode ver, isso removeu cerca de 15% das avaliações de nossos conjun -✏️ **Experimente!** Use a função `Dataset.sort()` para inspecionar as resenhas com o maior número de palavras. Consulte a [documentação](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) para ver qual argumento você precisa usar para classificar as avaliações por tamanho em ordem decrescente. +✏️ **Experimente!** Use a função `Dataset.sort()` para inspecionar as resenhas com o maior número de palavras. Consulte a [documentação](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) para ver qual argumento você precisa usar para classificar as avaliações por tamanho em ordem decrescente. @@ -384,7 +384,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -Oh não! Isso não funcionou! Por que não? Observar a mensagem de erro nos dará uma pista: há uma incompatibilidade nos comprimentos de uma das colunas, sendo uma de comprimento 1.463 e a outra de comprimento 1.000. Se você consultou a [documentação] do `Dataset.map()` (https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), você deve se lembrar de que é o número de amostras passadas para a função que estamos mapeando; aqui, esses 1.000 exemplos forneceram 1.463 novos recursos, resultando em um erro de forma. +Oh não! Isso não funcionou! Por que não? Observar a mensagem de erro nos dará uma pista: há uma incompatibilidade nos comprimentos de uma das colunas, sendo uma de comprimento 1.463 e a outra de comprimento 1.000. Se você consultou a [documentação] do `Dataset.map()` (https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map), você deve se lembrar de que é o número de amostras passadas para a função que estamos mapeando; aqui, esses 1.000 exemplos forneceram 1.463 novos recursos, resultando em um erro de forma. O problema é que estamos tentando misturar dois conjuntos de dados diferentes de tamanhos diferentes: as colunas `drug_dataset` terão um certo número de exemplos (os 1.000 em nosso erro), mas o `tokenized_dataset` que estamos construindo terá mais (o 1.463 na mensagem de erro). Isso não funciona para um `Dataset`, portanto, precisamos remover as colunas do conjunto de dados antigo ou torná-las do mesmo tamanho do novo conjunto de dados. Podemos fazer o primeiro com o argumento `remove_columns`: diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index 0018334e6..2a64e2471 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -46,7 +46,7 @@ Podemos ver que há 15.518.009 linhas e 2 colunas em nosso conjunto de dados - i -✎ Por padrão, 🤗 Datasets descompactará os arquivos necessários para carregar um dataset. Se você quiser preservar espaço no disco rígido, você pode passar `DownloadConfig(delete_extracted=True)` para o argumento `download_config` de `load_dataset()`. Consulte a [documentação](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) para obter mais detalhes. +✎ Por padrão, 🤗 Datasets descompactará os arquivos necessários para carregar um dataset. Se você quiser preservar espaço no disco rígido, você pode passar `DownloadConfig(delete_extracted=True)` para o argumento `download_config` de `load_dataset()`. Consulte a [documentação](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) para obter mais detalhes. diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx index 9207fa010..23e3831dc 100644 --- a/chapters/pt/chapter5/5.mdx +++ b/chapters/pt/chapter5/5.mdx @@ -430,7 +430,7 @@ Legal, nós enviamos nosso conjunto de dados para o Hub e está disponível para -💡 Você também pode enviar um conjunto de dados para o Hugging Face Hub diretamente do terminal usando `huggingface-cli` e um pouco de magia Git. Consulte o [guia do 🤗 Datasets](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) para obter detalhes sobre como fazer isso. +💡 Você também pode enviar um conjunto de dados para o Hugging Face Hub diretamente do terminal usando `huggingface-cli` e um pouco de magia Git. Consulte o [guia do 🤗 Datasets](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) para obter detalhes sobre como fazer isso. diff --git a/chapters/pt/chapter5/6.mdx b/chapters/pt/chapter5/6.mdx index c77091eef..f0a868c57 100644 --- a/chapters/pt/chapter5/6.mdx +++ b/chapters/pt/chapter5/6.mdx @@ -188,7 +188,7 @@ Ok, isso nos deu alguns milhares de comentários para trabalhar! -✏️ **Experimente!** Veja se você pode usar `Dataset.map()` para explodir a coluna `comments` de `issues_dataset` _sem_ recorrer ao uso de Pandas. Isso é um pouco complicado; você pode achar útil para esta tarefa a seção ["Mapeamento em lote"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) da documentação do 🤗 Dataset. +✏️ **Experimente!** Veja se você pode usar `Dataset.map()` para explodir a coluna `comments` de `issues_dataset` _sem_ recorrer ao uso de Pandas. Isso é um pouco complicado; você pode achar útil para esta tarefa a seção ["Mapeamento em lote"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch#batch-mapping) da documentação do 🤗 Dataset. diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 8bc60860c..ee3b0d8f1 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -24,18 +24,27 @@ - local: chapter1/9 title: Итоги - local: chapter1/10 - title: Проверка знаний + title: Тест в конце главы -- title: 2. Использование библиотеки 🤗 Transformers +- title: 2. Использование 🤗 Transformers sections: - local: chapter2/1 title: Введение - local: chapter2/2 - title: Внутри конвейера + title: За конвейером - local: chapter2/3 title: Модели + - local: chapter2/4 + title: Токенизаторы + - local: chapter2/5 + title: Обработка множественных последовательностей + - local: chapter2/6 + title: Собираем все воедино - local: chapter2/7 title: Базовое использование завершено! + - local: chapter2/8 + title: Тест в конце главы + quiz: 2 - title: 3. Fine-tuning предобученной модели sections: @@ -51,8 +60,9 @@ - local: chapter3/5 title: Fine-tuning, итоги! - local: chapter3/6 - title: Итоговый тест по главе + title: Тест в конце главы quiz: 3 + - title: 4. Hugging Face Hub sections: - local: chapter4/1 @@ -66,8 +76,9 @@ - local: chapter4/5 title: Первая часть завершена! - local: chapter4/6 - title: Итоговый тест по главе + title: Тест в конце главы quiz: 4 + - title: 5. Библиотека 🤗 Datasets sections: - local: chapter5/1 @@ -83,14 +94,111 @@ - local: chapter5/7 title: 🤗 Datasets, итоги! - local: chapter5/8 - title: Тест по главе 5 + title: Тест в конце главы + - title: 6. Бибилиотека 🤗 Tokenizers - sections: + sections: - local: chapter6/1 title: Введение - local: chapter6/2 - title: Обучение токенизатора на основе существующего + title: Обучение нового токенизатора на основе старого + - local: chapter6/3 + title: Особые возможности быстрых токенизаторов + - local: chapter6/3b + title: Быстрые токенизаторы в QA конвейере + - local: chapter6/4 + title: Нормализация и предварительная токенизация + - local: chapter6/5 + title: Токенизация Byte-Pair Encoding + - local: chapter6/6 + title: Токенизация WordPiece + - local: chapter6/7 + title: Токенизация Unigram + - local: chapter6/8 + title: Создание токенизатора, блок за блоком + - local: chapter6/9 + title: Токенизаторы, проверка! + - local: chapter6/10 + title: Тест в конце главы + quiz: 6 + +- title: 7. Основные задачи NLP + sections: + - local: chapter7/1 + title: Введение + - local: chapter7/2 + title: Классификация токенов + - local: chapter7/3 + title: Дообучение модели маскированного языкового моделирования + - local: chapter7/4 + title: Перевод + - local: chapter7/5 + title: Суммаризация + - local: chapter7/6 + title: Обучение каузальной языковой модели с нуля + - local: chapter7/7 + title: Ответы на вопросы + - local: chapter7/8 + title: Освоение NLP + - local: chapter7/9 + title: Тест в конце главы + quiz: 7 + +- title: 8. Как попросить о помощи + sections: + - local: chapter8/1 + title: Введение + - local: chapter8/2 + title: Что делать, если возникла ошибка + - local: chapter8/3 + title: Обращение за помощью на форумах + - local: chapter8/4_tf + title: Отладка обучения + - local: chapter8/4 + title: Отладка обучения + local_fw: { pt: chapter8/4, tf: chapter8/4_tf } + - local: chapter8/5 + title: Как написать хорошее сообщение об ошибке (issue) + - local: chapter8/6 + title: Часть 2 завершена! + - local: chapter8/7 + title: Тест в конце главы + quiz: 8 + +- title: 9. Создание и распространение демо + new: true + subtitle: Я обучил модель, но как мне ее продемонстрировать? + sections: + - local: chapter9/1 + title: Введение в Gradio + - local: chapter9/2 + title: Создание вашего первого демо + - local: chapter9/3 + title: Понимание класса Interface + - local: chapter9/4 + title: Делимся демонстрациями с другими + - local: chapter9/5 + title: Интеграция с Hugging Face Hub + - local: chapter9/6 + title: Расширенные возможности Interface + - local: chapter9/7 + title: Введение в Gradio Blocks + - local: chapter9/8 + title: Gradio, проверка! + - local: chapter9/9 + title: Тест в конце главы + quiz: 9 + +- title: События курса + sections: + - local: events/1 + title: Встречи и семинары + - local: events/2 + title: Событие посвященное выходу 2 части курса + - local: events/3 + title: Вечеринка Gradio Blocks + - title: Глоссарий sections: - local: glossary/1 - title: Глоссарий \ No newline at end of file + title: Глоссарий diff --git a/chapters/ru/chapter0/1.mdx b/chapters/ru/chapter0/1.mdx index 6e42a9fc7..f2f9f5215 100644 --- a/chapters/ru/chapter0/1.mdx +++ b/chapters/ru/chapter0/1.mdx @@ -1,6 +1,6 @@ # Введение -Добро пожаловать на курс от Hugging Face! Это введение поможет настроить рабочее окружение. Если вы только начинаете курс, мы рекомендуем сначала заглянуть в [Главу 1](/course/ru/chapter1), затем вернуться и настроить среду, чтобы попробовать запустить код самостоятельно. +Добро пожаловать на курс от Hugging Face! Это введение поможет настроить рабочее окружение. Если вы только начинаете курс, мы рекомендуем сначала заглянуть в [Главу 1](../chapter1/1), затем вернуться и настроить среду, чтобы попробовать запустить код самостоятельно. Все библиотеки, которые мы будем использовать в этом курсе, доступны в качестве Python-пакетов. В этом уроке мы покажем, как установить окружение и необходимые библиотеки. @@ -85,7 +85,7 @@ ls -a source .env/bin/activate # Деактивировать окружение -source .env/bin/deactivate +deactivate ``` Вы можете убедиться, что окружение активировано с помощью команды `which python`: если ее результат указывает на виртуальное окружение, значит, вы успешно активировали его! diff --git a/chapters/ru/chapter1/10.mdx b/chapters/ru/chapter1/10.mdx index 124998f73..1487786bd 100644 --- a/chapters/ru/chapter1/10.mdx +++ b/chapters/ru/chapter1/10.mdx @@ -1,6 +1,6 @@ -# Проверка знаний[[end-of-chapter-quiz]] +# Тест в конце главы[[end-of-chapter-quiz]] -Как вы могли заметить в [Главе 1](/course/chapter1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. +Как вы видели в [Главе 1](../chapter1), модели трансформеров обычно очень большие. С миллионами и десятками *миллиардов* параметров, обучение и развертывание этих моделей - сложная задача. Кроме того, поскольку новые модели выходят практически ежедневно и каждая из них имеет свою собственную реализацию, попробовать их все - задача не из легких. -Библиотека 🤗 Transformers была создана для решения этой проблемы. Её цель — предоставить единый API, с помощью которого можно загружать, обучать и сохранять любую модель трансформера. Основными функциями библиотеки являются: +Для решения этой проблемы была создана библиотека 🤗 Transformers. Ее цель - предоставить единый API, с помощью которого можно загрузить, обучить и сохранить любую модель Transformer. Основными особенностями библиотеки являются: -- **Удобство в использовании**: Скачивание, загрузку и использование современной модели NLP для вывода данных, можно выполнять всего двумя строками кода. -- **Гибкость**: По своей сути все модели представляют собой простые классы библиотек PyTorch `nn.Module` или TensorFlow `tf.keras.Model` и могут обрабатываться, как и любые другие модели, в соответствующих средах машинного обучения (МО). -- **Простота**: В библиотеке практически не используются абстракции. "Все в одном файле" является основной концепцией: прямой проход модели полностью определяется в одном файле, так что сам код понятен, но при этом доступен для взлома. +- **Простота использования**: Скачать, загрузить и использовать современную модель NLP для инференса можно всего в две строчки кода. +- **Гибкость**: По своей сути все модели представляют собой простые классы PyTorch `nn.Module` или TensorFlow `tf.keras.Model` и могут быть обработаны как любые другие модели в соответствующих фреймворках машинного обучения (ML). +- **Простота**: В библиотеке почти нет абстракций. Концепция "Все в одном файле" является основной: прямой проход модели полностью определяется в одном файле, так что сам код понятен и доступен для изменения. -Последняя особенность сильно отличает библиотеку 🤗 Transformers от других библиотек машинного обучения. Модели не строятся на модулях, которые являются общими для всех файлов; вместо этого каждая модель имеет свои собственные слои. Это не только делает модели более доступными и понятными, но и позволяет легко экспериментировать с одной моделью, не затрагивая другие. +Эта последняя особенность делает 🤗 Transformers совершенно непохожей на другие ML-библиотеки. Модели не строятся на основе модулей +которые совместно используются в разных файлах; вместо этого каждая модель имеет свои собственные слои. Помимо того, что это делает модели более доступными и понятными, это позволяет легко экспериментировать с одной моделью, не затрагивая другие. -Эта глава начнается со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воспроизвести функцию `pipeline()` представленную в [Главе 1](/course/chapter1). Далее мы обсудим API модели: углубимся в классы модели и конфигурации и покажем, как загружать модель и как она обрабатывает числовые входные данные для получения прогнозов. +Эта глава начнется со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воссоздать функцию `pipeline()`, представленную в [Главе 1](../chapter1). Далее мы обсудим API модели: мы погрузимся в модель и классы конфигурации, покажем, как загрузить модель и как она обрабатывает числовые данные для вывода прогнозов. -Затем мы рассмотрим API токенизатора, который является другим основным компонентом функции `pipeline()`. Токенизаторы берут на себя первый и последний этапы обработки, обрабатывая преобразование текста в числовые входные данные для нейронной сети и обратное преобразование в текст, когда это необходимо. Наконец, мы покажем вам, как обработывается передача нескольких предложений в модель с помощью подготовленных пакетов, а затем завершим все это более детальным рассмотрением высокоуровневой функции `tokenizer()`. +Затем мы рассмотрим API токенизатора, который является другим основным компонентом функции `pipeline()`. Токенизаторы берут на себя первый и последний шаги препроцессинга, обработку преобразования текста в числовые входы для нейронной сети и обратное преобразование в текст, когда это необходимо. Наконец, мы покажем вам, как обрабатывать несколько предложений, передавая их в модель в подготовленном батче, затем завершим все это более подробным рассмотрением высокоуровневой функции `tokenizer()`. -⚠️ Чтобы воспользоваться всеми функциями, доступными в Model Hub и 🤗 Transformers, мы рекомендуем создать учетную запись. +⚠️ Чтобы воспользоваться всеми возможностями, доступными в Model Hub и 🤗 Transformers, мы рекомендуем создать учетную запись. \ No newline at end of file diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index ff5c6a630..b61f2f25d 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -1,14 +1,14 @@ -# Внутри конвейера +# За конвейером[[behind-the-pipeline]] {#if fw === 'pt'} {:else} @@ -16,14 +16,14 @@ {/if} -Это первый раздел, содержание которого будет немного отличаться в зависимости от того, используете ли вы PyTorch или TensorFlow. Нажмите переключатель над заголовком, чтобы выбрать предпочитаемую платформу! +Это первый раздел, в котором содержание немного отличается в зависимости от того, используете ли вы PyTorch или TensorFlow. Переключите переключатель в верхней части заголовка, чтобы выбрать платформу, которую вы предпочитаете! {#if fw === 'pt'} @@ -32,7 +32,7 @@ {/if} -Давайте начнем с готового примера, взглянув на то, что происходило за кулисами, когда мы выполняли следующий код в [Главе 1](/course/chapter1): +Давайте начнем с полноценного примера и посмотрим, что произошло за кулисами, когда мы выполнили следующий код в [Главе 1](../chapter1): ```python from transformers import pipeline @@ -46,33 +46,33 @@ classifier( ) ``` -и на выходе получали: +и получили: ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}, {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -Как мы уже увидели в [Главе 1](/course/chapter1), данный конвейер включает в себя три шага: предварительная обработка, передача входных данных через модель и постобработка: +Как мы видели в [Главе 1] (../chapter1), этот конвейер объединяет три этапа: предобработку, пропуск входных данных через модель и постобработку:
-Полный конвейер NLP: токенизация текста, преобразование в идентификаторы и вывод с помощью модели Transformer и слоя 'head' модели. - +Полный конвейер NLP: токенизация текста, конвертация в идентификаторы, инференс через модель Transformer и голову модели. +
-Давайте вкратце рассмотрим каждый из этих этапов. +Давайте быстро пройдемся по каждому из них. -## Предварительная обработка с помощью токенизатора +## Предобработка с помощью токенизатора[[preprocessing-with-a-tokenizer]] -Как и другие нейронные сети, модели Transformer не могут обрабатывать необработанный текст напрямую, поэтому первым шагом нашего конвейера является преобразование входных текстовых данных в числа, понятные модели. Для этого мы используем *токенизатор*, который будет отвечать за: +Как и другие нейронные сети, модели Transformer не могут напрямую обрабатывать сырой текст, поэтому первым шагом нашего конвейера является преобразование текстовых данных в числа, которые сможет воспринимать модель. Для этого мы используем *токенизатор*, который будет отвечать за: -- Разделение входных данных на слова, подслова или символы (например, знаки препинания), которые называются *токенами* -- Отображение каждого токена в целое число +- Разбиение входных данных на слова, подслова или символы (например, пунктуацию), которые называются *токенами* +- Сопоставление каждого токена с целым числом - Добавление дополнительных входных данных, которые могут быть полезны для модели -Всю эту предварительную обработку необходимо выполнять точно так же, как и при предварительном обучении модели, поэтому сначала нам нужно загрузить эту информацию из [Model Hub](https://huggingface.co/models). Для этого мы используем класс `AutoTokenizer` и его метод `from_pretrained()`. Используя имя контрольной точки нашей модели, он автоматически извлекает данные, связанные с токенизатором модели, и кэширует их (поэтому они загружаются только при первом запуске кода ниже). +Вся эта предобработка должна быть выполнена точно так же, как и при предварительном обучении модели, поэтому сначала нам нужно загрузить эту информацию из [Model Hub](https://huggingface.co/models). Для этого мы используем класс `AutoTokenizer` и его метод `from_pretrained()`. Используя имя контрольной точки нашей модели, он автоматически получает данные, ассоциированные с токенизатором модели, и кэширует их (таким образом, они скачиваются только в первый раз, когда вы выполняете приведенный ниже код). -Поскольку контрольной точкой конвейра `sentiment-analysis` по-умолчанию является модель `distilbert-base-uncased-finetuned-sst-2-english` (вы можете увидеть карточку модели [здесь](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), мы выполним следующие команды: +Поскольку контрольной точкой по умолчанию конвейера `sentiment-analysis` является `distilbert-base-uncased-finetuned-sst-2-english` (вы можете посмотреть ее карточку модели [здесь](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), мы выполняем следующее: ```python from transformers import AutoTokenizer @@ -81,12 +81,11 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" tokenizer = AutoTokenizer.from_pretrained(checkpoint) ``` -После того, как мы сосздадим токенизатор, мы сможем напрямую передать ему наши предложения, и получить словарь, готовый для использования в нашей модели! Осталось только преобразовать список входных идентификаторов в тензоры. +Когда у нас есть токенизатор, мы можем напрямую передать ему наши предложения и получить в ответ словарь, готовый к передаче в нашу модель! Осталось только преобразовать список входных идентификаторов в тензоры. -Вы можете использовать библиотеку 🤗 Transformers, не беспокоясь о том, какой фреймворк ML используется в качестве бэкэнда; это может быть PyTorch или TensorFlow или Flax для некоторых моделей. Однако модели Transformer принимают только *тензоры* в качестве входных данных. Если вы впервые слышите о тензорах, вы можете представлять их как массивы NumPy. Массив NumPy может быть скаляром (0D), вектором (1D), матрицой (2D) или иметь больше измерений. Фактически это тензор; тензоры других платформ машинного обучения ведут себя аналогично, и обычно их так же просто создавать, как массивы NumPy. +Вы можете использовать 🤗 Transformers, не задумываясь о том, какой ML-фреймворк используется в качестве бэкенда; это может быть PyTorch или TensorFlow, или Flax для некоторых моделей. Однако модели Transformer принимают на вход только *тензоры*. Если вы впервые слышите о тензорах, то можете считать их массивами NumPy. Массив NumPy может быть скаляром (0D), вектором (1D), матрицей (2D) или иметь больше измерений. По сути, это тензор; тензоры других ML-фреймворков ведут себя аналогично, и их обычно так же просто инстанцировать, как и массивы NumPy. - -Чтобы указать тип тензоров, которые мы хотим получить (PyTorch, TensorFlow, или обычный NumPy), мы используем аргумент `return_tensors`: +Чтобы указать тип тензоров, которые мы хотим получить в ответ (PyTorch, TensorFlow или обычный NumPy), мы используем аргумент `return_tensors`: {#if fw === 'pt'} ```python @@ -108,11 +107,11 @@ print(inputs) ``` {/if} -Не беспокойтесь пока о параметрах дополнения (padding) и усечения (truncation); мы объясним это позже. Здесь главное помнить, что вы можете передать одно предложение или список предложений, а также указать тип тензоров, которые вы хотите получить обратно (если тип не передан, в результате вы получите список из списков). +Не беспокойтесь пока о дополнении и усечении, мы расскажем об этом позже. Главное, что нужно запомнить: вы можете передать одно предложение или список предложений, а также указать тип тензоров, которые вы хотите получить в ответ (если тип не указан, то в результате вы получите список списков). {#if fw === 'pt'} -Вот как результаты выглядят в виде тензоров PyTorch: +Вот как выглядят результаты в виде тензоров PyTorch: ```python out { @@ -122,13 +121,13 @@ print(inputs) ]), '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, 0, 0, 0, 0, 0, 0, 0, 0, 0] + [1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0] ]) } ``` {:else} -Вот как результаты выглядят в виде тензоров TensorFlow: +Вот как выглядят результаты в виде тензоров TensorFlow: ```python out { @@ -140,18 +139,18 @@ print(inputs) 'attention_mask': } ``` {/if} -Сам вывод представляет собой словарь, содержащий два ключа, `input_ids` и `attention_mask`. `input_ids` содержит две строки целых чисел (по одному для каждого предложения), которые являются уникальными идентификаторами токенов в каждом предложении. Мы объясним, что такое `attention_mask` позже в этой главе. +Сам результат представляет собой словарь, содержащий два ключа, `input_ids` и `attention_mask`. `input_ids` содержит две строки целых чисел (по одной на каждое предложение), которые являются уникальными идентификаторами токенов в каждом предложении. Мы объясним, что такое `attention_mask` позже в этой главе. -## Проходим через модель +## Проходя сквозь модель[[going-through-the-model]] {#if fw === 'pt'} -Мы можем загрузить нашу предварительно обученную модель так же, как и наш токенизатор. Библиотека 🤗 Transformers предоставляет класс `AutoModel` который также имеет метод `from_pretrained()`: +Мы можем загрузить нашу предварительно обученную модель так же, как мы это делали с нашим токенизатором. 🤗 Transformers предоставляет класс `AutoModel`, у которого также есть метод `from_pretrained()`: ```python from transformers import AutoModel @@ -160,7 +159,7 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModel.from_pretrained(checkpoint) ``` {:else} -Мы можем загрузить нашу предварительно обученную модель так же, как и наш токенизатор. Библиотека 🤗 Transformers предоставляет класс `TFAutoModel` который также имеет метод `from_pretrained`: +Мы можем загрузить нашу предварительно обученную модель так же, как мы это делали с нашим токенизатором. 🤗 Transformers предоставляет класс `TFAutoModel`, в котором также есть метод `from_pretrained`: ```python from transformers import TFAutoModel @@ -170,25 +169,25 @@ model = TFAutoModel.from_pretrained(checkpoint) ``` {/if} -В этом фрагменте кода мы загрузили ту же контрольную точку, которую использовали в нашем конвейере ранее (на самом деле она уже должна была быть закэширована) и создали с ее помощью экземпляр модели. +В этом фрагменте кода мы загрузили ту же контрольную точку, которую использовали в нашем конвейере ранее (на самом деле она уже должна была быть кэширована), и инстанцировали модель с ее помощью. -Эта архитектура содержит только базовый модуль Transformer: при некоторых входных данных он выводит то, что мы будем называть *скрытыми состояниями*, также известными как *параметры*. Для каждого входного набора данных модели мы получим многомерный вектор, представляющий **контекстное понимание этого входного набора моделью Transformer**. +Эта архитектура содержит только базовый модуль Transformer: при наличии некоторых входных данных он выводит то, что мы будем называть *скрытыми состояниями (hidden states)*, также известными как *признаки (features)*. Для каждого входа модели мы получим многомерный вектор, представляющий **контекстное понимание этого входа моделью Transformer**. -Если вы пока не понимаете в чем смысл, не беспокойтесь об этом. Мы объясним все это позже. +Если вы не поняли смысла, не волнуйтесь. Мы объясним всё это позже. -Хотя эти скрытые состояния могут быть полезны сами по себе, они обычно являются входными данными для другой части модели, известной как слой *head*. В [Главе 1](/course/chapter1) разные задачи могли бы выполняться с одной и той же архитектурой, но с каждой из этих задач будет связан отдельный слой "head". +Хотя эти скрытые состояния могут быть полезны сами по себе, обычно они являются входными данными для другой части модели, известной как *голова (head)*. В [Главе 1](../chapter1) различные задачи могли быть выполнены с помощью одной и той же архитектуры, но каждая из этих задач будет связана с разной головой. -### Многомерный вектор, что это? +### Многомерный вектор?[[a-high-dimensional-vector]] -Вектор, выводимый модулем Transformer, обычно является большим. И как правило, он имеет три параметра: +Вектор, возвращаемый модулем Transformer, обычно большой. Обычно он имеет три измерения: -- **Размер пакета**: Количество последовательностей, обрабатываемых одновременно (в нашем примере 2). -- **Длина последовательности**: Длина числового представления последовательности (в нашем примере 16). -- **Размер скрытого слоя сети**: Количество измерений вектора каждого входного параметра модели. +- **Размер батча (Batch size)**: Количество последовательностей, обрабатываемых за один раз (в нашем примере - 2). +- **Длина последовательности (Sequence length)**: Длина числового представления последовательности (в нашем примере - 16). +- **Скрытый размер (Hidden size)**: Размерность вектора каждого входа модели. -Его называют "многомерный" из-за последнего значения. Размер скрытого слоя сети может быть очень большим (768 обычно используется для небольших моделей, а в больших моделях он может достигать 3072 или более). +О нем говорят как о "многомерном" из-за последнего значения. Скрытый размер может быть очень большим (768 - обычное явление для небольших моделей, а в больших моделях он может достигать 3072 и более). -Мы можем увидеть это, если передадим входные данные, которые мы предварительно обработали, в нашу модель: +Мы можем убедиться в этом, если подадим в нашу модель входные данные, которые мы подвергли предобработке: {#if fw === 'pt'} ```python @@ -210,24 +209,24 @@ print(outputs.last_hidden_state.shape) ``` {/if} -Обратите внимание, что выходные данные моделей 🤗 Transformers ведут себя как именованные кортежи или словари. Вы можете получить доступ к элементам по атрибутам (как это сделали мы) или по ключу (`outputs["last_hidden_state"]`), или даже по индексу, если вы точно знаете, где находится то, что вы ищете (`outputs[0]`). +Обратите внимание, что выходы моделей 🤗 Transformers ведут себя как `именованные кортежи` или словари. Вы можете обращаться к элементам по атрибутам (как мы это делали), по ключу (`outputs["last_hidden_state"]`) или даже по индексу, если вы точно знаете, где находится искомый элемент (`outputs[0]`). -### Слои "head" модели: Разбираемся в цифрах +### Головы моделей: Извлечение смысла из чисел[[model-heads-making-sense-out-of-numbers]] -Слои "head" модели принимают многомерный вектор скрытых состояний в качестве входных данных и проецируют их в другое измерение. Они обычно состоят из одного или нескольких линейных слоев: +Головы модели принимают на вход многомерный вектор скрытых состояний и проецируют его на другое измерение. Обычно они состоят из одного или нескольких линейных слоев:
-Нейронная сеть Transformer перед слоем 'head'. - +Сеть-трансформер с головой. +
-Выходные данные модели Transformer отправляются непосредственно в слой "head" модели для обработки. +Выход модели Transformer передается непосредственно в голову модели для обработки. -На данной диаграмме модель представлена слоем вложений и последующими слоями. Слой вложений преобразует каждый входной идентификатор полученный токенизатором, в вектор, который представляет собой связанный с ним токен. Последующие слои манипулируют этими векторами, используя механизм внимания, чтобы получить окончательное представление предложений. +На этой диаграмме модель представлена слоем эмбеддингов и последующими слоями. Слой эмбеддингов преобразует каждый входной идентификатор в токенизированном входе в вектор, который представляет соответствующий токен. Последующие слои манипулируют этими векторами с помощью механизма внимания, чтобы получить окончательное представление предложений. -В 🤗 Transformersдоступно множество различных архитектур, каждая из которых предназначена для решения конкретной задачи. Вот неполный список: +Существует множество различных архитектур 🤗 Transformers, каждая из которых предназначена для решения определенной задачи. Вот неполный список: -- `*Model` (извлекает скрытые состояния) +- `*Model` (извлечение скрытых состояний) - `*ForCausalLM` - `*ForMaskedLM` - `*ForMultipleChoice` @@ -237,7 +236,7 @@ print(outputs.last_hidden_state.shape) - и другие 🤗 {#if fw === 'pt'} -Для нашего примера нам понадобится модель со слоем "head" для классификации последовательностей (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Итак, на самом деле мы будем использовать не класс `AutoModel`, а `AutoModelForSequenceClassification`: +Для нашего примера нам понадобится модель с головой классификации последовательности (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Поэтому мы будем использовать не класс `AutoModel`, а `AutoModelForSequenceClassification`: ```python from transformers import AutoModelForSequenceClassification @@ -247,7 +246,7 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint) outputs = model(**inputs) ``` {:else} -Для нашего примера нам понадобится модель со слоем "head" для классификации последовательностей (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Итак, на самом деле мы будем использовать не класс `TFAutoModel`, а `TFAutoModelForSequenceClassification`: +Для нашего примера нам понадобится модель с головой классификации последовательности (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Поэтому мы будем использовать не класс `TFAutoModel`, а `TFAutoModelForSequenceClassification`: ```python from transformers import TFAutoModelForSequenceClassification @@ -258,7 +257,7 @@ outputs = model(inputs) ``` {/if} -Теперь, если мы посмотрим на форму наших входных данных, размерность будет намного ниже: слой "head" модели принимает в качестве входных данных многомерные векторы, которые мы видели ранее, и выводит векторы, содержащие всего два значения (по одному на метку): +Теперь, если мы посмотрим на форму наших выходов, размерность будет гораздо ниже: голова модели принимает на вход высокоразмерные векторы, которые мы видели ранее, и возвращает векторы, содержащие два значения (по одному на метку): ```python print(outputs.logits.shape) @@ -274,11 +273,11 @@ torch.Size([2, 2]) ``` {/if} -Поскольку у нас всего два предложения и две метки, результат, который мы получаем от нашей модели, имеет форму 2 x 2. +Поскольку у нас всего два предложения и две метки, результат, полученный с помощью нашей модели, имеет форму 2 x 2. -## Постобработка выходных данных +## Постобработка вывода[[postprocessing-the-output]] -Значения, которые мы получаем в качестве выходных данных нашей модели, не обязательно имеют смысл сами по себе. Давайте посмотрим: +Значения, которые мы получаем на выходе из нашей модели, не всегда имеют смысл сами по себе. Давайте посмотрим: ```python print(outputs.logits) @@ -297,7 +296,7 @@ tensor([[-1.5607, 1.6123], ``` {/if} -Наша модель предсказала `[-1.5607, 1.6123]` для первого предложения и `[ 4.1692, -3.3464]` для второго. Это не вероятности, а *логиты*, необработанные, ненормализованные оценки, выводимые последним слоем модели. Для преобразования в вероятности, они должны пройти через слой [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (все модели 🤗 Transformers выводят логиты, поскольку функция потерь для обучения обычно объединяет последнюю функцию активации, такую как SoftMax, с фактической функцией потерь, такой как перекрестная энтропия): +Наша модель спрогнозировала `[-1.5607, 1.6123]` для первого предложения и `[ 4.1692, -3.3464]` для второго. Это не вероятности, а *логиты*, сырые, ненормированные оценки, выведенные последним слоем модели. Чтобы преобразовать их в вероятности, они должны пройти через слой [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (все модели 🤗 Transformers выводят логиты, поскольку функция потерь для обучения обычно объединяет последнюю функцию активации, такую как SoftMax, с фактической функцией потерь, такой как кросс-энтропия): {#if fw === 'pt'} ```py @@ -328,9 +327,9 @@ tf.Tensor( ``` {/if} -Теперь мы видим, что модель предсказала `[0.0402, 0.9598]` для первого предложения и `[0.9995, 0.0005]` для второго. Это легко узнаваемые оценки вероятности. +Теперь мы видим, что модель спрогнозировала `[0.0402, 0.9598]` для первого предложения и `[0.9995, 0.0005]` для второго. Это узнаваемые оценки вероятности. -Чтобы получить метки, соответствующие каждой позиции, мы можем проверить атрибут `id2label` в конфигурации модели (подробнее об этом в следующем разделе): +Чтобы получить метки, соответствующие каждой позиции, мы можем обратиться к атрибуту `id2label` в конфигурации модели ( более подробно об этом в следующем разделе): ```python model.config.id2label @@ -340,15 +339,15 @@ model.config.id2label {0: 'NEGATIVE', 1: 'POSITIVE'} ``` -Теперь мы можем сделать вывод, что модель предсказала следующее: +Теперь мы можем сделать вывод, что модель спрогнозировала следующее: - Первое предложение: NEGATIVE: 0.0402, POSITIVE: 0.9598 - Второе предложение: NEGATIVE: 0.9995, POSITIVE: 0.0005 -Мы успешно воспроизвели три этапа конвейера: предварительную обработку с помощью токенизаторов, передачу входных данных через модель и постобработку! Теперь давайте уделим некоторое время тому, чтобы углубиться в каждый из этих шагов. +Мы успешно воспроизвели три этапа конвейера: предобработку с помощью токенизаторов, прохождение входных данных через модель и постобработку! Теперь давайте уделим немного времени тому, чтобы углубиться в каждый из этих этапов. -✏️ **Попробуйте это сделать!** Выберите два (или более) собственных текста и пропустите их через конвейер `sentiment-analysis`. Затем повторите шаги, которые вы видели здесь, и убедитесь, что вы получаете такие же результаты! +✏️ **Попробуйте! ** Выберите два (или более) собственных текста и пропустите их через конвейер `sentiment-analysis`. Затем повторите описанные здесь шаги и убедитесь, что вы получили те же результаты! diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx index 41d226813..42296bcba 100644 --- a/chapters/ru/chapter2/3.mdx +++ b/chapters/ru/chapter2/3.mdx @@ -1,14 +1,14 @@ -# Модели +# Модели[[models]] {#if fw === 'pt'} {:else} @@ -16,8 +16,8 @@ {/if} @@ -29,46 +29,46 @@ {/if} {#if fw === 'pt'} -В этом разделе мы подробнее рассмотрим создание и использование модели. Мы будем использовать класс `AutoModel`, который удобен, когда вы хотите создать экземпляр любой модели из контрольной точки. +В этом разделе мы подробно рассмотрим создание и использование модели. Мы будем использовать класс `AutoModel`, который удобен, когда вы хотите инстанцировать любую модель из контрольной точки. -Класс `AutoModel` и все его родственники на самом деле являются простыми оболочками для большого количества моделей, доступных в библиотеке. Это умная оболочка, поскольку она может автоматически угадывать подходящую архитектуру модели для вашей контрольной точки, а затем создает экземпляр модели с этой архитектурой. +Класс `AutoModel` и все его представители на самом деле являются простыми обертками для широкого спектра моделей, доступных в библиотеке. Это умная обертка, поскольку она может автоматически определить архитектуру модели, подходящую для вашей контрольной точки, а затем инстанцировать модель с этой архитектурой. {:else} -In this section we'll take a closer look at creating and using a model. We'll use the `TFAutoModel` class, which is handy when you want to instantiate any model from a checkpoint. +В этом разделе мы подробно рассмотрим создание и использование модели. Мы будем использовать класс `TFAutoModel`, который удобен, когда вы хотите инстанцировать любую модель из контрольной точки. -Класс `TFAutoModel` и все его родственники на самом деле являются простыми оболочками для большого количества моделей, доступных в библиотеке. Это умная оболочка, поскольку она может автоматически угадывать подходящую архитектуру модели для вашей контрольной точки, а затем создает экземпляр модели с этой архитектурой. +Класс `TFAutoModel` и все его родственники на самом деле являются простыми обертками для широкого спектра моделей, доступных в библиотеке. Это умная обертка, поскольку она может автоматически определить подходящую архитектуру модели для вашей контрольной точки, а затем инстанцировать модель с этой архитектурой. {/if} -Однако, если вы знаете тип модели, которую хотите использовать, вы можете использовать класс, который напрямую определяет ее архитектуру. Давайте посмотрим, как это работает с моделью BERT. +Однако если вы знаете тип модели, которую хотите использовать, вы можете напрямую использовать класс, определяющий ее архитектуру. Давайте рассмотрим, как это работает на примере модели BERT. -## Создание модели Transformer +## Создание Transformer[[creating-a-transformer]] -Первое, что нам нужно будет сделать для инициализации модели BERT, это загрузить объект конфигурации: +Первое, что нам нужно сделать для инициализации модели BERT, - загрузить объект конфигурации: {#if fw === 'pt'} ```py from transformers import BertConfig, BertModel -# Building the config +# Создание конфигурации config = BertConfig() -# Building the model from the config +# Создание модели на основе конфигурации model = BertModel(config) ``` {:else} ```py from transformers import BertConfig, TFBertModel -# Building the config +# Создание конфигурации config = BertConfig() -# Building the model from the config +# Создание модели на основе конфигурации model = TFBertModel(config) ``` {/if} -Конфигурация содержит множество атрибутов, которые используются для построения модели: +Конфигурация содержит множество атрибутов, которые используются для создания модели: ```py print(config) @@ -86,11 +86,11 @@ BertConfig { } ``` -Хотя вы еще не видели, что делают все эти атрибуты, вы должны узнать некоторые из них: атрибут `hidden_size` определяет размер вектора `hidden_states`, а `num_hidden_layers` определяет количество слоев, которые имеет модель. +Хотя вы еще не видели, что делают все эти атрибуты, вы должны узнать некоторые из них: атрибут `hidden_size` определяет размер вектора `hidden_states`, а `num_hidden_layers` определяет количество слоев в модели Transformer. -### Различные способы загрузки +### Различные методы загрузки[[different-loading-methods]] -Создание модели из конфигурации по умолчанию инициализирует ее случайными значениями: +При создании модели из конфигурации по умолчанию она инициализируется случайными значениями: {#if fw === 'pt'} ```py @@ -112,9 +112,9 @@ model = TFBertModel(config) ``` {/if} -Модель можно использовать в этом состоянии, но она будет выводить тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для решения поставленной задачи, но, как вы видели в [Главе 1](/course/chapter1), это потребовало бы много времени и большого количества данных, а также имело бы значительное воздействие на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность делиться и повторно использовать модели, которые уже были обучены. +Модель можно использовать и в таком состоянии, но она будет выдавать тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для конкретной задачи, но, как вы видели в [Главе 1](../chapter1), это потребовало бы много времени и большого количества данных, а также оказало бы немалое влияние на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность обмениваться уже обученными моделями и повторно их использовать. -Загрузить уже обученную модель Transformer очень просто — мы можем сделать это с помощью метода `from_pretrained()`: +Загрузить уже обученную модель Transformer очень просто - мы можем сделать это с помощью метода `from_pretrained()`: {#if fw === 'pt'} ```py @@ -123,7 +123,7 @@ from transformers import BertModel model = BertModel.from_pretrained("bert-base-cased") ``` -Как вы видели ранее, мы могли бы заменить `BertModel` эквивалентным классом `AutoModel`. С этого момента мы начнем делать так, поскольку таким образом создается код, не зависящий от контрольных точек; если ваш код работает для одной контрольной точки, он должен беспрепятственно работать с другой. Это применимо, даже если архитектура отличается, при условии, что контрольная точка была обучена для аналогичной задачи (например, задачи анализа тональности). +Как вы видели ранее, мы можем заменить `BertModel` на эквивалентный класс `AutoModel`. В дальнейшем мы будем поступать именно так, поскольку таким образом мы получаем код, не зависящий от контрольных точек; если ваш код работает на одной контрольной точке, он должен без проблем работать и на другой. Это касается даже разных архитектур, если контрольная точка была обучена для схожей задачи (например, задачи анализа настроений). {:else} ```py @@ -132,27 +132,27 @@ from transformers import TFBertModel model = TFBertModel.from_pretrained("bert-base-cased") ``` -Как вы видели ранее, мы могли бы заменить `TFBertModel` эквивалентным классом `TFAutoModel`. С этого момента мы начнем делать так, поскольку таким образом создается код, не зависящий от контрольных точек; если ваш код работает для одной контрольной точки, он должен беспрепятственно работать с другой. Это применимо, даже если архитектура отличается, при условии, что контрольная точка была обучена для аналогичной задачи (например, задачи анализа тональности). +Как вы видели ранее, мы можем заменить `TFBertModel` на эквивалентный класс `TFAutoModel`. В дальнейшем мы будем поступать именно так, поскольку таким образом мы получаем код, не зависящий от контрольных точек; если ваш код работает на одной контрольной точке, он должен без проблем работать и на другой. Это касается даже разных архитектур, если контрольная точка была обучена для схожей задачи (например, задачи анализа настроений). {/if} -В приведенном выше примере кода мы не использовали `BertConfig`, а вместо этого загрузили предварительно обученную модель с помощью идентификатора `bert-base-cased`. Это контрольная точка модели, которую обучили сами авторы BERT; вы можете найти более подробную информацию о ней в её [карточке модели](https://huggingface.co/bert-base-cased). +В приведенном выше примере кода мы не использовали `BertConfig`, а вместо этого загрузили предварительно обученную модель через идентификатор `bert-base-cased`. Это контрольная точка модели, которая была обучена самими авторами BERT; более подробную информацию о ней можно найти в ее [карточке модели](https://huggingface.co/bert-base-cased). -Теперь эта модель инициализирована со всеми весами контрольной точки. Её можно использовать непосредственно для логического вывода на задачах, для которых она обучалась, а также её можно точно донастроить для новой задачи. Тренируясь с предварительно обученными весами, а не с нуля, мы можем быстро добиться хороших результатов. +Теперь эта модель инициализирована всеми весами контрольной точки. Ее можно использовать непосредственно для инференса на задачах, для которых она была обучена, а также для дообучения на новой задаче. Обучаясь с предварительно подготовленными весами, а не с нуля, мы можем быстро добиться хороших результатов. -Веса будут загружены и кэшированы (поэтому будущие вызовы метода `from_pretrained()` не будут загружать их повторно) в папку кеша, которая по умолчанию находится в *~/.cache/huggingface/transformers*. Вы можете настроить папку кэша, установив переменную среды `HF_HOME`. +Веса были загружены и кэшированы (чтобы последующие вызовы метода `from_pretrained()` не загружали их заново) в папке кэша, которая по умолчанию находится в *~/.cache/huggingface/transformers*. Вы можете настроить папку кэша, установив переменную окружения `HF_HOME`. -Идентификатор, используемый для загрузки модели, может быть идентификатором любой модели в Model Hub, если он совместим с архитектурой BERT. Полный список доступных контрольных точек моделей BERT можно найти [здесь](https://huggingface.co/models?filter=bert). +Идентификатор, используемый для загрузки модели, может быть идентификатором любой модели на Model Hub, если она совместима с архитектурой BERT. Полный список доступных контрольных точек BERT можно найти [здесь](https://huggingface.co/models?filter=bert). -### Способы сохранения +### Методы сохранения[[saving-methods]] -Сохранить модель так же просто, как и загрузить - для этого мы используем метод `save_pretrained()`, аналогичный методу `from_pretrained()`: +Сохранить модель так же просто, как и загрузить ее - мы используем метод `save_pretrained()`, который аналогичен методу `from_pretrained()`: ```py model.save_pretrained("directory_on_my_computer") ``` -Данный код сохранит два файла на вашем диске: +При этом на диск сохраняются два файла: {#if fw === 'pt'} ``` @@ -168,21 +168,21 @@ config.json tf_model.h5 ``` {/if} -Если вы посмотрите на файл *config.json*, вы увидите атрибуты, необходимые для построения архитектуры модели. Этот файл также содержит некоторые метаданные, например, откуда появилась контрольная точка и какую версию 🤗 Transformers вы использовали, когда в последний раз сохраняли контрольную точку. +Если вы посмотрите на файл *config.json*, то узнаете атрибуты, необходимые для построения архитектуры модели. Этот файл также содержит некоторые метаданные, такие как место создания контрольной точки и версию 🤗 Transformers, которую вы использовали при последнем сохранении контрольной точки. {#if fw === 'pt'} -Файл *pytorch_model.bin* известен как *словарь состояний*; он содержит все веса вашей модели. Эти два файла идут рука об руку; конфигурация необходима, чтобы знать архитектуру вашей модели, в то время как веса модели являются параметрами вашей модели. +Файл *pytorch_model.bin* известен как *словарь состояний (state dictionary)*; он содержит все веса вашей модели. Эти два файла неразрывно связаны друг с другом; конфигурация необходима для того, чтобы знать архитектуру модели, а веса модели - это ее параметры. {:else} -Файл *tf_model.h5* известен как *словарь состояний*; он содержит все веса вашей модели. Эти два файла идут рука об руку; конфигурация необходима, чтобы знать архитектуру вашей модели, в то время как веса модели являются параметрами вашей модели. +Файл *tf_model.h5* известен как *словарь состояний (state dictionary)*; он содержит все веса вашей модели. Эти два файла неразрывно связаны друг с другом; конфигурация необходима для того, чтобы знать архитектуру модели, а веса модели - это ее параметры. {/if} -## Использование модели Transformer для логического вывода +## Использование модели Transformer для инференса[[using-a-transformer-model-for-inference]] -Теперь, когда вы знаете, как загружать и сохранять модель, давайте попробуем использовать ее для построения некоторых предсказаний. Модели Transformer могут обрабатывать только числа — числа, которые генерирует токенизатор. Но прежде чем мы обсудим токенизаторы, давайте рассмотрим, какие входные данные принимает модель. +Теперь, когда вы знаете, как загружать и сохранять модель, давайте попробуем использовать ее для прогнозирования. Модели Transformer могут обрабатывать только числа - числа, которые генерирует токенизатор. Но прежде чем мы обсудим токенизаторы, давайте узнаем, какие входные данные (входы) принимает модель. -Токенизаторы могут позаботиться о преобразовании входных данных в соответствующие тензоры фреймворка, но чтобы помочь вам понять, что происходит, мы кратко рассмотрим, что необходимо сделать, прежде чем отправлять входные данные в модель. +Токенизаторы могут позаботиться о приведении входных данных к тензорам соответствующего фреймворка, но чтобы помочь вам понять, что происходит, мы кратко рассмотрим, что нужно сделать перед передачей входных данных в модель. Допустим, у нас есть несколько последовательностей: @@ -190,7 +190,7 @@ config.json tf_model.h5 sequences = ["Hello!", "Cool.", "Nice!"] ``` -Токенизатор преобразует их в словарные индексы, которые обычно называются *входными идентификаторами*. Каждая последовательность теперь представляет собой список чисел! В результате получается: +Токенизатор преобразует их в индексы словаря, которые обычно называются *идентификаторами входов (input IDs)*. Теперь каждая последовательность представляет собой список чисел! В результате на выходе получаем: ```py no-format encoded_sequences = [ @@ -200,7 +200,7 @@ encoded_sequences = [ ] ``` -Это список закодированных последовательностей: список списков. Тензоры принимают только прямоугольные формы (например, матрицы). Этот "массив" уже имеет прямоугольную форму, поэтому преобразовать его в тензор несложно: +Это список закодированных последовательностей: список списков. Тензоры принимают только прямоугольную форму (подумайте о матрицах). Этот "массив" уже имеет прямоугольную форму, поэтому преобразовать его в тензор очень просто: {#if fw === 'pt'} ```py @@ -216,12 +216,13 @@ model_inputs = tf.constant(encoded_sequences) ``` {/if} -### Использование тензоров в качестве входных данных для модели +### Использование тензоров в качестве входов в модель[[using-the-tensors-as-inputs-to-the-model]] -Использовать тензоры с моделью чрезвычайно просто — мы просто вызываем модель с входными данными: +Использовать тензоры с моделью очень просто - мы просто вызываем модель с входами: ```py output = model(model_inputs) ``` -В то время как модель принимает множество различных аргументов, необходимы только входные идентификаторы. Позже мы объясним, для чего применяются другие аргументы и когда они требуются, но сначала нам нужно поближе познакомиться с токенизаторами, которые используются для создания входных данных, понятных модели Transformer. +Хотя модель принимает множество различных аргументов, только идентификаторы входов являются необходимыми. О том, что делают остальные аргументы и когда они нужны, мы расскажем позже, +но сначала нам нужно подробнее рассмотреть токенизаторы, которые формируют входные данные (входы), которые может понять модель Transformer. diff --git a/chapters/ru/chapter2/4.mdx b/chapters/ru/chapter2/4.mdx new file mode 100644 index 000000000..45b0bb20b --- /dev/null +++ b/chapters/ru/chapter2/4.mdx @@ -0,0 +1,240 @@ + + +# Токенизаторы[[tokenizers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Токенизаторы - один из основных компонентов конвейера NLP. Они служат одной цели: преобразовать текст в данные, которые могут быть обработаны моделью. Модели могут обрабатывать только числа, поэтому токенизаторы должны преобразовывать наш текст в числовые данные. В этом разделе мы рассмотрим, что именно происходит в конвейере токенизации. + +В задачах NLP данные, которые обычно подвергаются обработке, представляют собой необработанный текст. Вот пример такого текста: + +``` +Jim Henson was a puppeteer +``` + +Однако модели могут обрабатывать только числа, поэтому нам нужно найти способ преобразовать исходный текст в числа. Этим занимаются токенизаторы, и существует множество способов сделать это. Цель состоит в том, чтобы найти наиболее осмысленное представление - то есть то, которое имеет наибольший смысл для модели, - и, если возможно, наименьшее представление. + +Давайте рассмотрим несколько примеров алгоритмов токенизации и постараемся ответить на некоторые вопросы, которые могут у вас возникнуть по токенизации. + +## На основе слов[[word-based]] + + + +Первый тип токенайзера, который приходит на ум, - это _на основе слов (word-based)_. Как правило, его очень легко настроить и использовать с помощью всего нескольких правил, и он часто дает достойные результаты. Например, на изображении ниже цель состоит в том, чтобы разбить исходный текст на слова и найти для каждого из них числовое представление: + +
+ Пример токенизации на базе слов. + +
+ +Разделить текст можно разными способами. Например, мы можем использовать пробельные символы, чтобы разделить текст на слова, применив функцию Python `split()`: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +Существуют также разновидности токенизаторов слов, которые содержат дополнительные правила для пунктуации. Используя такой токенизатор, мы можем получить довольно большие "словари", где словарь определяется общим количеством независимых токенов, которые есть в нашем корпусе. + +Каждому слову присваивается идентификатор, начиная с 0 и заканчивая размером словаря. Модель использует эти идентификаторы для идентификации каждого слова. + +Если мы хотим полностью покрыть язык с помощью токенизатора, основанного на словах, нам понадобится идентификатор для каждого слова в языке, что приведет к созданию огромного количества токенов. Например, в английском языке более 500 000 слов, поэтому, чтобы построить карту соответствия каждого слова входному идентификатору, нам нужно будет отслеживать такое количество идентификаторов. Кроме того, такие слова, как "dog", представляются иначе, чем слова типа "dogs", и модель изначально не будет знать, что "dog" и "dogs" похожи: она определит эти два слова как несвязанные. То же самое относится и к другим похожим словам, например "run" и "running", которые модель изначально не будет воспринимать как похожие. + +Наконец, нам нужен специальный токен для обозначения слов, которых нет в нашем словаре. Это так называемый "unknown" токен, часто представляемый как "[UNK]" или "<unk>". Обычно это плохой знак, если вы видите, что токенизатор выдает много таких токенов, поскольку он не смог получить разумное представление слова, и вы теряете информацию на этом этапе. При создании словаря целью является сделать это таким образом, чтобы токенизатор как можно меньше слов токенизировал как неизвестный токен. + +Один из способов уменьшить количество неизвестных токенов - это пойти на один уровень глубже, используя _основанный на символах (character-based)_ токенизатор. + +## На основе символов[[character-based]] + + + +Токенизаторы на основе символов (character-based) разбивают текст на символы, а не на слова. Это дает два основных преимущества: + +- Словарь намного меньше. +- Неизвестных токенов гораздо меньше, поскольку каждое слово может быть образовано из символов. + +Но и здесь возникают некоторые вопросы, связанные с пробелами и пунктуацией: + +
+ Пример токенизации, основанной на символах. + +
+ +Такой подход тоже не идеален. Поскольку представление теперь основано на символах, а не на словах, можно утверждать, что интуитивно оно менее осмысленно: каждый символ сам по себе мало что значит, в то время как в случае со словами это не так. Однако это опять же зависит от языка: например, в Китайском языке каждый символ несет больше информации, чем символ в латинском языке. + +Еще один момент, который следует учитывать, - это то, что в итоге мы получим очень большое количество токенов для обработки нашей моделью: если при использовании токенизатора, основанного на словах, слово будет состоять только из одного токена, то при преобразовании в символы оно может легко превратиться в 10 или более токенов. + +Чтобы получить лучшее из обоих миров, мы можем использовать третью технику, которая объединяет эти два подхода: *токенизацию по подсловам (subword tokenization)*. + +## Токенизация по подсловам[[subword-tokenization]] + + + +Алгоритмы токенизации подслов (subword tokenization) основываются на принципе, согласно которому часто используемые слова не должны разбиваться на более мелкие подслова, а редкие слова должны быть разложены на значимые подслова. + +Например, "annoyingly" может считаться редким словом и может быть разложено на "annoying" и "ly". Оба они, скорее всего, будут чаще появляться как самостоятельные подслова, но в то же время значение " annoyingly" сохраняется за счет составного значения "annoying" и "ly". + +Вот пример, показывающий, как алгоритм токенизации подслов будет токенизировать последовательность "Let's do tokenization!": + +
+ Алгоритм токенизации по подсловам. + +
+ +Эти подслова в конечном итоге несут в себе большой семантический смысл: например, в приведенном выше примере "tokenization" было разделено на "token" и "ization" - два токена, которые несут в себе семантический смысл и при этом занимают мало места (для представления длинного слова требуется всего два токена). Это позволяет нам получить относительно хорошее покрытие при небольшом размере словаря и почти полном отсутствии неизвестных токенов. + +Этот подход особенно полезен в агглютинативных языках, таких как турецкий, где вы можете образовывать (почти) произвольно длинные сложные слова, соединяя подслова. + +### И не только![[and-more]] + +Неудивительно, что существует множество других техник. Вот лишь некоторые из них: + +- Byte-level BPE, на уровне байтов, используется в GPT-2 +- WordPiece, используемый в BERT +- SentencePiece или Unigram, используемый в нескольких многоязычных моделях + +Теперь у вас должно быть достаточно знаний о том, как работают токенизаторы, чтобы приступить к работе с API. + +## Загрузка и сохранение[[loading-and-saving]] + +Загрузка и сохранение токенизаторов так же проста, как и в случае с моделями. Фактически, они основаны на тех же двух методах: `from_pretrained()` и `save_pretrained()`. Эти методы загружают или сохраняют алгоритм, используемый токенизатором (что-то вроде *архитектуры* модели), а также его словарь (что-то вроде *весов* модели). + +Загрузка токенизатора BERT, обученного на той же контрольной точке, что и BERT, выполняется так же, как и загрузка модели, за исключением того, что мы используем класс `BertTokenizer`: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Подобно `AutoModel`, класс `AutoTokenizer` будет захватывать нужный класс токенизатора в библиотеке, основываясь на имени контрольной точки, и может быть использован непосредственно с любой контрольной точкой: + +{:else} +Подобно `TFAutoModel`, класс `AutoTokenizer` захватит нужный класс токенизатора в библиотеке, основываясь на имени контрольной точки, и может быть использован непосредственно с любой контрольной точкой: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Теперь мы можем использовать токенизатор, как показано в предыдущем разделе: + +```python +tokenizer("Using a Transformer network is simple") +``` + +```python out +{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], + 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], + 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +Сохранение токенизатора идентично сохранению модели: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Подробнее о `token_type_ids` мы поговорим в [Главе 3](../chapter3/1), а ключ `attention_mask` мы объясним чуть позже. Сначала давайте посмотрим, как генерируются `input_ids`. Для этого нам понадобится рассмотреть промежуточные методы токенизатора. + +## Кодирование[[encoding]] + + + +Перевод текста в числа называется _кодированием (encoding)_. Кодирование выполняется в два этапа: токенизация, а затем преобразование во входные идентификаторы. + +Как мы уже видели, первым шагом является разбиение текста на слова (или части слов, знаки препинания и т. д.), обычно называемые *токенами*. Существует множество правил, которые могут управлять этим процессом, поэтому нам нужно инстанцировать токенизатор, используя имя модели, чтобы убедиться, что мы используем те же правила, которые были использованы во время предварительного обучения модели. + +Второй шаг - преобразование этих токенов в числа, чтобы мы могли построить из них тензор и передать его в модель. Для этого у токенизатора есть *словарь*, который мы загружаем, когда инстанцируем его с помощью метода `from_pretrained()`. Опять же, нам нужно использовать тот же словарь, который использовался при предварительном обучении модели. + +Чтобы лучше понять эти два этапа, мы рассмотрим их по отдельности. Обратите внимание, что мы будем использовать некоторые методы, выполняющие части конвейера токенизации отдельно, чтобы показать вам промежуточные результаты этих шагов, но на практике вы должны вызывать токенизатор непосредственно на ваших входных данных (как показано в разделе 2). + +### Токенизация[[tokenization]] + +Процесс токенизации выполняется методом `tokenize()` токенизатора: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + +sequence = "Using a Transformer network is simple" +tokens = tokenizer.tokenize(sequence) + +print(tokens) +``` + +Результатом работы этого метода является список строк, или токенов: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Этот токенизатор является токенизатором подслов: он разбивает слова до тех пор, пока не получит токены, которые могут быть представлены в его словаре. В данном случае слово `transformer` разбивается на два токена: `transform` и `##er`. + +### От токенов к идентификаторам входа[[from-tokens-to-input-ids]] + +Преобразование во входные идентификаторы выполняется методом токенизатора `convert_tokens_to_ids()`: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Эти выходы, преобразованные в тензор соответствующего фреймворка, могут быть использованы в качестве входов в модель, как было показано ранее в этой главе. + + + +✏️ **Попробуйте! ** Повторите два последних шага (токенизацию и преобразование во входные идентификаторы) на входных предложениях, которые мы использовали в разделе 2 ("I've been waiting for a HuggingFace course my whole life." и "I hate this so much!"). Убедитесь, что вы получили те же самые входные идентификаторы, которые мы получали ранее! + + + +## Декодирование[[decoding]] + +*Декодирование* происходит наоборот: из индексов словаря мы хотим получить строку. Это можно сделать с помощью метода `decode()` следующим образом: + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Обратите внимание, что метод `decode` не только преобразует индексы обратно в токены, но и группирует токены, которые были частью одних и тех же слов, чтобы создать читаемое предложение. Такое поведение будет очень полезно, когда мы будем использовать модели, прогнозирующие новый текст (либо текст, сгенерированный из подсказки (prompt), либо для решения задачи преобразования последовательности-в-последовательность (sequence-to-sequence), такой как перевод или резюмирование). + +Теперь вы должны понимать, какие атомарные операции может выполнять токенизатор: токенизация, преобразование в идентификаторы и преобразование идентификаторов обратно в строку. Однако мы лишь пощупали верхушку айсберга. В следующем разделе мы рассмотрим ограничения нашего подхода и посмотрим, как их преодолеть. diff --git a/chapters/ru/chapter2/5.mdx b/chapters/ru/chapter2/5.mdx new file mode 100644 index 000000000..5dfa9c630 --- /dev/null +++ b/chapters/ru/chapter2/5.mdx @@ -0,0 +1,338 @@ + + +# Обработка нескольких последовательностей[[handling-multiple-sequences]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +В предыдущем разделе мы рассмотрели самый простой вариант использования: проведение инференса на одной последовательности небольшой длины. Однако уже сейчас возникают некоторые вопросы: + +- Как нам работать с несколькими последовательностями? +- Как нам работать с несколькими последовательностями *разной длины*? +- Являются ли индексы словаря единственными входными данными, которые позволяют модели работать хорошо? +- Существует ли такая вещь, как слишком длинная последовательность? + +Давайте посмотрим, какие проблемы возникают в связи с этими вопросами и как их можно решить с помощью 🤗 Transformers API. + +## Модели ожидают батч входов[[models-expect-a-batch-of-inputs]] + +В предыдущем упражнении вы видели, как последовательности преобразуются в списки чисел. Давайте преобразуем этот список чисел в тензор и передадим его в модель: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = torch.tensor(ids) +# Эта строка выдаст ошибку. +model(input_ids) +``` + +```python out +IndexError: Dimension out of range (expected to be in range of [-1, 0], but got 1) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = tf.constant(ids) +# Эта строка выдаст ошибку. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +О нет! Почему это не удалось? Мы следовали шагам из конвейера в разделе 2. + +Проблема в том, что мы отправили в модель одну последовательность, в то время как 🤗 модели Transformers по умолчанию ожидают несколько предложений. Здесь мы попытались сделать все то, что токенизатор делал за кулисами, когда мы применяли его к `последовательности`. Но если вы приглядитесь, то увидите, что токенизатор не просто преобразовал список входных идентификаторов в тензор, а добавил к нему еще одно измерение: + +{#if fw === 'pt'} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="pt") +print(tokenized_inputs["input_ids"]) +``` + +```python out +tensor([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, + 2607, 2026, 2878, 2166, 1012, 102]]) +``` +{:else} +```py +tokenized_inputs = tokenizer(sequence, return_tensors="tf") +print(tokenized_inputs["input_ids"]) +``` + +```py out + +``` +{/if} + +Давайте попробуем еще раз и добавим новое измерение: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = torch.tensor([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) + +input_ids = tf.constant([ids]) +print("Input IDs:", input_ids) + +output = model(input_ids) +print("Logits:", output.logits) +``` +{/if} + +Мы выводим входные идентификаторы, а также результирующие логиты - вот результат: + +{#if fw === 'pt'} +```python out +Input IDs: [[ 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012]] +Logits: [[-2.7276, 2.8789]] +``` +{:else} +```py out +Input IDs: tf.Tensor( +[[ 1045 1005 2310 2042 3403 2005 1037 17662 12172 2607 2026 2878 + 2166 1012]], shape=(1, 14), dtype=int32) +Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) +``` +{/if} + +*Батчинг* - это отправка нескольких предложений через модель одновременно. Если у вас есть только одно предложение, вы можете просто создать батч с одной последовательностью: + +``` +batched_ids = [ids, ids] +``` + +Это батч из двух одинаковых последовательностей! + + + +✏️ **Попробуйте!** Преобразуйте этот список `batched_ids` в тензор и пропустите его через вашу модель. Проверьте, что вы получаете те же логиты, что и раньше (но дважды)! + + + +Батчинг позволяет модели работать, когда вы подаете ей несколько последовательностей. Использование нескольких последовательностей так же просто, как и создание батча с одной последовательностью. Однако есть и вторая проблема. Когда вы пытаетесь собрать в батч два (или более) предложения, они могут быть разной длины. Если вы когда-нибудь работали с тензорами, то знаете, что они должны иметь прямоугольную форму, поэтому вы не сможете напрямую преобразовать список входных идентификаторов в тензор. Чтобы обойти эту проблему, мы обычно прибегаем к *дополнению (pad)* входных данных. + +## Дополнение входов[[padding-the-inputs]] + +Следующий список списков не может быть преобразован в тензор: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Чтобы обойти эту проблему, мы будем использовать *дополнение (padding)*, чтобы придать тензорам прямоугольную форму. Дополнение обеспечивает одинаковую длину всех предложений, добавляя специальное слово *токен дополнения* к предложениям с меньшим количеством значений. Например, если у вас есть 10 предложений с 10 словами и 1 предложение с 20 словами, то при дополнении все предложения будут состоять из 20 слов. В нашем примере результирующий тензор выглядит следующим образом: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +Идентификатор токена дополнения можно найти в `tokenizer.pad_token_id`. Давайте используем его и отправим наши два предложения в модель по отдельности и батчем: + +{#if fw === 'pt'} +```py no-format +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(torch.tensor(sequence1_ids)).logits) +print(model(torch.tensor(sequence2_ids)).logits) +print(model(torch.tensor(batched_ids)).logits) +``` + +```python out +tensor([[ 1.5694, -1.3895]], grad_fn=) +tensor([[ 0.5803, -0.4125]], grad_fn=) +tensor([[ 1.5694, -1.3895], + [ 1.3373, -1.2163]], grad_fn=) +``` +{:else} +```py no-format +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) + +sequence1_ids = [[200, 200, 200]] +sequence2_ids = [[200, 200]] +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +print(model(tf.constant(sequence1_ids)).logits) +print(model(tf.constant(sequence2_ids)).logits) +print(model(tf.constant(batched_ids)).logits) +``` + +```py out +tf.Tensor([[ 1.5693678 -1.3894581]], shape=(1, 2), dtype=float32) +tf.Tensor([[ 0.5803005 -0.41252428]], shape=(1, 2), dtype=float32) +tf.Tensor( +[[ 1.5693681 -1.3894582] + [ 1.3373486 -1.2163193]], shape=(2, 2), dtype=float32) +``` +{/if} + +Что-то не так с логитами в наших батчах: во втором ряду должны быть те же логиты, что и для второго предложения, но мы получили совершенно другие значения! + +Это связано с тем, что ключевой особенностью моделей Transformer являются слои внимания (attention layers), которые *контекстуализируют* каждый токен. Они учитывают токены дополнений, так как рассматривают все токены последовательности. Чтобы получить одинаковый результат при прохождении через модель отдельных предложений разной длины или при прохождении батча с одинаковыми предложениями и дополнениями, нам нужно указать слоям внимания игнорировать дополняющие токены. Для этого используется маска внимания (attention mask). + +## Маски внимания[[attention-masks]] + +*Маски внимания (Attention masks)* - это тензоры той же формы, что и тензор входных идентификаторов, заполненные 0 и 1: 1 означает, что соответствующие токены должны "привлекать внимание", а 0 означает, что соответствующие токены не должны "привлекать внимание" (т.е. должны игнорироваться слоями внимания модели). + +Дополним предыдущий пример маской внимания: + +{#if fw === 'pt'} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(torch.tensor(batched_ids), attention_mask=torch.tensor(attention_mask)) +print(outputs.logits) +``` + +```python out +tensor([[ 1.5694, -1.3895], + [ 0.5803, -0.4125]], grad_fn=) +``` +{:else} +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200, tokenizer.pad_token_id], +] + +attention_mask = [ + [1, 1, 1], + [1, 1, 0], +] + +outputs = model(tf.constant(batched_ids), attention_mask=tf.constant(attention_mask)) +print(outputs.logits) +``` + +```py out +tf.Tensor( +[[ 1.5693681 -1.3894582 ] + [ 0.5803021 -0.41252586]], shape=(2, 2), dtype=float32) +``` +{/if} + +Теперь мы получим такие же логиты для второго предложения в батче. + +Обратите внимание, что последнее значение второй последовательности - это идентификатор дополнения (padding ID), который в маске внимания имеет значение 0. + + + +✏️ **Попробуйте! ** Примените токенизацию вручную к двум предложениям, использованным в разделе 2 ("I've been waiting for a HuggingFace course my whole life." и "I hate this so much!"). Пропустите их через модель и проверьте, что вы получите те же логиты, что и в разделе 2. Теперь объедините их в батч с использованием токена дополнения, а затем создайте соответствующую маску внимания. Проверьте, что при прохождении через модель вы получаете те же результаты! + + + +## Более длинные последовательности[[longer-sequences]] + +В моделях Transformer существует ограничение на длину последовательностей, которые мы можем передавать моделям. Большинство моделей работают с последовательностями длиной до 512 или 1024 токенов и терпят крах при необходимости обработки более длинных последовательностей. Есть два решения этой проблемы: + +- Использовать модель с большей поддерживаемой длиной последовательности. +- Усечение (truncate) последовательностей. + +Модели имеют разную поддерживаемую длину последовательности, а некоторые специализируются на работе с очень длинными последовательностями. Одним из примеров является [Longformer](https://huggingface.co/docs/transformers/model_doc/longformer), а другим - [LED](https://huggingface.co/docs/transformers/model_doc/led). Если вы работаете над задачей, требующей очень длинных последовательностей, мы рекомендуем вам обратить внимание на эти модели. + +В противном случае мы рекомендуем использовать усечение последовательностей, указав параметр `max_sequence_length`: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/ru/chapter2/6.mdx b/chapters/ru/chapter2/6.mdx new file mode 100644 index 000000000..6097ad0fa --- /dev/null +++ b/chapters/ru/chapter2/6.mdx @@ -0,0 +1,164 @@ + + +# Собираем все воедино[[putting-it-all-together]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +В последних нескольких разделах мы старались делать большую часть работы вручную. Мы изучили, как работают токенизаторы, рассмотрели токенизацию, преобразование во входные идентификаторы, дополнении, усечении и маски внимания. + +Однако, как мы видели в разделе 2, 🤗 Transformers API может обработать все это для нас с помощью высокоуровневой функции, которую мы рассмотрим здесь. Когда вы вызываете свой `tokenizer` непосредственно на предложении, вы получаете обратно входы, готовые к передаче в вашу модель: + +```py +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +Здесь переменная `model_inputs` содержит все, что необходимо для нормальной работы модели. Для DistilBERT это идентификаторы входов, а также маска внимания. В других моделях, принимающих дополнительные входы, они также будут формироваться выходами объекта `tokenizer`. + +Как мы увидим на нескольких примерах ниже, это очень мощный метод. Во-первых, он может токенизировать одну последовательность: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +Он также обрабатывает несколько последовательностей одновременно, без каких-либо изменений в API: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +Он может быть дополнен исходя из нескольких целей: + +```py +# Дополнение последовательностей до максимальной длины последовательности +model_inputs = tokenizer(sequences, padding="longest") + +# Дополнение последовательностей до максимальной длины модели +# (512 для BERT или DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Дополнение последовательностей до заданной максимальной длины +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +Он также может выполнять усечение последовательностей: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Усечение последовательностей, длина которых превышает максимальную длину модели +# (512 для BERT или DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Усечение последовательностей, длина которых превышает заданную максимальную длину +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +Объект `tokenizer` может выполнять преобразование в тензоры конкретных фреймворков, которые затем могут быть напрямую переданы в модель. Например, в следующем примере кода мы задаем токенизатору возвращать тензоры для различных фреймворков - `"pt"` возвращает тензоры PyTorch, `"tf"` возвращает тензоры TensorFlow, а `"np"` возвращает массивы NumPy: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Вернуть тензоры PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Вернуть тензоры TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Вернуть массивы NumPy +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Специальные токены[[special-tokens]] + +Если мы посмотрим на идентификаторы входа, возвращаемые токенизатором, то увидим, что они немного отличаются от тех, что мы получали ранее: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +print(model_inputs["input_ids"]) + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +print(ids) +``` + +```python out +[101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102] +[1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] +``` + +Один идентификатор токена был добавлен в начале, а другой - в конце. Давайте декодируем две последовательности идентификаторов, приведенные выше, чтобы понять, в чем дело: + +```py +print(tokenizer.decode(model_inputs["input_ids"])) +print(tokenizer.decode(ids)) +``` + +```python out +"[CLS] i've been waiting for a huggingface course my whole life. [SEP]" +"i've been waiting for a huggingface course my whole life." +``` + +Токенизатор добавил специальное слово `[CLS]` в начале и специальное слово `[SEP]` в конце. Это связано с тем, что модель была предварительно обучена с ними, поэтому для получения тех же результатов при инференсе нам нужно добавить и их. Обратите внимание, что некоторые модели не добавляют специальные слова или добавляют другие слова; модели также могут добавлять эти специальные слова только в начале или только в конце. В любом случае, токенизатор знает, какие из них ожидаются, и справится с этим сам. + +## Подведение итогов: От токенизатора к модели[[wrapping-up-from-tokenizer-to-model]] + +Теперь, когда мы рассмотрели все отдельные шаги, которые использует объект `tokenizer` при работе с текстами, давайте в последний раз посмотрим, как он может работать с множественными последовательностями (дополнение!), очень длинными последовательностями (усечение!) и несколькими типами тензоров с помощью своего основного API: + +{#if fw === 'pt'} +```py +import torch +from transformers import AutoTokenizer, AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") +output = model(**tokens) +``` +{:else} +```py +import tensorflow as tf +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") +output = model(**tokens) +``` +{/if} diff --git a/chapters/ru/chapter2/7.mdx b/chapters/ru/chapter2/7.mdx index 708d47430..13c62e066 100644 --- a/chapters/ru/chapter2/7.mdx +++ b/chapters/ru/chapter2/7.mdx @@ -1,18 +1,18 @@ -# Базовое использование завершено! +# Базовое использование завершено![[basic-usage-completed]] -Отличная работа, вы прошли курс до текущего момента! Напомним, что в этой главе вы: +Отличная работа по изучению курса до этого места! Напомним, что в этой главе вы: -- Изучил основные строительные блоки модели Transformer. +- Узнали об основных составляющих блоках модели Transformer. - Узнали, из чего состоит конвейер токенизации. -- Увидел, как использовать модель Transformer на практике. -- Научились использовать токенизатор для преобразования текста в тензоры, понятные модели. -- Настроили токенизатор и модель так, чтобы было возможно перейти от текста к прогнозированию. -- Изучили ограничения для входных идентификаторов и узнал о масках внимания. -- Поэкспериментировали с универсальными и настраиваемыми методами токенизатора. +- Узнали, как использовать модель Transformer на практике. +- Узнали, как использовать токенизатор для преобразования текста в тензоры, понятные модели. +- Настроили токенизатор и модель вместе, чтобы перейти от текста к прогнозам. +- Узнали об ограничениях входных идентификаторов и познакомились с масками внимания. +- Поиграли с универсальными и настраиваемыми методами токенизатора. -Теперь вы сможете свободно ориентироваться в документации 🤗 Transformers: словарный запас будет для вас знаком, и к тому уже вы видели методы, которые будете использовать большую часть времени. +С этого момента вы должны свободно ориентироваться в документации 🤗 Transformers: лексикон будет звучать знакомо, и вы уже познакомились с методами, которые будете использовать чаще всего. diff --git a/chapters/ru/chapter2/8.mdx b/chapters/ru/chapter2/8.mdx new file mode 100644 index 000000000..395cf3d7c --- /dev/null +++ b/chapters/ru/chapter2/8.mdx @@ -0,0 +1,310 @@ + + + + +# Тест в конце главы[[end-of-chapter-quiz]] + + + +### 1. Каков порядок работы конвейера языкового моделирования? + + + +### 2. Сколько измерений имеет тензор, выводимый базовой моделью Transformer, и каковы они? + + + +### 3. Что из перечисленного ниже является примером токенизации по подсловам? + + + +### 4. Что такое голова модели? + + + +{#if fw === 'pt'} +### 5. Что такое AutoModel? + +AutoTrain?" + }, + { + text: "Объект, возвращающий правильную архитектуру на основе контрольной точки", + explain: "Именно: в AutoModel для возврата правильной архитектуры достаточно знать контрольную точку, с которой нужно инициализироваться.", + correct: true + }, + { + text: "Модель, которая автоматически определяет язык, используемый для входов, чтобы загрузить правильные веса", + explain: "Неверно; хотя некоторые контрольные точки и модели способны работать с несколькими языками, встроенных инструментов для автоматического выбора контрольной точки в зависимости от языка не существует. Вам следует обратиться в Model Hub, чтобы найти лучшую контрольную точку для вашей задачи!" + } + ]} +/> + +{:else} +### 5. Что такое TFAutoModel? + +AutoTrain?" + }, + { + text: "Объект, возвращающий правильную архитектуру на основе контрольной точки", + explain: "Именно так: в TFAutoModel для возврата правильной архитектуры достаточно знать контрольную точку, с которой нужно инициализироваться.", + correct: true + }, + { + text: "Модель, которая автоматически определяет язык, используемый на входах, чтобы загрузить правильные веса", + explain: "Неверно; хотя некоторые контрольные точки и модели способны работать с несколькими языками, встроенных инструментов для автоматического выбора контрольной точки в зависимости от языка не существует. Вам следует обратиться в Model Hub, чтобы найти лучшую контрольную точку для вашей задачи!" + } + ]} +/> + +{/if} + +### 6. На какие техники следует обратить внимание при объединении в батч последовательностей разной длины? + + + +### 7. В чем смысл применения функции SoftMax к логитам, выводимым моделью классификации последовательностей? + + + +### 8. Какой метод является основным в API токенизатора? + +encode, поскольку он может кодировать текст в идентификаторы и идентификаторы в прогнозы.", + explain: "Неверно! Если метод encode существует в токенизаторах, то в моделях его нет." + }, + { + text: "Вызов объекта токенизатора напрямую.", + explain: "Точно! Метод __call__ токенизатора - это очень мощный метод, который может обрабатывать практически все. Это также метод, используемый для получения прогнозов из модели.", + correct: true + }, + { + text: "pad", + explain: "Неверно! Дополнение очень полезно, но это всего лишь одна из частей API токенизатора." + }, + { + text: "tokenize", + explain: "Метод tokenize, пожалуй, один из самых полезных методов, но он не является ядром API токенизатора." + } + ]} +/> + +### 9. Что содержит переменная `result` в этом примере кода? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +__call__ или convert_tokens_to_ids!" + }, + { + text: "Строка, содержащая все токены", + explain: "Это было бы неоптимально, поскольку цель состоит в том, чтобы разбить строку на множество токенов." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Есть ли что-то неправильное в следующем коде? + +```py +from transformers import AutoTokenizer, AutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = AutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + + + +{:else} +### 10. Что-то не так с приведенным ниже кодом? + +```py +from transformers import AutoTokenizer, TFAutoModel + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +model = TFAutoModel.from_pretrained("gpt2") + +encoded = tokenizer("Hey!", return_tensors="pt") +result = model(**encoded) +``` + + + +{/if} diff --git a/chapters/ru/chapter3/1.mdx b/chapters/ru/chapter3/1.mdx index 476152eb3..953a2bd45 100644 --- a/chapters/ru/chapter3/1.mdx +++ b/chapters/ru/chapter3/1.mdx @@ -7,7 +7,7 @@ classNames="absolute z-10 right-0 top-0" /> -В [главе 2](/course/ru/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: +В [главе 2](../chapter2/1) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: {#if fw === 'pt'} * Как подготовить большой датасет из Model Hub diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 250e553b1..57d761056 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -23,7 +23,7 @@ {/if} {#if fw === 'pt'} -Продолжим с примером из [предыдущей главы](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: +Продолжим с примером из [предыдущей главы](../chapter2/1), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: ```python import torch @@ -48,7 +48,7 @@ loss.backward() optimizer.step() ``` {:else} -Continuing with the example from the [previous chapter](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow: +Continuing with the example from the [previous chapter](../chapter2/1), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow: ```python import tensorflow as tf @@ -84,10 +84,14 @@ model.train_on_batch(batch, labels) {/if} -Hub содержит не только модели, там также расположено множество датасетов на различных языках. Вы можете посмотреть на них [тут](https://huggingface.co/datasets), а также мы рекомендуем попровать загрузить новый датасет после того, как вы изучите текущий раздел (см. документацию [здесь](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Но сейчас вернемся к датасету MRPC! Это один из 10 датасетов из состава [GLUE](https://gluebenchmark.com/), который является тестом для производительности моделей машинного обучения в задачах классификации текста. +Hub содержит не только модели, там также расположено множество датасетов на различных языках. Вы можете посмотреть на них [тут](https://huggingface.co/datasets), а также мы рекомендуем попровать загрузить новый датасет после того, как вы изучите текущий раздел (см. документацию [здесь](https://huggingface.co/docs/datasets/loading)). Но сейчас вернемся к датасету MRPC! Это один из 10 датасетов из состава [GLUE](https://gluebenchmark.com/), который является тестом для производительности моделей машинного обучения в задачах классификации текста. Библиотека 🤗 Datasets предоставляет возможность использовать очень простую команду для загрузки и кэширования датасета с Hub. Мы можем загрузить датасет следующим образом: + +⚠️ **Предупреждение** Убедитесь, что `datasets` установлены, выполнив `pip install datasets`. Затем загрузите набор данных MRPC и выведите его, чтобы увидеть, что он содержит. + + ```py from datasets import load_dataset @@ -159,7 +163,7 @@ raw_train_dataset.features {/if} -Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](/course/ru/chapter2), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: +Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](../chapter2/1), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: ```py from transformers import AutoTokenizer @@ -185,7 +189,7 @@ inputs } ``` -Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](/course/ru/chapter2), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. +Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](../chapter2/1), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. @@ -216,13 +220,13 @@ tokenizer.convert_ids_to_tokens(inputs["input_ids"]) Обратите внимание, что если вы выберете другой чекпоинт, `token_type_ids` необязательно будут присутствовать в ваших токенизированных входных данных (например, они не возвращаются, если вы используете модель DistilBERT). Они возвращаются только тогда, когда модель будет знать, что с ними делать, потому что она видела их во время предобучения. -В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](/course/ru/chapter1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. +В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](../chapter1/1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. В этой задаче модели на вход подаются пары предложений (со случайно замаскированными токенами), от модели требуется предсказать, является ли следующее предложение продолжением текущего. Чтобы задача не была слишком тривиальной, половина времени модель обучается на соседних предложениях из одного документа, другую половину на парах предложений, взятых из разных источников. В общем случае вам не нужно беспокоиться о наличии `token_type_ids` в ваших токенизированных данных: пока вы используете одинаковый чекпоинт и для токенизатора, и для модели – токенизатор будет знать, как нужно обработать данные. -Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](/course/ru/chapter2) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](/course/chapter2). Итак, один из способов предобработать обучающий датасет такой: +Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](../chapter2/1) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](../chapter2). Итак, один из способов предобработать обучающий датасет такой: ```py tokenized_dataset = tokenizer( @@ -235,7 +239,7 @@ tokenized_dataset = tokenizer( Это хорошо работает, однако есть недостаток, который формирует токенизатор (с ключами, `input_ids`, `attention_mask`, и `token_type_ids`, и значениями в формате списка списков). Это будет работать только если у нас достаточно оперативной памяти (RAM) для хранения целого датасета во время токенизации (в то время как датасеты из библиотеки 🤗 Datasets являются [Apache Arrow](https://arrow.apache.org/) файлами, хранящимися на диске; они будут загружены только в тот момент, когда вы их будете запрашивать). -Чтобы хранить данные в формате датасета, мы будем использовать методы [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Это позволит нам сохранить высокую гибкость даже если нам нужно что-то большее, чем просто токенизация. Метод `map()` работает так: применяет некоторую функцию к каждому элементу датасета, давайте определим функцию, которая токенизирует наши входные данные: +Чтобы хранить данные в формате датасета, мы будем использовать методы [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Это позволит нам сохранить высокую гибкость даже если нам нужно что-то большее, чем просто токенизация. Метод `map()` работает так: применяет некоторую функцию к каждому элементу датасета, давайте определим функцию, которая токенизирует наши входные данные: ```py def tokenize_function(example): diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx index 8da9c30d1..4f00a73b3 100644 --- a/chapters/ru/chapter3/3.mdx +++ b/chapters/ru/chapter3/3.mdx @@ -45,11 +45,11 @@ training_args = TrainingArguments("test-trainer") -💡 Если вы хотите автоматически загружать модель на Hub во время обучения, передайте аргумент `push_to_hub=True` в `TrainingArguments`. Мы больше узнаем об этом в главе [Chapter 4](/course/chapter4/3). +💡 Если вы хотите автоматически загружать модель на Hub во время обучения, передайте аргумент `push_to_hub=True` в `TrainingArguments`. Мы больше узнаем об этом в [главе 4](../chapter4/3). -Второй шаг – задание модели. Так же, как и в [предыдущей главе](/course/chapter2), мы будем использовать класс `AutoModelForSequenceClassification` с двумя лейблами: +Второй шаг – задание модели. Так же, как и в [предыдущей главе](../chapter2/1), мы будем использовать класс `AutoModelForSequenceClassification` с двумя лейблами: ```py from transformers import AutoModelForSequenceClassification @@ -57,7 +57,7 @@ from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -После создания экземпляра предобученной модели будет распечатано предупреждение (в [главе 2](/course/chapter2) мы с таким не сталкивались). Это происходит потому, что BERT не был предобучен для задачи классификации пар предложений, его последний слой не будет использован, вместо него будет добавлен слой, позволяющий работать с такой задачей. Предупреждения сообщают, что некоторые веса не будут использованы (как раз тех слоев, которые не будут использоваться) и для новых будут инициализированы случайные веса. В заключении предлагается обучить модель, что мы и сделаем прямо сейчас. +После создания экземпляра предобученной модели будет распечатано предупреждение (в [главе 2](../chapter2/1) мы с таким не сталкивались). Это происходит потому, что BERT не был предобучен для задачи классификации пар предложений, его последний слой не будет использован, вместо него будет добавлен слой, позволяющий работать с такой задачей. Предупреждения сообщают, что некоторые веса не будут использованы (как раз тех слоев, которые не будут использоваться) и для новых будут инициализированы случайные веса. В заключении предлагается обучить модель, что мы и сделаем прямо сейчас. После того, как мы загрузили модель, мы можем определить `Trainer` и передать туда нужные объекты: `model`, `training_args`, обучающую и валидационную выборки, `data_collator` и `tokenizer` @@ -105,7 +105,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) Результат функции `predict()` - другой именованный кортеж с полями `predictions`, `label_ids` и `metrics`. Поле `metrics` будет содержать значение лосса на нашем датасете и значения метрик. После реализации функции `compute_metrics()` и передачи ее в `Trainer` поле `metrics` также будет содержать результат функции `compute_metrics()`. -Как можно заметить, `predictions` - массив 408 х 2 (408 - число элементов в датасете, который мы использовали). Это логиты для каждого элемента нашего датасета, переданного в `predict()` (как вы видели в [предыдущей главе](/course/chapter2) все модели Трансформеров возвращают логиты). Чтобы превратить их в предсказания и сравнить с нашими лейблами, нам необходимо узнать индекс максимального элемента второй оси: +Как можно заметить, `predictions` - массив 408 х 2 (408 - число элементов в датасете, который мы использовали). Это логиты для каждого элемента нашего датасета, переданного в `predict()` (как вы видели в [предыдущей главе](../chapter2/1) все модели Трансформеров возвращают логиты). Чтобы превратить их в предсказания и сравнить с нашими лейблами, нам необходимо узнать индекс максимального элемента второй оси: ```py import numpy as np diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 92b68b191..bbe24dee5 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -58,7 +58,7 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( -Как и в [предыдущей главе](/course/ru/chapter2), мы будем использовать класс `TFAutoModelForSequenceClassification` с двумя метками: +Как и в [предыдущей главе](../chapter2/1), мы будем использовать класс `TFAutoModelForSequenceClassification` с двумя метками: ```py from transformers import TFAutoModelForSequenceClassification @@ -66,7 +66,7 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -Вы заметите, что в отличие от [Главы 2](/course/ru/chapter2), вы получаете предупреждение после создания экземпляра этой предварительно обученной модели. Это связано с тем, что BERT не был предварительно обучен классификации пар предложений, поэтому последний слой предварительно обученной модели был отброшен, а вместо него был вставлен новый слой, подходящий для классификации последовательностей. Предупреждения указывают на то, что некоторые веса не использовались (те, которые соответствуют удаленным слоям), а некоторые другие были инициализированы случайным образом (те, что для новых слоев). В заключение предлагается обучить модель, что мы и собираемся сделать сейчас. +Вы заметите, что в отличие от [Главы 2](../chapter2/1), вы получаете предупреждение после создания экземпляра этой предварительно обученной модели. Это связано с тем, что BERT не был предварительно обучен классификации пар предложений, поэтому последний слой предварительно обученной модели был отброшен, а вместо него был вставлен новый слой, подходящий для классификации последовательностей. Предупреждения указывают на то, что некоторые веса не использовались (те, которые соответствуют удаленным слоям), а некоторые другие были инициализированы случайным образом (те, что для новых слоев). В заключение предлагается обучить модель, что мы и собираемся сделать сейчас. Чтобы точно настроить модель в нашем наборе данных, нам просто нужно вызвать `compile()` у нашей модели, а затем передать наши данные в метод `fit()`. Это запустит процесс fine tuning (который должен занять пару минут на графическом процессоре) и сообщит о значениях функции потерь при обучении, а также о значениях функции потерь на валидации. @@ -147,7 +147,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -💡 Если вы хотите автоматически загружать свою модель на Hub во время обучения, вы можете передать `PushToHubCallback` в метод `model.fit()`. Мы узнаем об этом больше в [Chapter 4](/course/chapter4/3). +💡 Если вы хотите автоматически загружать свою модель на Hub во время обучения, вы можете передать `PushToHubCallback` в метод `model.fit()`. Мы узнаем об этом больше в [Главе 4](../chapter4/3). @@ -188,4 +188,4 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l Точные результаты, которые вы получите, могут отличаться, так как случайная инициализация параметров выходных слоев модели может изменить показатели. Здесь мы видим, что наша модель имеет точность 85,78% на валидационном наборе и оценку F1 89,97. Это две метрики, используемые для оценки результатов датасета MRPC для теста GLUE. В таблице в [документации BERT] (https://arxiv.org/pdf/1810.04805.pdf) сообщается о балле F1 88,97% для базовой модели. Это была модель, которая не чувствительна к регистру текста, в то время как сейчас мы используем модель, учитывающую регистр, что и объясняет лучший результат. -На этом введение в fine tuning с помощью Keras API завершено. Пример выполнения этого для наиболее распространенных задач NLP будет дан в [Главе 7](/course/ru/chapter7). Если вы хотите отточить свои навыки работы с Keras API, попробуйте точно настроить модель в наборе данных GLUE SST-2, используя обработку данных, которую вы выполнили в разделе 2. +На этом введение в fine tuning с помощью Keras API завершено. Пример выполнения этого для наиболее распространенных задач NLP будет дан в [Главе 7](../chapter7). Если вы хотите отточить свои навыки работы с Keras API, попробуйте точно настроить модель в наборе данных GLUE SST-2, используя обработку данных, которую вы выполнили в разделе 2. diff --git a/chapters/ru/chapter3/6.mdx b/chapters/ru/chapter3/6.mdx index bef136a13..862484c2c 100644 --- a/chapters/ru/chapter3/6.mdx +++ b/chapters/ru/chapter3/6.mdx @@ -2,7 +2,7 @@ -# Итоговый тест по главе +# Тест в конце главы[[end-of-chapter-quiz]] -# Итоговый тест по главе +# Тест в конце главы[[end-of-chapter-quiz]] -Аргумент `data_files` функции `load_dataset()` очень гибкий и может являться путем к файлу, списком путей файлов или словарем, в котором указаны названия сплитов (обучающего и тестового) и пути к соответствующим файлам. Вы также можете найти все подходящие файлы в директории с использованием маски по правилам Unix-консоли (т.е. указать путь к директории и указать `data_files="*.json"` для конкретного сплита). Более подробно это изложено в [документации](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) 🤗 Datasets. +Аргумент `data_files` функции `load_dataset()` очень гибкий и может являться путем к файлу, списком путей файлов или словарем, в котором указаны названия сплитов (обучающего и тестового) и пути к соответствующим файлам. Вы также можете найти все подходящие файлы в директории с использованием маски по правилам Unix-консоли (т.е. указать путь к директории и указать `data_files="*.json"` для конкретного сплита). Более подробно это изложено в [документации](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets. @@ -156,7 +156,7 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -✏️ **Попробуйте!** Выберите другой датасет, расположенный на GitHub или в архиве [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) и попробуйте загрузить его с локальной машины и с удаленного сервера. В качестве бонуса попробуйте загрузить датасет в формате CSV или обычного тектового файла (см. детали по поддерживаемым форматам в [документации](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files)). +✏️ **Попробуйте!** Выберите другой датасет, расположенный на GitHub или в архиве [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) и попробуйте загрузить его с локальной машины и с удаленного сервера. В качестве бонуса попробуйте загрузить датасет в формате CSV или обычного тектового файла (см. детали по поддерживаемым форматам в [документации](https://huggingface.co/docs/datasets/loading#local-and-remote-files)). diff --git a/chapters/ru/chapter5/3.mdx b/chapters/ru/chapter5/3.mdx index 378b05e24..49b35ca2c 100644 --- a/chapters/ru/chapter5/3.mdx +++ b/chapters/ru/chapter5/3.mdx @@ -13,7 +13,7 @@ ## Управление данными -Как и в Pandas, 🤗 Datasets предоставляет несколько функция для управления содержимым объектов `Dataset` и `DatasetDict`. Мы уже познакомились с методом `Dataset.map()` в [главе 3](/course/ru/chapter3), а далее мы посмотрим на другие функции, имеющиеся в нашем распоряжении. +Как и в Pandas, 🤗 Datasets предоставляет несколько функция для управления содержимым объектов `Dataset` и `DatasetDict`. Мы уже познакомились с методом `Dataset.map()` в [главе 3](../chapter3/1), а далее мы посмотрим на другие функции, имеющиеся в нашем распоряжении. Для этого примера мы будем использовать датасет [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29), расположенный на сервере [UC Irvine Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php) и содержащий отзывы пациентов на различные лекарства, сведения о состоянии пациентов и рейтинг удовлетворенности, выраженный в 10-балльной шкале. @@ -95,7 +95,7 @@ DatasetDict({ -Далее нормализуем все лейблы столбца `condition` с применением `Dataset.map()`. Так же, как мы делали токенизацию в [главе 3](/course/ru/chapter3), мы можем определить простую функцию, которая будет применения для всех строк каждого сплита в `drug_dataset`: +Далее нормализуем все лейблы столбца `condition` с применением `Dataset.map()`. Так же, как мы делали токенизацию в [главе 3](../chapter3/1), мы можем определить простую функцию, которая будет применения для всех строк каждого сплита в `drug_dataset`: ```py def lowercase_condition(example): @@ -122,7 +122,7 @@ def filter_nones(x): lambda : ``` -где `lambda` - одно из [ключевых](https://docs.python.org/3/reference/lexical_analysis.html#keywords) слов Python, а `` - список или множество разделенных запятой значений, которые пойдут на вход функции, и `` задает операции, которые вы хотите применить к аргументам. Например, мы можем задать простую лямбда-функцию, которая возводит в квадрат числа: +где `lambda` - одно из [ключевых](https://docs.python.org/3/reference/lexical_analysis#keywords) слов Python, а `` - список или множество разделенных запятой значений, которые пойдут на вход функции, и `` задает операции, которые вы хотите применить к аргументам. Например, мы можем задать простую лямбда-функцию, которая возводит в квадрат числа: ``` lambda x : x * x @@ -238,7 +238,7 @@ print(drug_dataset.num_rows) -✏️ **Попробуйте!** Используйте функцию `Dataset.sort()` для проверки наиболее длинных отзывов. Изучите [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) чтобы понять, какой аргумент нужно передать в функцию, чтобы сортировка произошла в убывающем порядке. +✏️ **Попробуйте!** Используйте функцию `Dataset.sort()` для проверки наиболее длинных отзывов. Изучите [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) чтобы понять, какой аргумент нужно передать в функцию, чтобы сортировка произошла в убывающем порядке. @@ -277,7 +277,7 @@ new_drug_dataset = drug_dataset.map( Если вы запустите этот код в блокноте, вы увидите, что эта команда выполняется намного быстрее, чем предыдущая. И это не потому, что наши отзывы уже были HTML-экранированными — если вы повторно выполните инструкцию из предыдущего раздела (без `batched=True`), это займет столько же времени, сколько и раньше. Это связано с тем, что обработка списков обычно выполняется быстрее, чем выполнение того же кода в цикле `for`, мы также повышаем производительность за счет одновременного доступа к множеству элементов, а не по одному. -Использование `Dataset.map()` с `batched=True` – хороший способ «разблокировать» скоростные ограничения "быстрых" токенизаторов, с которыми мы познакомимся в [главе 6](/course/chapter6), которые могут быстро токенизировать большие списки текста. Например, чтобы токенизировать все отзывы на лекарства с помощью быстрого токенизатора, мы можем использовать функцию, подобную этой: +Использование `Dataset.map()` с `batched=True` – хороший способ «разблокировать» скоростные ограничения "быстрых" токенизаторов, с которыми мы познакомимся в [главе 6](../chapter6), которые могут быстро токенизировать большие списки текста. Например, чтобы токенизировать все отзывы на лекарства с помощью быстрого токенизатора, мы можем использовать функцию, подобную этой: ```python from transformers import AutoTokenizer @@ -289,7 +289,7 @@ def tokenize_function(examples): return tokenizer(examples["review"], truncation=True) ``` -Как вы видели в [главе 3](/course/ru/chapter3), мы можем передать один или несколько элементов в токенизатор, так что мы можем использовать эту функцию без параметра `batched=True`. Давайте воспользуемся этой возможностью и сравним производительность. В ноутбуке можно замерить время выполнения функции путем добавления `%time` перед строкой кода, время исполнения которой вы хотите измерить: +Как вы видели в [главе 3](../chapter3/1), мы можем передать один или несколько элементов в токенизатор, так что мы можем использовать эту функцию без параметра `batched=True`. Давайте воспользуемся этой возможностью и сравним производительность. В ноутбуке можно замерить время выполнения функции путем добавления `%time` перед строкой кода, время исполнения которой вы хотите измерить: ```python no-format %time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) @@ -344,7 +344,7 @@ Options | Fast tokenizer | Slow tokenizer -Объединение всей этой функциональности во всего лишь один метод само по себе прекрасно, но это еще не все! Используя `Dataset.map()` и `batched=True` вы можете поменять число элементов в датасете. Это очень полезно во множестве ситуаций, например, когда вы хотите создать несколько обучающих признаков из одного экземпляра текста. Мы воспользуеся этой возможностью на этапе препроцессинга для нескольких NLP-задач, которые рассмотрим в [главе 7](/course/ru/chapter7) +Объединение всей этой функциональности во всего лишь один метод само по себе прекрасно, но это еще не все! Используя `Dataset.map()` и `batched=True` вы можете поменять число элементов в датасете. Это очень полезно во множестве ситуаций, например, когда вы хотите создать несколько обучающих признаков из одного экземпляра текста. Мы воспользуеся этой возможностью на этапе препроцессинга для нескольких NLP-задач, которые рассмотрим в [главе 7](../chapter7) @@ -385,9 +385,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -О, нет! Не сработало! Почему? Посмотрим на ошибку: несовпадение в длинах, один из которых длиной 1463, а другой – 1000. Если вы обратитесь в [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) `Dataset.map()`, вы можете увидеть, что одно из этих чисел – число объектов, поданных на вход функции, а другое – - -Oh no! That didn't work! Why not? Looking at the error message will give us a clue: there is a mismatch in the lengths of one of the columns, one being of length 1,463 and the other of length 1,000. If you've looked at the [documentation](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), you may recall that it's the number of samples passed to the function that we are mapping; here those 1,000 examples gave 1,463 new features, resulting in a shape error. +О, нет! Не сработало! Почему? Посмотрим на ошибку: несовпадение в длинах, один из которых длиной 1463, а другой – 1000. Если вы обратитесь в [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) `Dataset.map()`, вы можете увидеть, что одно из этих чисел – число объектов, поданных на вход функции, а другое – Проблема заключается в том, что мы пытаемся смешать два разных датасета разной размерности: число колонок датасета `drug_dataset` равняется 1000, а нужный нам `tokenized_dataset` имеет 1463 колонки. Чтобы избежать этой ошибки, необходимо удалить несколько столбцов из старого датасета и сделать оба датасета одинакового размера. Мы можем достичь этого с помощью аргумента `remove_columns`: @@ -641,7 +639,7 @@ DatasetDict({ }) ``` -Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](/course/ru/chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. +Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](../chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. ## Сохранение датасетов @@ -727,7 +725,7 @@ for split, dataset in drug_dataset_clean.items(): {"patient_id":141780,"drugName":"Escitalopram","condition":"depression","review":"\"I seemed to experience the regular side effects of LEXAPRO, insomnia, low sex drive, sleepiness during the day. I am taking it at night because my doctor said if it made me tired to take it at night. I assumed it would and started out taking it at night. Strange dreams, some pleasant. I was diagnosed with fibromyalgia. Seems to be helping with the pain. Have had anxiety and depression in my family, and have tried quite a few other medications that haven't worked. Only have been on it for two weeks but feel more positive in my mind, want to accomplish more in my life. Hopefully the side effects will dwindle away, worth it to stick with it from hearing others responses. Great medication.\"","rating":9.0,"date":"May 29, 2011","usefulCount":10,"review_length":125} ``` -Мы можем использовать приёмы из [раздела 2](/course/ru/chapter5/2) для загрузки JSON-файлов: +Мы можем использовать приёмы из [раздела 2](../chapter5/2) для загрузки JSON-файлов: ```py data_files = { @@ -740,8 +738,8 @@ drug_dataset_reloaded = load_dataset("json", data_files=data_files) Вот и все, что нужно для нашего экскурса при работе с 🤗 Datasets! Мы очистили датасет для обучения модели, вот некоторые идеи, которые вы могли бы реализовать самостоятельно: -1. Примените знания из [раздела 3](/course/ru/chapter3) для обучения классификатора, который может предсказывать состояние пациента по отзыву на лекарство. -2. Используйте pipeline `summarization` из [раздела 1](/course/ru/chapter1)для генерации саммари отзывов. +1. Примените знания из [раздела 3](../chapter3/1) для обучения классификатора, который может предсказывать состояние пациента по отзыву на лекарство. +2. Используйте pipeline `summarization` из [раздела 1](../chapter1/1)для генерации саммари отзывов. Далее мы посмотрим, как 🤗 Datasets могут помочь вам в работе с громадными датасетами, которые _невозможно_ обработать на вашем ноутбуке! diff --git a/chapters/ru/chapter5/4.mdx b/chapters/ru/chapter5/4.mdx index d96ca1b35..c62459ec4 100644 --- a/chapters/ru/chapter5/4.mdx +++ b/chapters/ru/chapter5/4.mdx @@ -23,7 +23,7 @@ The Pile — это корпус текстов на английском язы !pip install zstandard ``` -Затем мы можем загрузить набор данных, используя метод для подгрузки файлов, который мы изучили в [разделе 2](/course/ru/chapter5/2): +Затем мы можем загрузить набор данных, используя метод для подгрузки файлов, который мы изучили в [разделе 2](../chapter5/2): ```py from datasets import load_dataset @@ -45,7 +45,7 @@ Dataset({ -✎ По умолчанию 🤗 Datasets распаковывает файлы, необходимые для загрузки набора данных. Если вы хотите сохранить место на жестком диске, вы можете передать `DownloadConfig(delete_extracted=True)` в аргумент `download_config` функции `load_dataset()`. Дополнительные сведения см. в [документации](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig). +✎ По умолчанию 🤗 Datasets распаковывает файлы, необходимые для загрузки набора данных. Если вы хотите сохранить место на жестком диске, вы можете передать `DownloadConfig(delete_extracted=True)` в аргумент `download_config` функции `load_dataset()`. Дополнительные сведения см. в [документации](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig). @@ -157,7 +157,7 @@ next(iter(pubmed_dataset_streamed)) 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection.\nTo determine the prevalence of hypoxaemia in children aged under 5 years suffering acute lower respiratory infections (ALRI), the risk factors for hypoxaemia in children under 5 years of age with ALRI, and the association of hypoxaemia with an increased risk of dying in children of the same age ...'} ``` -Элементы из потокового набора данных можно обрабатывать на лету с помощью `IterableDataset.map()`, что полезно во время обучения, если вам нужно токенизировать входные данные. Процесс точно такой же, как тот, который мы использовали для токенизации нашего набора данных в [Главе 3] (/course/ru/chapter3), с той лишь разницей, что выходные данные возвращаются один за другим: +Элементы из потокового набора данных можно обрабатывать на лету с помощью `IterableDataset.map()`, что полезно во время обучения, если вам нужно токенизировать входные данные. Процесс точно такой же, как тот, который мы использовали для токенизации нашего набора данных в [Главе 3](../chapter3/1), с той лишь разницей, что выходные данные возвращаются один за другим: ```py from transformers import AutoTokenizer diff --git a/chapters/ru/chapter5/6.mdx b/chapters/ru/chapter5/6.mdx index b238ba8a9..117b06c60 100644 --- a/chapters/ru/chapter5/6.mdx +++ b/chapters/ru/chapter5/6.mdx @@ -22,13 +22,13 @@ {/if} -В [разделе 5](/course/ru/chapter5/5) мы создали набор данных о issues и комментариях GitHub из репозитория 🤗 Datasets. В этом разделе мы будем использовать эту информацию для создания поисковой системы, которая поможет нам найти ответы на самые насущные вопросы о библиотеке! +В [разделе 5](../chapter5/5) мы создали набор данных о issues и комментариях GitHub из репозитория 🤗 Datasets. В этом разделе мы будем использовать эту информацию для создания поисковой системы, которая поможет нам найти ответы на самые насущные вопросы о библиотеке! ## Использование эмбеддингов для семанического поиска -Как мы видели в [Главе 1](/course/ru/chapter1), языковые модели на основе Transformer представляют каждую лексему в текстовом фрагменте как _эмбеддинг-вектор_. Оказывается, можно «объединить» отдельные вложения, чтобы создать векторное представление для целых предложений, абзацев или (в некоторых случаях) документов. Затем эти вложения можно использовать для поиска похожих документов в корпусе путем вычисления скалярного произведения (или какой-либо другой метрики сходства) между каждым вложением и возврата документов с наибольшим перекрытием. +Как мы видели в [Главе 1](../chapter1/1), языковые модели на основе Transformer представляют каждую лексему в текстовом фрагменте как _эмбеддинг-вектор_. Оказывается, можно «объединить» отдельные вложения, чтобы создать векторное представление для целых предложений, абзацев или (в некоторых случаях) документов. Затем эти вложения можно использовать для поиска похожих документов в корпусе путем вычисления скалярного произведения (или какой-либо другой метрики сходства) между каждым вложением и возврата документов с наибольшим перекрытием. В этом разделе мы будем использовать вложения для разработки семантической поисковой системы. Эти поисковые системы предлагают несколько преимуществ по сравнению с традиционными подходами, основанными на сопоставлении ключевых слов в запросе с документами. @@ -51,7 +51,7 @@ data_files = hf_hub_url( ) ``` -С URL-адресом, сохраненным в `data_files`, мы можем загрузить удаленный набор данных, используя метод, представленный в [раздел 2](/course/ru/chapter5/2): +С URL-адресом, сохраненным в `data_files`, мы можем загрузить удаленный набор данных, используя метод, представленный в [раздел 2](../chapter5/2): ```py from datasets import load_dataset @@ -190,7 +190,7 @@ Dataset({ -✏️ **Попробуйте!** Посмотрите, сможете ли вы использовать `Dataset.map()`, чтобы развернуть столбец `comments` столбца `issues_dataset` _без_ использования Pandas. Это немного сложно; вы можете найти раздел ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) документации 🤗 Datasets, полезным для этой задачи. +✏️ **Попробуйте!** Посмотрите, сможете ли вы использовать `Dataset.map()`, чтобы развернуть столбец `comments` столбца `issues_dataset` _без_ использования Pandas. Это немного сложно; вы можете найти раздел ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) документации 🤗 Datasets, полезным для этой задачи. @@ -236,7 +236,7 @@ comments_dataset = comments_dataset.map(concatenate_text) ## Создание текстовых эмбединнгов -В [Главе 2](/course/ru/chapter2) мы видели, что можно получить эмбеддингов токенов с помощью класса AutoModel. Все, что нам нужно сделать, это выбрать подходящую контрольную точку для загрузки модели. К счастью, есть библиотека под названием `sentence-transformers`, предназначенная для создания эмбеддингов. Как описано в [документации](https://www.sbert.net/examples/applications/semantic-search/README.html#симметричный-vs-асимметричный-semantic-search) библиотеки, наш вариант использования является примером _асимметричного семантического поиска_ потому что у нас есть короткий запрос, ответ на который мы хотели бы найти в более длинном документе, например, в комментарии к проблеме. В удобной [таблице обзора модели](https://www.sbert.net/docs/pretrained_models.html#model-overview) в документации указано, что контрольная точка `multi-qa-mpnet-base-dot-v1` имеет лучшую производительность для семантического поиска, поэтому мы будем использовать её для нашего приложения. Мы также загрузим токенизатор, используя ту же контрольную точку: +В [Главе 2](../chapter2/1) мы видели, что можно получить эмбеддингов токенов с помощью класса AutoModel. Все, что нам нужно сделать, это выбрать подходящую контрольную точку для загрузки модели. К счастью, есть библиотека под названием `sentence-transformers`, предназначенная для создания эмбеддингов. Как описано в [документации](https://www.sbert.net/examples/applications/semantic-search/README.html#симметричный-vs-асимметричный-semantic-search) библиотеки, наш вариант использования является примером _асимметричного семантического поиска_ потому что у нас есть короткий запрос, ответ на который мы хотели бы найти в более длинном документе, например, в комментарии к проблеме. В удобной [таблице обзора модели](https://www.sbert.net/docs/pretrained_models.html#model-overview) в документации указано, что контрольная точка `multi-qa-mpnet-base-dot-v1` имеет лучшую производительность для семантического поиска, поэтому мы будем использовать её для нашего приложения. Мы также загрузим токенизатор, используя ту же контрольную точку: {#if fw === 'pt'} diff --git a/chapters/ru/chapter5/7.mdx b/chapters/ru/chapter5/7.mdx index d40c7f871..687891774 100644 --- a/chapters/ru/chapter5/7.mdx +++ b/chapters/ru/chapter5/7.mdx @@ -13,4 +13,4 @@ - Создавать свой собственный набор данных и отправлять его в Hugging Face Hub. - Строить свои эмбеддинги документов с помощью модели Transformer и создавать семантический поисковик с помощью FAISS. -В [Главе 7](/course/ru/chapter7) мы будем использовать все это с пользой, поскольку мы углубимся в основные задачи NLP, для которых отлично подходят модели Transformer. Однако, прежде чем идти вперед, проверьте свои знания о 🤗 Datasets с помощью быстрого теста! +В [Главе 7](../chapter7) мы будем использовать все это с пользой, поскольку мы углубимся в основные задачи NLP, для которых отлично подходят модели Transformer. Однако, прежде чем идти вперед, проверьте свои знания о 🤗 Datasets с помощью быстрого теста! diff --git a/chapters/ru/chapter5/8.mdx b/chapters/ru/chapter5/8.mdx index 717a1b959..1810b32bd 100644 --- a/chapters/ru/chapter5/8.mdx +++ b/chapters/ru/chapter5/8.mdx @@ -1,6 +1,6 @@ -# Тест по главе 5 +# Тест в конце главы[[end-of-chapter-quiz]] -В [главе 3](/course/ru/chapter3), мы рассмотрели, как настроить модель под конкретную задачу. Когда мы это делаем, мы используем тот же токенизатор, с помощью которого была предварительно обучена модель, но что нам делать, когда мы хотим обучить модель с нуля? В этих случаях использование токенизатора, предварительно обученного на корпусе из другого домена или языка, обычно неоптимально. Например, токенизатор, обученный на английском корпусе, будет плохо работать с корпусом японских текстов, поскольку использование пробелов и пунктуации в этих двух языках сильно различается. +В [Главе 3](../chapter3/1) мы рассмотрели, как дообучить модель для конкретной задачи. При этом мы используем тот же токенизатор, на котором была предварительно обучена модель, но что делать, когда мы хотим обучить модель с нуля? В таких случаях использование токенизатора, который был предварительно обучен на корпусе из другой области или языка, как правило, является неоптимальным. Например, токенизатор, обученный на корпусе английских текстов, будет плохо работать на корпусе японских текстов, поскольку использование пробелов и знаков препинания в этих двух языках сильно отличается. -В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем его можно было использовать для предобучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers), которая предоставляет «быстрые» токенизаторы в [🤗 Transformers](https://github.com/huggingface/transformers). Мы внимательно рассмотрим функции, предоставляемые этой библиотекой, и выясним, чем быстрые токенизаторы отличаются от «медленных» версий. +В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем использовать его для предварительного обучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers), которая предоставляет "быстрые" токенизаторы в библиотеке [🤗 Transformers](https://github.com/huggingface/transformers). Мы подробно рассмотрим возможности, которые предоставляет эта библиотека, и выясним, чем быстрые токенизаторы отличаются от "медленных" версий. -Темы, которые мы рассмотрим: +Мы рассмотрим следующие темы: -* Как обучить новый токенизатор, аналогичный тому, который используется конкретной моделью, на новом корпусе текстов +* Как обучить новый токенизатор, аналогичный тому, который используется в данной контрольной точке, на новом корпусе текстов * Особенности быстрых токенизаторов -* Различия между тремя основными алгоритмами токенизации составных частей слов, используемыми сегодня в NLP. -* Как создать токенизатор с нуля с помощью библиотеки 🤗 Tokenizers и обучить его на собственных данных +* Различия между тремя основными алгоритмами токенизации по подсловам, используемыми в NLP сегодня +* Как создать токенизатор с нуля с помощью библиотеки 🤗 Tokenizers и обучить его на некоторых данных -Методы, представленные в этой главе, подготовят вас к разделу [главы 7](/course/ru/chapter7/6), где мы рассмотрим создание языковой модели для исходного кода Python. Давайте начнем с рассмотрения того, что значит «обучить» токенизатор. \ No newline at end of file +Техники, представленные в этой главе, подготовят вас к разделу в [Главе 7](../chapter7/6), где мы рассмотрим создание языковой модели по исходному коду Python. Для начала давайте разберемся, что значит "обучить" токенизатор. \ No newline at end of file diff --git a/chapters/ru/chapter6/10.mdx b/chapters/ru/chapter6/10.mdx new file mode 100644 index 000000000..45ff73711 --- /dev/null +++ b/chapters/ru/chapter6/10.mdx @@ -0,0 +1,283 @@ + + +# Тест в конце главы[[end-of-chapter-quiz]] + + + +Давайте проверим, чему вы научились в этой главе! + +### 1. Когда следует обучать новый токенизатор? + + + +### 2. В чем преимущество использования генератора списков текстов по сравнению со списком списков текстов при использовании `train_new_from_iterator()`? + +train_new_from_iterator().", + explain: "Список списков текстов - это особый вид генератора списков текстов, поэтому метод примет и его. Попробуйте еще раз!" + }, + { + text: "Это позволит избежать загрузки в память сразу всего набора данных.", + explain: "Точно! Каждый батч текстов будет освобождаться из памяти при итерации, и выигрыш будет особенно заметен, если вы используете библиотеку 🤗 Datasets для хранения текстов.", + correct: true + }, + { + text: "Это позволит библиотеке 🤗 Tokenizers использовать многопроцессорность (multiprocessing).", + explain: "Нет, она будет использовать многопроцессорность в любом случае." + }, + { + text: "Обученный вами токенизатор будет генерировать более качественные тексты.", + explain: "Токенизатор не генерирует текст - вы не путаете его с языковой моделью?" + } + ]} +/> + +### 3. Каковы преимущества использования "быстрого" токенизатора? + + + +### 4. Как конвейер `token-classification` обрабатывает сущности, которые охватывают несколько токенов? + + + +### 5. Как конвейер `question-answering` обрабатывает длинные контексты? + + + +### 6. Что такое нормализация? + + + +### 7. Что такое предварительная токенизация для токенизатора по подсловам? + + + +### 8. Выберите предложения, которые относятся к модели токенизации BPE. + + + +### 9. Выберите предложения, которые относятся к модели токенизации WordPiece. + + + +### 10. Выберите предложения, которые относятся к модели токенизации Unigram. + + diff --git a/chapters/ru/chapter6/2.mdx b/chapters/ru/chapter6/2.mdx index 08c77aac3..089e23962 100644 --- a/chapters/ru/chapter6/2.mdx +++ b/chapters/ru/chapter6/2.mdx @@ -1,4 +1,4 @@ -# Обучение нового токенизатора на основе существующего +# Обучение нового токенизатора на основе старого[[training-a-new-tokenizer-from-an-old-one]] -Если языковая модель недоступна на интересующем вас языке или если ваш корпус сильно отличается от того, на котором обучалась ваша языковая модель, вы, скорее всего, захотите переобучить модель с нуля, используя токенизатор, адаптированный к вашим данным. Это потребует обучения нового токенизатора на вашем наборе данных. Но что именно это означает? Когда мы впервые рассмотрели токенизаторы в [Главе 2](/course/chapter2), мы увидели, что большинство моделей Transformer используют _алгоритм токенизации составных частей слов_ (_subword tokenization algorithm_). Чтобы определить, какие подслова представляют интерес и чаще всего встречаются в имеющемся корпусе, токенизатору необходимо внимательно изучить все тексты в корпусе — процесс, который мы называем «обучением». Точные правила, управляющие этим обучением, зависят от типа используемого токенизатора, и мы рассмотрим три основных алгоритма позже в этой главе. +Если языковая модель не доступна на интересующем вас языке или ваш корпус сильно отличается от того, на котором обучалась языковая модель, вам, скорее всего, придется заново обучать модель с нуля, используя токенизатор, адаптированный к вашим данным. Для этого потребуется обучить новый токенизатор на вашем наборе данных. Но что именно это значит? Когда мы впервые рассматривали токенизаторы в [Главе 2](../chapter2/1), мы увидели, что большинство моделей трансформеров используют _алгоритм токенизации по подсловам_. Чтобы определить, какие подслова представляют интерес и наиболее часто встречаются в корпусе, токенизатор должен внимательно изучить все тексты в корпусе - этот процесс мы называем *обучением*. Точные правила обучения зависят от типа используемого токенизатора, далее в этой главе мы рассмотрим три основных алгоритма. -⚠️ Обучение токенизатора — это не то же самое, что обучение модели! Обучение модели использует стохастический градиентный спуск, чтобы уменьшить значение функции потерь для каждого батча данных. Он рандомизирован по своей природе (это означает, что вы должны зафиксировать несколько начальных значений, чтобы получить одинаковые результаты при выполнении одной и той же тренировки дважды). Обучение токенизатора — это статистический процесс, который пытается определить, какие подслова лучше всего выбирать для данного корпуса, а точные правила, используемые для их выбора, зависят от алгоритма токенизации. Он детерминирован, то есть вы всегда получаете одни и те же результаты при обучении с одним и тем же алгоритмом на одном и том же корпусе. +⚠️ Обучение токенизатора - это не то же самое, что обучение модели! При обучении модели используется стохастический градиентный спуск, чтобы сделать потери немного меньше для каждого батча. Оно рандомизировано по своей природе (это означает, что вам нужно задать некоторое число seed, чтобы получить одинаковые результаты при повторном обучении). Обучение токенизатора - это статистический процесс, который пытается определить, какие подслова лучше всего выбрать для данного корпуса, а точные правила, используемые для их выбора, зависят от алгоритма токенизации. Это детерминированный процесс, то есть вы всегда получите одинаковые результаты при обучении одного и того же алгоритма на одном и том же корпусе. -## Сбор корпуса слов +## Сбор корпуса слов[[assembling-a-corpus]] -В 🤗 Transformers есть очень простой API, который вы можете использовать для обучения нового токенизатора с теми же характеристиками, что и у существующего: `AutoTokenizer.train_new_from_iterator()`. Чтобы увидеть это в действии, предположим, что мы хотим обучить GPT-2 с нуля, но на языке, отличном от английского. Нашей первой задачей будет сбор большого количества данных на этом языке в обучающем корпусе. Чтобы предоставить примеры, понятные каждому, мы не будем использовать здесь язык, подобный русскому или китайскому, а скорее специализированный английский язык: код Python. +В 🤗 Transformers есть очень простой API, который можно использовать для обучения нового токенизатора с теми же характеристиками, что и у существующего: `AutoTokenizer.train_new_from_iterator()`. Чтобы увидеть это в действии, предположим, что мы хотим обучить GPT-2 с нуля, но на языке, отличном от английского. Нашей первой задачей будет собрать много данных на этом языке в обучающий корпус. Чтобы примеры были понятны всем, мы будем использовать не русский или китайский язык, а будем использовать специализированный английский: Python-код. -Библиотека [🤗 Datasets](https://github.com/huggingface/datasets) может помочь нам собрать корпус исходного кода Python. Мы будем использовать обычную функцию `load_dataset()` для загрузки и кэширования набора данных [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Этот набор данных был создан для [соревнования CodeSearchNet](https://wandb.ai/github/CodeSearchNet/benchmark) и содержит миллионы функций из библиотек с открытым исходным кодом на GitHub на нескольких языках программирования. Здесь мы загрузим часть Python этого набора данных: +Библиотека [🤗 Datasets](https://github.com/huggingface/datasets) может помочь нам собрать корпус исходного кода Python. Мы воспользуемся обычной функцией `load_dataset()` для загрузки и кэширования набора данных [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Этот набор данных был создан для конкурса [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) и содержит миллионы функций из библиотек с открытым исходным кодом с GitHub на нескольких языках программирования. Здесь мы загрузим Python-часть этого набора данных: ```py from datasets import load_dataset -# Это может занять некоторое время – заварите себе чаю! +# Загрузка может занять несколько минут, так что выпейте кофе или чай, пока ждете! raw_datasets = load_dataset("code_search_net", "python") ``` -Мы можем взглянуть на обучающий сплит данных, чтобы увидеть, к каким столбцам у нас есть доступ: +Мы можем взглянуть на тренировочную часть датасета, чтобы узнать, к каким столбцам у нас есть доступ: ```py raw_datasets["train"] @@ -45,13 +45,14 @@ Dataset({ num_rows: 412178 }) ``` -Мы видим, что набор данных отделяет строки документации от кода и предлагает токенизацию обоих. Здесь мы просто будем использовать столбец `whole_func_string` для обучения нашего токенизатора. Мы можем посмотреть на пример одной из этих функций, проиндексировав раздел `train`: + +Мы видим, что набор данных отделяет документацию от кода и предлагает токенизировать и то, и другое. Здесь мы просто используем колонку `whole_func_string` для обучения нашего токенизатора. Мы можем посмотреть пример одной из этих функций, обратившись к соответствующему индексу в части `train`: ```py print(raw_datasets["train"][123456]["whole_func_string"]) ``` -должно быть распечатано следующее: +который должен вывести следующее: ```out def handle_simple_responses( @@ -68,16 +69,16 @@ def handle_simple_responses( return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) ``` -Первое, что нам нужно сделать, это преобразовать набор данных в _итератор_ списков текстов, например, в список списков текстов. Использование списков текстов позволит нашему токенизатору работать быстрее (обучение на пакетах текстов вместо обработки отдельных текстов по одному), и он должен быть итерируемым объектом, если мы хотим избежать хранения всего набора данных в памяти. Если ваш корпус огромен, вы захотите воспользоваться тем фактом, что 🤗 Datasets не загружают все в оперативную память, а сохраняют элементы набора данных на диске. +Первое, что нам нужно сделать, это преобразовать набор данных в _итератор_ списков текстов -- например, список списков текстов. Использование списков текстов позволит нашему токенизатору работать быстрее (обучение на батчах текстов вместо обработки отдельных текстов по одному), и это должен быть итератор, если мы хотим избежать необходимости держать в памяти все сразу. Если ваш корпус огромен, вы захотите воспользоваться тем, что 🤗 Datasets не загружает все в RAM, а хранит элементы набора данных на диске. -Следующее действие создаст список списков по 1000 текстов в каждом, но загрузит все в память: +Следующее действие создаст список списков по 1 000 текстов в каждом, но загрузит все в память: ```py -# Если ваш датасет маленький – оставьте эту строку закомментированной! +# Не раскоментируйте следующую строку кода, если только ваш набор данных не маленький! # training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] ``` -Используя генератор Python, мы можем избежать загрузки Python чего-либо в память до тех пор, пока это действительно необходимо. Чтобы создать такой генератор, вам нужно всего лишь заменить квадратные скобки круглыми: +Используя генератор Python, мы можем не загружать ничего в память Python до тех пор, пока это действительно не понадобится. Чтобы создать такой генератор, нужно просто заменить скобки на круглые скобки: ```py training_corpus = ( @@ -86,9 +87,9 @@ training_corpus = ( ) ``` -Эта строка кода не извлекает никаких элементов набора данных; он просто создает объект, который вы можете использовать в цикле for Python. Тексты будут загружаться только тогда, когда они вам нужны (то есть, когда вы находитесь на этапе цикла `for`, который их требует), и за один раз будет загружено только 1000 текстов. Таким образом, вы не исчерпаете всю свою память, даже если обрабатываете огромный набор данных. +Эта строка кода не получает никаких элементов из набора данных; она просто создает объект, который можно использовать в цикле Python `for`. Тексты будут загружаться только тогда, когда они вам нужны (то есть когда вы находитесь на том шаге цикла `for`, где они требуются), и за один раз будет загружено только 1 000 текстов. Таким образом, вы не исчерпаете всю память, даже если обрабатываете огромный набор данных. -Проблема с объектом-генератором заключается в том, что его можно использовать только один раз. Итак, вместо того, чтобы дважды давать нам список первых 10 цифр: +Проблема с объектом-генератором заключается в том, что он может быть использован только один раз. Поэтому вместо того, чтобы выдать нам список первых 10 цифр дважды: ```py gen = (i for i in range(10)) @@ -96,14 +97,14 @@ print(list(gen)) print(list(gen)) ``` -мы получим их только один раз, дальше список станет пустым: +мы получаем его один раз, а затем пустой список: ```python out [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] ``` -Вот почему мы определяем функцию, которая вместо этого возвращает генератор: +Поэтому мы определяем функцию, которая возвращает генератор: ```py def get_training_corpus(): @@ -126,11 +127,11 @@ def get_training_corpus(): yield samples["whole_func_string"] ``` -который будет производить точно такой же генератор, как и раньше, но позволяет вам использовать более сложную логику, чем в обычном list comprehension. +который выдает точно такой же генератор, как и предыдущий, но позволяет использовать более сложную логику, чем при работе с list comprehension. -## Обучение нового токенизатора +## Обучение нового токенизатора[[training-a-new-tokenizer]] -Теперь, когда у нас есть корпус в виде итератора пакетов текстов, мы готовы обучить новый токенизатор. Для этого нам сначала нужно загрузить токенизатор, который мы хотим связать с нашей моделью (здесь, GPT-2): +Теперь, когда у нас есть корпус в виде итератора батчей текстов, мы готовы обучить новый токенизатор. Для этого нам сначала нужно загрузить токенизатор, который мы хотим использовать в паре с нашей моделью (здесь GPT-2): ```py from transformers import AutoTokenizer @@ -138,9 +139,9 @@ from transformers import AutoTokenizer old_tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -Несмотря на то, что мы собираемся обучить новый токенизатор, мы используем конкретный алгоритм (который был использован в GPT-2). Таким образом, нам не нужно будет указывать что-либо об алгоритме токенизации или специальных токенах, которые мы хотим использовать; наш новый токенизатор будет точно таким же, как GPT-2, и единственное, что изменится, — это словарный запас, который будет определен обучением на нашем корпусе. +Несмотря на то, что мы собираемся обучить новый токенизатор, это хорошая идея сделать это, не начиная все с нуля. Таким образом, нам не придется ничего уточнять об алгоритме токенизации или специальных токенах, которые мы хотим использовать; наш новый токенизатор будет точно таким же, как GPT-2, и единственное, что изменится, - это словарный запас, который будет определен в результате обучения на нашем корпусе. -Сначала давайте посмотрим, как этот токенизатор будет обрабатывать пример функции: +Сначала давайте посмотрим, как будет работать этот токенизатор с примером функции: ```py example = '''def add_numbers(a, b): @@ -156,21 +157,21 @@ tokens 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -Этот токенизатор имеет несколько специальных символов, таких как `Ġ` и `Ċ`, которые обозначают пробелы и символы новой строки соответственно. Как мы видим, это не слишком эффективно: токенизатор возвращает отдельные токены для каждого пробела, в то время как он мог бы сгруппировать уровни отступа (поскольку наборы из четырех или восьми пробелов будут очень распространены в коде). Он также немного странно разделял имя функции из-за используемого символа `_`. +Этот токенизатор имеет несколько специальных символов, таких как `Ġ` и `Ċ`, которые обозначают пробелы и новые строки, соответственно. Как мы видим, это не слишком эффективно: токенизатор возвращает отдельные токены для каждого пробела, в то время как он мог бы группировать уровни отступов (поскольку наборы из четырех или восьми пробелов будут очень часто встречаться в коде). Он также немного странно разделил имя функции, не ожидая увидеть слова с символом `_`. -Давайте обучим новый токенизатор и посмотрим, решит ли он эти проблемы. Для этого воспользуемся методом `train_new_from_iterator()`: +Давайте обучим новый токенизатор и посмотрим, решит ли он эти проблемы. Для этого мы воспользуемся методом `train_new_from_iterator()`: ```py tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) ``` -Этот процесс может занять некоторое время, если ваш корпус очень большой, но для этого набора данных из 1,6 ГБ текстов это невероятно быстро (1 минута 16 секунд на процессоре AMD Ryzen 9 3900X с 12 ядрами). +Выполнение этой команды может занять много времени, если ваш корпус очень большой, но для данного набора данных с 1,6 ГБ текстов она работает молниеносно (1 минута 16 секунд на процессоре AMD Ryzen 9 3900X с 12 ядрами). -Обратите внимание, что `AutoTokenizer.train_new_from_iterator()` работает только в том случае, если используемый вами токенизатор является «быстрым» токенизатором. Как вы увидите в следующем разделе, библиотека 🤗 Transformers содержит два типа токенизаторов: одни написаны исключительно на Python, а другие (более быстрые) поддерживаются библиотекой 🤗 Tokenizers, написанной на [Rust]( https://www.rust-lang.org). Python — это язык, который чаще всего используется для обработки данных и приложений глубокого обучения, но когда что-то нужно распараллелить, чтобы работать быстро, это приходится писать на другом языке. Например, умножение матриц, лежащее в основе вычисления модели, написано в CUDA, оптимизированной библиотеке C для графических процессоров. +Обратите внимание, что `AutoTokenizer.train_new_from_iterator()` работает только в том случае, если используемый вами токенизатор является "быстрым" токенизатором. Как вы увидите в следующем разделе, библиотека 🤗 Transformers содержит два типа токенизаторов: одни написаны исключительно на Python, а другие (быстрые) опираются на библиотеку 🤗 Tokenizers, которая написана на языке программирования [Rust](https://www.rust-lang.org). Python - это язык, который чаще всего используется для приложений data science и deep learning, но когда что-то нужно распараллелить для быстроты, это приходится писать на другом языке. Например, матричные умножения, которые лежат в основе вычислений модели, написаны на CUDA, оптимизированной библиотеке языка C для GPU. -Обучение нового токенизатора на чистом Python было бы мучительно медленным, поэтому мы разработали библиотеку 🤗 Tokenizers. Обратите внимание, что так же, как вам не нужно было изучать язык CUDA, чтобы иметь возможность выполнять свою модель на пакете входных данных на графическом процессоре, вам не нужно будет изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки Python для многих методов, которые внутренне вызывают некоторый фрагмент кода в Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](/course/ru/chapter3), токенизации пакета батча данных. +Обучение совершенно нового токенизатора на чистом Python было бы мучительно медленным, поэтому мы разработали библиотеку 🤗 Tokenizers. Обратите внимание, что так же как вам не нужно было изучать язык CUDA, чтобы выполнить свою модель на батче входных данных на GPU, вам не понадобится изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки к Python для многих методов, которые внутренне вызывают некоторые части кода на Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](../chapter3/1), токенизации батча входных данных. -Большинство моделей Transformer имеют быстрый токенизатор (есть некоторые исключения, которые вы можете проверить [здесь](https://huggingface.co/transformers/#supported-frameworks)), а API `AutoTokenizer` всегда выбирает быстрый токенизатор для вас, если он доступен. В следующем разделе мы рассмотрим некоторые другие специальные функции быстрых токенизаторов, которые будут действительно полезны для таких задач, как классификация токенов и ответы на вопросы. Однако, прежде чем углубляться в это, давайте попробуем наш новый токенизатор на предыдущем примере: +В большинстве моделей Transformer доступен быстрый токенизатор (есть некоторые исключения, о которых вы можете узнать [здесь](https://huggingface.co/transformers/#supported-frameworks)), а API `AutoTokenizer` всегда выбирает быстрый токенизатор, если он доступен. В следующем разделе мы рассмотрим некоторые другие особенности быстрых токенизаторов, которые будут очень полезны для таких задач, как классификация токенов и ответы на вопросы. Однако прежде чем погрузиться в эту тему, давайте попробуем наш новый токенизатор на предыдущем примере: ```py tokens = tokenizer.tokenize(example) @@ -182,8 +183,7 @@ tokens 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -Здесь мы снова видим специальные символы `Ġ` и `Ċ`, которые обозначают пробелы и символы новой строки, но мы также можем видеть, что наш токенизатор изучил некоторые токены, очень специфичные для корпуса функций Python: например, есть `ĊĠĠĠ ` токен, который представляет отступ, и токен `Ġ"""`, который представляет три кавычки, с которых начинается строка документации. Токенизатор также правильно разделяет имя функции по символу `_`. Это довольно компактное представление; для сравнения используем простой английский токенизатор на том же примере даст нам более длинное предложение: - +Здесь мы снова видим специальные символы `Ġ` и `Ċ`, обозначающие пробелы и новые строки, но мы также видим, что наш токенизатор выучил некоторые токены, которые очень специфичны для корпуса функций Python: например, есть токен `ĊĠĠĠ`, который обозначает отступ, и токен `Ġ"""`, который обозначает три кавычки, с которых начинается doc-строка. Токенизатор также правильно разделил имя функции на `_`. Это довольно компактное представление; для сравнения, использование токенизатора простого английского языка для того же примера даст нам более длинное предложение: ```py print(len(tokens)) @@ -195,7 +195,7 @@ print(len(old_tokenizer.tokenize(example))) 36 ``` -Давайте взглянем на еще один пример: +Давайте рассмотрим другой пример: ```python example = """class LinearLayer(): @@ -217,17 +217,17 @@ tokenizer.tokenize(example) 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] ``` -В дополнение к токену, соответствующему отступу, здесь мы также можем видеть токен для двойного отступа: `ĊĠĠĠĠĠĠĠ`. Специальные слова Python, такие как `class`, `init`, `call`, `self` и `return` токенизатор корректно разбивает имена даже в верблюжьем регистре: `LinearLayer` токенизируется как `["ĠLinear", "Layer"]`. +В дополнение к токену, соответствующему отступу, здесь мы также видим токен для двойного отступа: `ĊĠĠĠĠĠĠĠĠĠ`. Специальные слова Python, такие как `class`, `init`, `call`, `self` и `return`, обрабатываются как один токен, и мы видим, что наряду с разделением на `_` и `.` токенизатор правильно разделяет даже имена с camel-case: `LinearLayer` обрабатывается как `["ĠLinear", "Layer"]`. -## Сохранение токенизатора +## Сохранение токенизатора[[saving-the-tokenizer]] -Чтобы убедиться, что мы сможем использовать его позже, нам нужно сохранить наш новый токенизатор. Как и в случае с моделями, это делается с помощью метода `save_pretrained()`: +Чтобы убедиться, что мы сможем использовать его позже, нам нужно сохранить наш новый токенизатор. Как и для моделей, это делается с помощью метода `save_pretrained()`: ```py tokenizer.save_pretrained("code-search-net-tokenizer") ``` -Будет создана новая папка с именем *code-search-net-tokenizer*, которая будет содержать все файлы, которые необходимо использовать токенизатору. Если вы хотите поделиться этим токенизатором со своими коллегами и друзьями, вы можете загрузить его в Hub, войдя в свою учетную запись. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом: +В результате будет создана новая папка с именем *code-search-net-tokenizer*, в которой будут содержаться все файлы, необходимые токенизатору для загрузки. Если вы хотите поделиться этим токенизатором со своими коллегами и друзьями, вы можете загрузить его на Hub, войдя в свою учетную запись. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом: ```python from huggingface_hub import notebook_login @@ -235,23 +235,23 @@ from huggingface_hub import notebook_login notebook_login() ``` -Это отобразит виджет, где вы можете ввести свои учетные данные для входа в Hugging Face. Если вы не работаете в блокноте, просто введите в терминале следующую строку: +Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. Если вы работаете не в блокноте, просто введите следующую строку в терминале: ```bash huggingface-cli login ``` -После входа в систему вы можете активировать свой токенизатор, выполнив следующую команду: +После того как вы авторизовались, вы можете опубликовать свой токенизатор, выполнив следующую команду: ```py tokenizer.push_to_hub("code-search-net-tokenizer") ``` -Это создаст новый репозиторий в вашем пространстве имен с именем `code-search-net-tokenizer`, содержащий файл токенизатора. Затем вы можете загрузить токенизатор из любого места с помощью метода `from_pretrained()`: +Это создаст новое хранилище в вашем пространстве имен с именем `code-search-net-tokenizer`, содержащее файл токенизатора. Затем вы можете загрузить токенизатор где угодно с помощью метода `from_pretrained()`: ```py -# Измените "huggingface-course" на ваше название пространства +# Замените "huggingface-course" ниже своим реальным пространством имен, чтобы использовать свой собственный токенизатор tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -Теперь у вас все готово для обучения языковой модели с нуля и ее точной настройки для вашей задачи! Мы вернемся к этому в [Главе 7](/course/ru/chapter7), но сначала в оставшейся части этой главы мы более подробно рассмотрим быстрые токенизаторы и подробно рассмотрим, что на самом деле происходит, когда мы вызываем метод ` train_new_from_iterator()`. +Теперь вы готовы обучить языковую модель с нуля и дообучить ее в соответствии с поставленной задачей! Мы займемся этим в [Главе 7](../chapter7), но сначала в этой главе мы рассмотрим быстрые токенизаторы и подробно изучим, что происходит при вызове метода `train_new_from_iterator()`. diff --git a/chapters/ru/chapter6/3.mdx b/chapters/ru/chapter6/3.mdx new file mode 100644 index 000000000..214e2ba13 --- /dev/null +++ b/chapters/ru/chapter6/3.mdx @@ -0,0 +1,474 @@ + + +# Особые возможности быстрых токенизаторов[[fast-tokenizers-special-powers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +В этом разделе мы подробно рассмотрим возможности токенизаторов в 🤗 Transformers. До сих пор мы использовали их только для токенизации входных данных или декодирования идентификаторов обратно в текст, но токенизаторы -- особенно те, которые поддерживаются библиотекой 🤗 Tokenizers - могут делать гораздо больше. Чтобы проиллюстрировать эти дополнительные возможности, мы рассмотрим, как воспроизвести результаты конвейеров `token-classification` (которые мы назвали `ner`) и `question-answering`, с которыми мы впервые столкнулись в [Главе 1](../chapter1/1). + + + +В дальнейшем обсуждении мы будем часто проводить различие между "медленными" и "быстрыми" токенизаторами. Медленные токенизаторы - это те, что написаны на Python в библиотеке 🤗 Transformers, а быстрые версии - это те, что предоставляются в 🤗 Tokenizers, которые написаны на Rust. Если вы помните таблицу из [Главы 5](../chapter5/3), в которой приводилось, сколько времени потребовалось быстрому и медленному токенизаторам для токенизации датасета Drug Review Dataset, вы должны иметь представление о том, почему мы называем их быстрыми и медленными: + +| | Быстрый токенизатор | Медленный токенизатор +:--------------:|:----------------------:|:----------------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Когда вы токенизируете одно предложение, вы не всегда увидите разницу в скорости между медленной и быстрой версиями одного и того же токенизатора. Более того, быстрая версия может быть даже медленнее! Только при параллельной токенизации большого количества текстов вы сможете увидеть разницу. + + + +## Batch encoding[[batch-encoding]] + + + +Результат работы токенизатора - это не простой словарь Python; то, что мы получаем, - это специальный объект `BatchEncoding`. Это подкласс словаря (именно поэтому мы раньше могли без проблем индексировать результат), но с дополнительными методами, которые в основном используются быстрыми токенизаторами. + +Помимо возможностей распараллеливания, ключевой функцией быстрых токенизаторов является то, что они всегда отслеживают исходный диапазон текстов, из которых взяты конечные токены, - эту функцию мы называем *сопоставление смещений (offset mapping)*. Это, в свою очередь, открывает такие возможности, как сопоставление каждого слова с порожденными им токенами или сопоставление каждого символа исходного текста с токеном, в котором он находится, и наоборот. + +Давайте посмотрим на пример: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +encoding = tokenizer(example) +print(type(encoding)) +``` + +Как уже говорилось, на выходе токенизатора мы получаем объект `BatchEncoding`: + +```python out + +``` + +Поскольку класс `AutoTokenizer` по умолчанию выбирает быстрый токенизатор, мы можем использовать дополнительные методы, которые предоставляет объект `BatchEncoding`. У нас есть два способа проверить, является ли наш токенизатор быстрым или медленным. Мы можем проверить атрибут `is_fast` у `tokenizer`: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +или проверьте тот же атрибут нашего `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Давайте посмотрим, что позволяет нам сделать быстрый токенизатор. Во-первых, мы можем получить доступ к токенам без необходимости преобразовывать идентификаторы обратно в токены: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +В данном случае токен с индексом 5 - это `##yl`, который является частью слова "Sylvain" в исходном предложении. Мы также можем использовать метод `word_ids()`, чтобы получить индекс слова, из которого происходит каждый токен: + +```py +encoding.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] +``` + + +Мы можем видеть, что специальные токены токенизатора `[CLS]` и `[SEP]` сопоставляются с `None`, а затем каждый токен сопоставляется со словом, от которого он происходит. Это особенно полезно для определения того, находится ли токен в начале слова или два токена в одном и том же слове. Для этого мы могли бы использовать префикс `##`, но он работает только для токенизаторов типа BERT; этот метод работает для любого типа токенизаторов, лишь бы он был быстрым. В следующей главе мы увидим, как можно использовать эту возможность для применения меток, которые мы имеем для каждого слова, к токенам в таких задачах, как распознавание именованных сущностей (NER) и тегирование частей речи (part-of-speech - POS). Мы также можем использовать ее для маскирования всех токенов, происходящих от одного и того же слова, при моделировании языка по маске (masked language modeling) (эта техника называется _маскированием всего слова (whole word masking)_). + + + +Понятие "слово" очень сложное. Например, "I'll" (сокращение от "I will") считается одним или двумя словами? На самом деле это зависит от токенизатора и применяемой им операции предварительной токенизации. Некоторые токенизаторы просто разделяют пробелы, поэтому они будут считать это одним словом. Другие используют пунктуацию поверх пробелов, поэтому будут считать это двумя словами. + +✏️ **Попробуйте!** Создайте токенизатор из контрольных точек `bert-base-cased` и `roberta-base` и токенизируйте с их помощью "81s". Что вы заметили? Каковы идентификаторы слов? + + + +Аналогично, существует метод `sentence_ids()`, который мы можем использовать для сопоставления токена с предложением, из которого оно взято (хотя в этом случае ту же информацию может дать и `token_type_ids`, возвращаемый токенизатором). + +Наконец, с помощью методов `word_to_chars()` или `token_to_chars()` и `char_to_word()` или `char_to_token()` мы можем сопоставить любое слово или токен с символами в оригинальном тексте и наоборот. Например, метод `word_ids()` сообщил нам, что `##yl` является частью слова с индексом 3, но какое это слово в предложении? Мы можем выяснить это следующим образом: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Как мы уже говорили, все это происходит благодаря тому, что быстрый токенизатор отслеживает, из какого участка текста происходит каждый токен, в списке *смещений (offsets)*. Чтобы проиллюстрировать их использование, далее мы покажем, как воспроизвести результаты конвейера `token-classification` вручную. + + + +✏️ **Попробуйте!** Создайте свой собственный пример текста и посмотрите, сможете ли вы понять, какие токены связаны с идентификаторами слов, а также как извлечь диапазоны символов для одного слова. Чтобы получить бонусные очки, попробуйте использовать два предложения в качестве входных данных и посмотрите, будут ли идентификаторы предложений иметь для вас смысл. + + + +## Внутри конвейера `token-classification`[[inside-the-token-classification-pipeline]] + +В [Главе 1](../chapter1/1) мы впервые попробовали применить NER - когда задача состоит в том, чтобы определить, какие части текста соответствуют сущностям, таким как люди, места или организации - с помощью функции 🤗 Transformers `pipeline()`. Затем, в [Главе 2](../chapter2/1), мы увидели, как конвейер объединяет три этапа, необходимые для получения прогнозов из необработанного текста: токенизацию, прохождение входных данных через модель и постобработку. Первые два шага в конвейере `token-classification` такие же, как и в любом другом конвейере, но постобработка немного сложнее - давайте посмотрим, как это сделать! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Получение базовых результатов с помощью конвейера[[getting-the-base-results-with-the-pipeline]] + +Для начала возьмем конвейер token classification, чтобы получить результаты для сравнения вручную. По умолчанию используется модель [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); она выполняет NER на предложениях: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Модель правильно идентифицировала каждый токен, сгенерировав "Sylvain", как человека, каждый токен, сгенерированный "Hugging Face", как организацию, а токен "Brooklyn" - как местоположение. Мы также можем попросить конвейер сгруппировать токены, которые соответствуют одной и той же сущности: + +```py +from transformers import pipeline + +token_classifier = pipeline("token-classification", aggregation_strategy="simple") +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Выбранная `aggregation_strategy` изменит оценки, вычисляемые для каждой сгруппированной сущности. При использовании значения `"simple"` оценка является средним значением оценок каждого токена данной сущности: например, оценка "Sylvain" является средним значением оценок, которые мы видели в предыдущем примере для токенов `S`, `##yl`, `##va` и `##in`. Другие доступные стратегии: + +- `"first"`, где оценка каждой сущности - это оценка первого токена этой сущности (так, для "Sylvain" это будет 0,993828, оценки токена `S`) +- `"max"`, где оценка каждой сущности - это максимальная оценка токенов в этой сущности (так, для ""Hugging Face"" это будет 0.98879766, оценки "Face"). +- `"average"`, где оценка каждой сущности - это средняя оценка слов, составляющих эту сущность (таким образом, для слова ""Sylvain"" не будет никаких отличий от стратегии `"simple"`, но "Hugging Face" будет иметь оценку 0.9819, среднюю оценку для "Hugging", 0.975, и "Face", 0.98879) + +Теперь давайте посмотрим, как получить эти результаты без использования функции `pipeline()`! + +### От входных данных к прогнозам[[from-inputs-to-predictions]] + +{#if fw === 'pt'} + +Сначала нам нужно токенизировать наш ввод и пропустить его через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `AutoXxx`, а затем используем их в нашем примере: + +```py +from transformers import AutoTokenizer, AutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="pt") +outputs = model(**inputs) +``` + +Поскольку мы используем `AutoModelForTokenClassification`, мы получаем один набор логитов для каждого токена во входной последовательности: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +Сначала нам нужно токенизировать наши входные данные и пропустить их через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `TFAutoXxx`, а затем используем их в нашем примере: + +```py +from transformers import AutoTokenizer, TFAutoModelForTokenClassification + +model_checkpoint = "dbmdz/bert-large-cased-finetuned-conll03-english" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForTokenClassification.from_pretrained(model_checkpoint) + +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +inputs = tokenizer(example, return_tensors="tf") +outputs = model(**inputs) +``` + +Поскольку мы используем `TFAutoModelForTokenClassification`, мы получаем один набор логитов для каждого токена во входной последовательности: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +У нас есть батч с 1 последовательностью из 19 токенов, и модель имеет 9 различных меток, поэтому выход модели имеет форму 1 x 19 x 9. Как и для конвейера классификации текста, мы используем функцию softmax для преобразования этих логитов в вероятности и берем argmax для получения прогнозов (обратите внимание, что мы можем взять argmax для логитов, потому что softmax не меняет порядок): + +{#if fw === 'pt'} + +```py +import torch + +probabilities = torch.nn.functional.softmax(outputs.logits, dim=-1)[0].tolist() +predictions = outputs.logits.argmax(dim=-1)[0].tolist() +print(predictions) +``` + +{:else} + +```py +import tensorflow as tf + +probabilities = tf.math.softmax(outputs.logits, axis=-1)[0] +probabilities = probabilities.numpy().tolist() +predictions = tf.math.argmax(outputs.logits, axis=-1)[0] +predictions = predictions.numpy().tolist() +print(predictions) +``` + +{/if} + +```python out +[0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] +``` + +Атрибут `model.config.id2label` содержит отображение индексов в метки, которые мы можем использовать для осмысления прогнозов: + +```py +model.config.id2label +``` + +```python out +{0: 'O', + 1: 'B-MISC', + 2: 'I-MISC', + 3: 'B-PER', + 4: 'I-PER', + 5: 'B-ORG', + 6: 'I-ORG', + 7: 'B-LOC', + 8: 'I-LOC'} +``` + +Как мы видели ранее, существует 9 меток: `O` - это метка для токенов, которые не входят ни в одну именованную сущность (она означает "вне"), а затем у нас есть две метки для каждого типа сущности (miscellaneous, person, organization и location). Метка `B-XXX` указывает на то, что токен находится в начале сущности `XXX`, а метка `I-XXX` указывает на то, что токен находится внутри сущности `XXX`. Таким образом, в данном примере мы ожидаем, что наша модель классифицирует токен `S` как `B-PER` (начало сущности person), а токены `##yl`, `##va` и `##in` как `I-PER` (внутри сущности person). + +Вы можете подумать, что модель в данном случае ошиблась, поскольку присвоила всем четырем токенам метку `I-PER`, но это не совсем так. На самом деле существует два формата для меток `B-` и `I-`: *IOB1* и *IOB2*. Формат IOB2 (розовый цвет ниже) - это тот, который мы представили, в то время как в формате IOB1 (синий цвет) метки, начинающиеся с `B-`, используются только для разделения двух соседних сущностей одного типа. Используемая нами модель была дообучена на наборе данных, использующем этот формат, поэтому она присваивает токену `S` метку `I-PER`. + +
+IOB1 vs IOB2 format + +
+ +С помощью этой карты мы можем воспроизвести (почти полностью) результаты первого конвейера - мы можем просто получить оценку и метку каждого токена, который не был классифицирован как `O`: + +```py +results = [] +tokens = inputs.tokens() + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + results.append( + {"entity": label, "score": probabilities[idx][pred], "word": tokens[idx]} + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S'}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl'}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va'}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in'}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu'}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging'}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face'}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] +``` + +Это очень похоже на то, что у нас было раньше, за одним исключением: конвейер также предоставил нам информацию о `start` и `end` каждой сущности в исходном предложении. Вот тут-то и пригодится наше сопоставление смещений. Чтобы получить смещения, нам нужно просто установить `return_offsets_mapping=True`, когда мы применяем токенизатор к нашим входным данным: + +```py +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +inputs_with_offsets["offset_mapping"] +``` + +```python out +[(0, 0), (0, 2), (3, 7), (8, 10), (11, 12), (12, 14), (14, 16), (16, 18), (19, 22), (23, 24), (25, 29), (30, 32), + (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] +``` + +Каждый кортеж - это участок текста, соответствующий каждому токену, где `(0, 0)` зарезервировано для специальных токенов. Мы уже видели, что токен с индексом 5 - это `##yl`, который имеет `(12, 14)` в качестве смещения. Если мы возьмем соответствующий фрагмент в нашем примере: + + +```py +example[12:14] +``` + +мы получим нужный участок текста без использования `##`: + +```python out +yl +``` + +Используя это, мы можем дополнить предыдущие результаты: + +```py +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +for idx, pred in enumerate(predictions): + label = model.config.id2label[pred] + if label != "O": + start, end = offsets[idx] + results.append( + { + "entity": label, + "score": probabilities[idx][pred], + "word": tokens[idx], + "start": start, + "end": end, + } + ) + +print(results) +``` + +```python out +[{'entity': 'I-PER', 'score': 0.9993828, 'index': 4, 'word': 'S', 'start': 11, 'end': 12}, + {'entity': 'I-PER', 'score': 0.99815476, 'index': 5, 'word': '##yl', 'start': 12, 'end': 14}, + {'entity': 'I-PER', 'score': 0.99590725, 'index': 6, 'word': '##va', 'start': 14, 'end': 16}, + {'entity': 'I-PER', 'score': 0.9992327, 'index': 7, 'word': '##in', 'start': 16, 'end': 18}, + {'entity': 'I-ORG', 'score': 0.97389334, 'index': 12, 'word': 'Hu', 'start': 33, 'end': 35}, + {'entity': 'I-ORG', 'score': 0.976115, 'index': 13, 'word': '##gging', 'start': 35, 'end': 40}, + {'entity': 'I-ORG', 'score': 0.98879766, 'index': 14, 'word': 'Face', 'start': 41, 'end': 45}, + {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Это то же самое, что мы получили от первого конвейера! + +### Группировка сущностей[[grouping-entities]] + +Использование смещений для определения начального и конечного ключей для каждой сущности удобно, но эта информация не является строго необходимой. Однако когда мы захотим сгруппировать сущности вместе, смещения избавят нас от большого количества беспорядочного кода. Например, если бы мы хотели сгруппировать токены `Hu`, `##gging` и `Face`, мы могли бы создать специальные правила, согласно которым первые два должны быть присоединены, удалив `##`, а `Face` должен быть добавлен через пробел, поскольку он не начинается с `##` - но это будет работать только для данного конкретного типа токенизатора. Для токенизатора SentencePiece или Byte-Pair-Encoding нам придется написать другой набор правил (о них мы поговорим позже в этой главе). + +С помощью смещений весь этот пользовательский код отпадает: мы просто можем взять в исходном тексте промежуток, который начинается с первого токена и заканчивается последним. Так, в случае с токенами `Hu`, `##gging` и `Face` мы должны начать с символа 33 (начало `Hu`) и закончить символом 45 (конец `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Чтобы написать код для постобработки прогнозов при группировке сущностей, мы будем группировать сущности, которые идут подряд и помечены `I-XXX`, за исключением первой, которая может быть помечена как `B-XXX` или `I-XXX` (таким образом, мы прекращаем группировать сущность, когда получаем `O`, новый тип сущности, или `B-XXX`, который говорит нам, что начинается сущность того же типа): + +```py +import numpy as np + +results = [] +inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) +tokens = inputs_with_offsets.tokens() +offsets = inputs_with_offsets["offset_mapping"] + +idx = 0 +while idx < len(predictions): + pred = predictions[idx] + label = model.config.id2label[pred] + if label != "O": + # Удалим B- или I- + label = label[2:] + start, _ = offsets[idx] + + # Соберём все токены, помеченные I-меткой + all_scores = [] + while ( + idx < len(predictions) + and model.config.id2label[predictions[idx]] == f"I-{label}" + ): + all_scores.append(probabilities[idx][pred]) + _, end = offsets[idx] + idx += 1 + + # Оценка является средним значением всех оценок токенов в этой сгруппированной сущности + score = np.mean(all_scores).item() + word = example[start:end] + results.append( + { + "entity_group": label, + "score": score, + "word": word, + "start": start, + "end": end, + } + ) + idx += 1 + +print(results) +``` + +И мы получаем те же результаты, что и со вторым конвейером! + +```python out +[{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960204, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Еще один пример задачи, в которой эти смещения чрезвычайно полезны, - question answering. Погружение в этот конвейер, которое мы сделаем в следующем разделе, также позволит нам взглянуть на последнюю особенность токенизаторов в библиотеке 🤗 Transformers: работа с переполненными токенами (overflowing tokens), когда мы усекаем входные данные до заданной длины. diff --git a/chapters/ru/chapter6/3b.mdx b/chapters/ru/chapter6/3b.mdx new file mode 100644 index 000000000..6ad4b6ed0 --- /dev/null +++ b/chapters/ru/chapter6/3b.mdx @@ -0,0 +1,642 @@ + + +# Быстрые токенизаторы в QA конвейере[[fast-tokenizers-in-the-qa-pipeline]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Теперь мы погрузимся в конвейер `question-answering` и посмотрим, как использовать смещения (offsets) для получения ответа на вопрос из контекста, подобно тому, как мы делали это для сгруппированных сущностей в предыдущем разделе. Затем мы посмотрим, как работать с очень длинными контекстами, которые в итоге будут обрезаны. Вы можете пропустить этот раздел, если вас не интересует задача ответа на вопрос. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Использование конвейера `question-answering`[[using-the-question-answering-pipeline]] + +Как мы видели в [Главе 1](../chapter1/1), для получения ответа на вопрос мы можем использовать конвейер `question-answering` следующим образом: + +```py +from transformers import pipeline + +question_answerer = pipeline("question-answering") +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch, and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question = "Which deep learning libraries back 🤗 Transformers?" +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +В отличие от других конвейеров, которые не могут обрезать и разбивать на части тексты, длина которых превышает максимально допустимую моделью (и поэтому могут пропустить информацию в конце документа), этот конвейер может работать с очень длинными контекстами и вернет ответ на вопрос, даже если он находится в конце: + +```py +long_context = """ +🤗 Transformers: State of the Art NLP + +🤗 Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +🤗 Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internals are exposed as consistently as possible. + - Model files can be used independently of the library for quick experiments. + +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Давайте посмотрим, как он справится со всем этим! + +## Использование модели для ответа на вопросы[[using-a-model-for-question-answering]] + +Как и в любом другом конвейере, мы начинаем с токенизации входных данных, а затем отправляем их через модель. По умолчанию для конвейера `question-answering` используется контрольная точка [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (слово "squad" в названии происходит от набора данных, на котором дообучили модель; подробнее о наборе данных SQuAD мы поговорим в [главе 7](../chapter7/7)): + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="pt") +outputs = model(**inputs) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModelForQuestionAnswering + +model_checkpoint = "distilbert-base-cased-distilled-squad" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) + +inputs = tokenizer(question, context, return_tensors="tf") +outputs = model(**inputs) +``` + +{/if} + +Обратите внимание, что мы токенизируем вопрос и контекст как пару, причем вопрос стоит первым. + +
+An example of tokenization of question and context + +
+ +Модели для ответов на вопросы работают немного иначе, чем модели, которые мы рассматривали до сих пор. На примере картинки выше модель была обучена предсказывать индекс токена, с которого начинается ответ (здесь 21), и индекс токена, на котором ответ заканчивается (здесь 24). Вот почему эти модели возвращают не один тензор логитов, а два: один для логитов, соответствующих начальному токену ответа, и один для логитов, соответствующих конечному токену ответа. Поскольку в данном случае у нас только один вход, содержащий 66 токенов, мы получаем: + +```py +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([1, 66]) torch.Size([1, 66]) +``` + +{:else} + +```python out +(1, 66) (1, 66) +``` + +{/if} + +Чтобы преобразовать эти логиты в вероятности, мы применим функцию softmax, но перед этим нам нужно убедиться, что мы маскируем индексы, которые не являются частью контекста. Наш вход - `[CLS] вопрос [SEP] контекст [SEP]`, поэтому нам нужно замаскировать токены вопроса, а также токен `[SEP]`. Однако мы оставим токен `[CLS]`, поскольку некоторые модели используют его для указания на то, что ответ не находится в контексте. + +Поскольку впоследствии мы будем применять softmax, нам просто нужно заменить логиты, которые мы хотим замаскировать, на большое отрицательное число. Здесь мы используем `-10000`: + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Маскируем все, кроме токенов контекста +mask = [i != 1 for i in sequence_ids] +# Демаскируем токен [CLS] +mask[0] = False +mask = torch.tensor(mask)[None] + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +import tensorflow as tf + +sequence_ids = inputs.sequence_ids() +# Маскируем все, кроме токенов контекста +mask = [i != 1 for i in sequence_ids] +# Демаскируем токен [CLS] +mask[0] = False +mask = tf.constant(mask)[None] + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Теперь, когда мы правильно замаскировали логиты, соответствующие позициям, которые мы не хотим предсказывать, мы можем применить softmax: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1)[0] +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1)[0] +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1)[0].numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() +``` + +{/if} + +На этом этапе мы могли бы взять argmax вероятностей начала и конца - но в итоге мы можем получить начальный индекс, который больше конечного, поэтому нам нужно принять еще несколько мер предосторожности. Мы вычислим вероятности каждого возможного `start_index` и `end_index`, где `start_index <= end_index`, а затем возьмем кортеж `(start_index, end_index)` с наибольшей вероятностью. + +Если предположить, что события "Ответ начинается на `start_index`" и "Ответ заканчивается на `end_index`" независимы, то вероятность того, что ответ начинается на `start_index` и заканчивается на `end_index`, равна: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Таким образом, чтобы вычислить все оценки, нам нужно просто вычислить все произведения \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) где `start_index <= end_index`. + +Сначала вычислим все возможные произведения: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Затем мы замаскируем значения, где `start_index > end_index`, установив для них значение `0` (все остальные вероятности - положительные числа). Функция `torch.triu()` возвращает верхнюю треугольную часть двумерного тензора, переданного в качестве аргумента, поэтому она сделает эту маскировку за нас: + +```py +scores = torch.triu(scores) +``` + +{:else} + +Затем мы замаскируем значения, где `start_index > end_index`, установив для них значение `0` (все остальные вероятности - положительные числа). Функция `np.triu()` возвращает верхнюю треугольную часть двумерного тензора, переданного в качестве аргумента, поэтому она сделает эту маскировку за нас: + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Теперь нам осталось получить индекс максимума. Поскольку PyTorch вернет индекс в плоском тензоре, для получения `start_index` и `end_index` нам нужно воспользоваться операциями получения целой части `//` и остатка `%` от деления: + +```py +max_index = scores.argmax().item() +start_index = max_index // scores.shape[1] +end_index = max_index % scores.shape[1] +print(scores[start_index, end_index]) +``` + +Мы еще не закончили, но, по крайней мере, у нас уже есть корректная оценка ответа (вы можете проверить это, сравнив ее с первым результатом в предыдущем разделе): + +```python out +0.97773 +``` + + + +✏️ **Попробуйте!** Вычислите начальный и конечный индексы для пяти наиболее вероятных ответов. + + + +У нас есть `start_index` и `end_index` ответа в терминах токенов, так что теперь нам просто нужно преобразовать их в индексы символов в контексте. Именно здесь смещения будут очень полезны. Мы можем захватить их и использовать, как мы это делали в задаче token classification: + +```py +inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) +offsets = inputs_with_offsets["offset_mapping"] + +start_char, _ = offsets[start_index] +_, end_char = offsets[end_index] +answer = context[start_char:end_char] +``` + +Осталось только отформатировать все, чтобы получить результат: + +```py +result = { + "answer": answer, + "start": start_char, + "end": end_char, + "score": scores[start_index, end_index], +} +print(result) +``` + +```python out +{'answer': 'Jax, PyTorch and TensorFlow', + 'start': 78, + 'end': 105, + 'score': 0.97773} +``` + +Отлично! Это то же самое, что и в нашем первом примере! + + + +✏️ **Попробуйте! ** Используйте лучшие оценки, которые вы вычислили ранее, чтобы показать пять наиболее вероятных ответов. Чтобы проверить результаты, вернитесь к первому конвейеру и передайте `top_k=5` при его вызове. + + + +## Обработка длинных контекстов[[handling-long-contexts]] + +Если мы попытаемся токенизировать вопрос и длинный контекст, который мы использовали в качестве примера ранее, мы получим количество токенов, превышающее максимальную длину, используемую в конвейере `question-answering` (которая составляет 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Поэтому нам нужно обрезать входные данные до максимальной длины. Есть несколько способов сделать это, но мы не хотим усекать вопрос, а только контекст. Поскольку контекст - это второе предложение, мы используем стратегию усечения `" only_second"`. Проблема, которая возникает в этом случае, заключается в том, что ответ на вопрос может не находиться в усеченном контексте. Например, здесь мы выбрали вопрос, ответ на который находится в конце контекста, а когда мы его усекаем, то этого ответа в нём нет: + +```py +inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") +print(tokenizer.decode(inputs["input_ids"])) +``` + +```python out +""" +[CLS] Which deep learning libraries back [UNK] Transformers? [SEP] [UNK] Transformers : State of the Art NLP + +[UNK] Transformers provides thousands of pretrained models to perform tasks on texts such as classification, information extraction, +question answering, summarization, translation, text generation and more in over 100 languages. +Its aim is to make cutting-edge NLP easier to use for everyone. + +[UNK] Transformers provides APIs to quickly download and use those pretrained models on a given text, fine-tune them on your own datasets and +then share them with the community on our model hub. At the same time, each python module defining an architecture is fully standalone and +can be modified to enable quick research experiments. + +Why should I use transformers? + +1. Easy-to-use state-of-the-art models: + - High performance on NLU and NLG tasks. + - Low barrier to entry for educators and practitioners. + - Few user-facing abstractions with just three classes to learn. + - A unified API for using all our pretrained models. + - Lower compute costs, smaller carbon footprint: + +2. Researchers can share trained models instead of always retraining. + - Practitioners can reduce compute time and production costs. + - Dozens of architectures with over 10,000 pretrained models, some in more than 100 languages. + +3. Choose the right framework for every part of a model's lifetime: + - Train state-of-the-art models in 3 lines of code. + - Move a single model between TF2.0/PyTorch frameworks at will. + - Seamlessly pick the right framework for training, evaluation and production. + +4. Easily customize a model or an example to your needs: + - We provide examples for each architecture to reproduce the results published by its original authors. + - Model internal [SEP] +""" +``` + +Это означает, что модели будет сложно выбрать правильный ответ. Чтобы исправить это, конвейер `question-answering` позволяет нам разбить контекст на более мелкие фрагменты, указав максимальную длину. Чтобы убедиться, что мы не разбиваем контекст на фрагменты именно в том месте, которое не позволяет найти ответ, он также включает некоторое перекрытие между фрагментами. + +Мы можем заставить токенизатор (быстрый или медленный) сделать это за нас, добавив `return_overflowing_tokens=True`, и указать желаемое перекрытие с помощью аргумента `stride`. Вот пример, использующий небольшое предложение: + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +inputs = tokenizer( + sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] This sentence is not [SEP]' +'[CLS] is not too long [SEP]' +'[CLS] too long but we [SEP]' +'[CLS] but we are going [SEP]' +'[CLS] are going to split [SEP]' +'[CLS] to split it anyway [SEP]' +'[CLS] it anyway. [SEP]' +``` + +Как мы видим, предложение было разбито на части таким образом, что каждая запись в `inputs["input_ids"]` содержит не более 6 токенов (чтобы последняя запись была такого же размера, как и остальные, нам придется добавить дополняющие токены (padding tokens)), и между каждой частью есть перекрытие в 2 токена. + +Давайте посмотрим на результат токенизации: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Как и ожидалось, мы получаем идентификаторы входов и маску внимания. Последний ключ, `overflow_to_sample_mapping`, представляет собой карту, которая говорит нам, какому предложению соответствует каждый из результатов - здесь у нас есть 7 результатов, которые все происходят из (единственного) предложения, которое мы передали токенизатору: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +Это более полезно, когда мы токенизируем несколько предложений вместе. Например, так: + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + "This sentence is shorter but will still get split.", +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +gets us: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +что означает, что первое предложение разбито на 7 частей, как и раньше, а следующие 4 части взяты из второго предложения. + +Теперь давайте вернемся к нашему длинному контексту. По умолчанию конвейер `question-answering` использует максимальную длину 384, как мы уже упоминали ранее, и stride 128, что соответствует тому, как была дообучена модель (вы можете настроить эти параметры, передав аргументы `max_seq_len` и `stride` при вызове конвейера). Таким образом, мы будем использовать эти параметры при токенизации. Мы также добавим дополняющие токены (padding tokens) (чтобы иметь образцы одинаковой длины, чтобы можно было строить тензоры), а также запросим смещения: + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Эти `inputs` будут содержать идентификаторы входов и маски внимания, которые ожидает модель, а также смещения и `overflow_to_sample_mapping`, о которых мы только что говорили. Поскольку эти два параметра не используются моделью, мы выкинем их из `inputs` (и не будем хранить карту, поскольку она здесь не нужна) перед преобразованием в тензор: + +{#if fw === 'pt'} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("pt") +print(inputs["input_ids"].shape) +``` + +```python out +torch.Size([2, 384]) +``` + +{:else} + +```py +_ = inputs.pop("overflow_to_sample_mapping") +offsets = inputs.pop("offset_mapping") + +inputs = inputs.convert_to_tensors("tf") +print(inputs["input_ids"].shape) +``` + +```python out +(2, 384) +``` + +{/if} + +Наш длинный контекст был разделен на две части, а это значит, что после того, как он пройдет через нашу модель, у нас будет два набора начальных и конечных логитов: + +```py +outputs = model(**inputs) + +start_logits = outputs.start_logits +end_logits = outputs.end_logits +print(start_logits.shape, end_logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 384]) torch.Size([2, 384]) +``` + +{:else} + +```python out +(2, 384) (2, 384) +``` + +{/if} + +Как и раньше, мы сначала маскируем токены, которые не являются частью контекста, прежде чем использовать softmax. Мы также маскируем все дополняющие токены (padding tokens) (отмеченные маской внимания): + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Маскируем все, кроме токенов контекста +mask = [i != 1 for i in sequence_ids] +# Демаскируем токен [CLS]. +mask[0] = False +# Маскируем все [PAD] токены +mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) + +start_logits[mask] = -10000 +end_logits[mask] = -10000 +``` + +{:else} + +```py +sequence_ids = inputs.sequence_ids() +# Маскируем все, кроме токенов контекста +mask = [i != 1 for i in sequence_ids] +# Демаскируем токен [CLS]. +mask[0] = False +# Маскируем все [PAD] токены +mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) + +start_logits = tf.where(mask, -10000, start_logits) +end_logits = tf.where(mask, -10000, end_logits) +``` + +{/if} + +Затем мы можем использовать softmax для преобразования логитов в вероятности: + +{#if fw === 'pt'} + +```py +start_probabilities = torch.nn.functional.softmax(start_logits, dim=-1) +end_probabilities = torch.nn.functional.softmax(end_logits, dim=-1) +``` + +{:else} + +```py +start_probabilities = tf.math.softmax(start_logits, axis=-1).numpy() +end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() +``` + +{/if} + +Следующий шаг аналогичен тому, что мы делали для малого контекста, но мы повторяем его для каждого из наших двух фрагментов. Мы присваиваем оценку всем возможным фрагментам ответа, а затем выбираем фрагмент с наилучшей оценкой: + +{#if fw === 'pt'} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = torch.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{:else} + +```py +candidates = [] +for start_probs, end_probs in zip(start_probabilities, end_probabilities): + scores = start_probs[:, None] * end_probs[None, :] + idx = np.triu(scores).argmax().item() + + start_idx = idx // scores.shape[1] + end_idx = idx % scores.shape[1] + score = scores[start_idx, end_idx].item() + candidates.append((start_idx, end_idx, score)) + +print(candidates) +``` + +{/if} + +```python out +[(0, 18, 0.33867), (173, 184, 0.97149)] +``` + +Эти два кандидата соответствуют лучшим ответам, которые модель смогла найти в каждом фрагменте. Модель гораздо больше уверена в том, что правильный ответ находится во второй части (это хороший знак!). Теперь нам нужно сопоставить эти два диапазона токенов с диапазонами символов в контексте (для получения ответа нам нужно сопоставить только второй, но интересно посмотреть, что модель выбрала в первом фрагменте). + + + +✏️ **Попробуйте!** Адаптируйте приведенный выше код, чтобы он возвращал оценки и промежутки для пяти наиболее вероятных ответов (в целом, а не по частям). + + + +`offsets`, которую мы взяли ранее, на самом деле является списком смещений, по одному списку на каждый фрагмент текста: + +```py +for candidate, offset in zip(candidates, offsets): + start_token, end_token, score = candidate + start_char, _ = offset[start_token] + _, end_char = offset[end_token] + answer = long_context[start_char:end_char] + result = {"answer": answer, "start": start_char, "end": end_char, "score": score} + print(result) +``` + +```python out +{'answer': '\n🤗 Transformers: State of the Art NLP', 'start': 0, 'end': 37, 'score': 0.33867} +{'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} +``` + +Если мы проигнорируем первый результат, то получим тот же результат, что и в нашем конвейере для этого длинного контекста - ура! + + + +✏️ **Попробуйте!** Используйте лучшие оценки, которые вы вычислили ранее, чтобы показать пять наиболее вероятных ответов (для всего контекста, а не для каждого фрагмента). Чтобы проверить результаты, вернитесь к первому конвейеру и передайте `top_k=5` при его вызове. + + + +На этом мы завершаем наше глубокое погружение в возможности токенизатора. В следующей главе мы снова применим все это на практике, когда покажем, как дообучить модель для ряда распространенных задач NLP. diff --git a/chapters/ru/chapter6/4.mdx b/chapters/ru/chapter6/4.mdx new file mode 100644 index 000000000..fc5eaecf0 --- /dev/null +++ b/chapters/ru/chapter6/4.mdx @@ -0,0 +1,123 @@ +# Нормализация и предварительная токенизация[[normalization-and-pre-tokenization]] + + + +Прежде чем мы более подробно рассмотрим три наиболее распространенных алгоритма токенизации подслов, используемых в моделях Transformer (Byte-Pair Encoding [BPE], WordPiece и Unigram), мы сначала рассмотрим предварительную обработку, которую каждый токенизатор применяет к тексту. Вот высокоуровневый обзор этапов конвейера токенизации: + +
+The tokenization pipeline. + +
+ +Перед тем как разбить текст на подтокены (в соответствии со выбранной моделью), токенизатор выполняет два шага: _нормализацию_ и _претокенизацию_. + +## Нормализация[[normalization]] + + + +Шаг нормализации включает в себя некоторую общую очистку, например, удаление ненужных пробельных символов, понижение регистра и/или удаление ударений. Если вы знакомы с [Unicode normalization](http://www.unicode.org/reports/tr15/) (например, NFC или NFKC), это также может быть применено токенизатором. + +У 🤗 Transformers `tokenizer` есть атрибут `backend_tokenizer`, который предоставляет доступ к базовому токенизатору из библиотеки 🤗 Tokenizers: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +Атрибут `normalizer` объекта `tokenizer` имеет метод `normalize_str()`, который мы можем использовать, чтобы увидеть, как выполняется нормализация: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +В этом примере, поскольку мы выбрали контрольную точку `bert-base-uncased`, нормализация применила нижний регистр и удалила ударения. + + + +✏️ **Попробуйте!** Загрузите токенизатор из контрольной точки `bert-base-cased` и передайте ему тот же пример. Какие основные различия вы можете увидеть между версией токенизатора cased и uncased? + + + +## Предварительная токенизация[[pre-tokenization]] + + + +Как мы увидим в следующих разделах, токенизатор не может быть обучен только на сыром тексте. Сначала необходимо разбить текст на небольшие части, например, на слова. Именно в этом и заключается этап предварительной токенизации. Как мы видели в [Главе 2](../chapter2/1), токенизатор на основе слов (word-based tokenizer) может просто разбить необработанный текст на слова по пробелам и знакам пунктуации. Эти слова станут границами подтокенов, которые токенизатор сможет выучить в процессе обучения. + +Чтобы увидеть, как быстрый токенизатор выполняет предварительную токенизацию, мы можем воспользоваться методом `pre_tokenize_str()` атрибута `pre_tokenizer` объекта `tokenizer`: + +```py +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] +``` + +Обратите внимание, что токенизатор уже следит за смещениями, и именно поэтому он может дать нам сопоставление смещений, которое мы использовали в предыдущем разделе. Здесь токенизатор игнорирует два пробела и заменяет их одним, но смещение перескакивает между `are` и `you`, чтобы учесть это. + +Поскольку мы используем токенизатор BERT, предварительная токенизация включает часть пробельных символов и пунктуацию. Другие токенизаторы могут иметь другие правила для этого шага. Например, если мы используем токенизатор GPT-2: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +он также выполнит разбиение по пробельным символам и пунктуации, но сохранит пробелы и заменит их символом `Ġ`, что позволит ему восстановить исходные пробелы, если мы декодируем токены: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Также обратите внимание, что в отличие от токенизатора BERT, этот токенизатор не игнорирует двойной пробел. + +В качестве последнего примера рассмотрим токенизатор T5, основанный на алгоритме SentencePiece: + +```py +tokenizer = AutoTokenizer.from_pretrained("t5-small") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +```python out +[('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] +``` + +Как и токенизатор GPT-2, этот сохраняет пробелы и заменяет их специальным токеном (`_`), но токенизатор T5 делает разбиение только по пробелам, а не по знакам препинания. Также обратите внимание, что он по умолчанию добавляет пробел в начале предложения (перед `Hello`) и игнорирует двойной пробел между `are` и `you`. + +Теперь, когда мы немного познакомились с тем, как обрабатывают текст различные токенизаторы, можно приступить к изучению самих алгоритмов, лежащих в их основе. Мы начнем с краткого обзора широко применяемого SentencePiece; затем, в следующих трех разделах, мы рассмотрим, как работают три основных алгоритма, используемых для токенизации по подсловам. + +## SentencePiece[[sentencepiece]] + +[SentencePiece](https://github.com/google/sentencepiece) - это алгоритм токенизации для предварительной обработки текста, который можно использовать с любой из моделей, которые мы рассмотрим в следующих трех разделах. Он рассматривает текст как последовательность символов Unicode и заменяет пробелы специальным символом `▁`. При использовании в сочетании с алгоритмом Unigram (см. [раздел 7](../chapter7/7)) он даже не требует шага предварительной токенизации, что очень полезно для языков, где символ пробела не используется (например, китайского или японского). + +Другой главной особенностью SentencePiece является *обратимая токенизация*: поскольку в нем нет специальной обработки пробелов, декодирование токенов осуществляется просто путем их конкатенации и замены `_` на пробелы - в результате получается нормализованный текст. Как мы видели ранее, токенизатор BERT удаляет повторяющиеся пробелы, поэтому его токенизация не является обратимой. + +## Обзор алгоритма[[algorithm-overview]] + +В следующих разделах мы рассмотрим три основных алгоритма токенизации по подсловам: BPE (используется в GPT-2 и других моделях), WordPiece (используется, например, в BERT) и Unigram (используется в T5 и других моделях). Прежде чем мы приступим, вот краткий обзор того, как работает каждый из них. Не стесняйтесь возвращаться к этой таблице после прочтения каждого из следующих разделов, если вам еще не все понятно. + + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Обучение | Начинается с маленького словаря и изучает правила слияния токенов | Начинается с маленького словаря и изучает правила слияния токенов | Начинается с большого словаря и изучает правила удаления токенов +Шаг обучения | Объединяет токены, соответствующие наиболее часто встречающейся паре | Объединяет токены, соответствующие паре с наилучшей оценкой, основанной на частоте пары, отдавая предпочтение парам, где каждый отдельный токен встречается реже | Удаляет все токены в словаре, что минимизирует потери, вычисленные для всего корпуса. +Обучение | Слияние правил и словаря | Только словарь | Словарь с оценкой каждого токена +Кодирование | Разбивает слово на символы и применяет слияния, полученные во время обучения | Находит самое длинное подслово, начиная с начала, которое есть в словаре, затем делает то же самое для остальной части слова | Находит наиболее вероятное разбиение на токены, используя оценки, полученные во время обучения + +А теперь давайте погрузимся в BPE! \ No newline at end of file diff --git a/chapters/ru/chapter6/5.mdx b/chapters/ru/chapter6/5.mdx new file mode 100644 index 000000000..fa05c49ff --- /dev/null +++ b/chapters/ru/chapter6/5.mdx @@ -0,0 +1,360 @@ +# Токенизация Byte-Pair Encoding[[byte-pair-encoding-tokenization]] + + + +Byte-Pair Encoding (BPE) изначально была разработана как алгоритм для сжатия текстов, а затем использовалась OpenAI для токенизации при предварительном обучении модели GPT. Она используется во многих моделях трансформеров, включая GPT, GPT-2, RoBERTa, BART и DeBERTa. + + + + + +💡 В этом разделе подробно рассматривается BPE, вплоть до демонстрации полной реализации. Вы можете пропустить этот раздел, если вам нужен только общий обзор алгоритма токенизации. + + + +## Алгоритм обучения[[training-algorithm]] + +Обучение BPE начинается с вычисления уникального набора слов, используемых в корпусе (после завершения этапов нормализации и предварительной токенизации), затем создается словарь, в который заносятся все символы, используемые для записи этих слов. В качестве очень простого примера предположим, что в нашем корпусе используются следующие пять слов: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +Тогда базовым словарем будет `["b", "g", "h", "n", "p", "s", "u"]`. В реальном мире этот базовый словарь будет содержать, как минимум, все символы ASCII, а возможно, и некоторые символы Unicode. Если в примере, который вы обрабатываете, используется символ, которого нет в обучающем корпусе, этот символ будет преобразован в неизвестный токен. Это одна из причин, по которой многие модели NLP очень плохо анализируют контент с эмоджи, например. + + + +Токенизаторы GPT-2 и RoBERTa (которые довольно похожи) имеют умный способ решения этой проблемы: они рассматривают слова не как символы Unicode, а как байты. Таким образом, базовый словарь имеет небольшой размер (256), но все символы, которые вы можете придумать, все равно будут включены и не будут преобразованы в неизвестный токен. Этот трюк называется *byte-level BPE*. + + + +После получения базового словаря мы добавляем новые токены, пока не достигнем желаемого объема словаря, обучаясь *слияниям*, которые представляют собой правила слияния двух элементов существующего словаря в новый. Таким образом, в начале эти слияния будут создавать токены с двумя символами, а затем, по мере обучения, более длинные подслова. + +На любом шаге обучения токенизатора алгоритм BPE будет искать наиболее частую пару существующих токенов (под "парой" здесь понимаются два последовательных токена в слове). Эта наиболее часто встречающаяся пара и будет объединена, после чего все повторяется для следующего шага. + +Возвращаясь к нашему предыдущему примеру, предположим, что слова имеют следующую частоту: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +значение `" hug"` встречалось в корпусе 10 раз, `"pug"` - 5 раз, `"pun"` - 12 раз, `"bun"` - 4 раза, и `"hugs"` - 5 раз. Мы начинаем обучение с разбиения каждого слова на части символов (те, которые формируют наш начальный словарь), чтобы мы могли рассматривать каждое слово как список токенов: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Затем мы посмотрим на пары. Пара `("h", "u")` присутствует в словах `"hug"` и `"hugs"`, всего 15 раз в корпусе. Однако это не самая частая пара: эта честь принадлежит `("u", "g")`, которая присутствует в словах `"hug"`, `"pug"` и `"hugs"`, в общей сложности 20 раз в словаре. + +Таким образом, первое правило слияния, выученное токенизатором, - `("u", "g") -> "ug"`, что означает, что `"ug"` будет добавлено в словарь, и эта пара должна быть объединена во всех словах корпуса. В конце этого этапа словарь и корпус выглядят следующим образом: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5) +``` + +Теперь у нас есть несколько пар, в результате которых получается токен длиннее двух символов: например, пара `("h", "ug")` (встречается в корпусе 15 раз). Самая частая пара на этом этапе - `("u", "n")`, однако она встречается в корпусе 16 раз, поэтому второе выученное правило слияния - `("u", "n") -> "un"`. Добавив это в словарь и объединив все существующие вхождения, мы получаем: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +Corpus: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) +``` + +Теперь наиболее частой парой является `("h", "ug")`, поэтому мы изучаем правило слияния `("h", "ug") -> "hug"`, что дает нам первый трехбуквенный токен. После слияния корпус выглядит следующим образом: + +``` +Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) +``` + +И продолжаем в том же духе, пока не достигнем желаемого размера словаря. + + + +✏️ **Теперь ваша очередь!** Как вы думаете, каким будет следующее правило слияния? + + + +## Алгоритм токенизации[[tokenization-algorithm]] + +Токенизация следует за процессом обучения в том смысле, что новые входные данные подвергаются токенизации путем применения следующих шагов: + +1. Нормализация +2. Предварительная токенизация +3. Разделение слов на отдельные символы +4. Применение правил слияния, изученных по порядку, к этим частям + +Возьмем пример, который мы использовали во время обучения, с тремя выученными правилами слияния: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Слово `"bug"` будет токенизировано как `["b", "ug"]`. Слово `"mug"`, однако, будет токенизировано как `["[UNK]", "ug"]`, поскольку буква `"m"` отсутствует в базовом словаре. Аналогично, слово `"thug" будет токенизировано как `["[UNK]", "hug"]`: буква `"t" отсутствует в базовом словаре, и применение правил слияния приводит сначала к слиянию `"u"` и `"g"`, а затем к слиянию `"h"` и `"ug"`. + + + +✏️ ** Теперь ваша очередь!** Как вы думаете, как будет токенизировано слово `'unhug'`? + + + +## Реализация BPE[[implementing-bpe]] + +Теперь давайте посмотрим на реализацию алгоритма BPE. Это не будет оптимизированная версия, которую вы сможете использовать на большом корпусе; мы просто хотим показать вам код, чтобы вы могли лучше понять алгоритм. + +Для начала нам нужен корпус текста, поэтому давайте создадим простой корпус с несколькими предложениями: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Далее нам нужно предварительно токенизировать корпус в слова. Поскольку мы воспроизводим токенизатор BPE (например, GPT-2), для предварительной токенизации мы будем использовать токенизатор `gpt2`: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Затем мы вычисляем частоту каждого слова в корпусе, как и при предварительной токенизации: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) + +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +print(word_freqs) +``` + +```python out +defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'ĠCourse': 1, '.': 4, 'Ġchapter': 1, + 'Ġabout': 1, 'Ġtokenization': 1, 'Ġsection': 1, 'Ġshows': 1, 'Ġseveral': 1, 'Ġtokenizer': 1, 'Ġalgorithms': 1, + 'Hopefully': 1, ',': 1, 'Ġyou': 1, 'Ġwill': 1, 'Ġbe': 1, 'Ġable': 1, 'Ġto': 1, 'Ġunderstand': 1, 'Ġhow': 1, + 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) +``` + +Следующий шаг - составление базового словаря, состоящего из всех символов, используемых в корпусе: + +```python +alphabet = [] + +for word in word_freqs.keys(): + for letter in word: + if letter not in alphabet: + alphabet.append(letter) +alphabet.sort() + +print(alphabet) +``` + +```python out +[ ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', 'p', 'r', 's', + 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] +``` + +Мы также добавляем специальные токены, используемые моделью, в начало этого словаря. В случае GPT-2 единственным специальным токеном является `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Теперь нам нужно разделить каждое слово на отдельные символы, чтобы можно было начать обучение: + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Теперь, когда мы готовы к обучению, давайте напишем функцию, которая вычисляет частоту каждой пары. Нам нужно будет использовать ее на каждом шаге обучения: + +```python +def compute_pair_freqs(splits): + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + pair_freqs[pair] += freq + return pair_freqs +``` + +Давайте посмотрим на часть этого словаря после первых разделений: + +```python +pair_freqs = compute_pair_freqs(splits) + +for i, key in enumerate(pair_freqs.keys()): + print(f"{key}: {pair_freqs[key]}") + if i >= 5: + break +``` + +```python out +('T', 'h'): 3 +('h', 'i'): 3 +('i', 's'): 5 +('Ġ', 'i'): 2 +('Ġ', 't'): 7 +('t', 'h'): 3 +``` + +Теперь, чтобы найти наиболее часто встречающуюся пару, нужно всего лишь сделать быстрый цикл: + +```python +best_pair = "" +max_freq = None + +for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + +print(best_pair, max_freq) +``` + +```python out +('Ġ', 't') 7 +``` + +Итак, первое слияние, которое нужно выучить, это `('Ġ', 't') -> 'Ġt'`, и мы добавляем `'Ġt'` в словарь: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Чтобы продолжить, нам нужно применить это объединение в нашем экземпляре `splits` словаря. Давайте напишем для этого еще одну функцию: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + split = split[:i] + [a + b] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +И мы можем посмотреть на результат первого слияния: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Теперь у нас есть все, что нужно, чтобы проитерироваться до тех пор, пока мы не выучим все слияния, которые нам нужны. Пусть размер словаря будет 50: + +```python +vocab_size = 50 + +while len(vocab) < vocab_size: + pair_freqs = compute_pair_freqs(splits) + best_pair = "" + max_freq = None + for pair, freq in pair_freqs.items(): + if max_freq is None or max_freq < freq: + best_pair = pair + max_freq = freq + splits = merge_pair(*best_pair, splits) + merges[best_pair] = best_pair[0] + best_pair[1] + vocab.append(best_pair[0] + best_pair[1]) +``` + +В результате мы выучили 19 правил слияния (исходный словарь имел размер 31 - 30 символов в алфавите плюс специальный токен): + +```py +print(merges) +``` + +```python out +{('Ġ', 't'): 'Ġt', ('i', 's'): 'is', ('e', 'r'): 'er', ('Ġ', 'a'): 'Ġa', ('Ġt', 'o'): 'Ġto', ('e', 'n'): 'en', + ('T', 'h'): 'Th', ('Th', 'is'): 'This', ('o', 'u'): 'ou', ('s', 'e'): 'se', ('Ġto', 'k'): 'Ġtok', + ('Ġtok', 'en'): 'Ġtoken', ('n', 'd'): 'nd', ('Ġ', 'is'): 'Ġis', ('Ġt', 'h'): 'Ġth', ('Ġth', 'e'): 'Ġthe', + ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} +``` + +А словарь состоит из специального токена, начального алфавита и всех результатов слияния: + +```py +print(vocab) +``` + +```python out +['<|endoftext|>', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'k', 'l', 'm', 'n', 'o', + 'p', 'r', 's', 't', 'u', 'v', 'w', 'y', 'z', 'Ġ', 'Ġt', 'is', 'er', 'Ġa', 'Ġto', 'en', 'Th', 'This', 'ou', 'se', + 'Ġtok', 'Ġtoken', 'nd', 'Ġis', 'Ġth', 'Ġthe', 'in', 'Ġab', 'Ġtokeni'] +``` + + + +💡 Использование `train_new_from_iterator()` на том же корпусе не приведет к созданию точно такого же словаря. Это связано с тем, что при выборе наиболее частотной пары мы выбираем первую попавшуюся, в то время как библиотека 🤗 Tokenizers выбирает первую пару, основываясь на ее внутренних ID. + + + +Чтобы токенизировать новый текст, мы предварительно токенизируем его, разбиваем на части, а затем применяем все изученные правила слияния: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + splits = [[l for l in word] for word in pre_tokenized_text] + for pair, merge in merges.items(): + for idx, split in enumerate(splits): + i = 0 + while i < len(split) - 1: + if split[i] == pair[0] and split[i + 1] == pair[1]: + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[idx] = split + + return sum(splits, []) +``` + +Мы можем попробовать это на любом тексте, состоящем из символов алфавита: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Наша реализация будет выбрасывать ошибку при наличии неизвестного символа, поскольку мы ничего не сделали для их обработки. На самом деле в GPT-2 нет неизвестного токена (невозможно получить неизвестный символ при использовании BPE на уровне байтов), но здесь это может произойти, поскольку мы не включили все возможные байты в начальный словарь. Этот аспект BPE выходит за рамки данного раздела, поэтому мы опустили подробности. + + + +Вот и все об алгоритме BPE! Далее мы рассмотрим WordPiece. \ No newline at end of file diff --git a/chapters/ru/chapter6/6.mdx b/chapters/ru/chapter6/6.mdx new file mode 100644 index 000000000..a462d82f2 --- /dev/null +++ b/chapters/ru/chapter6/6.mdx @@ -0,0 +1,374 @@ +# Токенизация WordPiece[[wordpiece-tokenization]] + + + +WordPiece - это алгоритм токенизации, разработанный Google для предварительного обучения BERT. Впоследствии он был повторно использован во многих моделях трансформеров, основанных на BERT, таких как DistilBERT, MobileBERT, Funnel Transformers и MPNET. Он очень похож на BPE в плане обучения, но фактическая токенизация выполняется по-другому. + + + + + +💡 В этом разделе подробно рассматривается WordPiece, вплоть до демонстрации полной реализации. Вы можете пропустить его, если вам нужен только общий обзор алгоритма токенизации. + + + +## Алгоритм обучения[[training-algorithm]] + + + +⚠️ Google никогда не предоставлял открытый доступ к своей реализации алгоритма обучения WordPiece, поэтому все вышесказанное - это наши предположения, основанные на опубликованных материалах. Возможно, они точны не на 100 %. + + + +Как и BPE, WordPiece начинает работу с небольшого словаря, включающего специальные токены, используемые моделью, и начальный алфавит. Поскольку модель идентифицирует подслова путем добавления префикса (как `##` для BERT), каждое слово первоначально разбивается на части путем добавления этого префикса ко всем символам внутри слова. Так, например, `"word"` разбивается на части следующим образом: + +``` +w ##o ##r ##d +``` + +Таким образом, начальный алфавит содержит все символы, присутствующие в начале слова, и символы, присутствующие внутри слова, которым предшествует префикс WordPiece. + +Затем, как и в случае с BPE, WordPiece изучает правила слияния. Основное отличие заключается в способе выбора пары для слияния. Вместо того чтобы выбирать наиболее частую пару, WordPiece рассчитывает оценку для каждой пары по следующей формуле: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +Деля частоту пары на произведение частот каждой из ее частей, алгоритм отдает предпочтение слиянию пар, отдельные части которых встречаются в словаре реже. Например, он не обязательно объединит `("un", "##able")`, даже если эта пара встречается в словаре очень часто, потому что две пары `"un"` и `"##able"`, скорее всего, встречаются в большом количестве других слов и имеют высокую частоту. Напротив, такая пара, как `("hu", "##gging")`, вероятно, будет объединена быстрее (при условии, что слово "hugging" часто встречается в словаре), поскольку `"hu"` и `"##gging"` по отдельности, скорее всего, встречаются реже. + +Давайте рассмотрим тот же словарь, который мы использовали в учебном примере BPE: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Рабиение здесь будет следующим: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +поэтому исходный словарь будет иметь вид `["b", "h", "p", "##g", "##n", "##s", "##u"]` (если мы пока забудем о специальных токенах). Самая частая пара - `("##u", "##g")` (встречается 20 раз), но индивидуальная частота `"##u"` очень высока, поэтому ее оценка не самая высокая (она составляет 1/36). Все пары с `"##u"` фактически имеют такую же оценку (1/36), поэтому лучшую оценку получает пара `("##g", "##s")` - единственная, в которой нет `"##u"` - с оценкой 1/20, и первым выученным слиянием будет `("##g", "##s") -> ("##gs")`. + +Обратите внимание, что при слиянии мы удаляем `##` между двумя токенами, поэтому мы добавляем `"##gs"` в словарь и применяем слияние в словах корпуса: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +Corpus: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) +``` + +В этот момент `"##u"` находится во всех возможных парах, поэтому все они получают одинаковый балл. Допустим, в этом случае первая пара объединяется, так что `("h", "##u") -> "hu"`. Это приводит нас к: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +Corpus: ("hu" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +Затем следующую лучшую оценку разделяют `("hu", "##g")` и `("hu", "##gs")` (1/15, по сравнению с 1/21 для всех остальных пар), поэтому первая пара с наибольшей оценкой объединяется: + +``` +Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) +``` + +и мы продолжаем так до тех пор, пока не достигнем необходимого размера словаря. + + + +✏️ **Теперь ваша очередь!** Каким будет следующее правило слияния? + + + +## Алгоритм токенизации[[tokenization-algorithm]] + +Токенизация в WordPiece и BPE отличается тем, что WordPiece сохраняет только конечный словарь, а не выученные правила слияния. Начиная со слова, которое нужно токенизировать, WordPiece находит самое длинное подслово, которое есть в словаре, а затем разбивает его на части. Например, если мы используем словарь, изученный в примере выше, для слова `" hugs"` самым длинным подсловом, начиная с начала, которое находится в словаре, является `"hug"`, поэтому мы делим его на части и получаем `["hug", "##s"]`. Затем мы продолжаем с `"##s"`, которое находится в словаре, поэтому токенизация `"hugs"` будет `["hug", "##s"]`. + +В BPE мы бы применили слияния, выученные по порядку, и токенизировали это как `["hu", "##gs"]`, поэтому кодировка отличается. + +В качестве другого примера посмотрим, как будет токенизировано слово `"bugs"`. `"b"` - самое длинное подслово, начинающееся с начала слова, которое есть в словаре, поэтому мы делим его на части и получаем `["b", "##ugs"]`. Затем `"##u"` - самое длинное подслово, начинающееся в начале `"##ugs"`, которое есть в словаре, поэтому мы делим его на части и получаем `["b", "##u", "##gs"]`. Наконец, `"##gs"` находится в словаре, так что этот последний список является токеном `"bugs"`. + +Когда токенизация доходит до стадии, когда невозможно найти подслово в словаре, все слово токенизируется как неизвестное - так, например, `"mug"` будет токенизировано как `["[UNK]"]`, как и `"bum"` (даже если мы можем начать с `"b"` и `"##u"`, `"##m"` не входит в словарь, и результирующий токен будет просто `["[UNK]"]`, а не `["b", "##u", "[UNK]"]`). Это еще одно отличие от BPE, который классифицирует как неизвестные только отдельные символы, отсутствующие в словаре. + + + +✏️ **Теперь ваша очередь!** Как будет токенизировано слово `"pugs"`? + + + +## Реализация WordPiece[[implementing-wordpiece]] + +Теперь давайте посмотрим на реализацию алгоритма WordPiece. Как и в случае с BPE, это всего лишь учебный пример, и вы не сможете использовать его на большом корпусе. + +Мы будем использовать тот же корпус, что и в примере с BPE: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +Во-первых, нам нужно предварительно токенизировать корпус в слова. Поскольку мы воспроизводим токенизатор WordPiece (например, BERT), для предварительной токенизации мы будем использовать токенизатор `bert-base-cased`: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Затем мы вычисляем частоту каждого слова в корпусе, как и при предварительной токенизации: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +```python out +defaultdict( + int, {'This': 3, 'is': 2, 'the': 1, 'Hugging': 1, 'Face': 1, 'Course': 1, '.': 4, 'chapter': 1, 'about': 1, + 'tokenization': 1, 'section': 1, 'shows': 1, 'several': 1, 'tokenizer': 1, 'algorithms': 1, 'Hopefully': 1, + ',': 1, 'you': 1, 'will': 1, 'be': 1, 'able': 1, 'to': 1, 'understand': 1, 'how': 1, 'they': 1, 'are': 1, + 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) +``` + +Как мы уже видели, алфавит - это уникальное множество, состоящее из всех первых букв слов и всех остальных букв, которые встречаются в словах с префиксом `##`: + +```python +alphabet = [] +for word in word_freqs.keys(): + if word[0] not in alphabet: + alphabet.append(word[0]) + for letter in word[1:]: + if f"##{letter}" not in alphabet: + alphabet.append(f"##{letter}") + +alphabet.sort() +alphabet + +print(alphabet) +``` + +```python out +['##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', '##l', '##m', '##n', '##o', '##p', '##r', '##s', + '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', + 'w', 'y'] +``` + +Мы также добавляем специальные токены, используемые моделью, в начало этого словаря. В случае BERT это список `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Далее нам нужно разделить каждое слово на части, при этом все буквы, которые не являются первыми, должны иметь префикс `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Теперь, когда мы готовы к обучению, давайте напишем функцию, которая вычисляет оценку каждой пары. Нам нужно будет использовать ее на каждом шаге обучения: + +```python +def compute_pair_scores(splits): + letter_freqs = defaultdict(int) + pair_freqs = defaultdict(int) + for word, freq in word_freqs.items(): + split = splits[word] + if len(split) == 1: + letter_freqs[split[0]] += freq + continue + for i in range(len(split) - 1): + pair = (split[i], split[i + 1]) + letter_freqs[split[i]] += freq + pair_freqs[pair] += freq + letter_freqs[split[-1]] += freq + + scores = { + pair: freq / (letter_freqs[pair[0]] * letter_freqs[pair[1]]) + for pair, freq in pair_freqs.items() + } + return scores +``` + +Давайте посмотрим на часть этого словаря после первых разделений: + +```python +pair_scores = compute_pair_scores(splits) +for i, key in enumerate(pair_scores.keys()): + print(f"{key}: {pair_scores[key]}") + if i >= 5: + break +``` + +```python out +('T', '##h'): 0.125 +('##h', '##i'): 0.03409090909090909 +('##i', '##s'): 0.02727272727272727 +('i', '##s'): 0.1 +('t', '##h'): 0.03571428571428571 +('##h', '##e'): 0.011904761904761904 +``` + +Теперь для того, чтобы найти пару с наилучшим результатом, нужно всего лишь сделать быстрый цикл: + +```python +best_pair = "" +max_score = None +for pair, score in pair_scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + +print(best_pair, max_score) +``` + +```python out +('a', '##b') 0.2 +``` + +Итак, первое слияние, которое нужно выучить, это `('a', '##b') -> 'ab'`, и мы добавляем `'ab'` в словарь: + +```python +vocab.append("ab") +``` + +Чтобы продолжить, нам нужно применить это слияние в нашем словаре `splits`. Давайте напишем для этого еще одну функцию: + +```python +def merge_pair(a, b, splits): + for word in word_freqs: + split = splits[word] + if len(split) == 1: + continue + i = 0 + while i < len(split) - 1: + if split[i] == a and split[i + 1] == b: + merge = a + b[2:] if b.startswith("##") else a + b + split = split[:i] + [merge] + split[i + 2 :] + else: + i += 1 + splits[word] = split + return splits +``` + +И мы можем посмотреть на результат первого слияния: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Теперь у нас есть все, что нужно, чтобы зацикливать процесс до тех пор, пока мы не выучим все слияния, которые нам нужны. Давайте нацелимся на размер словаря равный 70: + +```python +vocab_size = 70 +while len(vocab) < vocab_size: + scores = compute_pair_scores(splits) + best_pair, max_score = "", None + for pair, score in scores.items(): + if max_score is None or max_score < score: + best_pair = pair + max_score = score + splits = merge_pair(*best_pair, splits) + new_token = ( + best_pair[0] + best_pair[1][2:] + if best_pair[1].startswith("##") + else best_pair[0] + best_pair[1] + ) + vocab.append(new_token) +``` + +Затем мы можем просмотреть созданный словарь: + +```py +print(vocab) +``` + +```python out +['[PAD]', '[UNK]', '[CLS]', '[SEP]', '[MASK]', '##a', '##b', '##c', '##d', '##e', '##f', '##g', '##h', '##i', '##k', + '##l', '##m', '##n', '##o', '##p', '##r', '##s', '##t', '##u', '##v', '##w', '##y', '##z', ',', '.', 'C', 'F', 'H', + 'T', 'a', 'b', 'c', 'g', 'h', 'i', 's', 't', 'u', 'w', 'y', 'ab', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +Как мы видим, по сравнению с BPE этот токенизатор быстрее выучивает части слов как токены. + + + +💡 Использование `train_new_from_iterator()` на одном и том же корпусе не приведет к точно такому же словарю. Это происходит потому, что библиотека 🤗 Tokenizers не реализует WordPiece для обучения (поскольку мы не полностью уверены в его внутреннем устройстве), а использует вместо него BPE. + + + +Чтобы токенизировать новый текст, мы предварительно токенизируем его, разбиваем на части, а затем применяем алгоритм токенизации к каждому слову. То есть начиная с первого слова мы ищем самое большое подслово и разбиваем его на части, затем мы повторяем процесс для второй части, и так далее для оставшейся части этого слова и следующих слов в тексте: + +```python +def encode_word(word): + tokens = [] + while len(word) > 0: + i = len(word) + while i > 0 and word[:i] not in vocab: + i -= 1 + if i == 0: + return ["[UNK]"] + tokens.append(word[:i]) + word = word[i:] + if len(word) > 0: + word = f"##{word}" + return tokens +``` + +Давайте проверим алгоритм на одном слове, которое есть в словаре, и на другом, которого нет: + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Теперь давайте напишем функцию, которая токенизирует текст: + +```python +def tokenize(text): + pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in pre_tokenize_result] + encoded_words = [encode_word(word) for word in pre_tokenized_text] + return sum(encoded_words, []) +``` + +Мы можем попробовать его на любом тексте: + +```python +tokenize("This is the Hugging Face course!") +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +Вот и все об алгоритме WordPiece! Теперь давайте посмотрим на Unigram. diff --git a/chapters/ru/chapter6/7.mdx b/chapters/ru/chapter6/7.mdx new file mode 100644 index 000000000..3b436be17 --- /dev/null +++ b/chapters/ru/chapter6/7.mdx @@ -0,0 +1,381 @@ +# Токенизация Unigram[[unigram-tokenization]] + + + +Алгоритм Unigram часто используется в SentencePiece, который является алгоритмом токенизации, применяемым в таких моделях, как AlBERT, T5, mBART, Big Bird и XLNet. + + + + + +💡 В этом разделе подробно рассматривается Unigram, вплоть до демонстрации полной реализации. Вы можете пропустить его, если вам нужен только общий обзор алгоритма токенизации. + + + +## Алгоритм обучения[[training-algorithm]] + +По сравнению с BPE и WordPiece, Unigram работает в другом направлении: он начинает с большого словарного запаса и удаляет из него токены, пока не достигнет желаемого размера словаря. Существует несколько вариантов создания базового словаря: например, мы можем взять наиболее часто встречающиеся подстроки в предварительно токенизированных словах или применить BPE к исходному корпусу с большим объемом словаря. + +На каждом шаге обучения алгоритм Unigram рассчитывает потери по корпусу с учетом текущего словарного запаса. Затем для каждого символа в словаре алгоритм вычисляет, насколько увеличится общая потеря, если этот символ будет удален, и ищет символы, которые увеличат ее меньше всего. Эти символы оказывают меньшее влияние на общую потерю по корпусу, поэтому в некотором смысле они "менее нужны" и являются лучшими кандидатами на удаление. + +Это очень дорогостоящая операция, поэтому мы удаляем не просто один символ, связанный с наименьшим увеличением потерь, а \\(p\\) (\\(p\\) - гиперпараметр, которым вы можете управлять, обычно 10 или 20) процентов символов, связанных с наименьшим увеличением потерь. Этот процесс повторяется до тех пор, пока словарь не достигнет желаемого размера. + +Обратите внимание, что мы никогда не удаляем базовые символы, чтобы убедиться, что любое слово может быть токенизировано. + +Итак, все еще немного туманно: основная часть алгоритма заключается в том, чтобы вычислить потери по корпусу и посмотреть, как они изменяются при удалении некоторых токенов из словаря, но мы еще не объяснили, как это сделать. Этот шаг зависит от алгоритма токенизации модели Unigram, поэтому мы рассмотрим его далее. + +Мы используем корпус текста из предыдущих примеров: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +и для этого примера мы возьмем все подстроки из исходного словаря: + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Алгоритм токенизации[[tokenization-algorithm]] + +Модель Unigram - это тип языковой модели, в которой каждый токен рассматривается как независимый от предшествующих ему. Это самая простая языковая модель в том смысле, что вероятность появления токена X с учетом предыдущего контекста - это просто вероятность появления токена X. Таким образом, если бы мы использовали модель Unigram для генерации текста, мы бы всегда предсказывали наиболее часто встречающийся токен. + +Вероятность данного токена - это его частота (количество раз, когда мы его находим) в исходном корпусе, деленная на сумму частот всех токенов в словаре (чтобы убедиться, что суммы вероятностей равны 1). Например, `"ug"` присутствует в `"hug"`, `"pug"` и `"hugs"`, поэтому его частота в нашем корпусе равна 20. + +Здесь приведены частоты всех возможных подслов в словаре: + +``` +("h", 15) ("u", 36) ("g", 20) ("hu", 15) ("ug", 20) ("p", 17) ("pu", 17) ("n", 16) +("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) +``` + +Итак, сумма всех частот равна 210, а вероятность появления подслова `"ug"`, таким образом, составляет 20/210. + + + +✏️ **Теперь ваша очередь!** Напишите код для вычисления вышеуказанных частот и дважды проверьте правильность приведенных результатов, а также общую сумму. + + + +Теперь для токенизации данного слова мы рассматриваем все возможные сегментации на токены и вычисляем вероятность каждого из них в соответствии с моделью Unigram. Поскольку все токены считаются независимыми, эта вероятность равна произведению вероятностей появления каждого токена. Например, при токенизации `["p", "u", "g"]` слова `"pug"` вероятность составляет: + +$$P([``p", ``u", ``g"]) = P(``p") \times P(``u") \times P(``g") = \frac{5}{210} \times \frac{36}{210} \times \frac{20}{210} = 0.000389$$ + +Для сравнения, токен `["pu", "g"]` имеет вероятность: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +так что один из них гораздо более вероятен. В целом, токенизации с наименьшим количеством токенов будут иметь наибольшую вероятность (из-за деления на 210, повторяющегося для каждого токена), что соответствует интуитивному желанию: разбить слово на наименьшее количество токенов. + +Токенизация слова с помощью модели Unigram - это токенизация с наибольшей вероятностью. В примере с `"pug"` приведены вероятности, которые мы получили бы для каждой возможной сегментации: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Так, `"pug"` будет токенизировано как `["p", "ug"]` или `["pu", "g"]`, в зависимости от того, какая из этих сегментаций встретится первой (отметим, что в большом корпусе подобные случаи равенства будут редки). + +В данном случае было легко найти все возможные сегментации и вычислить их вероятности, но в общем случае это будет немного сложнее. Для этого используется классический алгоритм, который называется *алгоритм Витерби (Viterbi algorithm)*. По сути, мы можем построить граф для выявления возможных сегментаций данного слова, сказав, что существует ветвь от символа _a_ до символа _b_, если подслово от _a_ до _b_ есть в словаре, и приписать этой ветви вероятность подслова. + +Чтобы найти путь в этом графе, который будет иметь наилучшую оценку, алгоритм Витерби определяет для каждой позиции в слове сегментацию с наилучшей оценкой, которая заканчивается на этой позиции. Поскольку мы идем от начала к концу, этот лучший результат можно найти, перебирая все подслова, заканчивающиеся на текущей позиции, а затем используя лучший результат токенизации с позиции, на которой начинается это подслово. Затем нужно просто развернуть путь, чтобы прийти к концу. + +Давайте рассмотрим пример с использованием нашего словаря и слова `"unhug"`. Для каждой позиции подслова с наилучшими оценками заканчиваются следующим образом: + +``` +Character 0 (u): "u" (score 0.171429) +Character 1 (n): "un" (score 0.076191) +Character 2 (h): "un" "h" (score 0.005442) +Character 3 (u): "un" "hu" (score 0.005442) +Character 4 (g): "un" "hug" (score 0.005442) +``` + +Таким образом, `"unhug"` будет токенизировано как `["un", "hug"]`. + + + +✏️ **Теперь ваша очередь!** Определите токенизацию слова `" huggun"` и его оценку. + + + +## Назад к обучению[[back-to-training]] + +Теперь, когда мы увидели, как работает токенизация, мы можем немного глубже изучить потери, используемые во время обучения. На любом этапе эта потеря вычисляется путем токенизации каждого слова в корпусе с использованием текущего словаря и модели Unigram, определяемой частотами каждого токена в корпусе (как было показано ранее). + +Каждое слово в корпусе имеет оценку, а потеря - это отрицательное логарифмическое правдоподобие этих оценок, то есть сумма для всех слов в корпусе всех `-log(P(word))`. + +Давайте вернемся к нашему примеру со следующим корпусом: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Токенизация каждого слова с соответствующими оценками: + +``` +"hug": ["hug"] (score 0.071428) +"pug": ["pu", "g"] (score 0.007710) +"pun": ["pu", "n"] (score 0.006168) +"bun": ["bu", "n"] (score 0.001451) +"hugs": ["hug", "s"] (score 0.001701) +``` + +Таким образом, потери будут: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Теперь нам нужно вычислить, как удаление каждого токена влияет на потери. Это довольно утомительно, поэтому мы просто сделаем это для двух токенов и оставим весь процесс на потом, когда у нас будет код, чтобы помочь нам. В этом (очень) конкретном случае у нас есть две эквивалентные токенизации всех слов: как мы видели ранее, например, `"pug"` может быть токенизировано `["p", "ug"]` с тем же результатом. Таким образом, удаление токена `"pu"` из словаря приведет к точно таким же потерям. + +С другой стороны, удаление `" hug"` усугубит потери, потому что токенизация `"hug"` и `"hugs"` станет: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Эти изменения приведут к увеличению потерь: + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Поэтому токен `"pu"`, вероятно, будет удален из словаря, но не `"hug"`. + +## Реализация Unigram[[implementing-unigram]] + +Теперь давайте реализуем все, что мы видели до сих пор, в коде. Как и в случае с BPE и WordPiece, это не эффективная реализация алгоритма Unigram (совсем наоборот), но она должна помочь вам понять его немного лучше. + +В качестве примера мы будем использовать тот же корпус текста, что и раньше: + +```python +corpus = [ + "This is the Hugging Face Course.", + "This chapter is about tokenization.", + "This section shows several tokenizer algorithms.", + "Hopefully, you will be able to understand how they are trained and generate tokens.", +] +``` + +На этот раз в качестве модели мы будем использовать `xlnet-base-cased`: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Как и в случае с BPE и WordPiece, мы начинаем с подсчета количества вхождений каждого слова в корпус: + +```python +from collections import defaultdict + +word_freqs = defaultdict(int) +for text in corpus: + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + new_words = [word for word, offset in words_with_offsets] + for word in new_words: + word_freqs[word] += 1 + +word_freqs +``` + +Затем нам нужно инициализировать наш словарь чем-то большим, чем размер словаря, который мы захотим получить в конце. Мы должны включить все основные символы (иначе мы не сможем токенизировать каждое слово), но для больших подстрок мы сохраним только самые распространенные, поэтому мы отсортируем их по частоте: + +```python +char_freqs = defaultdict(int) +subwords_freqs = defaultdict(int) +for word, freq in word_freqs.items(): + for i in range(len(word)): + char_freqs[word[i]] += freq + # Перебираем подслова длиной не менее 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Сортировка подслов по частоте +sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) +sorted_subwords[:10] +``` + +```python out +[('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] +``` + +Мы группируем символы с лучшими подсловами, чтобы получить начальный словарь размером 300: + +```python +token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] +token_freqs = {token: freq for token, freq in token_freqs} +``` + + + +💡 SentencePiece использует более эффективный алгоритм под названием Enhanced Suffix Array (ESA) для создания начального словаря. + + + +Далее мы вычисляем сумму всех частот, чтобы преобразовать частоты в вероятности. Для нашей модели мы будем хранить логарифмы вероятностей, потому что численно стабильнее складывать логарифмы, чем перемножать маленькие числа, и это упростит вычисление потерь модели: + +```python +from math import log + +total_sum = sum([freq for token, freq in token_freqs.items()]) +model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Теперь основная функция - это функция токенизации слов с помощью алгоритма Витерби. Как мы уже видели, этот алгоритм вычисляет наилучшую сегментацию каждой подстроки слова, которую мы будем хранить в переменной с именем `best_segmentations`. Мы будем хранить по одному словарю на каждую позицию в слове (от 0 до его полной длины), с двумя ключами: индекс начала последнего токена в лучшей сегментации и оценка лучшей сегментации. По индексу начала последнего токена мы сможем получить полную сегментацию, когда список будет полностью заполнен. + +Пополнение списка осуществляется с помощью двух циклов: основной цикл просматривает каждую начальную позицию, а второй цикл перебирает все подстроки, начинающиеся с этой начальной позиции. Если подстрока есть в словаре, мы получаем новую сегментацию слова до этой конечной позиции, которую сравниваем с той, что хранится в `best_segmentations`. + +После завершения основного цикла мы просто начинаем с конца и переходим от одной начальной позиции к другой, записывая токены по мере продвижения, пока не достигнем начала слова: + +```python +def encode_word(word, model): + best_segmentations = [{"start": 0, "score": 1}] + [ + {"start": None, "score": None} for _ in range(len(word)) + ] + for start_idx in range(len(word)): + # Это должно быть правильно заполнено предыдущими шагами цикла + best_score_at_start = best_segmentations[start_idx]["score"] + for end_idx in range(start_idx + 1, len(word) + 1): + token = word[start_idx:end_idx] + if token in model and best_score_at_start is not None: + score = model[token] + best_score_at_start + # Если мы нашли лучшую сегментацию, заканчивающуюся на end_idx, мы обновляем + if ( + best_segmentations[end_idx]["score"] is None + or best_segmentations[end_idx]["score"] > score + ): + best_segmentations[end_idx] = {"start": start_idx, "score": score} + + segmentation = best_segmentations[-1] + if segmentation["score"] is None: + # Мы не нашли токенизацию слова -> возвращаем unknown + return [""], None + + score = segmentation["score"] + start = segmentation["start"] + end = len(word) + tokens = [] + while start != 0: + tokens.insert(0, word[start:end]) + next_start = best_segmentations[start]["start"] + end = start + start = next_start + tokens.insert(0, word[start:end]) + return tokens, score +``` + +Мы уже можем опробовать нашу первоначальную модель на некоторых словах: + +```python +print(encode_word("Hopefully", model)) +print(encode_word("This", model)) +``` + +```python out +(['H', 'o', 'p', 'e', 'f', 'u', 'll', 'y'], 41.5157494601402) +(['This'], 6.288267030694535) +``` + +Теперь легко вычислить потери модели на корпусе! + +```python +def compute_loss(model): + loss = 0 + for word, freq in word_freqs.items(): + _, word_loss = encode_word(word, model) + loss += freq * word_loss + return loss +``` + +Мы можем проверить его работу на имеющейся у нас модели: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Вычисление оценок для каждого токена также не представляет особой сложности; нам просто нужно вычислить потери для модели, полученные при удалении каждого токена: + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # Мы всегда храним токены длиной 1 + if len(token) == 1: + continue + model_without_token = copy.deepcopy(model) + _ = model_without_token.pop(token) + scores[token] = compute_loss(model_without_token) - model_loss + return scores +``` + +Мы можем попробовать это на заданном токене: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Поскольку `"ll"` используется в токенизации слова `"Hopefully"`, и его удаление, вероятно, заставит нас дважды использовать токен `"l"` вместо этого, мы ожидаем, что он будет иметь положительную потерю. `"his"` используется только внутри слова `"This"`, которое токенизируется само по себе, поэтому мы ожидаем, что потери будут нулевыми. Вот результаты: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Такой подход очень неэффективен, поэтому SentencePiece использует приближенную оценку потерь модели без токена X: вместо того чтобы начинать с нуля, он просто заменяет токен X его сегментацией в оставшемся словаре. Таким образом, все оценки могут быть вычислены одновременно с потерями модели. + + + +Когда этот процесс завершиться, останется только добавить в словарь специальные токены, используемые моделью, а затем итерироваться, пока мы не вычеркнем из словаря достаточно токенов, чтобы достичь желаемого размера: + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Удалите токены percent_to_remove с наименьшими оценками + for i in range(int(len(model) * percent_to_remove)): + _ = token_freqs.pop(sorted_scores[i][0]) + + total_sum = sum([freq for token, freq in token_freqs.items()]) + model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} +``` + +Затем, чтобы токенизировать некоторый текст, нам просто нужно применить предварительную токенизацию, а затем использовать нашу функцию `encode_word()`: + +```python +def tokenize(text, model): + words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text) + pre_tokenized_text = [word for word, offset in words_with_offsets] + encoded_words = [encode_word(word, model)[0] for word in pre_tokenized_text] + return sum(encoded_words, []) + + +tokenize("This is the Hugging Face course.", model) +``` + +```python out +['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] +``` + +Вот и все об Unigram! Надеемся, теперь вы чувствуете себя экспертом во всем, что касается токенизаторов. В следующем разделе мы рассмотрим блоки библиотеки 🤗 Tokenizers и покажем, как их можно использовать для создания собственного токенизатора. diff --git a/chapters/ru/chapter6/8.mdx b/chapters/ru/chapter6/8.mdx new file mode 100644 index 000000000..36b260a6b --- /dev/null +++ b/chapters/ru/chapter6/8.mdx @@ -0,0 +1,563 @@ +# Создание токенизатора, блок за блоком[[building-a-tokenizer-block-by-block]] + + + +Как мы уже видели в предыдущих разделах, токенизация состоит из нескольких этапов: + +- Нормализация (любая необходимая очистка текста, например, удаление пробелов или подчеркиваний, нормализация Unicode и т. д.) +- Предварительная токенизация (разделение входного текста на слова). +- Прогон входных данных через модель (использование предварительно токенизированных слов для создания последовательности токенов) +- Постобработка (добавление специальных токенов токенизатора, генерация маски внимания и идентификаторов типов токенов) + +В качестве напоминания вот еще один взгляд на общий процесс: + +
+The tokenization pipeline. + +
+ +Библиотека 🤗 Tokenizers была создана для того, чтобы предоставить несколько вариантов каждого из этих шагов, которые вы можете смешивать и сочетать между собой. В этом разделе мы рассмотрим, как можно создать токенизатор с нуля, а не обучать новый токенизатор на основе старого, как мы делали в [разделе 2](../chapter6/2). После этого вы сможете создать любой токенизатор, который только сможете придумать! + + + +Точнее, библиотека построена вокруг центрального класса `Tokenizer`, а строительные блоки сгруппированы в подмодули: + +- `normalizers` содержит все возможные типы нормализаторов текста `Normalizer`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` содержит все возможные типы предварительных токенизаторов `PreTokenizer`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` содержит различные типы моделей `Model`, которые вы можете использовать, такие как `BPE`, `WordPiece` и `Unigram` (полный список [здесь](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` содержит все различные типы `Trainer`, которые вы можете использовать для обучения модели на корпусе (по одному на каждый тип модели; полный список [здесь](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` содержит различные типы постпроцессоров `PostProcessor`, которые вы можете использовать (полный список [здесь](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` содержит различные типы декодеров `Decoder`, которые вы можете использовать для декодирования результатов токенизации (полный список [здесь](https://huggingface.co/docs/tokenizers/components#decoders)). + +Весь список блоков вы можете найти [здесь](https://huggingface.co/docs/tokenizers/components). + +## Получение корпуса текста[[acquiring-a-corpus]] + +Для обучения нашего нового токенизатора мы будем использовать небольшой корпус текстов (чтобы примеры выполнялись быстро). Шаги по сбору корпуса аналогичны тем, что мы делали в [начале этой главы](../chapter6/2), но на этот раз мы будем использовать набор данных [WikiText-2](https://huggingface.co/datasets/wikitext): + +```python +from datasets import load_dataset + +dataset = load_dataset("wikitext", name="wikitext-2-raw-v1", split="train") + + +def get_training_corpus(): + for i in range(0, len(dataset), 1000): + yield dataset[i : i + 1000]["text"] +``` + +Функция `get_training_corpus()` - это генератор, который выдает батч из 1000 текстов, которые мы будем использовать для обучения токенизатора. + +🤗 Токенизаторы также можно обучать непосредственно на текстовых файлах. Вот как мы можем сгенерировать текстовый файл, содержащий все тексты/входы из WikiText-2, который мы можем использовать локально: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Далее мы покажем вам, как блок за блоком построить собственные токенизаторы BERT, GPT-2 и XLNet. Это даст нам пример каждого из трех основных алгоритмов токенизации: WordPiece, BPE и Unigram. Начнем с BERT! + +## Создание токенизатора WordPiece с нуля[[building-a-wordpiece-tokenizer-from-scratch]] + +Чтобы создать токенизатор с помощью библиотеки 🤗 Tokenizers, мы начнем с инстанцирования объектов `Tokenizer` и `model`, затем установим для их атрибутов `normalizer`, `pre_tokenizer`, `post_processor` и `decoder` нужные нам значения. + +Для этого примера мы создадим `Tokenizer` с моделью WordPiece: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Мы должны указать `unk_token`, чтобы модель знала, что возвращать, когда она встречает символы, которых раньше не видела. Другие аргументы, которые мы можем задать здесь, включают `vocab` нашей модели (мы собираемся обучать модель, поэтому нам не нужно его задавать) и `max_input_chars_per_word`, который определяет максимальную длину для каждого слова (слова длиннее переданного значения будут разбиты на части). + +Первым шагом токенизации является нормализация, поэтому начнем с нее. Поскольку BERT широко используется, существует `BertNormalizer` с классическими параметрами, которые мы можем установить для BERT: `lowercase` и `strip_accents`, которые не требуют пояснений; `clean_text` для удаления всех управляющих символов и замены повторяющихся пробелов на один; и `handle_chinese_chars`, который расставляет пробелы вокруг китайских символов. Чтобы повторить токенизатор `bert-base-uncased`, мы можем просто установить этот нормализатор: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Однако, как правило, при создании нового токенизатора у вас не будет доступа к такому удобному нормализатору, уже реализованному в библиотеке 🤗 Tokenizers, поэтому давайте посмотрим, как создать нормализатор BERT вручную. Библиотека предоставляет нормализатор `Lowercase` и нормализатор `StripAccents`, и вы можете комбинировать несколько нормализаторов с помощью `Sequence`: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Мы также используем нормализатор Unicode `NFD`, поскольку в противном случае нормализатор `StripAccents` не сможет правильно распознать акцентированные символы и, следовательно, не удалит их. + +Как мы уже видели ранее, мы можем использовать метод `normalize_str()` нормализатора, чтобы проверить, как он влияет на данный текст: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Далее** если вы протестируете две версии предыдущих нормализаторов на строке, содержащей символ Unicode `u"\u0085"`, то наверняка заметите, что эти два нормализатора не совсем эквивалентны. +Чтобы не усложнять версию с `normalizers.Sequence`, мы не включили в нее Regex-замены, которые требует `BertNormalizer`, когда аргумент `clean_text` установлен в `True`, что является поведением по умолчанию. Но не волнуйтесь: можно получить точно такую же нормализацию без использования удобного `BertNormalizer`, добавив два `normalizers.Replace` в последовательность нормализаторов. + + + +Далее следует этап предварительной токенизации. Опять же, есть готовый `BertPreTokenizer`, который мы можем использовать: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Или мы можем создать его с нуля: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Обратите внимание, что токенизатор `Whitespace` разделяет пробельные символы и все символы, которые не являются буквами, цифрами или символом подчеркивания, поэтому технически он разделяет пробельные символы и знаки пунктуации: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Если вы хотите выполнять разделение только по пробельным символам, то вместо этого следует использовать предварительный токенизатор `WhitespaceSplit`: + +```python +pre_tokenizer = pre_tokenizers.WhitespaceSplit() +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] +``` + +Как и в случае с нормализаторами, вы можете использовать `Sequence` для комбинирования нескольких предварительных токенизаторов: + +```python +pre_tokenizer = pre_tokenizers.Sequence( + [pre_tokenizers.WhitespaceSplit(), pre_tokenizers.Punctuation()] +) +pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") +``` + +```python out +[('Let', (0, 3)), ("'", (3, 4)), ('s', (4, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre', (14, 17)), + ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] +``` + +Следующий шаг в конвейере токенизации - обработка входных данных с помощью модели. Мы уже указали нашу модель в инициализации, но нам все еще нужно обучить ее, для чего потребуется `WordPieceTrainer`. Главное, что нужно помнить при инстанцировании тренера в 🤗 Tokenizers, это то, что вам нужно передать ему все специальные токены, которые вы собираетесь использовать - иначе он не добавит их в словарь, поскольку их нет в обучающем корпусе: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +Помимо указания `vocab_size` и `special_tokens`, мы можем задать `min_frequency` (количество раз, которое должен встретиться токен, чтобы быть включенным в словарь) или изменить `continuing_subword_prefix` (если мы хотим использовать что-то отличное от `##`). + +Чтобы обучить нашу модель с помощью итератора, который мы определили ранее, достаточно выполнить эту команду: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Мы также можем использовать текстовые файлы для обучения нашего токенизатора, что будет выглядеть следующим образом (предварительно мы повторно инициализируем модель с пустым `WordPiece`): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +В обоих случаях мы можем проверить работу токенизатора на тексте, вызвав метод `encode()`: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +Полученное `encoding` представляет собой `Encoding`, которое содержит все необходимые результаты работы токенизатора в разных атрибутах: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` и `overflowing`. + +Последний шаг в конвейере токенизации - постобработка. Нам нужно добавить токен `[CLS]` в начале и токен `[SEP]` в конце (или после каждого предложения, если у нас есть пара предложений). Для этого мы будем использовать `TemplateProcessor`, но сначала нам нужно узнать идентификаторы токенов `[CLS]` и `[SEP]` в словаре: + +```python +cls_token_id = tokenizer.token_to_id("[CLS]") +sep_token_id = tokenizer.token_to_id("[SEP]") +print(cls_token_id, sep_token_id) +``` + +```python out +(2, 3) +``` + +Чтобы написать шаблон для `TemplateProcessor`, мы должны указать, как обрабатывать одно предложение и пару предложений. Для обоих случаев мы указываем специальные токены, которые мы хотим использовать; первое (или одиночное) предложение представлено `$A`, а второе предложение (если кодируется пара) представлено `$B`. Для каждого из них (специальных токенов и предложений) мы также указываем соответствующий идентификатор типа токена (token type ID) после двоеточия. + +Таким образом, классический шаблон BERT определяется следующим образом: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single=f"[CLS]:0 $A:0 [SEP]:0", + pair=f"[CLS]:0 $A:0 [SEP]:0 $B:1 [SEP]:1", + special_tokens=[("[CLS]", cls_token_id), ("[SEP]", sep_token_id)], +) +``` + +Обратите внимание, что нам нужно передать идентификаторы специальных токенов, чтобы токенизатор мог правильно преобразовать их в их идентификаторы. + +Как только это будет добавлено, вернемся к нашему предыдущему примеру: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +И на паре предложений мы получаем правильный результат: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences.") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '...', '[SEP]', 'on', 'a', 'pair', 'of', 'sentences', '.', '[SEP]'] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Мы почти закончили создание этого токенизатора с нуля - остался последний шаг - добавить декодер: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Давайте проверим его на нашем предыдущем `encoding`: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Отлично! Мы можем сохранить наш токенизатор в единственном JSON-файле следующим образом: + +```python +tokenizer.save("tokenizer.json") +``` + +Затем мы можем загрузить этот файл в объект `Tokenizer` с помощью метода `from_file()`: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Чтобы использовать этот токенизатор в 🤗 Transformers, мы должны обернуть его в `PreTrainedTokenizerFast`. Мы можем использовать либо общий класс, либо, если наш токенизатор соответствует существующей модели, использовать этот класс (здесь `BertTokenizerFast`). Если вы используете этот урок для создания нового токенизатора, вам придется использовать первый вариант. + +Чтобы обернуть токенизатор в `PreTrainedTokenizerFast`, мы можем либо передать собранный нами токенизатор как `tokenizer_object`, либо передать сохраненный файл токенизатора как `tokenizer_file`. Главное помнить, что нам придется вручную задавать все специальные токены, поскольку класс не может определить из объекта `tokenizer`, какой токен является токеном маски, токеном `[CLS]` и т. д.: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # В качестве альтернативы можно загрузить из файла токенизатора. + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Если вы используете определенный класс токенизатора (например, `BertTokenizerFast`), вам нужно будет указать только специальные токены, которые отличаются от токенов по умолчанию (здесь их нет): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Затем вы можете использовать этот токенизатор, как и любой другой токенизатор 🤗 Transformers. Вы можете сохранить его с помощью метода `save_pretrained()` или загрузить на хаб с помощью метода `push_to_hub()`. + +Теперь, когда мы рассмотрели, как создать токенизатор WordPiece, давайте сделаем то же самое для токенизатора BPE. Мы будем двигаться немного быстрее, поскольку вы знаете все шаги, и подчеркнем только различия. + +## Создание токенизатора BPE с нуля[[building-a-bpe-tokenizer-from-scratch]] + +Теперь давайте создадим токенизатор GPT-2. Как и в случае с токенизатором BERT, мы начнем с инициализации `Tokenizer` с моделью BPE: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Также, как и в случае с BERT, мы могли бы инициализировать эту модель словарем, если бы он у нас был (в этом случае нам нужно было бы передать `vocab` и `merges`), но поскольку мы будем обучать с нуля, нам не нужно этого делать. Нам также не нужно указывать `unk_token`, потому что GPT-2 использует byte-level BPE, который не требует этого. + +GPT-2 не использует нормализатор, поэтому мы пропускаем этот шаг и переходим непосредственно к предварительной токенизации: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +Опция, которую мы добавили к `ByteLevel`, заключается в том, чтобы не добавлять пробел в начале предложения (в противном случае это происходит по умолчанию). Мы можем посмотреть на предварительную токенизацию примера текста, как было показано ранее: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") +``` + +```python out +[('Let', (0, 3)), ("'s", (3, 5)), ('Ġtest', (5, 10)), ('Ġpre', (10, 14)), ('-', (14, 15)), + ('tokenization', (15, 27)), ('!', (27, 28))] +``` + +Далее следует модель, которую нужно обучить. Для GPT-2 единственным специальным токеном является токен конца текста: + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Как и в случае с `WordPieceTrainer`, а также `vocab_size` и `special_tokens`, мы можем указать `min_frequency`, если хотим, или если у нас есть суффикс конца слова (например, ``), мы можем задать его с помощью `end_of_word_suffix`. + +Этот токенизатор также может быть обучен на текстовых файлах: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Давайте посмотрим на пример токенизации текста: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Мы применяем постобработку на уровне байтов для токенизатора GPT-2 следующим образом: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +Опция `trim_offsets = False` указывает постпроцессору, что мы должны оставить смещения токенов, начинающихся с 'Ġ', как есть: таким образом, начало смещения будет указывать на пробел перед словом, а не на первый символ слова (поскольку пробел технически является частью токена). Давайте посмотрим на результат с текстом, который мы только что закодировали, где `'Ġtest'` - это токен с индексом 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Наконец, мы добавляем декодер на уровне байтов: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +и мы сможем перепроверить, правильно ли он работает: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Отлично! Теперь, когда мы закончили, мы можем сохранить токенизатор, как раньше, и обернуть его в `PreTrainedTokenizerFast` или `GPT2TokenizerFast`, если мы хотим использовать его в 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +или: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +В качестве последнего примера мы покажем вам, как создать токенизатор Unigram с нуля. + +## Создание токенизатора Unigram с нуля[[building-a-unigram-tokenizer-from-scratch]] + +Теперь давайте построим токенизатор XLNet. Как и в предыдущих токенизаторах, мы начнем с инициализации `Tokenizer` с моделью Unigram: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Опять же, мы могли бы инициализировать эту модель словарем, если бы он у нас был. + +Для нормализации XLNet использует несколько замен (которые пришли из SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Он заменяет `` и '' with " и любую последовательность из двух или более пробелов на один пробел, а также удаляет ударения в токенезируемых текстах. + +Предварительный токенизатор, который должен использоваться для любого токенизатора SentencePiece, - это `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Мы можем посмотреть на предварительную токенизацию примера текста, как было показано ранее: + +```python +tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") +``` + +```python out +[("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] +``` + +Далее следует модель, которую нужно обучить. В XLNet довольно много специальных токенов: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` +Очень важный аргумент, который не стоит забывать для `UnigramTrainer` - это `unk_token`. Мы также можем передавать другие аргументы, специфичные для алгоритма Unigram, такие как `shrinking_factor` для каждого шага удаления токенов (по умолчанию 0.75) или `max_piece_length` для указания максимальной длины данного токена (по умолчанию 16). + +Этот токенизатор также может быть обучен на текстовых файлах: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` +Давайте посмотрим на токенизацию примера текста: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Особенностью XLNet является то, что он помещает токен `` в конец предложения с идентификатором типа 2 (чтобы отличить его от других токенов). В результате он помещается слева. Мы можем разобраться со всеми специальными токенами и идентификаторами типов токенов с помощью шаблона, как в BERT, но сначала нам нужно получить идентификаторы токенов `` и ``: + +```python +cls_token_id = tokenizer.token_to_id("") +sep_token_id = tokenizer.token_to_id("") +print(cls_token_id, sep_token_id) +``` + +```python out +0 1 +``` + +Шаблон выглядит следующим образом: + +```python +tokenizer.post_processor = processors.TemplateProcessing( + single="$A:0 :0 :2", + pair="$A:0 :0 $B:1 :1 :2", + special_tokens=[("", sep_token_id), ("", cls_token_id)], +) +``` + +И мы можем проверить, как это работает, закодировав пару предложений: + +```python +encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") +print(encoding.tokens) +print(encoding.type_ids) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.', '.', '.', '', '▁', 'on', '▁', 'a', '▁pair', + '▁of', '▁sentence', 's', '!', '', ''] +[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] +``` + +Наконец, мы добавляем декодер `Metaspace`: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +и мы закончили работу с этим токенизатором! Мы можем сохранить токенизатор, как и раньше, и обернуть его в `PreTrainedTokenizerFast` или `XLNetTokenizerFast`, если мы хотим использовать его в 🤗 Transformers. При использовании `PreTrainedTokenizerFast` следует обратить внимание на то, что помимо специальных токенов, нам нужно указать библиотеке 🤗 Transformers на то, чтобы они располагались слева: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="", + eos_token="", + unk_token="", + pad_token="", + cls_token="", + sep_token="", + mask_token="", + padding_side="left", +) +``` + +Или альтернативно: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Теперь, когда вы увидели, как различные блоки используются для создания существующих токенизаторов, вы должны быть в состоянии написать любой токенизатор, который вы хотите, с помощью библиотеки 🤗 Tokenizers и использовать его в 🤗 Transformers. diff --git a/chapters/ru/chapter6/9.mdx b/chapters/ru/chapter6/9.mdx new file mode 100644 index 000000000..da0adfb0a --- /dev/null +++ b/chapters/ru/chapter6/9.mdx @@ -0,0 +1,16 @@ +# Токенизаторы, проверка![[tokenizers-check]] + + + +Отличная работа по завершению этой главы! + +После этого глубокого погружения в токенизаторы вы должны: + +- Уметь обучать новый токенизатор, используя старый в качестве шаблона. +- Понимать, как использовать смещения для сопоставления позиций токенов с их исходным положением в тексте +- Знать различия между BPE, WordPiece и Unigram. +- Уметь комбинировать блоки, предоставляемые библиотекой 🤗 Tokenizers, для создания собственного токенизатора +- Уметь использовать собственный токенизатор в библиотеке 🤗 Transformers diff --git a/chapters/ru/chapter7/1.mdx b/chapters/ru/chapter7/1.mdx new file mode 100644 index 000000000..47cf244b5 --- /dev/null +++ b/chapters/ru/chapter7/1.mdx @@ -0,0 +1,38 @@ + + +# Введение[[introduction]] + + + +В [Главе 3](../chapter3/1) вы узнали, как дообучить модель для классификации текстов. В этой главе мы рассмотрим следующие общие задачи NLP: + +- Классификация токенов (Token classification) +- Маскированное языковое моделирование (Masked language modeling, например, BERT) +- Резюмирование текста (Summarization) +- Перевод (Translation) +- Предварительное обучение каузального языкового моделирования (Causal language modeling, например, GPT-2) +- Ответы на вопросы (Question answering) + +{#if fw === 'pt'} + +Для этого вам понадобится использовать все, что вы узнали об API `Trainer` и библиотеке 🤗 Accelerate в [Главе 3](../chapter3/1), библиотеке 🤗 Datasets в [Главе 5](../chapter5/1) и библиотеке 🤗 Tokenizers в [Главе 6](../chapter6/1). Мы также загрузим наши результаты в хаб моделей, как мы делали это в [Главе 4](../chapter4/1), так что это действительно глава,в которой все собирается воедино! + +Каждый раздел можно читать независимо друг от друга, и в нем вы узнаете, как обучить модель с помощью API `Trainer` или с помощью собственного цикла обучения, используя 🤗 Accelerate. Вы можете пропустить любую часть и сосредоточиться на той, которая вас больше всего интересует: API `Trainer` отлично подходит для того, чтобы дообучить или обучить вашу модель, не беспокоясь о том, что происходит за кулисами, а цикл обучения с `Accelerate` позволит вам легче настроить любую часть, которую вы хотите. + +{:else} + +Для этого вам понадобится использовать все, что вы узнали об обучении моделей с помощью Keras API в [Главе 3](../chapter3/1), библиотеке 🤗 Datasets в [Главе 5](../chapter5/1) и библиотеке 🤗 Tokenizers в [Главе 6](../chapter6/1). Мы также загрузим наши результаты в хаб моделей, как мы делали это в [Главе 4](../chapter4/1), так что это действительно глава, в которой все собирается воедино! + +Каждый раздел можно читать самостоятельно. + +{/if} + + + + +Если вы будете читать разделы по порядку, то заметите, что в них довольно много общего в коде и тексте. Повторение сделано намеренно, чтобы вы могли погрузиться (или вернуться позже) в любую интересующую вас задачу и найти полный рабочий пример. + + diff --git a/chapters/ru/chapter7/2.mdx b/chapters/ru/chapter7/2.mdx new file mode 100644 index 000000000..edc843490 --- /dev/null +++ b/chapters/ru/chapter7/2.mdx @@ -0,0 +1,981 @@ + + +# Классификация токенов[[token-classification]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Первое приложение, которое мы рассмотрим, - это классификация токенов. Эта общая задача охватывает любую проблему, которую можно сформулировать как "присвоение метки каждому токену в предложении", например: + +- **Распознавание именованных сущностей (Named entity recognition - NER)**: Поиск сущностей (например, лиц, мест или организаций) в предложении. Это можно сформулировать как приписывание метки каждому токену, имея один класс для сущности и один класс для "нет сущности". +- **Морфологическая разметка (Part-of-speech tagging - POS)**: Пометить каждое слово в предложении как соответствующее определенной части речи (например, существительное, глагол, прилагательное и т. д.). +- **Выделение токенов (Chunking)**: Поиск токенов, принадлежащих одной и той же сущности. Эта задача (которая может быть объединена с POS или NER) может быть сформулирована как присвоение одной метки (обычно `B-`) всем токенам, которые находятся в начале фрагмента текста, другой метки (обычно `I-`) - токенам, которые находятся внутри фрагмента текста, и третьей метки (обычно `O`) - токенам, которые не принадлежат ни к одному фрагменту. + + + +Конечно, существует множество других типов задач классификации токенов; это лишь несколько показательных примеров. В этом разделе мы дообучим модель (BERT) для задачи NER, которая затем сможет вычислять прогнозы, подобные этому: + + + + +One-hot encoded labels for question answering. + + + +Вы можете найти модель, которую мы обучим и загрузим на хаб, и перепроверить ее предсказания [здесь](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn). + +## Подготовка данных[[preparing-the-data]] + +Прежде всего, нам нужен набор данных, подходящий для классификации токенов. В этом разделе мы будем использовать [набор данных CoNLL-2003](https://huggingface.co/datasets/conll2003), который содержит новости от Reuters. + + + +💡 Если ваш набор данных состоит из текстов, часть которых состоит из слов с соответствующими метками, вы сможете адаптировать описанные здесь процедуры обработки данных к своему набору данных. Обратитесь к [Главе 5](../chapter5/1), если вам нужно освежить в памяти то, как загружать собственные данные в `Dataset`. + + + +### Датасет CoNLL-2003[[the-conll-2003-dataset]] + +Для загрузки датасета CoNLL-2003 мы используем метод `load_dataset()` из библиотеки 🤗 Datasets: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Это позволит загрузить и кэшировать датасет, как мы видели в [Главе 3](../chapter3/1) для датасета GLUE MRPC. Изучение этого объекта показывает нам присутствующие столбцы и части тренировочного, проверочного и тестового наборов: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 14041 + }) + validation: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3250 + }) + test: Dataset({ + features: ['chunk_tags', 'id', 'ner_tags', 'pos_tags', 'tokens'], + num_rows: 3453 + }) +}) +``` + +В частности, мы видим, что датасет содержит метки для трех задач, о которых мы говорили ранее: NER, POS и chunking. Существенным отличием от других датасетов является то, что входные тексты представлены не как предложения или документы, а как списки слов (последний столбец называется `tokens`, но он содержит слова в том смысле, что это предварительно токинизированные входные данные, которые еще должны пройти через токенизатор для токенизации по подсловам). + +Давайте посмотрим на первый элемент обучающего набора: + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Поскольку мы хотим выполнить распознавание именованных сущностей, мы изучим теги NER: + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Это метки в виде целых чисел, готовые для обучения, но они не всегда полезны, когда мы хотим проанализировать данные. Как и в случае с классификацией текста, мы можем получить доступ к соответствию между этими целыми числами и названиями меток, посмотрев на атрибут `features` нашего датасета: + +```py +ner_feature = raw_datasets["train"].features["ner_tags"] +ner_feature +``` + +```python out +Sequence(feature=ClassLabel(num_classes=9, names=['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'], names_file=None, id=None), length=-1, id=None) +``` + +Таким образом, этот столбец содержит элементы, которые являются последовательностями `ClassLabel`. Тип элементов последовательности указан в атрибуте `feature` этого `ner_feature`, и мы можем получить доступ к списку имен, посмотрев на атрибут `names` этого `feature`: + +```py +label_names = ner_feature.feature.names +label_names +``` + +```python out +['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] +``` + +Мы уже видели эти метки при изучении конвейера `token-classification` в [Главе 6](../chapter6/3), но для краткости напомним: + +- `O` означает, что слово не соответствует какой-либо сущности. +- `B-PER`/`I-PER` означает, что слово соответствует началу/находится внутри сущности персоны *person*. +- `B-ORG`/`I-ORG` означает, что слово соответствует началу/находится внутри сущности *organization*. +- `B-LOC`/`I-LOC` означает, что слово соответствует началу/находится внутри сущности *location*. +- `B-MISC`/`I-MISC` означает, что слово соответствует началу/находится внутри сущности *miscellaneous*. + +Теперь декодирование меток, которые мы видели ранее, дает нам следующее: + +```python +words = raw_datasets["train"][0]["tokens"] +labels = raw_datasets["train"][0]["ner_tags"] +line1 = "" +line2 = "" +for word, label in zip(words, labels): + full_label = label_names[label] + max_length = max(len(word), len(full_label)) + line1 += word + " " * (max_length - len(word) + 1) + line2 += full_label + " " * (max_length - len(full_label) + 1) + +print(line1) +print(line2) +``` + +```python out +'EU rejects German call to boycott British lamb .' +'B-ORG O B-MISC O O O B-MISC O O' +``` + +В качестве примера смешивания меток `B-` и `I-`, вот что дает тот же код для элемента обучающего множества с индексом 4: + +```python out +'Germany \'s representative to the European Union \'s veterinary committee Werner Zwingmann said on Wednesday consumers should buy sheepmeat from countries other than Britain until the scientific advice was clearer .' +'B-LOC O O O O B-ORG I-ORG O O O B-PER I-PER O O O O O O O O O O O B-LOC O O O O O O O' +``` + +Как мы видим, сущностям, состоящим из двух слов, например "European Union" и "Werner Zwingmann", присваивается метка `B-` для первого слова и метка `I-` для второго. + + + +✏️ **Попробуйте!** Выведите те же два предложения с метками POS или chunking. + + + +### Обработка данных[[processing-the-data]] + + + +Как обычно, наши тексты должны быть преобразованы в идентификаторы токенов, прежде чем модель сможет понять их смысл. Как мы видели в [Главе 6](../chapter6/), существенным отличием задачи классификации токенов является то, что у нас есть предварительно токенизированные входные данные. К счастью, API токенизатора справляется с этим довольно легко; нам просто нужно предупредить `tokenizer` специальным флагом. + +Для начала давайте создадим объект `tokenizer`. Как мы уже говорили, мы будем использовать предварительно обученную модель BERT, поэтому начнем с загрузки и кэширования соответствующего токенизатора: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Вы можете заменить `model_checkpoint` на любую другую модель из [Hub](https://huggingface.co/models) или на локальную папку, в которой вы сохранили предварительно обученную модель и токенизатор. Единственное ограничение - токенизатор должен быть создан с помощью библиотеки 🤗 Tokenizers, поэтому существует "быстрая" версия. Вы можете увидеть все архитектуры, которые поставляются с быстрой версией в [этой большой таблице](https://huggingface.co/transformers/#supported-frameworks), а чтобы проверить, что используемый вами объект `tokenizer` действительно поддерживается 🤗 Tokenizers, вы можете посмотреть на его атрибут `is_fast`: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Для токенизации предварительно токинизированного ввода мы можем использовать наш `tokenizer`, как обычно, просто добавив `is_split_into_words=True`: + +```py +inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) +inputs.tokens() +``` + +```python out +['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] +``` + +Как мы видим, токенизатор добавил специальные токены, используемые моделью (`[CLS]` в начале и `[SEP]` в конце), и оставил большинство слов нетронутыми. Слово `lamb`, однако, было токенизировано на два подслова, `la` и `##mb`. Это вносит несоответствие между нашими входными данными и метками: список меток состоит всего из 9 элементов, в то время как наши входные данные теперь содержат 12 токенов. Учесть специальные токены легко (мы знаем, что они находятся в начале и в конце), но нам также нужно убедиться, что мы выровняли все метки с соответствующими словами. + +К счастью, поскольку мы используем быстрый токенизатор, у нас есть доступ к суперспособностям 🤗 Tokenizers, что означает, что мы можем легко сопоставить каждый токен с соответствующим словом (как показано в [Глава 6](../chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Немного поработав, мы сможем расширить список меток, чтобы он соответствовал токенам. Первое правило, которое мы применим, заключается в том, что специальные токены получают метку `-100`. Это связано с тем, что по умолчанию `-100` - это индекс, который игнорируется в функции потерь, которую мы будем использовать (кросс-энтропия). Затем каждый токен получает ту же метку, что и токен, с которого началось слово, в котором он находится, поскольку они являются частью одной и той же сущности. Для токенов, находящихся внутри слова, но не в его начале, мы заменяем `B-` на `I-` (поскольку такие токены не являются началом сущности): + +```python +def align_labels_with_tokens(labels, word_ids): + new_labels = [] + current_word = None + for word_id in word_ids: + if word_id != current_word: + # Начало нового слова! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Специальный токен + new_labels.append(-100) + else: + # То же слово, что и предыдущий токен + label = labels[word_id] + # Если метка B-XXX, заменяем ее на I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Давайте опробуем это на нашем первом предложении: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +word_ids = inputs.word_ids() +print(labels) +print(align_labels_with_tokens(labels, word_ids)) +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +``` + +Как мы видим, наша функция добавила `-100` для двух специальных токенов в начале и в конце и новый `0` для нашего слова, которое было разбито на две части. + + + +✏️ **Попробуйте!** Некоторые исследователи предпочитают назначать только одну метку на слово и присваивать `-100` другим подтокенам в данном слове. Это делается для того, чтобы длинные слова, часть которых состоит из множества субтокенов, не вносили значительный вклад в потери. + + + +Чтобы предварительно обработать весь наш датасет, нам нужно провести токенизацию всех входных данных и применить `align_labels_with_tokens()` ко всем меткам. Чтобы воспользоваться преимуществами скорости нашего быстрого токенизатора, лучше всего токенизировать много текстов одновременно, поэтому мы напишем функцию, которая обрабатывает список примеров и использует метод `Dataset.map()` с параметром `batched=True`. Единственное отличие от нашего предыдущего примера заключается в том, что функция `word_ids()` должна получить индекс примера, идентификаторы слов которого нам нужны, с учётом того что входными данными для токенизатора являются списки текстов (или, в нашем случае, списки слов), поэтому мы добавляем и это: + +```py +def tokenize_and_align_labels(examples): + tokenized_inputs = tokenizer( + examples["tokens"], truncation=True, is_split_into_words=True + ) + all_labels = examples["ner_tags"] + new_labels = [] + for i, labels in enumerate(all_labels): + word_ids = tokenized_inputs.word_ids(i) + new_labels.append(align_labels_with_tokens(labels, word_ids)) + + tokenized_inputs["labels"] = new_labels + return tokenized_inputs +``` + +Обратите внимание, что мы еще не добавляли во входные данные дополняющие токены; мы сделаем это позже, при создании батчей с помощью коллатора данных. + +Теперь мы можем применить всю эту предварительную обработку к другим частям нашего датасета: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Мы сделали самую сложную часть! Теперь, когда данные прошли предварительную обработку, само обучение будет выглядеть примерно так, как мы делали это в [Главе 3](../chapter3/1). + +{#if fw === 'pt'} + +## Дообучение модели с помощью API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]] + +Фактический код, использующий `Trainer`, будет таким же, как и раньше; единственные изменения - это способ объединения данных в батч и функция вычисления метрики. + +{:else} + +## Дообучение модели с помощью Keras[[fine-tuning-the-model-with-keras]] + +Фактический код, использующий Keras, будет очень похож на предыдущий; единственные изменения - это способ объединения данных в батч и функция вычисления метрики. + +{/if} + + +### Сопоставление данных[[data-collation]] + +Мы не можем просто использовать `DataCollatorWithPadding`, как в [Главе 3](../chapter3/1), потому что в этом случае дополняются только входные данные (идентификаторы входов, маска внимания и идентификаторы типов токенов). Здесь наши метки должны быть дополнены точно так же, как и входы, чтобы они оставались одного размера, используя `-100` в качестве значения, чтобы соответствующие прогнозы игнорировались при вычислении потерь. + +Все это делает [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Как и `DataCollatorWithPadding`, он принимает `токенизатор`, используемый для предварительной обработки входных данных: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification(tokenizer=tokenizer) +``` + +{:else} + +```py +from transformers import DataCollatorForTokenClassification + +data_collator = DataCollatorForTokenClassification( + tokenizer=tokenizer, return_tensors="tf" +) +``` + +{/if} + +Чтобы проверить его на нескольких примерах, мы можем просто вызвать его на списке примеров из нашего токенизированного обучающего набора: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) +batch["labels"] +``` + +```python out +tensor([[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100], + [-100, 1, 2, -100, -100, -100, -100, -100, -100, -100, -100, -100]]) +``` + +Давайте сравним это с метками для первого и второго элементов в нашем датасете: + +```py +for i in range(2): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] +[-100, 1, 2, -100] +``` + +{#if fw === 'pt'} + +Как мы видим, второй набор меток был дополнен до длины первого с помощью значения `-100`. + +{:else} + +Наш коллатор данных готов к работе! Теперь давайте используем его для создания датасета `tf.data.Dataset` с помощью метода `to_tf_dataset()`. Вы также можете использовать `model.prepare_tf_dataset()`, чтобы сделать это с меньшим количеством кода - вы увидите это в некоторых других разделах этой главы. + +```py +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) + +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["attention_mask", "input_ids", "labels", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + + + Следующая остановка: сама модель. + +{/if} + +{#if fw === 'tf'} + +### Определение модели[[defining-the-model]] + +Поскольку мы работаем над проблемой классификации токенов, мы будем использовать класс `TFAutoModelForTokenClassification`. Главное, что нужно помнить при определении этой модели, - это передать информацию о количестве имеющихся у нас меток. Проще всего передать это число с помощью аргумента `num_labels`, но если мы хотим получить красивый виджет инференса, подобный тому, что мы видели в начале этого раздела, то лучше задать правильные соответствия меток. + +Они должны быть заданы двумя словарями, `id2label` и `label2id`, которые содержат отображение идентификатора в метку и наоборот: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Теперь мы можем просто передать их в метод `TFAutoModelForTokenClassification.from_pretrained()`, и они будут заданы в конфигурации модели, затем правильно сохранены и загружены в Hub: + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Как и при определении `TFAutoModelForSequenceClassification` в [Главе 3](../chapter3/1), при создании модели выдается предупреждение о том, что некоторые веса не были использованы (веса из предварительно обученной головы), а другие веса инициализированы случайно (веса из новой головы классификации токенов), и что эту модель нужно обучить. Мы сделаем это через минуту, но сначала давайте перепроверим, что наша модель имеет правильное количество меток: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Если у вас есть модель с неправильным количеством меток, то при последующем вызове `model.fit()` вы получите непонятную ошибку. Это может вызвать раздражение при отладке, поэтому обязательно выполните эту проверку, чтобы убедиться, что у вас есть ожидаемое количество меток. + + + +### Дообучение модели[[fine-tuning-the-model]] + +Теперь мы готовы к обучению нашей модели! Однако сначала нам нужно сделать еще немного работы: войти в Hugging Face и определить гиперпараметры обучения. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. + +Если вы работаете не в блокноте, просто введите следующую строку в терминале: + +```bash +huggingface-cli login +``` + +После входа в аккаунт мы можем подготовить все необходимое для компиляции нашей модели. 🤗 Transformers предоставляет удобную функцию `create_optimizer()`, которая создаст вам оптимизатор `AdamW` с соответствующими настройками затухания весов и затухания скорости обучения, что позволит улучшить качество вашей модели по сравнению со встроенным оптимизатором `Adam`: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Обучение со смешанной точностью float16 +# Закомментируйте эту строку, если вы используете GPU, которому это не принесет никаких преимуществ +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное +# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это разбитое на батчи tf.data.Dataset, +# а не оригинальный датасет Hugging Face, поэтому его len() уже равен num_samples // batch_size. +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) +``` + +Обратите внимание, что мы не указываем аргумент `loss` в `compile()`. Это связано с тем, что модели могут вычислять потери внутри себя - если вы компилируете без потерь и предоставляете свои метки во входном словаре (как мы делаем в наших датасетах), то модель будет обучаться, используя эти внутренние потери, которые будут соответствовать задаче и типу выбранной вами модели. + +Далее мы определяем `PushToHubCallback` для загрузки нашей модели в Hub во время обучения модели с помощью этого обратного вызова: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-ner", tokenizer=tokenizer) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +С помощью аргумента `hub_model_id` можно указать полное имя репозитория, в который вы хотите передать модель (в частности, этот аргумент нужно использовать, чтобы передать модель в организацию). Например, когда мы отправили модель в [организацию `huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/bert-finetuned-ner"`. По умолчанию используемое хранилище будет находиться в вашем пространстве имен и называться в соответствии с заданной вами выходной директорией, например `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при вызове `model.fit()` и должны будете задать новое имя. + + + +Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на хаб в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине. + +На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи классификации токенов - поздравляем! Но насколько хороша наша модель на самом деле? Чтобы выяснить это, нам следует оценить некоторые метрики. + +{/if} + + +### Метрики[[metrics]] + +{#if fw === 'pt'} + +Чтобы `Trainer` вычислял метрику каждую эпоху, нам нужно определить функцию `compute_metrics()`, которая принимает массивы прогнозов и меток и возвращает словарь с именами и значениями метрик. + +Традиционно для оценки прогнозирования классификации токенов используется библиотека [*seqeval*](https://github.com/chakki-works/seqeval). Чтобы использовать эту метрику, сначала нужно установить библиотеку *seqeval*: + +```py +!pip install seqeval +``` + +Мы можем загрузить ее с помощью функции `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1): + +{:else} + +Традиционно для оценки прогнозирования классификации токенов используется библиотека [*seqeval*](https://github.com/chakki-works/seqeval). Чтобы использовать эту метрику, сначала нужно установить библиотеку *seqeval*: + +```py +!pip install seqeval +``` + +Мы можем загрузить ее с помощью функции `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1): + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +Эта метрика ведет себя не так, как стандартная accuracy: на самом деле она принимает списки меток как строки, а не как целые числа, поэтому нам нужно полностью декодировать прогноз и метки перед передачей их в метрику. Давайте посмотрим, как это работает. Сначала мы получим метки для нашего первого обучающего примера: + +```py +labels = raw_datasets["train"][0]["ner_tags"] +labels = [label_names[i] for i in labels] +labels +``` + +```python out +['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] +``` + +Затем мы можем создать фальшивые прогнозы для них, просто изменив значение в индексе 2: + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Обратите внимание, что метрика принимает список прогнозов (не только один) и список меток. Вот результат: + +```python out +{'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, + 'ORG': {'precision': 1.0, 'recall': 1.0, 'f1': 1.0, 'number': 1}, + 'overall_precision': 1.0, + 'overall_recall': 0.67, + 'overall_f1': 0.8, + 'overall_accuracy': 0.89} +``` + +{#if fw === 'pt'} + +Она возвращает огромное количество информации! Мы получаем оценки precision, recall и F1 для каждой отдельной сущности, а также в целом. Для расчета метрик мы сохраним только общую оценку, но вы можете настроить функцию `compute_metrics()` так, чтобы она возвращала все метрики, которые вы хотите получить. + +Эта функция `compute_metrics()` сначала берет argmax логитов, чтобы преобразовать их в прогнозы (как обычно, логиты и вероятности расположены в том же порядке, поэтому нам не нужно применять softmax). Затем нам нужно преобразовать метки и прогнозы из целых чисел в строки. Мы удаляем все значения, для которых метка равна `-100`, а затем передаем результаты в метод `metric.compute()`: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Удаляем игнорируемый индекс (специальные токены) и преобразуем в метки + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + all_metrics = metric.compute(predictions=true_predictions, references=true_labels) + return { + "precision": all_metrics["overall_precision"], + "recall": all_metrics["overall_recall"], + "f1": all_metrics["overall_f1"], + "accuracy": all_metrics["overall_accuracy"], + } +``` + +Теперь, когда это сделано, мы почти готовы к определению нашего `Trainer`. Нам просто нужна `model`, чтобы дообучить ее! + +{:else} + +Она возвращает огромное количество информации! Мы получаем оценки precision, recall и F1 для каждой отдельной сущности, а также в целом. Теперь давайте посмотрим, что произойдет, если мы попробуем использовать реальные прогнозы модели для вычисления реальных оценок. + +TensorFlow не любит конкатенировать наши прогнозы, поскольку они имеют переменную длину последовательности. Это означает, что мы не можем просто использовать `model.predict()` - но это нас не остановит. Мы будем получать прогнозы по батчу за раз и конкатенировать их в один большой длинный список по мере продвижения, отбрасывая токены `-100`, которые указывают на маскирование/дополнение, а затем вычислять метрики для списка в конце: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict_on_batch(batch)["logits"] + labels = batch["labels"] + predictions = np.argmax(logits, axis=-1) + for prediction, label in zip(predictions, labels): + for predicted_idx, label_idx in zip(prediction, label): + if label_idx == -100: + continue + all_predictions.append(label_names[predicted_idx]) + all_labels.append(label_names[label_idx]) +metric.compute(predictions=[all_predictions], references=[all_labels]) +``` + + +```python out +{'LOC': {'precision': 0.91, 'recall': 0.92, 'f1': 0.91, 'number': 1668}, + 'MISC': {'precision': 0.70, 'recall': 0.79, 'f1': 0.74, 'number': 702}, + 'ORG': {'precision': 0.85, 'recall': 0.90, 'f1': 0.88, 'number': 1661}, + 'PER': {'precision': 0.95, 'recall': 0.95, 'f1': 0.95, 'number': 1617}, + 'overall_precision': 0.87, + 'overall_recall': 0.91, + 'overall_f1': 0.89, + 'overall_accuracy': 0.97} +``` + +Как ваша модель показала себя по сравнению с нашей? Если вы получили похожие цифры, значит, ваше обучение прошло успешно! + +{/if} + +{#if fw === 'pt'} + +### Определение модели[[defining-the-model]] + +Поскольку мы работаем над проблемой классификации токенов, мы будем использовать класс `AutoModelForTokenClassification`. Главное, что нужно помнить при определении этой модели, - это передать информацию о количестве имеющихся у нас меток. Проще всего передать это число с помощью аргумента `num_labels`, но если мы хотим получить красивый виджет инференса, подобный тому, что мы видели в начале этого раздела, то лучше задать правильное сопоставление меток. + +Оно должно задаваться двумя словарями, `id2label` и `label2id`, которые содержат соответствие между идентификатором и меткой и наоборот: + +```py +id2label = {i: label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Теперь мы можем просто передать их в метод `AutoModelForTokenClassification.from_pretrained()`, и они будут заданы в конфигурации модели, а затем правильно сохранены и загружены в Hub: + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Как и в случае определения `AutoModelForSequenceClassification` в [Главе 3](../chapter3/1), при создании модели выдается предупреждение о том, что некоторые веса не были использованы (те, что были получены из предварительно обученной головы), а другие инициализированы случайно (те, что были получены из новой головы классификации токенов), и что эту модель необходимо обучить. Мы сделаем это через минуту, но сначала давайте перепроверим, что наша модель имеет правильное количество меток: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Если у вас есть модель с неправильным количеством меток, то при последующем вызове метода `Trainer.train()` вы получите непонятную ошибку (что-то вроде "CUDA error: device-side assert triggered"). Это главная причина ошибок, о которых сообщают пользователи, поэтому обязательно выполните эту проверку, чтобы убедиться, что у вас есть ожидаемое количество меток. + + + +### Дообучение модели[[fine-tuning-the-model]] + +Теперь мы готовы к обучению нашей модели! Нам осталось сделать две последние вещи, прежде чем мы определим наш `Trainer`: войти в Hugging Face и определить наши аргументы для обучения. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. + +Если вы работаете не в ноутбуке, просто введите следующую строку в терминале: + +```bash +huggingface-cli login +``` + +Как только это будет сделано, мы сможем определить наши `TrainingArguments`: + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-ner", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + push_to_hub=True, +) +``` + +Большинство из них вы уже видели: мы задаем некоторые гиперпараметры (например, скорость обучения, количество эпох для обучения и затухание весов) и указываем `push_to_hub=True`, чтобы указать, что мы хотим сохранить модель и оценить ее в конце каждой эпохи, а также что мы хотим загрузить наши результаты в Model Hub. Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите передать модель (в частности, этот аргумент нужно использовать, чтобы передать модель в организацию). Например, когда мы передавали модель в [организацию `huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/bert-finetuned-ner"` в `TrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, так что в нашем случае это будет `"sgugger/bert-finetuned-ner"`. + + + +💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите передать модель. Если это не так, вы получите ошибку при определении вашего `Trainer` и должны будете задать новое имя. + + + +Наконец, мы просто передаем все в `Trainer` и запускаем обучение: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation"], + data_collator=data_collator, + compute_metrics=compute_metrics, + tokenizer=tokenizer, +) +trainer.train() +``` + +Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается в Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине. + +После завершения обучения мы используем метод `push_to_hub()`, чтобы убедиться, что загружена самая последняя версия модели: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Эта команда возвращает URL только что выполненного commit, если вы хотите его проверить: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + + `Trainer` также создает черновик карточки модели со всеми результатами оценки и загружает его. На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи классификации токенов - поздравляем! + + Если вы хотите более глубоко погрузиться в цикл обучения, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate. + +## Индивидуальный цикл обучения[[a-custom-training-loop]] + +Теперь давайте рассмотрим полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет очень похож на тот, что мы делали в [Главе 3](../chapter3/4), с некоторыми изменениями для оценки. + +### Подготовка всего к обучению[[preparing-everything-for-training]] + +Сначала нам нужно создать `DataLoader` для наших датасетов. Мы используем наш `data_collator` в качестве `collate_fn` и перемешиваем обучающий набор, но не валидационный: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +Затем мы повторно инстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучать модель, а снова начинаем с предварительно обученной модели BERT: + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Тогда нам понадобится оптимизатор. Мы будем использовать классический `AdamW`, который похож на `Adam`, но с исправлениями в способе применения затухания весов: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Если вы обучаетесь на TPU, вам нужно будет перенести весь код, начиная с ячейки выше, в специальную функцию обучения. Подробнее смотрите [Главу 3](../chapter3/1). + + + +Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит его длину. Мы используем классический линейный планировшик скорости обучения до 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +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, +) +``` + +Наконец, чтобы передать нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала авторизуйтесь в Hugging Face, если вы еще не авторизованы. Мы определим имя репозитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный выбор; он просто должен содержать ваше имя пользователя, что и делает функция `get_full_repo_name()`): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-ner-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-ner-accelerate' +``` + +Затем мы можем клонировать этот репозиторий в локальную папку. Если она уже существует, эта локальная папка должна быть существующим клоном репозитория, с которым мы работаем: + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи. + +### Цикл обучения[[training-loop]] + +Теперь мы готовы написать полный цикл обучения. Чтобы упростить его оценочную часть, мы определяем функцию `postprocess()`, которая принимает прогнозы и метки и преобразует их в списки строк, как того ожидает наш объект `metric`: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Удаляем игнорируемый индекс (специальные токены) и преобразуем в метки + true_labels = [[label_names[l] for l in label if l != -100] for label in labels] + true_predictions = [ + [label_names[p] for (p, l) in zip(prediction, label) if l != -100] + for prediction, label in zip(predictions, labels) + ] + return true_labels, true_predictions +``` + +Затем мы можем написать цикл обучения. После определения прогресс-бара, чтобы следить за ходом обучения, цикл состоит из трех частей: + +- Само обучение представляет собой классическую итерацию по `train_dataloader`, прямой проход по модели, затем обратный проход и шаг оптимизатора. +- Оценка, в которой есть новшество после получения выходов нашей модели на батче: поскольку два процесса могли дополнять входы и метки до разных форм, нам нужно использовать `accelerator.pad_across_processes()`, чтобы сделать прогнозы и метки одинаковой формы перед вызовом метода `gather()`. Если мы этого не сделаем, оценка либо завершится с ошибкой, либо зависнет навсегда. Затем мы отправляем результаты в `metric.add_batch()` и вызываем `metric.compute()` после завершения цикла оценки. +- Сохранение и загрузка, где мы сначала сохраняем модель и токенизатор, а затем вызываем `repo.push_to_hub()`. Обратите внимание, что мы используем аргумент `blocking=False`, чтобы указать библиотеке 🤗 Hub на выполнение push в асинхронном процессе. Таким образом, обучение продолжается нормально, а эта (длинная) инструкция выполняется в фоновом режиме. + +Вот полный код цикла обучения: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Обучение + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Оценка + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Необходимо добавить предсказания и метки для gather + predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(predictions) + labels_gathered = accelerator.gather(labels) + + true_predictions, true_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=true_predictions, references=true_labels) + + results = metric.compute() + print( + f"epoch {epoch}:", + { + key: results[f"overall_{key}"] + for key in ["precision", "recall", "f1", "accuracy"] + }, + ) + + # Сохранение и загрузка + 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 + ) +``` + +Если вы впервые видите модель, сохраненную с помощью 🤗 Accelerate, давайте посмотрим на три строки кода, которые идут вместе с ним: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +Первая строка не требует пояснений: она указывает всем процессам подождать, пока все не окажутся на этой стадии, прежде чем продолжить работу. Это нужно для того, чтобы убедиться, что у нас одна и та же модель в каждом процессе перед сохранением. Затем мы берем `unwrapped_model`, которая является базовой моделью, которую мы определили. Метод `accelerator.prepare()` изменяет модель для работы в распределенном обучении, поэтому у нее больше не будет метода `save_pretrained()`; метод `accelerator.unwrap_model()` отменяет этот шаг. Наконец, мы вызываем `save_pretrained()`, но указываем этому методу использовать `accelerator.save()` вместо `torch.save()`. + +После того как это будет сделано, у вас должна получиться модель, выдающая результаты, очень похожие на те, что были обучены с помощью `Trainer`. Вы можете посмотреть модель, которую мы обучили с помощью этого кода, на [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете напрямую реализовать их, отредактировав код, показанный выше! + +{/if} + +## Использование дообученной модели[[using-the-fine-tuned-model]] + +Мы уже показали вам, как можно использовать модель, которую мы дообучили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нужно просто указать соответствующий идентификатор модели: + +```py +from transformers import pipeline + +# Замените это на свою собственную контрольную точку +model_checkpoint = "huggingface-course/bert-finetuned-ner" +token_classifier = pipeline( + "token-classification", model=model_checkpoint, aggregation_strategy="simple" +) +token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.9988506, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.9647625, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.9986118, 'word': 'Brooklyn', 'start': 49, 'end': 57}] +``` + +Отлично! Наша модель работает так же хорошо, как и модель по умолчанию для этого конвейера! diff --git a/chapters/ru/chapter7/3.mdx b/chapters/ru/chapter7/3.mdx new file mode 100644 index 000000000..da62408eb --- /dev/null +++ b/chapters/ru/chapter7/3.mdx @@ -0,0 +1,1044 @@ + + +# Дообучение модели маскированного языкового моделирования[[fine-tuning-a-masked-language-model]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Для многих приложений NLP, в которых используются модели-трансформеры, можно просто взять предварительно обученную модель из Hugging Face Hub и дообучить ее непосредственно на ваших данных для решения поставленной задачи. При условии, что корпус, использованный для предварительного обучения, не слишком отличается от корпуса, используемого для дообучения, трансферное обучение обычно дает хорошие результаты. + +Однако есть несколько случаев, когда перед обучением специфичной для конкретной задачи головы необходимо дообучить языковые модели на ваших данных. Например, если ваш датасет содержит юридические контракты или научные статьи, ванильная модель трансформер, такая как BERT, обычно рассматривает специфические для области слова в вашем корпусе как редкие токены, и результирующее качество может быть менее чем удовлетворительным. Дообучив языковую модель на данных из домена, вы сможете повысить качество работы во многих последующих задачах, а это значит, что обычно этот шаг нужно выполнить только один раз! + +Этот процесс дообучить предварительно обученную языковую модель на данных из домена обычно называют _доменной адаптацией (domain adaptation)_. Он был популяризирован в 2018 году благодаря [ULMFiT](https://arxiv.org/abs/1801.06146), которая стала одной из первых нейронных архитектур (основанных на LSTM), заставивших трансферное обучение действительно работать в NLP. Пример доменной адаптации с помощью ULMFiT показан на изображении ниже; в этом разделе мы сделаем нечто подобное, но с трансформером вместо LSTM! + +
+ULMFiT. + +
+ +К концу этого раздела у вас будет [модель маскированного языкового моделирования](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) на Hub, которая позволяет автоматически дописывать предложения, как показано ниже: + + + +Давайте погрузимся в работу! + + + + + +🙋 Если термины "маскированное моделирование языка (masked language modeling)" и "предварительно обученная модель (pretrained model)" кажутся вам незнакомыми, загляните в [Главу 1](../chapter1/1), где мы объясняем все эти основные понятия, сопровождая их видеороликами! + + + +## Выбор предварительно обученной модели для маскированного моделирования языка[[picking-a-pretrained-model-for-masked-language-modeling]] + +Для начала давайте выберем подходящую предварительно обученную модель для маскированного языкового моделирования. Как показано на следующем скриншоте, вы можете найти список кандидатов, применив фильтр "Fill-Mask" на [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): + +
+Hub models. +
+ +Хотя модели семейства BERT и RoBERTa являются наиболее загружаемыми, мы будем использовать модель под названием [DistilBERT](https://huggingface.co/distilbert-base-uncased) +которая может быть обучена гораздо быстрее и практически без потерь в качестве. Эта модель была обучена с помощью специальной техники, называемой [_дистилляцией знаний (knowledge distillation)_](https://en.wikipedia.org/wiki/Knowledge_distillation), когда большая "модель-учитель", такая как BERT, используется для обучения "модели-ученика", имеющей гораздо меньше параметров. Объяснение деталей дистилляции знаний завело бы нас слишком далеко в этом разделе, но если вам интересно, вы можете прочитать об этом в [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (в просторечии известном как учебник по Трансформерам). + +{#if fw === 'pt'} + +Давайте перейдем к загрузке DistilBERT с помощью класса `AutoModelForMaskedLM`: + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Мы можем узнать, сколько параметров имеет эта модель, вызвав метод `num_parameters()`: + +```python +distilbert_num_parameters = model.num_parameters() / 1_000_000 +print(f"'>>> DistilBERT number of parameters: {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT number of parameters: 110M'") +``` + +```python out +'>>> DistilBERT number of parameters: 67M' +'>>> BERT number of parameters: 110M' +``` + +{:else} + +Давайте загрузим DistilBERT, используя класс `AutoModelForMaskedLM`: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Мы можем узнать, сколько параметров имеет эта модель, вызвав метод `summary()`: + +```python +model.summary() +``` + +```python out +Model: "tf_distil_bert_for_masked_lm" +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +distilbert (TFDistilBertMain multiple 66362880 +_________________________________________________________________ +vocab_transform (Dense) multiple 590592 +_________________________________________________________________ +vocab_layer_norm (LayerNorma multiple 1536 +_________________________________________________________________ +vocab_projector (TFDistilBer multiple 23866170 +================================================================= +Total params: 66,985,530 +Trainable params: 66,985,530 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +Имея около 67 миллионов параметров, DistilBERT примерно в два раза меньше, чем базовая модель BERT, что примерно соответствует двукратному ускорению обучения - неплохо! Теперь посмотрим, какие виды токенов эта модель предсказывает как наиболее вероятные завершения небольшого образца текста: + +```python +text = "This is a great [MASK]." +``` + +Как люди, мы можем представить множество вариантов использования токена `[MASK]`, например "day (день)", "ride (поездка)" или "painting (картина)". Для предварительно обученных моделей прогнозы зависят от корпуса, на котором обучалась модель, поскольку она учится улавливать статистические закономерности, присутствующие в данных. Как и BERT, DistilBERT был предварительно обучен на датасетах [English Wikipedia](https://huggingface.co/datasets/wikipedia) и [BookCorpus](https://huggingface.co/datasets/bookcorpus), поэтому мы ожидаем, что прогнозы для `[MASK]` будут отражать эти домены. Для прогнозирования маски нам нужен токенизатор DistilBERT для создания входных данных для модели, поэтому давайте загрузим и его с хаба: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Теперь, имея токенизатор и модель, мы можем передать наш текстовый пример в модель, извлечь логиты и вывести 5 лучших кандидатов: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Определеним местоположения [MASK] и извлечем его логиты +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Выберем кандидатов [MASK] с наибольшими логитами +top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() + +for token in top_5_tokens: + print(f"'>>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}'") +``` + +{:else} + +```python +import numpy as np +import tensorflow as tf + +inputs = tokenizer(text, return_tensors="np") +token_logits = model(**inputs).logits +# Определеним местоположения [MASK] и извлечем его логиты +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Выберем кандидатов [MASK] с наибольшими логитами +# Мы выполняем отрицание массива перед argsort, чтобы получить самые большие, а не самые маленькие логиты +top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() + +for token in top_5_tokens: + print(f">>> {text.replace(tokenizer.mask_token, tokenizer.decode([token]))}") +``` + +{/if} + +```python out +'>>> This is a great deal.' +'>>> This is a great success.' +'>>> This is a great adventure.' +'>>> This is a great idea.' +'>>> This is a great feat.' +``` + +Из результатов видно, что предсказания модели относятся к повседневным терминам, что, пожалуй, неудивительно, учитывая основу английской Википедии. Давайте посмотрим, как мы можем изменить эту область на что-то более нишевое - сильно поляризованные обзоры фильмов! + + +## Датасет[[the-dataset]] + +Для демонстрации адаптации к домену мы используем знаменитый [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (или сокращенно IMDb) - корпус кинорецензий, который часто используется для тестирования моделей анализа настроения. Дообучив DistilBERT на этом корпусе, мы ожидаем, что языковая модель адаптирует свой словарный запас от фактических данных Википедии, на которых она была предварительно обучена, к более субъективным элементам кинорецензий. Мы можем получить данные из Hugging Face Hub с помощью функции `load_dataset()` из 🤗 Datasets: + +```python +from datasets import load_dataset + +imdb_dataset = load_dataset("imdb") +imdb_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + test: Dataset({ + features: ['text', 'label'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['text', 'label'], + num_rows: 50000 + }) +}) +``` + +Мы видим, что части `train` и `test` состоят из 25 000 отзывов каждая, а часть без меток, называемая `unsupervised`, содержит 50 000 отзывов. Давайте посмотрим на несколько примеров, чтобы получить представление о том, с каким текстом мы имеем дело. Как мы уже делали в предыдущих главах курса, мы используем функции `Dataset.shuffle()` и `Dataset.select()` для создания случайной выборки: + +```python +sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) + +for row in sample: + print(f"\n'>>> Review: {row['text']}'") + print(f"'>>> Label: {row['label']}'") +``` + +```python out + +'>>> Review: This is your typical Priyadarshan movie--a bunch of loony characters out on some silly mission. His signature climax has the entire cast of the film coming together and fighting each other in some crazy moshpit over hidden money. Whether it is a winning lottery ticket in Malamaal Weekly, black money in Hera Pheri, "kodokoo" in Phir Hera Pheri, etc., etc., the director is becoming ridiculously predictable. Don\'t get me wrong; as clichéd and preposterous his movies may be, I usually end up enjoying the comedy. However, in most his previous movies there has actually been some good humor, (Hungama and Hera Pheri being noteworthy ones). Now, the hilarity of his films is fading as he is using the same formula over and over again.

Songs are good. Tanushree Datta looks awesome. Rajpal Yadav is irritating, and Tusshar is not a whole lot better. Kunal Khemu is OK, and Sharman Joshi is the best.' +'>>> Label: 0' + +'>>> Review: Okay, the story makes no sense, the characters lack any dimensionally, the best dialogue is ad-libs about the low quality of movie, the cinematography is dismal, and only editing saves a bit of the muddle, but Sam" Peckinpah directed the film. Somehow, his direction is not enough. For those who appreciate Peckinpah and his great work, this movie is a disappointment. Even a great cast cannot redeem the time the viewer wastes with this minimal effort.

The proper response to the movie is the contempt that the director San Peckinpah, James Caan, Robert Duvall, Burt Young, Bo Hopkins, Arthur Hill, and even Gig Young bring to their work. Watch the great Peckinpah films. Skip this mess.' +'>>> Label: 0' + +'>>> Review: I saw this movie at the theaters when I was about 6 or 7 years old. I loved it then, and have recently come to own a VHS version.

My 4 and 6 year old children love this movie and have been asking again and again to watch it.

I have enjoyed watching it again too. Though I have to admit it is not as good on a little TV.

I do not have older children so I do not know what they would think of it.

The songs are very cute. My daughter keeps singing them over and over.

Hope this helps.' +'>>> Label: 1' +``` + +Да, это точно рецензии на фильмы, и если вы родились до 1990х, вам будет лучше понятен комментарий в последней рецензии о VHS-версии. 😜! Хотя нам не понадобятся эти метки для языкового моделирования, мы уже видим, что `0` обозначает отрицательный отзыв, а `1` - положительный. + + + +✏️ **Попробуйте!** Создайте случайную выборку из части `unsupervised` и проверьте, что метки не являются ни `0`, ни `1`. В процессе работы вы также можете проверить, что метки в частях `train` и `test` действительно равны `0` или `1` - это полезная проверка здравомыслия, которую каждый практикующий NLP должен выполнять в начале нового проекта! + + + +Теперь, когда мы вкратце ознакомились с данными, давайте перейдем к их подготовке к моделированию языка по маске. Как мы увидим, есть несколько дополнительных шагов, которые необходимо сделать по сравнению с задачами классификации последовательностей, которые мы рассматривали в [Главе 3](../chapter3/1). Поехали! + +## Предварительная обработка данных[[preprocessing-the-data]] + + + +Как для авторегрессивного, так и для масочного моделирования языка общим шагом предварительной обработки является объединение всех примеров, а затем разбиение всего корпуса на части одинакового размера. Это сильно отличается от нашего обычного подхода, когда мы просто проводим токенизацию отдельных примеров. Зачем конкатенируем все вместе? Причина в том, что отдельные примеры могут быть обрезаны, если они слишком длинные, и это приведет к потере информации, которая может быть полезна для задачи языкового моделирования! + +Итак, для начала мы проведем обычную токенизацию нашего корпуса, но _без_ задания параметра `truncation=True` в нашем токенизаторе. Мы также возьмем идентификаторы слов, если они доступны (а они будут доступны, если мы используем быстрый токенизатор, как описано в [Главе 6](../chapter6/3)), поскольку они понадобятся нам позже для маскирования целых слов. Мы обернем это в простую функцию, а пока удалим столбцы `text` и `label`, поскольку они нам больше не нужны: + +```python +def tokenize_function(examples): + result = tokenizer(examples["text"]) + if tokenizer.is_fast: + result["word_ids"] = [result.word_ids(i) for i in range(len(result["input_ids"]))] + return result + + +# Используйте batched=True для активации быстрой многопоточности! +tokenized_datasets = imdb_dataset.map( + tokenize_function, batched=True, remove_columns=["text", "label"] +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 25000 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'word_ids'], + num_rows: 50000 + }) +}) +``` + +Поскольку DistilBERT - это BERT-подобная модель, мы видим, что закодированные тексты состоят из `input_ids` и `attention_mask`, которые мы уже видели в других главах, а также из `word_ids`, которые мы добавили. + +Теперь, когда мы провели токенизацию рецензий на фильмы, следующий шаг - сгруппировать их вместе и разбить результат на части. Но какого размера должны быть эти части? В конечном итоге это будет зависеть от объема доступной памяти GPU, но хорошей отправной точкой будет узнать, каков максимальный размер контекста модели. Это можно сделать путем проверки атрибута `model_max_length` токенизатора: + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +Это значение берется из файла *tokenizer_config.json*, связанного с контрольной точкой; в данном случае мы видим, что размер контекста составляет 512 токенов, как и в случае с BERT. + + + +✏️ **Попробуйте!** Некоторые модели трансформеров, например [BigBird](https://huggingface.co/google/bigbird-roberta-base) и [Longformer](hf.co/allenai/longformer-base-4096), имеют гораздо большую длину контекста, чем BERT и другие ранние модели трансформеров. Инстанцируйте токенизатор для одной из этих контрольных точек и проверьте, что `model_max_length` согласуется с тем, что указано в описании модели. + + + +Поэтому для проведения экспериментов на GPU, подобных тем, что стоят в Google Colab, мы выберем что-нибудь поменьше, что может поместиться в памяти: + +```python +chunk_size = 128 +``` + + + +Обратите внимание, что использование небольшого размера фрагмента может быть вредным в реальных сценариях, поэтому следует использовать размер, соответствующий сценарию использования, к которому вы будете применять вашу модель. + + + +Теперь наступает самое интересное. Чтобы показать, как работает конкатенация, давайте возьмем несколько отзывов из нашего обучающего набора с токенизацией и выведем количество токенов в отзыве: + +```python +# С помощью среза получаем список списков для каждого признака +tokenized_samples = tokenized_datasets["train"][:3] + +for idx, sample in enumerate(tokenized_samples["input_ids"]): + print(f"'>>> Review {idx} length: {len(sample)}'") +``` + +```python out +'>>> Review 0 length: 200' +'>>> Review 1 length: 559' +'>>> Review 2 length: 192' +``` + +Затем мы можем объединить все эти примеры с помощью простого dictionary comprehension, как показано ниже: + +```python +concatenated_examples = { + k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() +} +total_length = len(concatenated_examples["input_ids"]) +print(f"'>>> Concatenated reviews length: {total_length}'") +``` + +```python out +'>>> Concatenated reviews length: 951' +``` + +Отлично, общая длина подтвердилась - теперь давайте разобьем конкатенированные обзоры на части размером `chunk_size`. Для этого мы перебираем признаки в `concatenated_examples` и используем list comprehension для создания срезов каждого признака. В результате мы получаем словарь фрагментов для каждого признака: + +```python +chunks = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() +} + +for chunk in chunks["input_ids"]: + print(f"'>>> Chunk length: {len(chunk)}'") +``` + +```python out +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 128' +'>>> Chunk length: 55' +``` + +Как видно из этого примера, последний фрагмент, как правило, будет меньше максимального размера фрагмента. Существует две основные стратегии решения этой проблемы: + +* Отбросить последний фрагмент, если он меньше `chunk_size`. +* Дополнить последний фрагмент до длины, равной `chunk_size`. + +Мы воспользуемся первым подходом, поэтому обернем всю описанную выше логику в одну функцию, которую можно применить к нашим токенизированным датасетам: + +```python +def group_texts(examples): + # Конкатенируем все тексты + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Вычисляем длину конкатенированных текстов + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # Отбрасываем последний фрагмент, если он меньше chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Разбиваем на фрагменты длиной max_len + result = { + k: [t[i : i + chunk_size] for i in range(0, total_length, chunk_size)] + for k, t in concatenated_examples.items() + } + # Создаем новый столбец меток + result["labels"] = result["input_ids"].copy() + return result +``` + +Обратите внимание, что на последнем шаге `group_texts()` мы создаем новый столбец `labels`, который является копией столбца `input_ids`. Как мы вскоре увидим, это связано с тем, что при моделировании языка по маске цель состоит в прогнозировании случайно замаскированных токенов во входном батче, и, создавая столбец `labels`, мы предоставляем базовую истину для нашей языковой модели, на которой она будет учиться. + +Теперь давайте применим `group_texts()` к нашим токенизированным датасетам, используя нашу надежную функцию `Dataset.map()`: + +```python +lm_datasets = tokenized_datasets.map(group_texts, batched=True) +lm_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 61289 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 59905 + }) + unsupervised: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 122963 + }) +}) +``` + +Вы можете видеть, что группировка и последующее разбиение текстов на фрагменты позволили получить гораздо больше примеров, чем наши первоначальные 25 000 примеров для частей `train` и `test`. Это потому, что теперь у нас есть примеры с _непрерывными токенами (contiguous tokens)_, которые охватывают несколько примеров из исходного корпуса. В этом можно убедиться, поискав специальные токены `[SEP]` и `[CLS]` в одном из фрагментов: + +```python +tokenizer.decode(lm_datasets["train"][1]["input_ids"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +В этом примере вы можете увидеть два перекрывающихся обзора фильмов: один - о старшей школе, другой - о бездомности. Давайте также посмотрим, как выглядят метки при моделировании языка по маске: + +```python out +tokenizer.decode(lm_datasets["train"][1]["labels"]) +``` + +```python out +".... at.......... high. a classic line : inspector : i'm here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn't! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless" +``` + +Как и ожидалось от нашей функции `group_texts()` выше, это выглядит идентично декодированным `input_ids` - но как тогда наша модель может чему-то научиться? Нам не хватает ключевого шага: вставки токенов `[MASK]` в случайные позиции во входных данных! Давайте посмотрим, как это можно сделать на лету во время дообучения с помощью специального коллатора данных. + +## Дообучение DistilBERT с помощью API `Trainer`[[fine-tuning-distilbert-with-the-trainer-api]] + +Дообучить модель моделирования языка по маске почти то же самое, что и дообучить модель классификации последовательностей, как мы делали в [Главе 3](../chapter3/1). Единственное отличие заключается в том, что нам нужен специальный коллатор данных, который может случайным образом маскировать некоторые токены в каждом батче текстов. К счастью, 🤗 Transformers поставляется со специальным `DataCollatorForLanguageModeling`, предназначенным именно для этой задачи. Нам нужно только передать ему токенизатор и аргумент `mlm_probability`, который указывает, какую долю токенов нужно маскировать. Мы выберем 15 % - это количество используется для BERT и является распространенным выбором в литературе: + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +Чтобы увидеть, как работает случайное маскирование, давайте отправим несколько примеров в коллатор данных. Поскольку он ожидает список `dict`, где каждый `dict` представляет собой один фрагмент непрерывного текста, мы сначала выполняем итерацию над датасетом, прежде чем отправить батч коллатору. Мы удаляем ключ `"word_ids"` для этого коллатора данных, поскольку он его не ожидает: + +```python +samples = [lm_datasets["train"][i] for i in range(2)] +for sample in samples: + _ = sample.pop("word_ids") + +for chunk in data_collator(samples)["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python output +'>>> [CLS] bromwell [MASK] is a cartoon comedy. it ran at the same [MASK] as some other [MASK] about school life, [MASK] as " teachers ". [MASK] [MASK] [MASK] in the teaching [MASK] lead [MASK] to believe that bromwell high\'[MASK] satire is much closer to reality than is " teachers ". the scramble [MASK] [MASK] financially, the [MASK]ful students whogn [MASK] right through [MASK] pathetic teachers\'pomp, the pettiness of the whole situation, distinction remind me of the schools i knew and their students. when i saw [MASK] episode in [MASK] a student repeatedly tried to burn down the school, [MASK] immediately recalled. [MASK]...' + +'>>> .... at.. [MASK]... [MASK]... high. a classic line plucked inspector : i\'[MASK] here to [MASK] one of your [MASK]. student : welcome to bromwell [MASK]. i expect that many adults of my age think that [MASK]mwell [MASK] is [MASK] fetched. what a pity that it isn\'t! [SEP] [CLS] [MASK]ness ( or [MASK]lessness as george 宇in stated )公 been an issue for years but never [MASK] plan to help those on the street that were once considered human [MASK] did everything from going to school, [MASK], [MASK] vote for the matter. most people think [MASK] the homeless' +``` + +Отлично, сработало! Мы видим, что токен `[MASK]` был случайным образом вставлен в различные места нашего текста. Это будут токены, которые наша модель должна будет предсказать в процессе обучения - и прелесть коллатора данных в том, что он будет случайным образом вставлять `[MASK]` в каждом батче! + + + +✏️ **Попробуйте!** Запустите приведенный выше фрагмент кода несколько раз, чтобы увидеть, как случайное маскирование происходит на ваших глазах! Также замените метод `tokenizer.decode()` на `tokenizer.convert_ids_to_tokens()`, чтобы увидеть, что иногда маскируется один токен из данного слова, а не все остальные. + + + +{#if fw === 'pt'} + +Одним из побочных эффектов случайного маскирования является то, что наши метрики оценки не будут детерминированными при использовании `Trainer`, поскольку мы используем один и тот же коллатор данных для обучающего и тестового наборов. Позже, когда мы рассмотрим дообучение с помощью 🤗 Accelerate, мы увидим, как можно использовать гибкость пользовательского цикла оценки, чтобы заморозить случайность. + +{/if} + +При обучении моделей для моделирования языка с маской можно использовать один из методов - маскировать целые слова, а не только отдельные токены. Такой подход называется _маскированием целых слов (whole word masking)_. Если мы хотим использовать маскирование целых слов, нам нужно будет самостоятельно создать коллатор данных. Коллатор данных - это просто функция, которая берет список примеров и преобразует их в батч, так что давайте сделаем это прямо сейчас! Мы будем использовать идентификаторы слов, вычисленные ранее, для создания карты между индексами слов и соответствующими токенами, затем случайным образом определим, какие слова нужно маскировать, и применим эту маску к входным данным. Обратите внимание, что все метки - `-100`, кроме тех, что соответствуют словам-маскам. + +{#if fw === 'pt'} + +```py +import collections +import numpy as np + +from transformers import default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Создаем отображение между словами и соответствующими индексами токенов + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Случайно маскируем слова + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data.data_collator import tf_default_data_collator + +wwm_probability = 0.2 + + +def whole_word_masking_data_collator(features): + for feature in features: + word_ids = feature.pop("word_ids") + + # Создаем отображение между словами и соответствующими индексами токенов + mapping = collections.defaultdict(list) + current_word_index = -1 + current_word = None + for idx, word_id in enumerate(word_ids): + if word_id is not None: + if word_id != current_word: + current_word = word_id + current_word_index += 1 + mapping[current_word_index].append(idx) + + # Случайно маскируем слова + mask = np.random.binomial(1, wwm_probability, (len(mapping),)) + input_ids = feature["input_ids"] + labels = feature["labels"] + new_labels = [-100] * len(labels) + for word_id in np.where(mask)[0]: + word_id = word_id.item() + for idx in mapping[word_id]: + new_labels[idx] = labels[idx] + input_ids[idx] = tokenizer.mask_token_id + feature["labels"] = new_labels + + return tf_default_data_collator(features) +``` + +{/if} + +Далее мы можем опробовать ее на тех же примерах, что и раньше: + +```py +samples = [lm_datasets["train"][i] for i in range(2)] +batch = whole_word_masking_data_collator(samples) + +for chunk in batch["input_ids"]: + print(f"\n'>>> {tokenizer.decode(chunk)}'") +``` + +```python out +'>>> [CLS] bromwell high is a cartoon comedy [MASK] it ran at the same time as some other programs about school life, such as " teachers ". my 35 years in the teaching profession lead me to believe that bromwell high\'s satire is much closer to reality than is " teachers ". the scramble to survive financially, the insightful students who can see right through their pathetic teachers\'pomp, the pettiness of the whole situation, all remind me of the schools i knew and their students. when i saw the episode in which a student repeatedly tried to burn down the school, i immediately recalled.....' + +'>>> .... [MASK] [MASK] [MASK] [MASK]....... high. a classic line : inspector : i\'m here to sack one of your teachers. student : welcome to bromwell high. i expect that many adults of my age think that bromwell high is far fetched. what a pity that it isn\'t! [SEP] [CLS] homelessness ( or houselessness as george carlin stated ) has been an issue for years but never a plan to help those on the street that were once considered human who did everything from going to school, work, or vote for the matter. most people think of the homeless' +``` + + + +✏️ **Попробуйте!** Запустите приведенный выше фрагмент кода несколько раз, чтобы увидеть, как случайное маскирование происходит на ваших глазах! Также замените метод `tokenizer.decode()` на `tokenizer.convert_ids_to_tokens()`, чтобы увидеть, что токены из данного слова всегда маскируются вместе. + + + +Теперь, когда у нас есть два колатора данных, остальные шаги по дообучению стандартны. Обучение может занять много времени в Google Colab, если вам не посчастливилось получить мифический GPU P100 😭, поэтому мы сначала уменьшим размер обучающего набора до нескольких тысяч примеров. Не волнуйтесь, мы все равно получим довольно приличную языковую модель! Быстрый способ уменьшить размер датасета в 🤗 Datasets - это функция `Dataset.train_test_split()`, которую мы рассматривали в [Главе 5](../chapter5/1): + +```python +train_size = 10_000 +test_size = int(0.1 * train_size) + +downsampled_dataset = lm_datasets["train"].train_test_split( + train_size=train_size, test_size=test_size, seed=42 +) +downsampled_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 10000 + }) + test: Dataset({ + features: ['attention_mask', 'input_ids', 'labels', 'word_ids'], + num_rows: 1000 + }) +}) +``` + +Это автоматически создаст новые части `train` и `test`, с размером обучающего набора в 10 000 примеров и валидацией в 10 % от этого количества - не стесняйтесь увеличить это значение, если у вас мощный GPU! Следующее, что нам нужно сделать, это авторизоваться на Hugging Face Hub. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +которая отобразит виджет, где вы можете ввести свои учетные данные. В качестве альтернативы можно выполнить команду: + +``` +huggingface-cli login +``` + +в вашем любимом терминале и авторизуйтесь там. + +{#if fw === 'tf'} + +После того как мы авторизовались, мы можем создавать наши датасеты `tf.data`. Для этого мы воспользуемся методом `prepare_tf_dataset()`, который использует нашу модель для автоматического инференса того, какие столбцы должны войти в датасет. Если вы хотите контролировать, какие именно столбцы будут использоваться, вы можете использовать метод `Dataset.to_tf_dataset()` вместо этого. Для простоты мы будем использовать стандартный коллатор данных, но вы также можете попробовать коллатор для маскировки слов целиком и сравнить результаты в качестве упражнения: + +```python +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Далее мы задаем гиперпараметры обучения и компилируем нашу модель. Мы используем функцию `create_optimizer()` из библиотеки 🤗 Transformers, которая возвращает нам оптимизатор `AdamW` с линейным затуханием скорости обучения. Мы также используем встроенные потери модели, которые используются по умолчанию, если в качестве аргумента `compile()` не указаны потери, и устанавливаем точность обучения на `"mixed_float16"`. Обратите внимание, что если вы используете Colab GPU или другой GPU, который не имеет поддержки ускорения float16, вам, вероятно, следует закомментировать эту строку. + +Кроме того, мы настроили `PushToHubCallback`, который будет сохранять модель в Hub после каждой эпохи. Вы можете указать имя репозитория, в который хотите отправить модель, с помощью аргумента `hub_model_id` (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, чтобы отправить модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, так что в нашем случае это будет `"lewtun/distilbert-finetuned-imdb"`. + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Обучение со смешанной точностью float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +model_name = model_checkpoint.split("/")[-1] +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +Теперь мы готовы запустить `model.fit()`, но перед этим давайте вкратце рассмотрим _перплексию (perplexity)_, которая является общей метрикой для оценки качества работы языковых моделей. + +{:else} + +После того как мы авторизовались, мы можем указать аргументы для `Trainer`: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Показываем потери при обучении для каждой эпохи +logging_steps = len(downsampled_dataset["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +training_args = TrainingArguments( + output_dir=f"{model_name}-finetuned-imdb", + overwrite_output_dir=True, + evaluation_strategy="epoch", + learning_rate=2e-5, + weight_decay=0.01, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + push_to_hub=True, + fp16=True, + logging_steps=logging_steps, +) +``` + +Здесь мы изменили несколько параметров по умолчанию, включая `logging_steps`, чтобы обеспечить отслеживание потерь при обучении в каждой эпохе. Мы также использовали `fp16=True`, чтобы включить обучение со смешанной точностью, что дает нам еще большее улучшение скорости. По умолчанию `Trainer` удаляет все столбцы, которые не являются частью метода `forward()` модели. Это означает, что если вы используете коллатор для маскировки слов целиком, вам также нужно установить `remove_unused_columns=False`, чтобы не потерять колонку `word_ids` во время обучения. + +Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в ырепозиторий организации). Например, когда мы отправили модель в репозиторий организации [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` в `TrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданной вами выходной директорией, так что в нашем случае это будет `"lewtun/distilbert-finetuned-imdb"`. + +Теперь у нас есть все компоненты для создания `Trainer`. Здесь мы просто используем стандартный `data_collator`, но вы можете попробовать коллатор для маскирования слов целиком и сравнить результаты в качестве упражнения: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=training_args, + train_dataset=downsampled_dataset["train"], + eval_dataset=downsampled_dataset["test"], + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Теперь мы готовы запустить `trainer.train()`, но перед этим давайте вкратце рассмотрим _перплексию (perplexity), которая является общей метрикой для оценки качества языковых моделей. + +{/if} + +### Перплексия языковых моделей[[perplexity-for-language-models]] + + + +В отличие от других задач, таких как классификация текстов или ответов на вопросы, где для обучения нам дается помеченный корпус, при языковом моделировании у нас нет никаких явных меток. Как же определить, какая языковая модель является хорошей? Как и в случае с функцией автокоррекции в вашем телефоне, хорошая языковая модель - это та, которая присваивает высокую вероятность грамматически правильным предложениям и низкую вероятность бессмысленным предложениям. Чтобы лучше представить себе, как это выглядит, вы можете найти в Интернете целые подборки "неудач автокоррекции", где модель в телефоне человека выдает довольно забавные (и часто неуместные) завершения! + +{#if fw === 'pt'} + +Если предположить, что наш тестовый набор состоит в основном из грамматически правильных предложений, то одним из способов измерения качества нашей языковой модели является подсчет вероятностей, которые она присваивает следующему слову во всех предложениях тестового набора. Высокие вероятности указывают на то, что модель не "удивлена" и не "перплексирована" не виденными ранее примерами, и предполагают, что она усвоила основные грамматические закономерности языка. Существуют различные математические определения перплексии, но то, которое мы будем использовать, определяет ее как экспоненту потери перекрестной энтропии. Таким образом, мы можем вычислить перплексию нашей предварительно обученной модели, используя функцию `Trainer.evaluate()` для вычисления потерь кросс-энтропии на тестовом наборе, а затем взяв экспоненту результата: + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +Если предположить, что наш тестовый набор состоит в основном из грамматически правильных предложений, то одним из способов измерения качества нашей языковой модели является подсчет вероятностей, которые она присваивает следующему слову во всех предложениях тестового набора. Высокие вероятности указывают на то, что модель не "удивлена" или не "перплексирована" не виденными ранее примерами, и предполагают, что она выучила основные грамматические закономерности языка. Существуют различные математические определения перплексии, но то, которое мы будем использовать, определяет ее как экспоненту потери перекрестной энтропии. Таким образом, мы можем вычислить перплексию нашей предварительно обученной модели, используя метод `model.evaluate()` для вычисления потери кросс-энтропии на тестовом наборе, а затем взяв экспоненту результата: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +Более низкий показатель перплексии означает лучшую языковую модель, и мы видим, что наша начальная модель имеет несколько большое значение. Давайте посмотрим, сможем ли мы снизить его, дообучив модель! Для этого сначала запустим цикл обучения: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +и затем вычислить результирующую перплексию на тестовом наборе, как и раньше: + +{#if fw === 'pt'} + +```python +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +```python +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 11.32 +``` + +Неплохо - это довольно значительное уменьшение перплексии, что говорит о том, что модель узнала что-то новое об области рецензий на фильмы! + +{#if fw === 'pt'} + +После завершения обучения мы можем отправить карточку модели с информацией об обучении в Hub (контрольные точки сохраняются во время самого обучения): + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **Попробуйте!** Запустите обучение, описанное выше, после замены коллатора данных на коллатор маскирующий все слово. Получили ли вы лучшие результаты? + + + +{#if fw === 'pt'} + +В нашем случае нам не нужно было делать ничего особенного с циклом обучения, но в некоторых случаях вам может понадобиться реализовать некоторую пользовательскую логику. Для таких случаев вы можете использовать 🤗 Accelerate - давайте посмотрим! + +## Дообучение DistilBERT с помощью 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]] + +Как мы видели на примере `Trainer`, дообучение модели маскированного языкового моделирования очень похоже на пример классификации текста из [Главы 3](../chapter3/1). Фактически, единственной особенностью является использование специального коллатора данных, о котором мы уже рассказывали ранее в этом разделе! + +Однако мы видели, что `DataCollatorForLanguageModeling` также применяет случайное маскирование при каждой оценке, поэтому мы увидим некоторые колебания в наших оценках перплексии при каждом цикле обучения. Один из способов устранить этот источник случайности - применить маскирование _один раз_ ко всему тестовому набору, а затем использовать стандартный коллатор данных в 🤗 Transformers для сбора батчей во время оценки. Чтобы увидеть, как это работает, давайте реализуем простую функцию, которая применяет маскирование к батчу, подобно нашей первой работе с `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Создаем новый "маскированный" столбец для каждого столбца в датасете + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Далее мы применим эту функцию к нашему тестовому набору и удалим столбцы без маскирования, чтобы заменить их столбцами с маскированием. Вы можете использовать маскирование целых слов, заменив `data_collator` выше на соответствующий, в этом случае вам следует удалить первую строку здесь: + +```py +downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) +eval_dataset = downsampled_dataset["test"].map( + insert_random_mask, + batched=True, + remove_columns=downsampled_dataset["test"].column_names, +) +eval_dataset = eval_dataset.rename_columns( + { + "masked_input_ids": "input_ids", + "masked_attention_mask": "attention_mask", + "masked_labels": "labels", + } +) +``` + +Затем мы можем настроить загрузчики данных как обычно, но для оценочного набора мы будем использовать `default_data_collator` из 🤗 Transformers: + +```python +from torch.utils.data import DataLoader +from transformers import default_data_collator + +batch_size = 64 +train_dataloader = DataLoader( + downsampled_dataset["train"], + shuffle=True, + batch_size=batch_size, + collate_fn=data_collator, +) +eval_dataloader = DataLoader( + eval_dataset, batch_size=batch_size, collate_fn=default_data_collator +) +``` + +Здесь мы следуем стандартным шагам 🤗 Accelerate. Первым делом загружаем свежую версию предварительно обученной модели: + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Затем нужно указать оптимизатор; мы будем использовать стандартный `AdamW`: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Теперь, имея эти объекты, мы можем подготовить все для обучения с помощью объекта `Accelerator`: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Теперь, когда наша модель, оптимизатор и загрузчики данных настроены, мы можем задать планировщик скорости обучения следующим образом: + +```python +from transformers import get_scheduler + +num_train_epochs = 3 +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, +) +``` + +Осталось сделать еще одну последнюю вещь перед обучением: создать репозиторий модели на Hugging Face Hub! Мы можем использовать библиотеку 🤗 Hub, чтобы сначала сгенерировать полное имя нашего репозитория: + +```python +from huggingface_hub import get_full_repo_name + +model_name = "distilbert-base-uncased-finetuned-imdb-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' +``` + +затем создадим и клонируем репозиторий, используя класс `Repository` из 🤗 Hub: + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +После этого остается только написать полный цикл обучения и оценки: + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Обучение + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Оценка + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + loss = outputs.loss + losses.append(accelerator.gather(loss.repeat(batch_size))) + + losses = torch.cat(losses) + losses = losses[: len(eval_dataset)] + try: + perplexity = math.exp(torch.mean(losses)) + except OverflowError: + perplexity = float("inf") + + print(f">>> Epoch {epoch}: Perplexity: {perplexity}") + + # Сохранение и загрузка + 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 + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +Круто, мы смогли оценить перплексию для каждой эпохи и обеспечить воспроизводимость нескольких циклов обучения! + +{/if} + +## Использование нашей дообученной модели[[using-our-fine-tuned-model]] + +Вы можете взаимодействовать с дообученной моделью либо с помощью ее виджета на Hub, либо локально с помощью `pipeline` из 🤗 Transformers. Давайте воспользуемся последним вариантом, чтобы загрузить нашу модель с помощью конвейера `fill-mask`: + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +Затем мы можем передать конвейеру наш пример текста " This is a great [MASK]" и посмотреть, каковы 5 лучших прогнозов: + +```python +preds = mask_filler(text) + +for pred in preds: + print(f">>> {pred['sequence']}") +``` + +```python out +'>>> this is a great movie.' +'>>> this is a great film.' +'>>> this is a great story.' +'>>> this is a great movies.' +'>>> this is a great character.' +``` + +Отлично - наша модель явно адаптировала свои веса, чтобы прогнозировать слова, которые сильнее ассоциируются с фильмами! + + + +На этом мы завершаем наш первый эксперимент по обучению языковой модели. В [разделе 6](../chapter7/6) вы узнаете, как обучить авторегрессионную модель типа GPT-2 с нуля; загляните туда, если хотите посмотреть, как можно предварительно обучить свою собственную модель трансформера! + + + +✏️ **Попробуйте!** Чтобы оценить преимущества адаптации к домену, дообучите классификатор на метках IMDb как для предварительно обученных, так и для дообученных контрольных точек DistilBERT. Если вам нужно освежить в памяти классификацию текстов, ознакомьтесь с [Главой 3](../chapter3/1). + + diff --git a/chapters/ru/chapter7/4.mdx b/chapters/ru/chapter7/4.mdx new file mode 100644 index 000000000..53d1155ef --- /dev/null +++ b/chapters/ru/chapter7/4.mdx @@ -0,0 +1,1002 @@ + + +# Перевод[[translation]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Теперь давайте погрузимся в перевод. Это еще одна [задача преобразования последовательности в последовательность (sequence-to-sequence)](../chapter1/7), что означает, что это проблема, которую можно сформулировать как переход от одной последовательности к другой. В этом смысле задача очень близка к [резюмированию (summarization)](../chapter7/6), и вы можете приспособить то, что мы здесь рассмотрим, к другим задачам преобразования последовательности в последовательность, таким как: + +- **Перенос стиля (Style transfer)**: Создание модели, которая *переводит* тексты, написанные в определенном стиле, в другой (например, из формального в повседневный или из шекспировского английского в современный). +- **Генеративные ответы на вопросы (Generative question answering)**: Создание модели, которая генерирует ответы на вопросы, учитывая контекст + + + +Если у вас есть достаточно большой корпус текстов на двух (или более) языках, вы можете обучить новую модель перевода с нуля, как мы это сделаем в разделе по [казуальному языковому моделированию (causal language modeling)](../chapter7/6). Однако быстрее будет дообучить существующую модель перевода, будь то многоязычная модель типа mT5 или mBART, которую нужно дообучить для конкретной пары языков, или даже модель, специализированная для перевода с одного языка на другой, которую нужно дообучить для конкретного корпуса. + +В этом разделе мы дообучим модель Marian, предварительно обученную переводу с английского на французский (поскольку многие сотрудники Hugging Face говорят на обоих этих языках), на датасете [KDE4](https://huggingface.co/datasets/kde4), который представляет собой набор локализованных файлов для приложений [KDE](https://apps.kde.org/). Модель, которую мы будем использовать, была предварительно обучена на большом корпусе французских и английских текстов, взятых из [Opus dataset](https://opus.nlpl.eu/), который фактически содержит датасет KDE4. Но даже если модель, которую мы используем, видела эти данные во время предварительного обучения, мы увидим, что после дообучения мы сможем получить ее лучшую версию. + +Когда мы закончим, у нас будет модель, способная делать прогнозы, подобные этому: + + + + +One-hot encoded labels for question answering. + + + +Как и в предыдущих разделах, вы можете найти актуальную модель, которую мы обучим и загрузим на Hub, используя приведенный ниже код, и перепроверить ее предсказания [здесь](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Подготовка данных[[preparing-the-data]] + +Чтобы дообучить или обучить модель перевода с нуля, нам понадобится датасет, подходящий для этой задачи. Как уже упоминалось, мы будем использовать [датасет KDE4](https://huggingface.co/datasets/kde4) , но вы можете легко адаптировать код для использования своих собственных данных, если у вас есть пары предложений на двух языках, с которых вы хотите переводить и на которые хотите переводить. Обратитесь к [Главе 5](../chapter5/1) если вам нужно вспомнить, как загружать пользовательские данные в `Dataset`. + +### Датасет KDE4[[the-kde4-dataset]] + +Как обычно, мы загружаем наш датасет с помощью функции `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Если вы хотите работать с другой парой языков, вы можете указать их по кодам. Всего для этого датасета доступно 92 языка; вы можете увидеть их все, развернув языковые теги в его [карточке датасета](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Давайте посмотрим на датасет: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +У нас есть 210 173 пары предложений, но в одной части, поэтому нам нужно создать собственный проверочный набор. Как мы видели в [Главе 5](../chapter5/1), у `Dataset` есть метод `train_test_split()`, который может нам помочь. Мы зададим seed для воспроизводимости: + +```py +split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) +split_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 189155 + }) + test: Dataset({ + features: ['id', 'translation'], + num_rows: 21018 + }) +}) +``` + +Мы можем переименовать ключ `"test"` в `"validation"` следующим образом: + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Теперь давайте рассмотрим один элемент датасета: + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +Мы получаем словарь с двумя предложениями на запрошенной паре языков. Одна из особенностей этого датасета, наполненного техническими терминами в области компьютерных наук, заключается в том, что все они полностью переведены на французский язык. Однако французские инженеры при разговоре оставляют большинство специфических для компьютерных наук слов на английском. Например, слово "threads" вполне может встретиться во французском предложении, особенно в техническом разговоре; но в данном датасете оно переведено как более правильное "fils de discussion". Используемая нами модель, предварительно обученная на большем корпусе французских и английских предложений, выбирает более простой вариант - оставить слово как есть: + +```py +from transformers import pipeline + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut pour les threads élargis'}] +``` + +Другой пример такого поведения можно увидеть на примере слова "plugin", которое официально не является французским словом, но большинство носителей языка поймут его и не станут переводить. +В датасете KDE4 это слово было переведено на французский как более официальное "module d'extension": + +```py +split_datasets["train"][172]["translation"] +``` + +```python out +{'en': 'Unable to import %1 using the OFX importer plugin. This file is not the correct format.', + 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} +``` + +Однако наша предварительно обученная модель придерживается компактного и знакомого английского слова: + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] +``` + +Будет интересно посмотреть, сможет ли наша дообученная модель уловить эти особенности датасета (спойлер: сможет). + + + + + +✏️ **Попробуйте!** Еще одно английское слово, которое часто используется во французском языке, - "email". Найдите в обучающем датасете первый образец, в котором используется это слово. Как оно переводится? Как предварительно обученная модель переводит то же английское предложение? + + + +### Предварительная обработка данных[[processing-the-data]] + + + +Вы уже должны знать, как это делается: все тексты нужно преобразовать в наборы идентификаторов токенов, чтобы модель могла понять их смысл. Для этой задачи нам понадобится токенизация как входных данных, так и целевых. Наша первая задача - создать объект `tokenizer`. Как отмечалось ранее, мы будем использовать предварительно обученную модель Marian English to French. Если вы будете пробовать этот код с другой парой языков, обязательно адаптируйте контрольную точку модели. Организация [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) предоставляет более тысячи моделей на разных языках. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") +``` + +Вы также можете заменить `model_checkpoint` на любую другую модель из [Hub](https://huggingface.co/models) или локальной папки, в которой вы сохранили предварительно обученную модель и токенизатор. + + + +💡 Если вы используете многоязыковой токенизатор, такой как mBART, mBART-50 или M2M100, вам нужно задать языковые коды ваших входных и целевых данных в токенизаторе, задав правильные значения параметрам `tokenizer.src_lang` и `tokenizer.tgt_lang`. + + + +Подготовка наших данных довольно проста. Нужно помнить только об одном: необходимо убедиться, что токенизатор обрабатывает целевые значения на выходном языке (здесь - французском). Вы можете сделать это, передав целевые данные в аргумент `text_targets` метода `__call__` токенизатора. + +Чтобы увидеть, как это работает, давайте обработаем по одному примеру каждого языка из обучающего набора: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence, text_target=fr_sentence) +inputs +``` + +```python out +{'input_ids': [47591, 12, 9842, 19634, 9, 0], 'attention_mask': [1, 1, 1, 1, 1, 1], 'labels': [577, 5891, 2, 3184, 16, 2542, 5, 1710, 0]} +``` + +Как мы видим, в выходных данных содержатся входные идентификаторы, связанные с английским предложением, а идентификаторы, связанные с французским, хранятся в поле `labels`. Если вы забудете указать, что выполняете токенизацию меток, они будут токенизированы входным токенизатором, что в случае с моделью Marian не приведет ни к чему хорошему: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(inputs["labels"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Как мы видим, при использовании английского токенизатора для предварительной обработки французского предложения получается гораздо больше токенов, поскольку токенизатор не знает ни одного французского слова (кроме тех, которые также встречаются в английском языке, например "discussion"). + +Поскольку `inputs` - это словарь с нашими обычными ключами (идентификаторы входных данных, маска внимания и т. д.), последним шагом будет определение функции предварительной обработки, которую мы будем применять к датасетам: + +```python +max_length = 128 + + +def preprocess_function(examples): + inputs = [ex["en"] for ex in examples["translation"]] + targets = [ex["fr"] for ex in examples["translation"]] + model_inputs = tokenizer( + inputs, text_target=targets, max_length=max_length, truncation=True + ) + return model_inputs +``` + +Обратите внимание, что мы установили одинаковую максимальную длину для наших входов и выходов. Поскольку тексты, с которыми мы имеем дело, довольно короткие, мы используем 128. + + + +💡 Если вы используете модель T5 (точнее, одну из контрольных точек `t5-xxx`), модель будет ожидать, что текстовые данные будут иметь префикс, указывающий на поставленную задачу, например `translate: English to French:`. + + + + + +⚠️ Мы не обращаем внимания на маску внимания целевых значений, так как модель не будет этого ожидать. Вместо этого метки, соответствующие токенам дополнения, должны быть заданы как `-100`, чтобы они игнорировались при вычислении потерь. Это будет сделано нашим коллатором данных позже, так как мы применяем динамическое дополнение (dynamic padding), но если вы используете дополнение (padding) здесь, вы должны адаптировать функцию предварительной обработки данных, чтобы установить все метки, соответствующие токену дополнения, в `-100`. + + + +Теперь мы можем применить эту функцию предварительной обработки ко всем частям нашего датасета за один раз: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Теперь, когда данные прошли предварительную обработку, мы готовы дообучить нашу предварительно обученную модель! + +{#if fw === 'pt'} + +## Дообучение модели с помощью API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]] + +Фактический код, использующий `Trainer`, будет таким же, как и раньше, с одним лишь небольшим изменением: мы используем [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), который является подклассом `Trainer`, что позволит нам правильно работать с оценкой, используя метод `generate()` для предсказания выходов на основе входов. Мы рассмотрим это более подробно, когда будем говорить о вычислении метрик. + +Прежде всего, нам нужна реальная модель, которую нужно дообучить. Мы воспользуемся обычным API `AutoModel`: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Дообучение модели с Keras[[fine-tuning-the-model-with-keras]] + +Прежде всего, нам нужна актуальная модель, которую нужно дообучить. Мы воспользуемся обычным API `AutoModel`: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Контрольная точка `Helsinki-NLP/opus-mt-en-fr` имеет только веса PyTorch, поэтому +вы получите ошибку, если попытаетесь загрузить модель без использования аргумента +`from_pt=True` в методе `from_pretrained()`. Когда вы указываете +`from_pt=True`, библиотека автоматически загрузит и преобразует +веса из PyTorch для вас. Как видите, очень просто переключаться между +фреймворками в 🤗 Transformers! + + + +{/if} + +Обратите внимание, что в этот раз мы используем модель, которая была обучена на задаче перевода и фактически уже может быть использована, поэтому нет предупреждения о пропущенных или новых инициализированных весах. + +### Сопоставление данных[[data-collation]] + +Для динамического батча нам понадобится коллатор данных для работы с дополнением (padding). Мы не можем просто использовать `DataCollatorWithPadding`, как в [Главе 3](../chapter3/1), потому что в этом случае будут заполнены только входы (идентификаторы входов, маска внимания и идентификаторы типов токенов). Наши метки также должны быть дополнены до максимальной длины, встречающейся в метках. И, как уже говорилось, добовляемое значение, используемое для дополнения меток, должно быть `-100`, а не добавочный токен токенизатора, чтобы эти добавочные значения игнорировались при вычислении потерь. + +Все это делает [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Как и `DataCollatorWithPadding`, он принимает `tokenizer`, используемый для препроцессирования входных данных, а также `model`. Это связано с тем, что данный коллатор данных также будет отвечать за подготовку входных идентификаторов декодера, которые представляют собой сдвинутые версии меток со специальным токеном в начале. Поскольку для разных архитектур этот сдвиг выполняется по-разному, `DataCollatorForSeq2Seq` должен знать объект `model`: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```py +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Чтобы протестировать его на нескольких примерах, мы просто вызываем его на списке примеров из нашего токинезированного обучающего набора: + +```py +batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) +batch.keys() +``` + +```python out +dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) +``` + +Мы можем проверить, что наши метки были дополнены до максимальной длины батча, с использованием `-100`: + +```py +batch["labels"] +``` + +```python out +tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, + -100, -100, -100, -100, -100, -100], + [ 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, + 550, 7032, 5821, 7907, 12649, 0]]) +``` + +Также мы можем взглянуть на идентификаторы входов декодера и убедиться, что они являются сдвинутыми версиями меток: + +```py +batch["decoder_input_ids"] +``` + +```python out +tensor([[59513, 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, + 59513, 59513, 59513, 59513, 59513, 59513], + [59513, 1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, + 817, 550, 7032, 5821, 7907, 12649]]) +``` + +Вот метки для первого и второго элементов в нашем датасете: + +```py +for i in range(1, 3): + print(tokenized_datasets["train"][i]["labels"]) +``` + +```python out +[577, 5891, 2, 3184, 16, 2542, 5, 1710, 0] +[1211, 3, 49, 9409, 1211, 3, 29140, 817, 3124, 817, 550, 7032, 5821, 7907, 12649, 0] +``` + +{#if fw === 'pt'} + +Мы передадим этот `data_collator` в `Seq2SeqTrainer`. Далее давайте рассмотрим метрику. + +{:else} + +Теперь мы можем использовать этот `data_collator` для преобразования каждого из наших датасетов в `tf.data.Dataset`, готовый к обучению: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Метрики[[metrics]] + + + +{#if fw === 'pt'} + +Свойство, которое `Seq2SeqTrainer` добавляет к своему суперклассу `Trainer`, - это возможность использовать метод `generate()` во время оценки или предсказания. Во время обучения модель будет использовать `decoder_input_ids` с маской внимания, гарантирующей, что она не будет использовать токены после токена, который пытается предсказать, чтобы ускорить обучение. Во время инференса мы не сможем использовать эти данные, так как у нас не будет меток, поэтому было бы неплохо оценить нашу модель с аналогичной настройкой. + +Как мы видели в [Главе 1](../chapter1/6), декодер выполняет инференс, предсказывая токены один за другим - то, что за кулисами реализовано в 🤗 Transformers методом `generate()`. Тренер `Seq2SeqTrainer` позволит нам использовать этот метод для оценки, если мы установим `predict_with_generate=True`. + +{/if} + +Традиционной метрикой, используемой для перевода, является [BLEU score](https://en.wikipedia.org/wiki/BLEU), представленная в [статье 2002 года](https://aclanthology.org/P02-1040.pdf) Кишором Папинени и др. BLEU score оценивает, насколько близки переводы к своим меткам. Он не измеряет разборчивость или грамматическую правильность сгенерированных моделью результатов, но использует статистические правила, чтобы гарантировать, что все слова в сгенерированных результатах также встречаются в целевых. Кроме того, существуют правила, наказывающие повторы одних и тех же слов, если они не повторяются в целевых словах (чтобы модель не выводила предложения типа `"the the the the the the the"), и вывод предложений, которые короче, чем целевые (чтобы модель не выводила предложения типа `"the"`). + +Один из недостатков BLEU заключается в том, что она предполагает, что текст уже прошел токенизацию, что затрудняет сравнение оценок между моделями, использующими различные токенизаторы. Поэтому сегодня для сравнения моделей перевода чаще всего используется метрика [SacreBLEU](https://github.com/mjpost/sacrebleu), которая устраняет этот недостаток (и другие) путем стандартизации этапа токенизации. Чтобы использовать эту метрику, сначала нужно установить библиотеку SacreBLEU: + +```py +!pip install sacrebleu +``` + +Затем мы можем загрузить ее с помощью `evaluate.load()`, как мы это делали в [Главе 3](../chapter3/1): + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +Эта метрика принимает тексты в качестве входных и целевых данных. Она рассчитана на использование нескольких приемлемых целей, так как часто существует несколько приемлемых переводов одного и того же предложения - в используемом нами датасете есть только один, но в NLP нередко встречаются датасеты, дающие несколько предложений в качестве меток. Таким образом, прогнозы должны представлять собой список предложений, а ссылки - список списков предложений. + +Попробуем рассмотреть пример: + +```py +predictions = [ + "This plugin lets you translate web pages between several languages automatically." +] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 46.750469682990165, + 'counts': [11, 6, 4, 3], + 'totals': [12, 11, 10, 9], + 'precisions': [91.67, 54.54, 40.0, 33.33], + 'bp': 0.9200444146293233, + 'sys_len': 12, + 'ref_len': 13} +``` + +Это дает оценку BLEU 46,75, что довольно хорошо - для сравнения, оригинальная модель Transformer в статье ["Attention Is All You Need" paper](https://arxiv.org/pdf/1706.03762.pdf) достигла оценки BLEU 41,8 на аналогичной задаче перевода с английского на французский! (Более подробную информацию об отдельных метриках, таких как `counts` и `bp`, можно найти в репозитории [SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74)). С другой стороны, если мы попробуем использовать два плохих типа предсказаний (много повторов или слишком короткие), которые часто получаются в моделях перевода, мы получим довольно плохие оценки BLEU: + +```py +predictions = ["This This This This"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 1.683602693167689, + 'counts': [1, 0, 0, 0], + 'totals': [4, 3, 2, 1], + 'precisions': [25.0, 16.67, 12.5, 12.5], + 'bp': 0.10539922456186433, + 'sys_len': 4, + 'ref_len': 13} +``` + +```py +predictions = ["This plugin"] +references = [ + [ + "This plugin allows you to automatically translate web pages between several languages." + ] +] +metric.compute(predictions=predictions, references=references) +``` + +```python out +{'score': 0.0, + 'counts': [2, 1, 0, 0], + 'totals': [2, 1, 0, 0], + 'precisions': [100.0, 100.0, 0.0, 0.0], + 'bp': 0.004086771438464067, + 'sys_len': 2, + 'ref_len': 13} +``` + +Оценка может варьироваться от 0 до 100, причем чем больше, тем лучше. + +{#if fw === 'tf'} + +Чтобы получить из результатов модели тексты, которые может использовать метрика, мы воспользуемся методом `tokenizer.batch_decode()`. Нам нужно только очистить все знаки `-100` в метках; токенизатор автоматически сделает то же самое для дополняющего токена. Определим функцию, которая берет нашу модель и датасет и вычисляет на нем метрики. Мы также используем трюк, который значительно повышает производительность, - компиляцию нашего кода генерации с помощью [XLA](https://www.tensorflow.org/xla), ускоренного компилятора линейной алгебры TensorFlow. XLA применяет различные оптимизации к графу вычислений модели, что приводит к значительному увеличению скорости и использования памяти. Как описано в блоге Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA лучше всего работает, когда наши входные формы не слишком сильно варьируются. Чтобы справиться с этим, мы разделим наши входные данные на части, кратные 128, и создадим новый датасет с коллатором с дополнением, а затем применим декоратор `@tf.function(jit_compile=True)` к нашей функции генерации, который обозначит всю функцию для компиляции с помощью XLA. + +```py +import numpy as np +import tensorflow as tf +from tqdm import tqdm + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=128 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=128, + ) + + +def compute_metrics(): + all_preds = [] + all_labels = [] + + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) + + result = metric.compute(predictions=all_preds, references=all_labels) + return {"bleu": result["score"]} +``` + +{:else} + +Чтобы получить из результатов модели тексты, которые может использовать метрика, мы воспользуемся методом `tokenizer.batch_decode()`. Нам просто нужно очистить все значения `-100` в метках (токенизатор автоматически сделает то же самое для дополняющего токена): + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # В случае, если модель возвращает больше, чем предсказанные логиты + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Заменяем -100 в метках, так как мы не можем их декодировать + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Немного простой постобработки + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + + result = metric.compute(predictions=decoded_preds, references=decoded_labels) + return {"bleu": result["score"]} +``` + +{/if} + +Теперь, когда все готово, мы готовы дообучить нашу модель! + + +### Дообучение модели[[fine-tuning-the-model]] + +Первый шаг - войти в Hugging Face, чтобы загрузить результаты в Model Hub. В блокноте есть удобная функция, которая поможет вам в этом: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. + +Если вы работаете не в блокноте, просто введите следующую строку в терминале: + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Прежде чем начать, давайте посмотрим, какие результаты мы получим от нашей модели без какого-либо обучения: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Как только это будет сделано, мы сможем подготовить все необходимое для компиляции и обучения нашей модели. Обратите внимание на использование `tf.keras.mixed_precision.set_global_policy("mixed_float16")` - это укажет Keras обучать с использованием float16, что может дать значительное ускорение на GPU, поддерживающих эту функцию (Nvidia 20xx/V100 или новее). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное +# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это батч tf.data.Dataset, +# а не оригинальный датасет Hugging Face, поэтому его len() уже равен num_samples // batch_size. +num_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_epochs + +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Обучение со смешанной точностью float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Далее мы определяем обратный вызов `PushToHubCallback` для загрузки нашей модели в Hub во время обучения, как мы видели в [разделе 2](../chapter7/2), а затем мы просто подгоняем модель с помощью этого обратного вызова: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir="marian-finetuned-kde4-en-to-fr", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, + validation_data=tf_eval_dataset, + callbacks=[callback], + epochs=num_epochs, +) +``` + +Обратите внимание, что с помощью аргумента `hub_model_id` можно указать имя репозитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` в `Seq2SeqTrainingArguments`. По умолчанию используемый репозиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, поэтому здесь это будет `"sgugger/marian-finetuned-kde4-en-to-fr"` (это модель, на которую мы ссылались в начале этого раздела). + + + +💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном репозитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при вызове `model.fit()` и должны будете задать новое имя. + + + +Наконец, давайте посмотрим, как выглядят наши метрики после завершения обучения: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи перевода - поздравляем! + +{:else} + +Когда это сделано, мы можем определить наши `Seq2SeqTrainingArguments`. Как и в случае с `Trainer`, мы используем подкласс `TrainingArguments`, который содержит еще несколько дополнительных полей: + +```python +from transformers import Seq2SeqTrainingArguments + +args = Seq2SeqTrainingArguments( + f"marian-finetuned-kde4-en-to-fr", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + per_device_train_batch_size=32, + per_device_eval_batch_size=64, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=3, + predict_with_generate=True, + fp16=True, + push_to_hub=True, +) +``` + +Помимо обычных гиперпараметров (таких как скорость обучения, количество эпох, размер батча и некоторое затухание веса), здесь есть несколько отличий по сравнению с тем, что мы видели в предыдущих разделах: + +- Мы не задаем никаких регулярных оценок, так как оценка занимает много времени; мы просто оценим нашу модель один раз до обучения и после. +- Мы установили `fp16=True`, что ускоряет обучение на современных GPU. +- Мы устанавливаем `predict_with_generate=True`, как обсуждалось выше. +- Мы используем `push_to_hub=True` для загрузки модели в Hub в конце каждой эпохи. + +Обратите внимание, что в аргументе `hub_model_id` можно указать полное имя розитория, в который вы хотите отправить модель (в частности, этот аргумент нужно использовать, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` в `Seq2SeqTrainingArguments`. По умолчанию используемый розиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным вами выходным каталогом, поэтому в нашем случае это будет `"sgugger/marian-finetuned-kde4-en-to-fr"` (это модель, на которую мы ссылались в начале этого раздела). + + + +💡 Если выходной каталог, который вы используете, уже существует, он должен быть локальным клоном того розитория, в который вы хотите выполнить push. Если это не так, вы получите ошибку при определении вашего `Seq2SeqTrainer` и должны будете задать новое имя. + + + + +Наконец, мы просто передаем все в `Seq2SeqTrainer`: + +```python +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, +) +``` + +Перед обучением мы сначала посмотрим, какую оценку получила наша модель, чтобы проверить, не ухудшаем ли мы результаты, дообучив ее. Выполнение этой команды займет некоторое время, поэтому во время ее выполнения можно выпить кофе: + +```python +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 1.6964408159255981, + 'eval_bleu': 39.26865061007616, + 'eval_runtime': 965.8884, + 'eval_samples_per_second': 21.76, + 'eval_steps_per_second': 0.341} +``` + +Оценка BLEU в 39 баллов не так уж плохо, что говорит о том, что наша модель уже хорошо справляется с переводом английских предложений на французский. + +Далее следует обучение, которое также займет некоторое время: + +```python +trainer.train() +``` + +Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине. + +После завершения обучения мы снова оцениваем нашу модель - надеемся, мы увидим некоторое улучшение в показателе BLEU! + +```py +trainer.evaluate(max_length=max_length) +``` + +```python out +{'eval_loss': 0.8558505773544312, + 'eval_bleu': 52.94161337775576, + 'eval_runtime': 714.2576, + 'eval_samples_per_second': 29.426, + 'eval_steps_per_second': 0.461, + 'epoch': 3.0} +``` + +Это улучшение почти на 14 пунктов, что очень хорошо. + +Наконец, мы используем метод `push_to_hub()`, чтобы убедиться, что загружена последняя версия модели. Тренер также создает карту модели со всеми результатами оценки и загружает ее. Эта карта модели содержит метаданные, которые помогают хабу моделей выбрать виджет для демонстрации инференса. Обычно ничего не нужно указывать, так как он сам определяет нужный виджет по классу модели, но в данном случае один и тот же класс модели может быть использован для всех видов задач, связанных с последовательностями, поэтому мы указываем, что это модель перевода: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +Эта команда возвращает URL-адрес только что выполненного коммита, если вы хотите его просмотреть: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать свою модель и поделиться ею с друзьями. Вы успешно дообучили модель для задачи перевода - поздравляем! + +Если вы хотите более глубоко погрузиться в цикл обучения, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate. + +{/if} + +{#if fw === 'pt'} + +## Индивидуальный цикл обучения[[a-custom-training-loop]] + +Теперь давайте рассмотрим полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет выглядеть примерно так же, как мы делали это в [Главе 2](../chapter7/2) и [Главе 3](../chapter3/4). + +### Подготовка всего к обучению[[preparing-everything-for-training]] + +Вы уже видели все это несколько раз, поэтому мы пройдемся по коду довольно быстро. Сначала мы создадим `DataLoader` из наших датасетов, после чего установим для датасетов формат `"torch"`, чтобы получить тензоры PyTorch: + +```py +from torch.utils.data import DataLoader + +tokenized_datasets.set_format("torch") +train_dataloader = DataLoader( + tokenized_datasets["train"], + shuffle=True, + collate_fn=data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], collate_fn=data_collator, batch_size=8 +) +``` + +Затем мы повторно инстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучение предыдущей модели, а начинаем с предварительно обученной модели: + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Тогда нам понадобится оптимизатор: + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`. Помните, что если вы хотите обучаться на TPU в блокноте Colab, вам нужно будет перенести весь этот код в функцию обучения, которая не должна выполнять ни одной ячейки, инстанцирующей `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит длину `DataLoader`. Мы используем классический линейный планировшик скорости обучения до 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +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, +) +``` + +Наконец, чтобы отправить нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не авторизованы. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный выбор; оно просто должно содержать ваше имя пользователя, что и делает функция `get_full_repo_name()`): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "marian-finetuned-kde4-en-to-fr-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' +``` + +Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть клоном того розитория, с которым мы работаем: + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи. + +### Цикл обучения[[training-loop]] + +Теперь мы готовы написать полный цикл обучения. Чтобы упростить его оценочную часть, мы определяем эту функцию `postprocess()`, которая принимает прогнозы и метки и преобразует их в списки строк, которые будет ожидать наш объект `metric`: + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Заменим -100 в метках, так как мы не можем их декодировать. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Немного простой постобработки + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +Цикл обучения очень похож на циклы в [разделе 2](../chapter7/2) и [главе 3](../chapter3/1), с некоторыми отличиями в части оценки - так что давайте сосредоточимся на этом! + +Первое, что следует отметить, это то, что мы используем метод `generate()` для вычисления прогнозов, но это метод нашей базовой модели, а не обернутой модели 🤗 Accelerate, созданной в методе `prepare()`. Поэтому мы сначала развернем модель, а затем вызовем этот метод. + +Второй момент заключается в том, что, как и в случае с [классификацией токенов](../chapter7/2), два процесса могут дополнить входы и метки до разных форм, поэтому мы используем `accelerator.pad_across_processes()`, чтобы сделать прогнозы и метки одинаковыми по форме перед вызовом метода `gather()`. Если мы этого не сделаем, оценка либо выдаст ошибку, либо зависнет навсегда. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Обучение + model.train() + for batch in train_dataloader: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Оценка + model.eval() + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + max_length=128, + ) + labels = batch["labels"] + + ## Необходимо дополнить прогнозы и метки + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) + + predictions_gathered = accelerator.gather(generated_tokens) + labels_gathered = accelerator.gather(labels) + + decoded_preds, decoded_labels = postprocess(predictions_gathered, labels_gathered) + metric.add_batch(predictions=decoded_preds, references=decoded_labels) + + results = metric.compute() + print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") + + # Сохранение и загрузка + 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 + ) +``` + +```python out +epoch 0, BLEU score: 53.47 +epoch 1, BLEU score: 54.24 +epoch 2, BLEU score: 54.44 +``` + +После этого у вас должна получиться модель, результаты которой будут очень похожи на модель, обученную с помощью `Seq2SeqTrainer`. Вы можете проверить модель, которую мы обучили с помощью этого кода, на [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете реализовать их напрямую, отредактировав код, показанный выше! + +{/if} + +## Использование дообученной модели[[using-the-fine-tuned-model]] + +Мы уже показали вам, как можно использовать модель, которую мы дообучили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нам просто нужно указать соответствующий идентификатор модели: + +```py +from transformers import pipeline + +# Замените это на свою собственную контрольную точку +model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" +translator = pipeline("translation", model=model_checkpoint) +translator("Default to expanded threads") +``` + +```python out +[{'translation_text': 'Par défaut, développer les fils de discussion'}] +``` + +Как и ожидалось, наша предварительно обученная модель адаптировала свои знания к корпусу, на котором мы ее дообучили, и вместо того, чтобы оставить английское слово "threads" в покое, она теперь переводит его на официальный французский вариант. То же самое относится и к слову " plugin ": + +```py +translator( + "Unable to import %1 using the OFX importer plugin. This file is not the correct format." +) +``` + +```python out +[{'translation_text': "Impossible d'importer %1 en utilisant le module externe d'importation OFX. Ce fichier n'est pas le bon format."}] +``` + +Еще один отличный пример доменной адаптации! + + + +✏️ **Попробуйте!** Что возвращает модель для примера со словом "email", который вы определили ранее? + + diff --git a/chapters/ru/chapter7/5.mdx b/chapters/ru/chapter7/5.mdx new file mode 100644 index 000000000..9c14bb75a --- /dev/null +++ b/chapters/ru/chapter7/5.mdx @@ -0,0 +1,1072 @@ + + +# Суммаризация[[summarization]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +В этом разделе мы рассмотрим, как модели Transformer могут быть использованы для сжатия длинных документов в краткое изложение - задача, известная как _суммаризация текста (text summarization)_. Это одна из самых сложных задач NLP, поскольку она требует целого ряда способностей, таких как понимание длинных отрывков и генерация связного текста, отражающего основные темы документа. Однако при правильном подходе суммаризация текста - это мощный инструмент, который может ускорить различные бизнес-процессы, избавив экспертов домена от необходимости детального прочтения длинных документов. + + + +Хотя на [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads) уже существуют различные дообученные модели для суммаризации, почти все они подходят только для англоязычных документов. Поэтому, чтобы добавить изюминку в этот раздел, мы обучим двухязыковую модель для английского и испанского языков. К концу этого раздела у вас будет [модель](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es), способная к суммаризации отзывов покупателей, как показано здесь: + + + +Как мы увидим, эти резюме кратки, поскольку они составляются на основе названий, которые покупатели указывают в своих отзывах о товаре. Для начала давайте соберем подходящий двуязыковой корпус для этой задачи. + +## Подготовка многоязыкового корпуса[[preparing-a-multilingual-corpus]] + +Для создания нашей двуязыковой суммаризации мы будем использовать [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi). Этот корпус состоит из отзывов о товарах Amazon на шести языках и обычно используется для тестирования многоязыковых классификаторов. Однако, поскольку каждый отзыв сопровождается коротким заголовком, мы можем использовать заголовки в качестве целевых резюме для обучения нашей модели! Чтобы начать работу, давайте загрузим английские и испанские подмножества из Hugging Face Hub: + +```python +from datasets import load_dataset + +spanish_dataset = load_dataset("amazon_reviews_multi", "es") +english_dataset = load_dataset("amazon_reviews_multi", "en") +english_dataset +``` + +```python out +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 + }) +}) +``` + +Как видите, для каждого языка есть 200 000 отзывов в части `train` и по 5 000 отзывов в частях `validation` и `test`. Интересующая нас информация о рецензиях содержится в столбцах `review_body` и `review_title`. Давайте рассмотрим несколько примеров, создав простую функцию, которая берет случайную выборку из обучающего множества с помощью методов, изученных в [Главе 5](../chapter5/1): + +```python +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) +``` + +```python out +'>> 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.' +``` + + + +✏️ **Попробуйте!** Измените random seed в команде `Dataset.shuffle()`, чтобы изучить другие отзывы в корпусе. Если вы владеете испанским языком, посмотрите на некоторые отзывы в `spanish_dataset`, чтобы понять, похожи ли их названия на разумные резюме. + + + +Эта выборка демонстрирует разнообразие отзывов, которые обычно можно найти в сети, - от положительных до отрицательных (и все, что между ними!). Хотя пример с названием "meh" не очень информативен, остальные названия выглядят как достойные резюме самих отзывов. Обучение модели суммаризации всех 400 000 отзывов заняло бы слишком много времени на одном GPU, поэтому вместо этого мы сосредоточимся на создании резюме для одного домена продуктов. Чтобы получить представление о том, какие домены мы можем выбрать, давайте преобразуем `english_dataset` в `pandas.DataFrame` и вычислим количество отзывов по каждой категории товаров: + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Show counts for top 20 products +english_df["product_category"].value_counts()[:20] +``` + +```python out +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 +``` + +Самые популярные товары в английском датасете - это бытовые товары, одежда и беспроводная электроника. Однако, чтобы поддержать тему Amazon, давайте сосредоточимся на суммаризации отзывов о книгах - в конце концов, именно для этого компания и была основана! Мы видим две категории товаров, которые подходят для этой цели (`book` и `digital_ebook_purchase`), поэтому давайте отфильтруем датасеты на обоих языках только для этих товаров. Как мы видели в [Главе 5](../chapter5/1), функция `Dataset.filter()` позволяет нам очень эффективно разделять датасет на части, поэтому мы можем определить простую функцию для этого: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Теперь, когда мы применим эту функцию к `english_dataset` и `spanish_dataset`, результат будет содержать только те строки, в которых присутствуют категории книг. Прежде чем применить фильтр, давайте изменим формат `english_dataset` с `"pandas"` обратно на `"arrow"`: + +```python +english_dataset.reset_format() +``` + +Затем мы можем применить функцию фильтрации, и в качестве проверки работоспособности давайте посмотрим на выборку отзывов, чтобы убедиться, что они действительно посвящены книгам: + +```python +spanish_books = spanish_dataset.filter(filter_books) +english_books = english_dataset.filter(filter_books) +show_samples(english_books) +``` + +```python out +'>> 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.' +``` + +Хорошо, мы видим, что отзывы не совсем о книгах и могут относиться к таким вещам, как календари и электронные приложения, например OneNote. Тем не менее, домен кажется подходящим для обучения модели суммаризации. Прежде чем мы рассмотрим различные модели, подходящие для этой задачи, нам осталось подготовить данные: объединить английские и испанские отзывы в один объект `DatasetDict`. 🤗Datasets предоставляет удобную функцию `concatenate_datasets()`, которая (как следует из названия) стекирует два объекта `Dataset` друг на друга. Таким образом, чтобы создать двуязыковой датасет, мы пройдемся по каждой части, объединим датасеты для этой части и перемешаем результат, чтобы наша модель не была слишком переобучена для одного языка: + +```python +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) + +# Посмотрим на несколько примеров +show_samples(books_dataset) +``` + +```python out +'>> 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' +``` + +Это определенно похоже на смесь английских и испанских обзоров! Теперь, когда у нас есть тренировочный корпус, осталось проверить распределение слов в рецензиях и их заголовках. Это особенно важно для задач суммаризации, где короткие эталонные резюме в данных могут склонять модель в сторону вывода только одного или двух слов в сгенерированных резюме. На графиках ниже показаны распределения слов, и мы видим, что названия сильно перекошены в сторону 1-2 слов: + +
+Word count distributions for the review titles and texts. + +
+ +Чтобы справиться с этой проблемой, мы отфильтруем примеры с очень короткими заголовками, чтобы наша модель могла создавать более интересные резюме. Поскольку мы имеем дело с английскими и испанскими текстами, мы можем использовать грубую эвристику для разделения названий по символам пробела, а затем использовать наш надежный метод `Dataset.filter()` следующим образом: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Теперь, когда мы подготовили корпус, давайте рассмотрим несколько возможных моделей Transformer, которые можно было бы дообучить на его основе! + +## Модели для суммаризации текста[[models-for-text-summarization]] + +Если задуматься, то суммаризация текста - это задача, похожая на машинный перевод: у нас есть текст, например рецензия, который мы хотели бы "перевести" в более короткую версию, передающую основные особенности исходного текста. Соответственно, большинство моделей Transformer для суммаризации используют архитектуру кодер-декодер, с которой мы впервые столкнулись в [Глава 1](../chapter1/1), хотя есть и исключения, например семейство моделей GPT, которые также могут использоваться для суммаризации в условиях few-shot настроек. В следующей таблице перечислены некоторые популярные предварительно обученные модели, которые можно дообучить для суммаризации. + +| Модель Transformer | Описание | Многоязычная? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Хотя GPT-2 обучен как авторегрессивная языковая модель, вы можете заставить его генерировать резюме, добавляя "TL;DR" в конце входного текста. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Использует цель предварительного обучения для предсказания замаскированных предложений в текстах с несколькими предложениями. Эта задача предварительного обучения ближе к суммаризации, чем к классическому языковому моделированию демонстрирует высокие результаты в популярных бенчмарках.| ❌ | +| [T5](https://huggingface.co/t5-base) | Универсальная трансформерная архитектура, которая формулирует все задачи в рамках преобразования текста в текст; например, входной формат модели для суммаризации документа - `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Многоязыковая версия T5, предварительно обученная на многоязыковом корпусе Common Crawl (mC4), охватывающем 101 язык. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Новая архитектура Transformer с кодером и стеком декодеров, обученных восстанавливать поврежденный входной сигнал, сочетает в себе схемы предварительного обучения BERT и GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Многоязыковая версия BART, предварительно обученная на 50 языках. | ✅ | + +Как видно из этой таблицы, большинство моделей Transformer для суммаризации (и вообще для большинства задач NLP) являются монолингвистическими. Это хорошо, если ваша задача на "высокоресурсном" языке, таком как английский или немецкий, но не очень хорошо для тысяч других языков, используемых по всему миру. К счастью, существует класс многоязыковых моделей Transformer, таких как mT5 и mBART, которые приходят на помощь. Эти модели предварительно обучаются с помощью языкового моделирования, но с изюминкой: вместо обучения на корпусе одного языка они обучаются совместно на текстах более чем на 50 языках одновременно! + +Мы сосредоточимся на mT5, интересной архитектуре, основанной на T5, которая была предварительно обучена на фреймворке "текс-в-текст" (text-to-text). В T5 каждая задача NLP формулируется в терминах префикса подсказки, например `summarize:`, который определяет, что модель должна адаптировать сгенерированный текст к подсказке. Как показано на рисунке ниже, это делает T5 чрезвычайно универсальным, поскольку вы можете решать множество задач с помощью одной модели! + +
+Different tasks performed by the T5 architecture. + +
+ +В mT5 не используются префиксы, но она обладает многими универсальными возможностями T5 и имеет многоязыковое преимущество. Теперь, когда мы выбрали модель, давайте посмотрим, как подготовить данные для обучения. + + + + +✏️ **Попробуйте!** После того как вы проработаете этот раздел, посмотрите, насколько хорошо mT5 сравнится с mBART, дообучив его тем же методам. Чтобы получить бонусные очки, вы также можете попробовать дообучить T5 только на английских рецензиях. Поскольку в T5 есть специальный префикс запроса, вам нужно будет добавить `summarize:` к входным примерам на следующих шагах предварительной обработки. + + + +## Предварительная обработка данных[[preprocessing-the-data]] + + + +Наша следующая задача - токенизация и кодирование отзывов и их заголовков. Как обычно, мы начинаем с загрузки токенизатора, связанного с контрольной точкой предварительно обученной модели. В качестве контрольной точки мы будем использовать `mt5-small`, чтобы можно было дообучить модель за разумное время: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 На ранних стадиях ваших NLP-проектов хорошей практикой является обучение класса "маленьких" моделей на небольшой выборке данных. Это позволит вам быстрее отлаживать и итерировать модели, чтобы создать сквозной рабочий процесс. Когда вы будете уверены в результатах, вы всегда сможете увеличить масштаб модели, просто изменив контрольную точку модели! + + + +Давайте протестируем токенизатор mT5 на небольшом примере: + +```python +inputs = tokenizer("I loved reading the Hunger Games!") +inputs +``` + +```python out +{'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} +``` + +Здесь мы видим знакомые нам `input_ids` и `attention_mask`, с которыми мы столкнулись в наших первых экспериментах по дообучению еще в [Главе 3](../chapter3/1). Давайте декодируем эти входные идентификаторы с помощью функции токенизатора `convert_ids_to_tokens()`, чтобы понять, с каким токенизатором мы имеем дело: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Специальный символ Юникода `▁` и токен конца последовательности `` указывают на то, что мы имеем дело с токенизатором SentencePiece, который основан на алгоритме сегментации Unigram, рассмотренном в [Главе 6](../chapter6/1). Unigram особенно полезен для многоязычных корпусов, поскольку он позволяет SentencePiece не зависеть от ударений, пунктуации и того факта, что во многих языках, например в японском, нет пробельных символов. + +Для токенизации нашего корпуса нам придется столкнуться с одной тонкостью, связанной с сумризацией: поскольку наши метки также являются текстом, возможно, что они превышают максимальный размер контекста модели. Это означает, что нам нужно применять усечение как к обзорам, так и к их заголовкам, чтобы не передавать в модель слишком длинные данные. Токенизаторы в 🤗 Transformers предоставляют интересный аргумент `text_target`, который позволяет вам токенизировать метки параллельно с входными данными. Вот пример того, как обрабатываются входные и целевые данные для mT5: + +```python +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 +``` + +Давайте пройдемся по этому коду, чтобы понять, что происходит. Первое, что мы сделали, это определили значения `max_input_length` и `max_target_length`, которые устанавливают верхние границы длины наших обзоров и заголовков. Поскольку тело обзора обычно намного больше заголовка, мы соответственно изменили эти значения. + +С помощью `preprocess_function()` можно провести токенизацию всего корпуса с помощью удобной функции `Dataset.map()`, которую мы часто использовали на протяжении всего курса: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Теперь, когда корпус был предварительно обработан, давайте посмотрим на некоторые метрики, которые обычно используются для суммаризации. Как мы увидим, не существует серебряной пули, когда дело доходит до измерения качества сгенерированного машиной текста. + + + +💡 Возможно, вы заметили, что выше в функции `Dataset.map()` мы использовали `batched=True`. Это кодирует примеры в батчах по 1 000 (по умолчанию) и позволяет использовать возможности многопоточности быстрых токенизаторов в 🤗 Transformers. По возможности, попробуйте использовать `batched=True`, чтобы получить максимальную отдачу от препроцессинга! + + + + +## Метрики для суммаризации текста[[metrics-for-text-summarization]] + + + +По сравнению с большинством других задач, которые мы рассматривали в этом курсе, измерение качества работы задач по созданию текста, таких как суммаризация или перевод, не так просто. Например, для рецензии типа "I loved reading the Hunger Games" существует множество правильных резюме, таких как "I loved the Hunger Games" или "Hunger Games is a great read". Очевидно, что применение какого-то точного соответствия между сгенерированным резюме и меткой не является хорошим решением - даже люди не справятся с такой метрикой, потому что у каждого из нас свой стиль написания. + +Для суммаризации одной из наиболее часто используемых метрик является [оценка ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (сокращение от Recall-Oriented Understudy for Gisting Evaluation). Основная идея этой метрики заключается в сравнении сгенерированного резюме с набором эталонных резюме, которые обычно создаются людьми. Чтобы сделать ее более точной, предположим, что мы хотим сравнить следующие два резюме: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +Одним из способов их сравнения может быть подсчет количества перекрывающихся слов, которых в данном случае будет 6. Однако это несколько грубовато, поэтому вместо этого ROUGE основывается на вычислении оценок _precision_ и _recall_ для перекрытия. + + + +🙋 Не волнуйтесь, если вы впервые слышите о precision и recall - мы вместе разберем несколько наглядных примеров, чтобы все стало понятно. Эти метрики обычно встречаются в задачах классификации, поэтому, если вы хотите понять, как определяются precision и recall в этом контексте, мы рекомендуем ознакомиться с `scikit-learn` [руководством](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Для ROUGE recall измеряет, насколько эталонное резюме соответствует сгенерированному. Если мы просто сравниваем слова, recall можно рассчитать по следующей формуле: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +Для нашего простого примера выше эта формула дает идеальный recall 6/6 = 1; то есть все слова в эталонном резюме были получены моделью. Это может показаться замечательным, но представьте, если бы сгенерированное нами резюме было "I really really loved reading the Hunger Games all night". Это тоже дало бы идеальный recall, но, возможно, было бы хуже, поскольку было бы многословным. Чтобы справиться с этими сценариями, мы также вычисляем precision, которая в контексте ROUGE измеряет, насколько сгенерированное резюме было релевантным: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Если применить это к нашему подробному резюме, то precision составит 6/10 = 0,6, что значительно хуже, чем precision 6/7 = 0,86, полученная при использовании более короткого резюме. На практике обычно вычисляют и precision, и recall, а затем F1-score (среднее гармоническое из precision и recall). Мы можем легко это сделать с помощью 🤗 Datasets, предварительно установив пакет `rouge_score`: + +```py +!pip install rouge_score +``` + +а затем загрузить метрику ROUGE следующим образом: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Затем мы можем использовать функцию `rouge_score.compute()`, чтобы рассчитать все метрики сразу: + +```python +scores = rouge_score.compute( + predictions=[generated_summary], references=[reference_summary] +) +scores +``` + +```python out +{'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))} +``` + +Ого, в этом выводе много информации - что же она означает? Во-первых, 🤗 Datasets действительно вычисляет доверительные интервалы для precision, recall и F1-score; это `low`, `mid`, и `high` атрибуты, которые вы можете здесь увидеть. Кроме того, 🤗 Dataset вычисляет различные оценки ROUGE, которые основаны на различных типах детализации текста при сравнении сгенерированных и эталонных резюме. Вариант `rouge1` представляет собой перекрытие униграмм - это просто модный способ сказать о перекрытии слов, и это именно та метрика, которую мы обсуждали выше. Чтобы убедиться в этом, давайте извлечем `среднее` значение наших оценок: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Отлично, показатели precision и recall совпадают! А как насчет других показателей ROUGE? `rouge2` измеряет перекрытие биграмм (считайте, что это перекрытие пар слов), а `rougeL` и `rougeLsum` измеряют самые длинные совпадающие последовательности слов, ища самые длинные общие подстроки в сгенерированных и эталонных резюме. Слово "sum" в `rougeLsum` означает, что эта метрика вычисляется для всего резюме, в то время как `rougeL` вычисляется как среднее по отдельным предложениям. + + + +✏️ **Попробуйте!** Создайте свой собственный пример сгенерированного и эталонного резюме и посмотрите, согласуются ли полученные оценки ROUGE с ручным расчетом по формулам precision и recall. Для получения бонусных очков разбейте текст на биграммы и сравните precision и recall для метрики `rouge2`. + + + +Мы будем использовать эту оценку ROUGE для отслеживания эффективности нашей модели, но перед этим давайте сделаем то, что должен сделать каждый хороший NLP-практик: создадим сильную, но простую базовую модель! + +### Создание сильного базового уровня[[creating-a-strong-baseline]] + +Для суммаризации текста обычно берут первые три предложения статьи, что часто называют базвым уровнем _lead-3_. Мы могли бы использовать символы полной остановки для отслеживания границ предложений, но это не поможет при использовании таких аббревиатур, как " U.S." или "U.N.". -- поэтому вместо этого мы воспользуемся библиотекой `nltk`, которая включает в себя лучший алгоритм для таких случаев. Вы можете установить пакет с помощью `pip` следующим образом: + +```python +!pip install nltk +``` + +а затем скачайте правила пунктуации: + +```python +import nltk + +nltk.download("punkt") +``` + +Далее мы импортируем токенизатор предложений из `nltk` и создадим простую функцию для извлечения первых трех предложений в обзоре. При суммаризации текста принято отделять каждое предложение новой строкой, поэтому давайте включим эту функцию и протестируем ее на обучающем примере: + +```python +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"])) +``` + +```python out +'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.' +``` + +Похоже, что это работает, так что теперь давайте реализуем функцию, которая извлекает эти "резюме" из датасета и вычисляет оценку ROUGE для базового уровня: + +```python +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"]) +``` + +Затем мы можем использовать эту функцию для вычисления оценок ROUGE на валидационном множестве и немного приукрасить их с помощью Pandas: + +```python +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 +``` + +```python out +{'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} +``` + +Мы видим, что оценка `rouge2` значительно ниже, чем у остальных; скорее всего, это отражает тот факт, что заголовки рецензий обычно лаконичны, и поэтому базовый уровень lead-3 слишком многословен. Теперь, когда у нас есть хороший базовый уровень для работы, давайте дообучим mT5! + +{#if fw === 'pt'} + +## Дообучение mT5 с API `Trainer`[[fine-tuning-mt5-with-the-trainer-api]] + +Дообучение модели суммаризации очень похоже на другие задачи, которые мы рассмотрели в этой главе. Первое, что нам нужно сделать, это загрузить предварительно обученную модель из контрольной точки `mt5-small`. Поскольку суммаризация - это задача преобразования последовательности в последовательность, мы можем загрузить модель с помощью класса `AutoModelForSeq2SeqLM`, который автоматически загрузит и кэширует веса: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Дообучение mT5 с Keras[[fine-tuning-mt5-with-keras]] + +Дообучение модели суммаризации очень похоже на другие задачи, которые мы рассмотрели в этой главе. Первое, что нам нужно сделать, это загрузить предварительно обученную модель из контрольной точки `mt5-small`. Поскольку суммаризация - это задача преобразования последовательности в последовательность, мы можем загрузить модель с помощью класса `TFAutoModelForSeq2SeqLM`, который автоматически загрузит и кэширует веса: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Если вы задаетесь вопросом, почему вы не видите предупреждений о необходимости дообучить модель для последующей задачи, то это потому, что для задач "последовательность-в-последовательность" мы сохраняем все веса сети. Сравните это с нашей моделью классификации текста из [Главы 3](../chapter3/1), где голова предварительно обученной модели была заменена на случайно инициализированную сеть. + + + +Следующее, что нам нужно сделать, это войти в Hugging Face Hub. Если вы выполняете этот код в ноутбуке, вы можете сделать это с помощью следующей полезной функции: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +которая отобразит виджет, где вы можете ввести свои учетные данные. Также вы можете запустить эту команду в терминале и войти в систему там: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Для вычисления оценки ROUGE в процессе обучения нам понадобится генерировать резюме. К счастью, 🤗 Transformers предоставляет специальные классы `Seq2SeqTrainingArguments` и `Seq2SeqTrainer`, которые могут сделать это за нас автоматически! Чтобы увидеть, как это работает, давайте сначала определим гиперпараметры и другие аргументы для наших экспериментов: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Выводим потери при обучении по каждой эпохе +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, +) +``` + +Здесь аргумент `predict_with_generate` был задан, чтобы указать, что мы должны генерировать резюме во время оценки, чтобы мы могли вычислить баллы ROUGE для каждой эпохи. Как обсуждалось в [Главе 1](../chapter1/1), декодер выполняет инференс, предсказывая токены по одному, и это реализуется методом модели `generate()`. Задание `predict_with_generate=True` указывает `Seq2SeqTrainer` на использование этого метода для оценки. Мы также скорректировали некоторые гиперпараметры по умолчанию, такие как скорость обучения, количество эпох и затухание весов, и задали параметр `save_total_limit`, чтобы сохранять только 3 контрольные точки во время обучения - это сделано потому, что даже "маленькая" версия mT5 использует около Гигабайта места на жестком диске, и мы можем сэкономить немного места, ограничив количество копий, которые мы сохраняем. + +Аргумент `push_to_hub=True` позволит нам отправить модель в Hub после обучения; вы найдете розиторий под своим профилем пользователя в месте, определенном `output_dir`. Обратите внимание, что вы можете указать имя розитория, в который хотите отправить модель, с помощью аргумента `hub_model_id` (в частности, вам нужно использовать этот аргумент, чтобы отправить модель в организацию). Например, когда мы отправили модель в организацию [`huggingface-course`](https://huggingface.co/huggingface-course), мы добавили `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` в `Seq2SeqTrainingArguments`. + +Следующее, что нам нужно сделать, это предоставить тренеру функцию `compute_metrics()`, чтобы мы могли оценить нашу модель во время обучения. Для суммаризации это немного сложнее, чем просто вызвать `rouge_score.compute()` для прогнозов модели, поскольку нам нужно _декодировать_ выводы и метки в текст, прежде чем мы сможем вычислить оценку ROUGE. Следующая функция делает именно это, а также использует функцию `sent_tokenize()` из `nltk` для разделения предложений резюме символом новой строки: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Декодируем сгенерированные резюме в текст + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Заменяем -100 в метках, поскольку мы не можем их декодировать + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Декодируем эталонные резюме в текст + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE ожидает символ новой строки после каждого предложения + 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] + # Вычисляем оценки ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Извлекаем медианные оценки + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Далее нам нужно определить коллатор данных для нашей задачи преобразования последовательности в последовательность. Поскольку mT5 является моделью трансформера кодер-декодер, одна из тонкостей подготовки наших батчей заключается в том, что во время декодирования нам нужно сдвинуть метки вправо на единицу. Это необходимо для того, чтобы декодер видел только предыдущие метки, а не текущие или будущие, которые модели было бы легко запомнить. Это похоже на то, как маскированное самовнимание применяется к входным данным в задаче типа [каузального языкового моделирования](../chapter7/6). + +К счастью, 🤗 Transformers предоставляет коллатор `DataCollatorForSeq2Seq`, который будет динамически дополнять входные данные и метки за нас. Чтобы инстанцировать этот коллатор, нам просто нужно предоставить `tokenizer` и `model`: + +{#if fw === 'pt'} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model) +``` + +{:else} + +```python +from transformers import DataCollatorForSeq2Seq + +data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="tf") +``` + +{/if} + +Давайте посмотрим, что выдает этот коллатор, когда ему передается небольшой батч примеров. Во-первых, нам нужно удалить столбцы со строками, потому что коллатор не будет знать, как вставлять эти элементы: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Поскольку коллатор ожидает список словарей `dict`, где каждый словарь `dict` представляет один пример в датасете, нам также необходимо привести данные к ожидаемому формату, прежде чем передавать их коллатору: + +```python +features = [tokenized_datasets["train"][i] for i in range(2)] +data_collator(features) +``` + +```python out +{'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]])} +``` + +Главное, что здесь нужно заметить, - это то, что первый пример длиннее второго, поэтому `input_ids` и `attention_mask` второго примера были дополнены справа токеном `[PAD]` (чей идентификатор равен `0`). Аналогично, мы видим, что `labels` были дополнены значением `-100`, чтобы функция потерь игнорировала токены дополнения. И наконец, мы видим новый `decoder_input_ids`, в котором метки сдвинуты вправо за счет вставки токена `[PAD]` в первую запись. + +{#if fw === 'pt'} + +Наконец-то у нас есть все необходимые ингредиенты для обучения! Теперь нам нужно просто создать тренер со стандартными аргументами: + +```python +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, +) +``` + +и запустить наш цикл обучения: + +```python +trainer.train() +``` + +Во время обучения вы должны видеть, как потери при обучении уменьшаются, а оценка ROUGE увеличивается с каждой эпохой. После завершения обучения вы можете увидеть итоговую оценку ROUGE, выполнив команду `Trainer.evaluate()`: + +```python +trainer.evaluate() +``` + +```python out +{'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} +``` + +Из оценок видно, что наша модель значительно превзошла базовый уровень lead-3 - отлично! Осталось отправить веса модели в Hub, как показано ниже: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Это позволит сохранить контрольную точку и файлы конфигурации в `output_dir`, а затем загрузить все файлы на Хаб. Указав аргумент `tags`, мы также гарантируем, что виджет на хабе будет предназначен для конвейера суммаризации, а не для конвейера генерации текста по умолчанию, связанного с архитектурой mT5 (более подробную информацию о тегах моделей можно найти в [🤗 документации по Hub](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). Вывод `trainer.push_to_hub()` - это URL на хэш Git-коммита, так что вы можете легко увидеть изменения, которые были сделаны в розитории модели! + +В завершение этого раздела рассмотрим, как можно дообучить mT5 с помощью низкоуровневых функций, предоставляемых 🤗 Accelerate. + +{:else} + +Мы почти готовы к обучению! Нам нужно только преобразовать наши датасеты в `tf.data.Dataset` с помощью коллатора данных, который мы определили выше, а затем выолнить `compile()` и `fit()` модели. Сначала датасеты: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Теперь определяем гиперпараметры обучения и компилируем: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное +# на общее количество эпох. Обратите внимание, что tf_train_dataset здесь - это батч tf.data.Dataset, +# а не оригинальный датасет Hugging Face, поэтому его len() уже равен num_samples // batch_size. +num_train_epochs = 8 +num_train_steps = len(tf_train_dataset) * num_train_epochs +model_name = model_checkpoint.split("/")[-1] + +optimizer, schedule = create_optimizer( + init_lr=5.6e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) + +model.compile(optimizer=optimizer) + +# Обучение со смешанной точностью float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +И наконец, мы подгоняем модель. Мы используем `PushToHubCallback` для сохранения модели на Hub после каждой эпохи, что позволит нам использовать ее позже для инференса: + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-amazon-en-es", tokenizer=tokenizer +) + +model.fit( + tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback], epochs=8 +) +``` + +Мы получили некоторые значения потерь во время обучения, но на самом деле нам хотелось бы увидеть метрику ROUGE, которую мы вычисляли ранее. Чтобы получить эти метрики, нам нужно сгенерировать выходные данные модели и преобразовать их в строки. Давайте создадим несколько списков меток и прогнозов для сравнения с метрикой ROUGE (обратите внимание, что если вы получаете ошибки импорта в этом разделе, вам может понадобиться команда `!pip install tqdm`). Мы также используем трюк, который значительно повышает производительность, - компиляцию генерируемого кода с помощью [XLA](https://www.tensorflow.org/xla), ускоренного компилятора линейной алгебры TensorFlow. XLA применяет различные оптимизации к графу вычислений модели, что приводит к значительному увеличению скорости и использования памяти. Как описано в Hugging Face [блоге](https://huggingface.co/blog/tf-xla-generate), XLA работает лучше всего, когда формы наших входных данных не слишком сильно различаются. Чтобы справиться с этим, мы дополним наши входные данные до кратных 128 и создадим новый датасет с помощью дополняющего коллатора, а затем применим декоратор `@tf.function(jit_compile=True)` к нашей функции генерации, который помечает всю функцию для компиляции с помощью XLA. + +```python +from tqdm import tqdm +import numpy as np + +generation_data_collator = DataCollatorForSeq2Seq( + tokenizer, model=model, return_tensors="tf", pad_to_multiple_of=320 +) + +tf_generate_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], + collate_fn=generation_data_collator, + shuffle=False, + batch_size=8, + drop_remainder=True, +) + + +@tf.function(jit_compile=True) +def generate_with_xla(batch): + return model.generate( + input_ids=batch["input_ids"], + attention_mask=batch["attention_mask"], + max_new_tokens=32, + ) + + +all_preds = [] +all_labels = [] +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = labels.numpy() + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + 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] + all_preds.extend(decoded_preds) + all_labels.extend(decoded_labels) +``` + +Когда у нас есть списки строк меток и прогнозов, вычислить оценку ROUGE очень просто: + +```python +result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True +) +result = {key: value.mid.fmeasure * 100 for key, value in result.items()} +{k: round(v, 4) for k, v in result.items()} +``` + +``` +{'rouge1': 31.4815, 'rouge2': 25.4386, 'rougeL': 31.4815, 'rougeLsum': 31.4815} +``` + + +{/if} + +{#if fw === 'pt'} + +## Дообучение mT5 с 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]] + +Дообучение нашей модели с помощью 🤗 Accelerate очень похоже на пример с классификацией текста, который мы рассматривали в [Главе 3](../chapter3/1). Основные отличия заключаются в необходимости явной генерации резюме во время обучения и определении способа вычисления оценок ROUGE (напомним, что `Seq2SeqTrainer` позаботился о генерации за нас). Давайте посмотрим, как мы можем реализовать эти два требования в 🤗 Accelerate! + +### Подготовка всего к обучению[[preparing-everything-for-training]] + +Первое, что нам нужно сделать, это создать `DataLoader` для каждой из наших частей. Поскольку загрузчики данных PyTorch ожидают батч тензоров, нам нужно задать формат `"torch"` в наших датасетах: + +```python +tokenized_datasets.set_format("torch") +``` + +Теперь, когда у нас есть датасеты, состоящие только из тензоров, следующее, что нужно сделать, - это снова инстанцировать `DataCollatorForSeq2Seq`. Для этого нам нужно предоставить свежую версию модели, поэтому давайте снова загрузим ее из нашего кэша: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Затем мы можем инстанцировать коллатор данных и использовать его для определения наших загрузчиков данных: + +```python +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 +) +``` + +Следующее, что нужно сделать, это определить оптимизатор, который мы хотим использовать. Как и в других наших примерах, мы будем использовать `AdamW`, который хорошо работает для большинства задач: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Наконец, мы передаем нашу модель, оптимизатор и загрузчики данных в метод `accelerator.prepare()`: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Если вы обучаете на TPU, вам нужно будет перенести весь приведенный выше код в специальную функцию обучения. Подробнее смотрите в [Главе 3](../chapter3/1). + + + +Теперь, когда мы подготовили наши объекты, осталось сделать три вещи: + +* Определить график скорости обучения. +* Реализовать функцию для постобработки резюме для оценки. +* Создать розиторий на Hub, в который мы можем отправить нашу модель. + +В качестве графика скорости обучения мы будем использовать стандартный линейный график из предыдущих разделов: + +```python +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, +) +``` + +Для постобработки нам нужна функция, которая разбивает сгенерированные резюме на предложения, разделенные символами новой строки. Именно такой формат ожидает метрика ROUGE, и мы можем достичь этого с помощью следующего фрагмента кода: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE ожидает символ новой строки после каждого предложения + 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 +``` + +Это должно показаться вам знакомым, если вы помните, как мы определяли функцию `compute_metrics()` для `Seq2SeqTrainer`. + +Наконец, нам нужно создать розиторий модели на Hugging Face Hub. Для этого мы можем использовать библиотеку 🤗Hub с соответствующим заголовком. Нам нужно только задать имя нашего розитория, а в библиотеке есть служебная функция для объединения идентификатора розитория с профилем пользователя: + +```python +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 +``` + +```python out +'lewtun/mt5-finetuned-amazon-en-es-accelerate' +``` + +Теперь мы можем использовать имя этого розитория для клонирования локальной версии в каталог результатов, в котором будут храниться результаты обучения: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Это позволит нам отправить результаты в Hub, вызвав метод `repo.push_to_hub()` во время обучения! Теперь давайте завершим наш анализ, написав цикл обучения. + +### Цикл обучения[[training-loop]] + +Цикл обучения суммаризации очень похож на другие примеры 🤗 Accelerate, с которыми мы сталкивались, и состоит из четырех основных этапов: + +1. Обучение модели путем итерации по всем примерам в `train_dataloader` на каждой эпохе. +2. Генерация резюме моделью в конце каждой эпохи, сначала генерируются токены, а затем они (и эталонные резюме) декодируются в текст. +3. Вычисление оценок ROUGE с помощью тех же приемов, которые мы рассмотрели ранее. +4. Сохранение контрольных точек и отправка всего в Hub. Здесь мы полагаемся на полезный аргумент `blocking=False` объекта `Repository`, чтобы мы могли отправить контрольные точки на каждой эпохе _асинхронно_. Это позволяет нам продолжать обучение, не дожидаясь медленной загрузки, связанной с моделью размером в гигабайт! + +Эти шаги можно увидеть в следующем блоке кода: + +```python +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): + # Обучение + 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) + + # Оценка + 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"] + + # Если мы не дополнили до максимальной длины, нам нужно дополнить и метки + 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() + + # Заменяем -100 в метках, поскольку мы не можем их декодировать + 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) + + # Вычисляем метрики + result = rouge_score.compute() + # Извлекаем медианные оценки ROUGE + 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) + + # Сохранение и загрузка + 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 + ) +``` + +```python out +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} +``` + +Вот и все! После запуска у вас будет модель и результаты, очень похожие на те, что мы получили с помощью `Trainer`. + +{/if} + +## Использование дообученной вами модели[[using-your-fine-tuned-model]] + +После того как вы отправили модель в Hub, вы можете работать с ней либо с помощью виджета инференса, либо с помощью объекта `pipeline`, как показано ниже: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Мы можем передать в наш конвейер несколько примеров из тестового набора (которые модель не видела), чтобы получить представление о качестве резюме. Для начала давайте реализуем простую функцию, которая будет показывать обзор, заголовок и сгенерированное резюме вместе: + +```python +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}'") +``` + +Давайте посмотрим на один из английских примеров, которые мы получаем: + +```python +print_summary(100) +``` + +```python out +'>>> 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' +``` + +Это не так уж плохо! Мы видим, что наша модель действительно способна выполнять _абстрактную_ суммуризацию, дополняя части обзора новыми словами. И, пожалуй, самый интересный аспект нашей модели - это то, что она билингвистическая, так что мы можем генерировать резюме и для испанских рецензий: + +```python +print_summary(0) +``` + +```python out +'>>> 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' +``` + +Резюме переводится как "Very easy to read" на английском языке, что, как мы видим, в данном случае было непосредственно взято из обзора. Тем не менее, это демонстрирует универсальность модели mT5 и дает вам представление о том, каково это - работать с многоязычным корпусом! + +Далее мы обратимся к несколько более сложной задаче: обучению языковой модели с нуля. diff --git a/chapters/ru/chapter7/6.mdx b/chapters/ru/chapter7/6.mdx new file mode 100644 index 000000000..6ade23c1c --- /dev/null +++ b/chapters/ru/chapter7/6.mdx @@ -0,0 +1,914 @@ + + +# Обучение каузальной языковой модели с нуля[[training-a-causal-language-model-from-scratch]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +До сих пор мы в основном использовали предварительно обученные модели и осуществляли их дообучение для новых случаев использования, повторно используя веса, полученные в ходе предварительного обучения. Как мы видели в [Главе 1](../chapter1/1), это принято называть _трансферным обучением_, и это очень успешная стратегия применения моделей Transformer для большинства реальных случаев использования, когда размеченных данных недостаточно. В этой главе мы применим другой подход и обучим совершенно новую модель с нуля. Это хороший подход, если у вас много данных, и они сильно отличаются от данных предварительного обучения, используемых для имеющихся моделей. Однако предварительное обучение языковой модели требует значительно больше вычислительных ресурсов, чем дообучение существующей. Примеры, когда имеет смысл обучать новую модель, включают датасеты, состоящие из музыкальных нот, молекулярных последовательностей, таких как ДНК, или языков программирования. Последние в недавнее время получили широкое распространение благодаря таким инструментам, как TabNine и GitHub's Copilot, работающим на основе модели Codex от OpenAI, которые могут генерировать длинные последовательности кода. Для решения этой задачи генерации текста лучше всего подходят авторегрессионные или каузальные языковые модели, такие как GPT-2. + +В этом разделе мы построим уменьшенную версию модели генерации кода: мы сосредоточимся на однострочных завершениях вместо полных функций или классов, используя подмножество кода Python. Работая с данными на Python, вы часто сталкиваетесь со стеком Data Science на Python, состоящем из библиотек `matplotlib`, `seaborn`, `pandas` и `scikit-learn`. При использовании этих фреймворков часто возникает необходимость поиска определенных команд, поэтому было бы неплохо, если бы мы могли использовать модель выполненяющую эти вызововы за нас. + + + +В [Главе 6](../chapter6/1) мы создали эффективный токенизатор для обработки исходного кода Python, но нам все еще нужен крупный датасет для предварительного обучения модели. Здесь мы применим наш токенизатор к корпусу кода Python, полученному из розиториев GitHub. Затем мы воспользуемся API `Trainer` и 🤗 Accelerate для обучения модели. Приступим! + + + +На самом деле это демонстрация модели, которая была обучена и загружена в Hub с помощью кода, приведенного в этом разделе. Вы можете найти ее [здесь](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Обратите внимание, что поскольку при генерации текста происходит некоторая рандомизация, вы, вероятно, получите немного другой результат. + +## Сбор данных[[gathering-the-data]] + +Код на Python в изобилии доступен в таких репозиториях кода, как GitHub, и мы можем использовать его для создания датасета путем поиска каждого розитория Python. Именно такой подход был использован в книге [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) для предварительного обучения большой модели GPT-2. Используя дамп GitHub объемом около 180 ГБ, содержащий примерно 20 миллионов файлов Python под названием `codeparrot`, авторы создали датасет, которым затем поделились на [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). + +Однако обучение на полном корпусе требует много времени и вычислений, а нам нужно только подмножество датасетов, связанных со стеком data science на Python. Итак, давайте начнем с фильтрации датасета `codeparrot` по всем файлам, включающим любую из библиотек из этого стека. Из-за большого размера датасета мы хотим избежать его загрузки; вместо этого мы будем использовать функцию потоковой передачи (streaming), чтобы фильтровать его на лету. Чтобы помочь нам отфильтровать примеры кода с использованием библиотек, о которых мы говорили ранее, мы воспользуемся следующей функцией: + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Давайте протестируем ее на двух примерах: + +```py +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] +example_1 = "import numpy as np" +example_2 = "import pandas as pd" + +print( + any_keyword_in_string(example_1, filters), any_keyword_in_string(example_2, filters) +) +``` + +```python out +False True +``` + +Мы можем использовать ее для создания функции, которая будет передавать датасет и отфильтровывать нужные нам элементы: + +```py +from collections import defaultdict +from tqdm import tqdm +from datasets import Dataset + + +def filter_streaming_dataset(dataset, filters): + filtered_dict = defaultdict(list) + total = 0 + for sample in tqdm(iter(dataset)): + total += 1 + if any_keyword_in_string(sample["content"], filters): + for k, v in sample.items(): + filtered_dict[k].append(v) + print(f"{len(filtered_dict['content'])/total:.2%} of data after filtering.") + return Dataset.from_dict(filtered_dict) +``` + +Затем мы можем просто применить эту функцию к потоковому датасету: + +```py +# Выполнение этой ячейки займет очень много времени, поэтому ее следует пропустить и перейти к +# следующей! +from datasets import load_dataset + +split = "train" # "valid" +filters = ["pandas", "sklearn", "matplotlib", "seaborn"] + +data = load_dataset(f"transformersbook/codeparrot-{split}", split=split, streaming=True) +filtered_data = filter_streaming_dataset(data, filters) +``` + +```python out +3.26% of data after filtering. +``` + +Таким образом, у нас остается около 3% от исходного датасета, что все равно довольно много - результирующий датасет занимает 6 ГБ и состоит из 600 000 Python-скриптов! + +Фильтрация полного датасета может занять 2-3 часа в зависимости от вашей машины и пропускной способности сети. Если вы не хотите выполнять этот длительный процесс самостоятельно, мы предоставляем отфильтрованный датасет на Hub для загрузки: + +```py +from datasets import load_dataset, DatasetDict + +ds_train = load_dataset("huggingface-course/codeparrot-ds-train", split="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="validation") + +raw_datasets = DatasetDict( + { + "train": ds_train, # .shuffle().select(range(50000)), + "valid": ds_valid, # .shuffle().select(range(500)) + } +) + +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 606720 + }) + valid: Dataset({ + features: ['repo_name', 'path', 'copies', 'size', 'content', 'license'], + num_rows: 3322 + }) +}) +``` + + + +Предварительное обучение языковой модели займет некоторое время. Мы рекомендуем сначала запустить цикл обучения на выборке данных, раскомментировав две частичные строки выше, и убедиться, что обучение успешно завершено и модели сохранены. Нет ничего обиднее, чем неудачное обучение на последнем этапе из-за того, что вы забыли создать папку или из-за опечатки в конце цикла обучения! + + + +Давайте рассмотрим пример из датасета. Мы покажем только первые 200 символов каждого поля: + +```py +for key in raw_datasets["train"][0]: + print(f"{key.upper()}: {raw_datasets['train'][0][key][:200]}") +``` + +```python out +'REPO_NAME: kmike/scikit-learn' +'PATH: sklearn/utils/__init__.py' +'COPIES: 3' +'SIZE: 10094' +'''CONTENT: """ +The :mod:`sklearn.utils` module includes various utilites. +""" + +from collections import Sequence + +import numpy as np +from scipy.sparse import issparse +import warnings + +from .murmurhash import murm +LICENSE: bsd-3-clause''' +``` + +Мы видим, что поле `content` содержит код, на котором мы хотим обучить нашу модель. Теперь, когда у нас есть датасет, нам нужно подготовить тексты, чтобы они были в формате, подходящем для предварительного обучения. + +## Подготовка датасета[[preparing-the-dataset]] + + + +Первым шагом будет токенизация данных, чтобы мы могли использовать их для обучения. Поскольку наша цель - автозаполнение коротких вызовов функций, мы можем оставить размер контекста относительно небольшим. Благодаря этому мы сможем обучать модель гораздо быстрее, и она будет занимать значительно меньше памяти. Если для вашего приложения важно, чтобы контекст был больше (например, если вы хотите, чтобы модель писала юнит-тесты на основе файла с определением функции), обязательно увеличьте это число, но не забывайте, что это приведет к увеличению объема памяти GPU. Пока что давайте зафиксируем размер контекста на 128 токенов, в отличие от 1 024 или 2 048, используемых в GPT-2 или GPT-3 соответственно. + +Большинство документов содержит гораздо больше 128 токенов, поэтому простое обрезание входных данных до максимальной длины приведет к тому, что большая часть нашего датасета будет удалена. Вместо этого мы используем параметр `return_overflowing_tokens` для токенизации всего ввода и разбиения его на части, как мы делали в [Главе 6](../chapter6/4). Мы также будем использовать параметр `return_length`, чтобы автоматически возвращать длину каждого созданного фрагмента. Часто последний фрагмент будет меньше размера контекста, и мы избавимся от этих фрагментов, чтобы избежать проблем с дополнением; на самом деле они нам не нужны, поскольку у нас и так много данных. + +
+Chunking a large texts in several pieces. + +
+ +Давайте посмотрим, как это работает, на первых двух примерах: + +```py +from transformers import AutoTokenizer + +context_length = 128 +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") + +outputs = tokenizer( + raw_datasets["train"][:2]["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, +) + +print(f"Input IDs length: {len(outputs['input_ids'])}") +print(f"Input chunk lengths: {(outputs['length'])}") +print(f"Chunk mapping: {outputs['overflow_to_sample_mapping']}") +``` + +```python out +Input IDs length: 34 +Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 117, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 41] +Chunk mapping: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] +``` + +Из этих двух примеров видно, что в общей сложности мы получили 34 сегмента. Взглянув на длину фрагментов, мы видим, что фрагменты в конце обоих документов содержат менее 128 токенов (117 и 41, соответственно). Это лишь малая часть всех имеющихся у нас фрагментов, поэтому мы можем смело отбросить их. С помощью поля `overflow_to_sample_mapping` мы также можем восстановить, какие фрагменты принадлежали каким входным примерам. + +В этой операции мы используем удобную особенность функции `Dataset.map()` из 🤗 Datasets, которая заключается в том, что она не требует отображения один к одному; как мы видели в [разделе 3](../chapter7/3), мы можем создавать батч с большим или меньшим количеством элементов, чем входной батч. Это полезно при выполнении таких операций, как аугментация или фильтрация данных, которые изменяют количество элементов. В нашем случае при токенизации каждого элемента на фрагменты заданного размера контекста мы создаем много примеров из каждого документа. Нам просто нужно убедиться, что мы удалили существующие столбцы, поскольку они имеют противоречивый размер. Если бы мы хотели их сохранить, то могли бы повторить их соответствующим образом и вернуть в рамках вызова `Dataset.map()`: + +```py +def tokenize(element): + outputs = tokenizer( + element["content"], + truncation=True, + max_length=context_length, + return_overflowing_tokens=True, + return_length=True, + ) + input_batch = [] + for length, input_ids in zip(outputs["length"], outputs["input_ids"]): + if length == context_length: + input_batch.append(input_ids) + return {"input_ids": input_batch} + + +tokenized_datasets = raw_datasets.map( + tokenize, batched=True, remove_columns=raw_datasets["train"].column_names +) +tokenized_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['input_ids'], + num_rows: 16702061 + }) + valid: Dataset({ + features: ['input_ids'], + num_rows: 93164 + }) +}) +``` + +Теперь у нас есть 16,7 миллиона примеров со 128 токенами в каждом, что соответствует примерно 2,1 миллиарда токенов в общей сложности. Для сравнения, модели OpenAI GPT-3 и Codex обучены на 300 и 100 миллиардах токенов соответственно, причем модели Codex инициализируются из контрольных точек GPT-3. Наша цель в этом разделе - не конкурировать с этими моделями, которые могут генерировать длинные, связные тексты, а создать уменьшенную версию, обеспечивающую быструю функцию автозаполнения для специалистов по обработке данных. + +Теперь, когда у нас есть готовый датасет, давайте создадим модель! + + + +✏️ **Попробуйте!** Избавление от всех фрагментов, размер которых меньше размера контекста, не является большой проблемой, поскольку мы используем небольшие контекстные окна. При увеличении размера контекста (или если у вас корпус коротких документов) доля отбрасываемых фрагментов также будет расти. Более эффективный способ подготовки данных - объединить все токенизированные примеры в батч с маркером `eos_token_id` между ними, а затем выполнить фрагментацию на конкатенированных последовательностях. В качестве упражнения измените функцию `tokenize()`, чтобы использовать этот подход. Обратите внимание, что вам нужно установить `truncation=False` и удалить другие аргументы из токенизатора, чтобы получить полную последовательность идентификаторов токенов. + + + + +## Инициализация новой модели[[initializing-a-new-model]] + +Наш первый шаг - инициализация свежей модели GPT-2. Мы будем использовать ту же конфигурацию для нашей модели, что и для маленькой модели GPT-2, поэтому загрузим предварительно обученную конфигурацию, убедимся, что размер токенизатора соответствует размеру словарного запаса модели, и передадим идентификаторы токенов `bos` и `eos` (начало и конец последовательности): + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, GPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +С этой конфигурацией мы можем загрузить новую модель. Обратите внимание, что впервые мы не используем функцию `from_pretrained()`, поскольку фактически инициализируем модель самостоятельно: + +```py +model = GPT2LMHeadModel(config) +model_size = sum(t.numel() for t in model.parameters()) +print(f"GPT-2 size: {model_size/1000**2:.1f}M parameters") +``` + +```python out +GPT-2 size: 124.2M parameters +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFGPT2LMHeadModel, AutoConfig + +config = AutoConfig.from_pretrained( + "gpt2", + vocab_size=len(tokenizer), + n_ctx=context_length, + bos_token_id=tokenizer.bos_token_id, + eos_token_id=tokenizer.eos_token_id, +) +``` + +С этой конфигурацией мы можем загрузить новую модель. Обратите внимание, что впервые мы не используем функцию `from_pretrained()`, поскольку фактически инициализируем модель самостоятельно: + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Создание модели +model.summary() +``` + +```python out +_________________________________________________________________ +Layer (type) Output Shape Param # +================================================================= +transformer (TFGPT2MainLayer multiple 124242432 +================================================================= +Total params: 124,242,432 +Trainable params: 124,242,432 +Non-trainable params: 0 +_________________________________________________________________ +``` + +{/if} + +Наша модель имеет 124 миллиона параметров, которые нам предстоит дообучить. Прежде чем начать обучение, нам нужно настроить коллатор данных, который займется созданием батчей. Мы можем использовать коллатор `DataCollatorForLanguageModeling`, который разработан специально для языкового моделирования (о чем недвусмысленно говорит его название). Помимо стекирования и дополнения батчей, он также заботится о создании меток языковой модели - в каузальном языковом моделировании входы тоже служат метками (просто сдвинутыми на один элемент), и этот коллатор данных создает их на лету во время обучения, так что нам не нужно дублировать `input_ids`. + +Обратите внимание, что `DataCollatorForLanguageModeling` поддерживает как маскированное языковое моделирование (masked language modeling - MLM), так и каузальное языковое моделирование (causal language modeling - CLM). По умолчанию он подготавливает данные для MLM, но мы можем переключиться на CLM, задав аргумент `mlm=False`: + +{#if fw === 'pt'} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False) +``` + +{:else} + +```py +from transformers import DataCollatorForLanguageModeling + +tokenizer.pad_token = tokenizer.eos_token +data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_tensors="tf") +``` + +{/if} + +Давайте посмотрим на пример: + +```py +out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) +for key in out: + print(f"{key} shape: {out[key].shape}") +``` + +{#if fw === 'pt'} + +```python out +input_ids shape: torch.Size([5, 128]) +attention_mask shape: torch.Size([5, 128]) +labels shape: torch.Size([5, 128]) +``` + +{:else} + +```python out +input_ids shape: (5, 128) +attention_mask shape: (5, 128) +labels shape: (5, 128) +``` + +{/if} + +Мы видим, что примеры были стекированы, и все тензоры имеют одинаковую форму. + +{#if fw === 'tf'} + +Теперь мы можем использовать метод `prepare_tf_dataset()` для преобразования наших датасетов в датасеты TensorFlow с помощью коллатора данных, который мы создали выше: + +```python +tf_train_dataset = model.prepare_tf_dataset( + tokenized_dataset["train"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_dataset["valid"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Сдвиг входов и меток для их выравнивания происходит внутри модели, поэтому коллатор данных просто копирует входы для создания меток. + + + + +Теперь у нас есть все необходимое для обучения нашей модели - в конце концов, это было не так уж и сложно! Прежде чем приступить к обучению, мы должны войти в Hugging Face. Если вы работаете в блокноте, вы можете сделать это с помощью следующей служебной функции: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Появится виджет, в котором вы можете ввести свои учетные данные для входа в Hugging Face. + +Если вы работаете не в блокноте, просто введите следующую строку в терминале: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Осталось только настроить аргументы обучения и запустить `Trainer`. Мы будем использовать косинусный график скорости обучения с некоторым разогревом (warmup) и эффективным размером батча в 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). Аккумулирование градиента используется, когда один батч не помещается в память, и инкрементально накапливает градиент за несколько проходов вперед/назад. Мы увидим это в действии, когда создадим цикл обучения с использованием 🤗 Accelerate. + +```py +from transformers import Trainer, TrainingArguments + +args = TrainingArguments( + output_dir="codeparrot-ds", + per_device_train_batch_size=32, + per_device_eval_batch_size=32, + evaluation_strategy="steps", + eval_steps=5_000, + logging_steps=5_000, + gradient_accumulation_steps=8, + num_train_epochs=1, + weight_decay=0.1, + warmup_steps=1_000, + lr_scheduler_type="cosine", + learning_rate=5e-4, + save_steps=5_000, + fp16=True, + push_to_hub=True, +) + +trainer = Trainer( + model=model, + tokenizer=tokenizer, + args=args, + data_collator=data_collator, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["valid"], +) +``` + +Теперь мы можем просто запустить `Trainer` и дождаться окончания обучения. В зависимости от того, запустите ли вы его на полном или на подмножестве обучающего набора, это займет 20 или 2 часа соответственно, так что захватите несколько чашек кофе и хорошую книгу для чтения! + +```py +trainer.train() +``` + +После завершения обучения мы можем отправить модель и токенизатор в Hub: + +```py +trainer.push_to_hub() +``` + +{:else} + +Осталось только настроить гиперпараметры обучения и вызвать `compile()` и `fit()`. Мы будем использовать график скорости обучения с некоторым разогревом, чтобы повысить стабильность обучения: + +```py +from transformers import create_optimizer +import tensorflow as tf + +num_train_steps = len(tf_train_dataset) +optimizer, schedule = create_optimizer( + init_lr=5e-5, + num_warmup_steps=1_000, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Обучение со смешанной точностью float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Теперь мы можем просто вызвать `model.fit()` и дождаться окончания обучения. В зависимости от того, запустите ли вы его на полном или на подмножестве обучающего набора, это займет 20 или 2 часа соответственно, так что захватите несколько чашек кофе и хорошую книгу для чтения! После завершения обучения мы можем отправить модель и токенизатор в Hub: + +```py +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="codeparrot-ds", tokenizer=tokenizer) + +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + + + +✏️ **Попробуйте!** Всего около 30 строк кода в дополнение к `TrainingArguments` понадобилось нам, чтобы перейти от сырых текстов к обучению GPT-2. Попробуйте это на своем датасете и посмотрите, сможете ли вы получить хорошие результаты! + + + + + +{#if fw === 'pt'} + +💡 Если у вас есть доступ к компьютеру с несколькими GPU, попробуйте запустить код на нем. `Trainer` автоматически управляет несколькими компьютерами, и это может значительно ускорить обучение. + +{:else} + +💡 Если у вас есть доступ к компьютеру с несколькими GPU, вы можете попробовать использовать контекст `MirroredStrategy` для существенного ускорения обучения. Вам нужно будет создать объект `tf.distribute.MirroredStrategy` и убедиться, что все методы `to_tf_dataset()` или `prepare_tf_dataset()`, а также создание модели и вызов `fit()` выполняются в его контексте `scope()`. Документацию на эту тему можно посмотреть [здесь](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Генерация кода с помощью конвейера[[code-generation-with-a-pipeline]] + +Настал момент истины: давайте посмотрим, насколько хорошо работает обученная модель! В логах мы видим, что потери постоянно снижаются, но чтобы проверить модель на практике, давайте посмотрим, насколько хорошо она работает на некоторых подсказках. Для этого мы обернем модель в `pipeline` для генерации текста и поместим ее на GPU для быстрой генерации, если таковой доступен: + +{#if fw === 'pt'} + +```py +import torch +from transformers import pipeline + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +pipe = pipeline( + "text-generation", model="huggingface-course/codeparrot-ds", device=device +) +``` + +{:else} + +```py +from transformers import pipeline + +course_model = TFGPT2LMHeadModel.from_pretrained("huggingface-course/codeparrot-ds") +course_tokenizer = AutoTokenizer.from_pretrained("huggingface-course/codeparrot-ds") +pipe = pipeline( + "text-generation", model=course_model, tokenizer=course_tokenizer, device=0 +) +``` + +{/if} + +Давайте начнем с простой задачи - создания диаграммы рассеивания (scatter plot): + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create scatter plot with x, y +plt.scatter(x, y) + +# create scatter +``` + +Результат выглядит корректно. Работает ли это также для `pandas` операции? Давайте посмотрим, сможем ли мы создать `DataFrame` из двух массивов: + +```py +txt = """\ +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# create some data +x = np.random.randn(100) +y = np.random.randn(100) + +# create dataframe from x and y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +Отлично, это правильный ответ -- хотя затем он снова вставляет столбец `x`. Поскольку количество генерируемых токенов ограничено, следующий цикл `for` обрывается. Давайте посмотрим, сможем ли мы сделать что-то более сложное, и чтобы модель помогла нам использовать операцию `groupby`: + +```py +txt = """\ +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# dataframe with profession, income and name +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculate the mean income per profession +profession = df.groupby(['profession']).mean() + +# compute the +``` + +Неплохо; это правильный способ сделать это. Наконец, давайте посмотрим, сможем ли мы также использовать его для `scikit-learn` и создать модель Random Forest: + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# fit random forest model with 300 estimators on X, y: +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +Глядя на эти несколько примеров, кажется, что модель усвоила часть синтаксиса стека Python Data Science. Конечно, нам нужно будет более тщательно оценить модель, прежде чем внедрять ее в реальный мир, но все же это впечатляющий прототип. + +{:else} + +Глядя на эти несколько примеров, кажется, что модель усвоила часть синтаксиса стека Python Data Science (конечно, нам нужно будет оценить это более тщательно, прежде чем разворачивать модель в реальном мире). Однако иногда требуется более тщательная настройка обучения модели, чтобы добиться необходимого качества работы для конкретного случая использования. Например, если мы хотим динамически обновлять размер батча или иметь условный цикл обучения, который пропускает плохие примеры на лету? Одним из вариантов может быть подкласс `Trainer` и добавление необходимых изменений, но иногда проще написать цикл обучения с нуля. Вот тут-то и приходит на помощь 🤗 Accelerate. + +{/if} + +{#if fw === 'pt'} + +## Обучение с 🤗 Accelerate[[training-with-accelerate]] + +Мы уже видели, как обучать модель с помощью `Trainer`, который позволяет сделать некоторые настройки. Однако иногда нам нужен полный контроль над циклом обучения, или мы хотим внести некоторые экзотические изменения. В этом случае 🤗 Accelerate - отличный выбор, и в этом разделе мы рассмотрим шаги по его использованию для обучения нашей модели. Чтобы сделать все более интересным, мы также добавим изюминку в цикл обучения. + + + +Поскольку нас в основном интересует разумное автодополнение для библиотек data science, имеет смысл придать больший вес обучающим примерам, в которых чаще используются эти библиотеки. Мы можем легко определить эти примеры по использованию таких ключевых слов, как `plt`, `pd`, `sk`, `fit` и `predict`, которые являются наиболее частыми именами для импорта `matplotlib.pyplot`, `pandas` и `sklearn`, а также шаблонам fit/predict для последней. Если каждый из них представлен в виде одного токена, мы можем легко проверить, встречаются ли они во входной последовательности. Токены могут иметь пробельный префикс, поэтому мы также проверим наличие таких версий в словаре токенизатора. Чтобы убедиться, что все работает, мы добавим один тестовый токен, который должен быть разбит на несколько токенов: + +```py +keytoken_ids = [] +for keyword in [ + "plt", + "pd", + "sk", + "fit", + "predict", + " plt", + " pd", + " sk", + " fit", + " predict", + "testtest", +]: + ids = tokenizer([keyword]).input_ids[0] + if len(ids) == 1: + keytoken_ids.append(ids[0]) + else: + print(f"Keyword has not single token: {keyword}") +``` + +```python out +'Keyword has not single token: testtest' +``` + +Отлично, похоже, это прекрасно работает! Теперь мы можем написать пользовательскую функцию потерь, которая принимает входную последовательность, логиты и ключевые токены, которые мы только что выбрали в качестве входных данных. Сначала нам нужно выровнять логиты и входные данные: входная последовательность, сдвинутая на единицу вправо, формирует метки, поскольку следующий токен является меткой для текущего токена. Мы можем добиться этого, начиная метки со второго токена входной последовательности, поскольку модель все равно не делает предсказания для первого токена. Затем мы отсекаем последний логит, поскольку у нас нет метки для токена, который следует за всей входной последовательностью. Таким образом, мы можем вычислить потери для каждого примера и подсчитать количество вхождений всех ключевых слов в каждом примере. Наконец, мы вычисляем средневзвешенное значение по всем примерам, используя вхождения в качестве весов. Поскольку мы не хотим отбрасывать все выборки, в которых нет ключевых слов, мы добавляем 1 к весам: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Сдвигаем так, чтобы токены < n предсказывали n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Вычисляем потери на каждый токен + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Изменение размера и средние потери на каждый пример + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Расчет и масштабирование весов + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Расчет взвешенного среднего + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Прежде чем приступить к обучению с этой потрясающей новой функцией потерь, нам нужно подготовить несколько вещей: + +- Нам нужны загрузчики данных, чтобы загружать данные батчами. +- Нам нужно настроить параметры затухания веса (weight decay). +- Время от времени мы хотим проводить оценку, поэтому имеет смысл обернуть код оценки в функцию. + +Давайте начнем с загрузчиков данных. Нам нужно только задать для датасета формат `"torch"`, а затем мы можем передать его в PyTorch `DataLoader` с соответствующим размером батча: + +```py +from torch.utils.data.dataloader import DataLoader + +tokenized_dataset.set_format("torch") +train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle=True) +eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32) +``` + +Далее мы группируем параметры, чтобы оптимизатор знал, какие из них получат дополнительное затухание веса. Обычно все смещения (bias) и весовые коэффициенты LayerNorm (LayerNorm weights) исключаются из этого правила; вот как мы можем это реализовать: + +```py +weight_decay = 0.1 + + +def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): + params_with_wd, params_without_wd = [], [] + for n, p in model.named_parameters(): + if any(nd in n for nd in no_decay): + params_without_wd.append(p) + else: + params_with_wd.append(p) + return [ + {"params": params_with_wd, "weight_decay": weight_decay}, + {"params": params_without_wd, "weight_decay": 0.0}, + ] +``` + +Поскольку мы хотим регулярно оценивать модель на валидационном множестве во время обучения, давайте напишем функцию и для этого. Она просто запускается через загрузчик оценочных данных и собирает все потери: + +```py +def evaluate(): + model.eval() + losses = [] + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + outputs = model(batch["input_ids"], labels=batch["input_ids"]) + + losses.append(accelerator.gather(outputs.loss)) + loss = torch.mean(torch.cat(losses)) + try: + perplexity = torch.exp(loss) + except OverflowError: + perplexity = float("inf") + return loss.item(), perplexity.item() +``` + +С помощью функции `evaluate()` мы можем сообщать о потерях и [перплексии](../chapter7/3) через регулярные промежутки времени. Далее мы переопределим нашу модель, чтобы убедиться, что мы снова обучаемся с нуля: + +```py +model = GPT2LMHeadModel(config) +``` + +Затем мы можем определить наш оптимизатор, используя предыдущую функцию для части параметров для затухания веса: + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Теперь давайте подготовим модель, оптимизатор и загрузчики данных, чтобы начать обучение: + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Если вы проводите обучение на TPU, вам нужно будет перенести весь код, начиная с ячейки выше, в выделенную функцию обучения. Подробнее смотрите [Главу 3](../chapter3/1). + + + +Теперь, когда мы отправили наш `train_dataloader` в `accelerator.prepare()`, мы можем использовать его длину для вычисления количества шагов обучения. Помните, что это всегда нужно делать после подготовки загрузчика данных, так как этот метод изменит его длину. Мы используем классический линейный график скорости обучения до 0: + +```py +from transformers import get_scheduler + +num_train_epochs = 1 +num_update_steps_per_epoch = len(train_dataloader) +num_training_steps = num_train_epochs * num_update_steps_per_epoch + +lr_scheduler = get_scheduler( + name="linear", + optimizer=optimizer, + num_warmup_steps=1_000, + num_training_steps=num_training_steps, +) +``` + +Наконец, чтобы отправить нашу модель в Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не вошли в него. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свой собственный вариант; он просто должен содержать ваше имя пользователя, что и делает функция `get_full_repo_name()`): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "codeparrot-ds-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/codeparrot-ds-accelerate' +``` + +Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть существующим клоном розитория, с которым мы работаем: + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи. + +Перед обучением давайте проведем быстрый тест, чтобы проверить, правильно ли работает функция оценки: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Это очень высокие значения для потерь и перплексии, но это неудивительно, ведь мы еще не обучили модель. Итак, у нас все готово для написания основной части скрипта обучения: цикла обучения. В цикле обучения мы выполняем итерации по загрузчику данных и передаем батчи в модель. С помощью логитов мы можем оценить нашу пользовательскую функцию потерь. Мы масштабируем потери по количеству шагов накопления градиента, чтобы не создавать больших потерь при агрегировании большего количества шагов. Перед оптимизацией мы также обрезаем градиенты для лучшей сходимости. Наконец, каждые несколько шагов мы оцениваем модель на оценочном наборе с помощью нашей новой функции `evaluate()`: + +```py +from tqdm.notebook import tqdm + +gradient_accumulation_steps = 8 +eval_steps = 5_000 + +model.train() +completed_steps = 0 +for epoch in range(num_train_epochs): + for step, batch in tqdm( + enumerate(train_dataloader, start=1), total=num_training_steps + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "samples": step * samples_per_step, + "steps": completed_steps, + "loss/train": loss.item() * gradient_accumulation_steps, + } + ) + loss = loss / gradient_accumulation_steps + accelerator.backward(loss) + if step % gradient_accumulation_steps == 0: + accelerator.clip_grad_norm_(model.parameters(), 1.0) + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + completed_steps += 1 + if (step % (eval_steps * gradient_accumulation_steps)) == 0: + eval_loss, perplexity = evaluate() + accelerator.print({"loss/eval": eval_loss, "perplexity": perplexity}) + model.train() + 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 step {step}", blocking=False + ) +``` + +Вот и все -- теперь у вас есть свой собственный цикл обучения для каузальных языковых моделей, таких как GPT-2, который вы можете дополнительно настроить под свои нужды. + + + +✏️ **Попробуйте!** Либо создайте свою собственную функцию потерь, подходящую для вашего случая, либо добавьте еще один пользовательский шаг в цикл обучения. + + + + + +✏️ **Попробуйте!** При проведении длительных экспериментов по обучению полезно регистрировать важные метрики с помощью таких инструментов, как TensorBoard или Weights & Biases. Добавьте соответствующее логирование в цикл обучения, чтобы вы всегда могли проверить, как проходит обучение. + + + +{/if} diff --git a/chapters/ru/chapter7/7.mdx b/chapters/ru/chapter7/7.mdx new file mode 100644 index 000000000..c3c6764c1 --- /dev/null +++ b/chapters/ru/chapter7/7.mdx @@ -0,0 +1,1203 @@ + + +# Ответы на вопросы[[question-answering]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Пришло время взглянуть на ответы на вопросы! Эта задача имеет множество разновидностей, но та, на которой мы сосредоточимся в этом разделе, называется *экстрактивным* ответом на вопросы. Она включает в себя постановку вопросов о документе и определение ответов в виде _участков текста_ в самом документе. + + + +Мы проведем дообучение BERT-модели на датасете [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), состоящем из вопросов, заданных краудворкерами по набору статей Википедии. В результате мы получим модель, способную вычислять прогнозы, подобные этому: + + + +На самом деле это демонстрация модели, которая была обучена и загружена на Hub с помощью кода, показанного в этом разделе. Вы можете найти ее и перепроверить прогнозы [здесь](https://huggingface.co/huggingface-course/bert-finetuned-squad?context=%F0%9F%A4%97+Transformers+is+backed+by+the+three+most+popular+deep+learning+libraries+%E2%80%94+Jax%2C+PyTorch+and+TensorFlow+%E2%80%94+with+a+seamless+integration+between+them.+It%27s+straightforward+to+train+your+models+with+one+before+loading+them+for+inference+with+the+other.&question=Which+deep+learning+libraries+back+%F0%9F%A4%97+Transformers%3F). + + + +💡 Модели, основанные только на энкодере, такие как BERT, как правило, отлично справляются с извлечением ответов на фактоидные вопросы типа "Кто изобрел архитектуру трансформера?", но плохо справляются с открытыми вопросами типа "Почему небо голубое?". В таких сложных случаях для синтеза информации обычно используются модели энкодеров-декодеров, такие как T5 и BART, что очень похоже на [сумризацию текста](/course/chapter7/5). Если вам интересен этот тип *генеративных* ответов на вопросы, рекомендуем ознакомиться с нашим [демо](https://yjernite.github.io/lfqa.html) основанным на [датасете ELI5](https://huggingface.co/datasets/eli5). + + + +## Подготовка данных[[preparing-the-data]] + +В качестве академического бенчмарка для экстрактивных ответов на вопросы чаще всего используется датасет [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), поэтому мы будем использовать именно его. Существует также более сложный датасет [SQuAD v2](https://huggingface.co/datasets/squad_v2), который включает вопросы, не имеющие ответа. Если ваш собственный датасет содержит столбец контекстов, столбец вопросов и столбец ответов, вы сможете адаптировать описанные ниже шаги. + +### Датасет SQuAD[[the-squad-dataset]] + +Как обычно, мы можем загрузить и кэшировать датасет всего за один шаг благодаря функции `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Мы можем взглянуть на этот объект, чтобы узнать больше о датасете SQuAD: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 87599 + }) + validation: Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 10570 + }) +}) +``` + +Похоже, у нас есть все необходимое в полях `context`, `question` и `answers`, так что давайте выведем их для первого элемента нашего обучающего набора: + +```py +print("Context: ", raw_datasets["train"][0]["context"]) +print("Question: ", raw_datasets["train"][0]["question"]) +print("Answer: ", raw_datasets["train"][0]["answers"]) +``` + +```python out +Context: 'Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend "Venite Ad Me Omnes". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive (and in a direct line that connects through 3 statues and the Gold Dome), is a simple, modern stone statue of Mary.' +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +Поля `context` и `question` очень просты в использовании. С полем `answers` немного сложнее, поскольку оно представляет собой словарь с двумя полями, которые оба являются списками. Именно такой формат будет ожидать метрика `squad` при оценке; если вы используете свои собственные данные, вам не обязательно беспокоиться о том, чтобы привести ответы к такому же формату. Поле `text` довольно очевидно, а поле `answer_start` содержит индекс начального символа каждого ответа в контексте. + +Во время обучения существует только один возможный ответ. Мы можем перепроверить это с помощью метода `Dataset.filter()`: + +```py +raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) +``` + +```python out +Dataset({ + features: ['id', 'title', 'context', 'question', 'answers'], + num_rows: 0 +}) +``` + +Для оценки, однако, существует несколько возможных ответов для каждого примера, которые могут быть одинаковыми или разными: + +```py +print(raw_datasets["validation"][0]["answers"]) +print(raw_datasets["validation"][2]["answers"]) +``` + +```python out +{'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]} +{'text': ['Santa Clara, California', "Levi's Stadium", "Levi's Stadium in the San Francisco Bay Area at Santa Clara, California."], 'answer_start': [403, 355, 355]} +``` + +Мы не будем углубляться в сценарий оценки, поскольку все это будет завернуто в метрику 🤗 Datasets для нас, но кратко суть в том, что некоторые вопросы имеют несколько возможных ответов, и этот сценарий будет сравнивать спрогнозированный ответ со всеми допустимыми ответами и выбирать лучший результат. Если мы посмотрим, например, на выборку с индексом 2: + +```py +print(raw_datasets["validation"][2]["context"]) +print(raw_datasets["validation"][2]["question"]) +``` + +```python out +'Super Bowl 50 was an American football game to determine the champion of the National Football League (NFL) for the 2015 season. The American Football Conference (AFC) champion Denver Broncos defeated the National Football Conference (NFC) champion Carolina Panthers 24–10 to earn their third Super Bowl title. The game was played on February 7, 2016, at Levi\'s Stadium in the San Francisco Bay Area at Santa Clara, California. As this was the 50th Super Bowl, the league emphasized the "golden anniversary" with various gold-themed initiatives, as well as temporarily suspending the tradition of naming each Super Bowl game with Roman numerals (under which the game would have been known as "Super Bowl L"), so that the logo could prominently feature the Arabic numerals 50.' +'Where did Super Bowl 50 take place?' +``` + +мы можем увидеть, что ответ действительно может быть одним из трех возможных вариантов, которые мы видели ранее. + +### Подготовка обучающих данных[[processing-the-training-data]] + + + +Начнем с предварительной подготовки обучающих данных. Самое сложное - сгенерировать метки для ответа на вопрос, которые будут представлять собой начальную и конечную позиции токенов, соответствующих ответу в контексте. + +Но не будем забегать вперед. Сначала нам нужно преобразовать текст во входных данных в идентификаторы, которые модель сможет понять, используя токенизатор: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Как упоминалось ранее, мы будем проводить дообучение модели BERT, но вы можете использовать любой другой тип модели, если в ней реализован быстрый токенизатор. Вы можете увидеть все архитектуры с быстрой версией в [этой большой таблице](https://huggingface.co/transformers/#supported-frameworks), а чтобы проверить, что используемый вами объект `tokenizer` действительно поддерживается 🤗 Tokenizers, вы можете посмотреть на его атрибут `is_fast`: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Мы можем передать нашему токенизатору вопрос и контекст вместе, и он правильно вставит специальные токены, чтобы сформировать предложение, подобное этому: + +``` +[CLS] question [SEP] context [SEP] +``` + +Давайте перепроверим: + +```py +context = raw_datasets["train"][0]["context"] +question = raw_datasets["train"][0]["question"] + +inputs = tokenizer(question, context) +tokenizer.decode(inputs["input_ids"]) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, ' +'the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin ' +'Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms ' +'upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred ' +'Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a ' +'replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette ' +'Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues ' +'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' +``` + +В качестве меток будут использоваться индексы токенов, начинающих и заканчивающих ответ, а задача модели - предсказать один начальный и конечный логит для каждого токена на входе, при этом теоретические метки будут выглядеть следующим образом: + +
+One-hot encoded labels for question answering. + +
+ +В данном случае контекст не слишком длинный, но некоторые примеры в датасете имеют очень длинные контексты, которые превысят установленную нами максимальную длину (которая в данном случае равна 384). Как мы видели в [Главе 6](../chapter6/4), когда изучали внутреннее устройство конвейера `question-answering`, мы будем работать с длинными контекстами, создавая несколько обучающих признаков из одной выборки нашего датасета, со скользящим окном между ними. + +Чтобы увидеть, как это работает на текущем примере, мы можем ограничить длину до 100 и использовать скользящее окно из 50 токенов. В качестве напоминания мы используем: + +- `max_length` для установки максимальной длины (здесь 100) +- `truncation="only_second"` для усечения контекста (который находится во второй позиции), когда вопрос с его контекстом слишком длинный +- `stride` для задания количества перекрывающихся токенов между двумя последовательными фрагментами (здесь 50) +- `return_overflowing_tokens=True`, чтобы сообщить токенизатору, что нам нужны переполненные токены (overflowing tokens) + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, +) + +for ids in inputs["input_ids"]: + print(tokenizer.decode(ids)) +``` + +```python out +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basi [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP] Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grotto, a Marian place of prayer and reflection. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 [SEP]' +'[CLS] To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France? [SEP]. It is a replica of the grotto at Lourdes, France where the Virgin Mary reputedly appeared to Saint Bernadette Soubirous in 1858. At the end of the main drive ( and in a direct line that connects through 3 statues and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' +``` + +Как мы можем видеть, наш пример был разбит на четыре входа, каждый из которых содержит вопрос и часть контекста. Обратите внимание, что ответ на вопрос ("Bernadette Soubirous") появляется только в третьем и последнем входе, поэтому, работая с длинными контекстами таким образом, мы создадим несколько обучающих примеров, в которых ответ не будет включен в контекст. Для этих примеров метками будут `start_position = end_position = 0` (таким образом мы предсказываем токен `[CLS]`). Мы также зададим эти метки в неудачном случае, когда ответ был усечен, так что у нас есть только его начало (или конец). Для примеров, где ответ полностью находится в контексте, метками будут индекс токена, с которого начинается ответ, и индекс токена, на котором ответ заканчивается. + +Датасет предоставляет нам начальный символ ответа в контексте, а прибавив к нему длину ответа, мы можем найти конечный символ в контексте. Чтобы сопоставить их с индексами токенов, нам нужно использовать сопоставление смещений, которое мы изучали в [Главе 6](../chapter6/4). Мы можем настроить наш токенизатор на их возврат, передав `return_offsets_mapping=True`: + +```py +inputs = tokenizer( + question, + context, + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +inputs.keys() +``` + +```python out +dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) +``` + +Как мы видим, нам возвращаются обычные идентификаторы входа, идентификаторы типов токенов и маска внимания, а также необходимое нам сопоставление смещений и дополнительный ключ `overflow_to_sample_mapping`. Соответствующее значение мы будем использовать при токенизации нескольких текстов одновременно (что мы и должны делать, чтобы извлечь выгоду из того, что наш токенизатор основан на Rust). Поскольку один образец может давать несколько признаков, он сопоставляет каждый признак с примером, из которого он произошел. Поскольку здесь мы токенизировали только один пример, мы получим список `0`: + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Но если мы проведем токенизацию большего количества примеров, это станет более полезным: + +```py +inputs = tokenizer( + raw_datasets["train"][2:6]["question"], + raw_datasets["train"][2:6]["context"], + max_length=100, + truncation="only_second", + stride=50, + return_overflowing_tokens=True, + return_offsets_mapping=True, +) + +print(f"The 4 examples gave {len(inputs['input_ids'])} features.") +print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") +``` + +```python out +'The 4 examples gave 19 features.' +'Here is where each comes from: [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3].' +``` + +Как мы видим, первые три примера (с индексами 2, 3 и 4 в обучающем наборе) дали по четыре признака, а последний пример (с индексом 5 в обучающем наборе) - 7 признаков. + +Эта информация будет полезна для сопоставления каждого полученного признака с соответствующей меткой. Как уже упоминалось ранее, этими метками являются: + +- `(0, 0)`, если ответ не находится в соответствующей области контекста +- `(start_position, end_position)`, если ответ находится в соответствующей области контекста, причем `start_position` - это индекс токена (во входных идентификаторах) в начале ответа, а `end_position` - индекс токена (во входных идентификаторах) в конце ответа + +Чтобы определить, какой из этих случаев имеет место, и, если нужно, позиции токенов, мы сначала находим индексы, с которых начинается и заканчивается контекст во входных идентификаторах. Для этого мы могли бы использовать идентификаторы типов токенов, но поскольку они не обязательно существуют для всех моделей (например, DistilBERT их не требует), вместо этого мы воспользуемся методом `sequence_ids()` из `BatchEncoding`, который возвращает наш токенизатор. + +Получив индексы токенов, мы смотрим на соответствующие смещения, которые представляют собой кортежи из двух целых чисел, обозначающих промежуток символов внутри исходного контекста. Таким образом, мы можем определить, начинается ли фрагмент контекста в этом признаке после ответа или заканчивается до начала ответа (в этом случае метка будет `(0, 0)`). Если это не так, мы зацикливаемся, чтобы найти первый и последний токен ответа: + +```py +answers = raw_datasets["train"][2:6]["answers"] +start_positions = [] +end_positions = [] + +for i, offset in enumerate(inputs["offset_mapping"]): + sample_idx = inputs["overflow_to_sample_mapping"][i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Найдём начало и конец контекста + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Если ответ не полностью находится внутри контекста, меткой будет (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # В противном случае это начальная и конечная позиции токенов + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + +start_positions, end_positions +``` + +```python out +([83, 51, 19, 0, 0, 64, 27, 0, 34, 0, 0, 0, 67, 34, 0, 0, 0, 0, 0], + [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) +``` + +Давайте посмотрим на несколько результатов, чтобы убедиться в правильности нашего подхода. Для первого признака мы находим `(83, 85)` в качестве меток, поэтому давайте сравним теоретический ответ с декодированным диапазоном лексем с 83 по 85 (включительно): + +```py +idx = 0 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +start = start_positions[idx] +end = end_positions[idx] +labeled_answer = tokenizer.decode(inputs["input_ids"][idx][start : end + 1]) + +print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") +``` + +```python out +'Theoretical answer: the Main Building, labels give: the Main Building' +``` + +Итак, это совпадение! Теперь проверим индекс 4, где мы установили метки на `(0, 0)`, что означает, что ответ не находится в фрагменте контекста этого признака: + +```py +idx = 4 +sample_idx = inputs["overflow_to_sample_mapping"][idx] +answer = answers[sample_idx]["text"][0] + +decoded_example = tokenizer.decode(inputs["input_ids"][idx]) +print(f"Theoretical answer: {answer}, decoded example: {decoded_example}") +``` + +```python out +'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]' +``` + +Действительно, мы не видим ответа в контексте. + + + +✏️ **Ваша очередь!** При использовании архитектуры XLNet дополнение применяется слева, а вопрос и контекст меняются местами. Адаптируйте весь код, который мы только что рассмотрели, к архитектуре XLNet (и добавьте `padding=True`). Имейте в виду, что токен `[CLS]` может не находиться в позиции 0 при использовании дополнения. + + + +Теперь, когда мы шаг за шагом разобрались с предварительной обработкой обучающих данных, мы можем сгруппировать их в функцию, которую будем применять ко всему датасету. Мы дополним каждый признак до максимальной длины, которую мы задали, поскольку большинство контекстов будут длинными (и соответствующие образцы будут разбиты на несколько признаков), поэтому применение динамического дополнения здесь не имеет реальной пользы: + +```py +max_length = 384 +stride = 128 + + +def preprocess_training_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + offset_mapping = inputs.pop("offset_mapping") + sample_map = inputs.pop("overflow_to_sample_mapping") + answers = examples["answers"] + start_positions = [] + end_positions = [] + + for i, offset in enumerate(offset_mapping): + sample_idx = sample_map[i] + answer = answers[sample_idx] + start_char = answer["answer_start"][0] + end_char = answer["answer_start"][0] + len(answer["text"][0]) + sequence_ids = inputs.sequence_ids(i) + + # Найдём начало и конец контекста + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Если ответ не полностью находится внутри контекста, меткой будет (0, 0) + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: + start_positions.append(0) + end_positions.append(0) + else: + # В противном случае это начальная и конечная позиции токенов + idx = context_start + while idx <= context_end and offset[idx][0] <= start_char: + idx += 1 + start_positions.append(idx - 1) + + idx = context_end + while idx >= context_start and offset[idx][1] >= end_char: + idx -= 1 + end_positions.append(idx + 1) + + inputs["start_positions"] = start_positions + inputs["end_positions"] = end_positions + return inputs +``` + +Обратите внимание, что мы определили две константы для определения максимальной длины и длины скользящего окна, а также добавили немного очистки перед токенизацией: некоторые вопросы в датасете SQuAD имеют лишние пробелы в начале и конце, которые ничего не добавляют (и занимают место при токенизации, если вы используете модель вроде RoBERTa), поэтому мы удалили эти лишние пробелы. + +Чтобы применить эту функцию ко всему обучающему набору, мы используем метод `Dataset.map()` с флагом `batched=True`. Это необходимо, так как мы изменяем длину датасета (поскольку один пример может давать несколько обучающих признаков): + +```py +train_dataset = raw_datasets["train"].map( + preprocess_training_examples, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +len(raw_datasets["train"]), len(train_dataset) +``` + +```python out +(87599, 88729) +``` + +Как мы видим, предварительная обработка добавила около 1 000 признаков. Теперь наш обучающий набор готов к использованию - давайте займемся предварительной обработкой валидационного набора! + +### Подготовка валидационных данных[[processing-the-validation-data]] + +Предварительная обработка валидационных данных будет немного проще, поскольку нам не нужно генерировать метки (если только мы не хотим вычислять потери на валидации, но это число не поможет нам понять, насколько хороша модель). Настоящей радостью будет интерпретация прогнозов модели в диапазонах исходного контекста. Для этого нам нужно хранить как сопоставления смещений, так и способ сопоставления каждого созданного признака с оригинальным примером, из которого он взят. Поскольку в исходном датасете есть столбец ID, мы будем использовать этот ID. + +Единственное, что мы добавим сюда, - это немного почистим сопоставления смещений. Они будут содержать смещения для вопроса и контекста, но на этапе постобработки у нас не будет возможности узнать, какая часть входных идентификаторов соответствует контексту, а какая - вопросу (метод `sequence_ids()`, который мы использовали, доступен только для выхода токенизатора). Поэтому мы установим смещения, соответствующие вопросу, в `None`: + +```py +def preprocess_validation_examples(examples): + questions = [q.strip() for q in examples["question"]] + inputs = tokenizer( + questions, + examples["context"], + max_length=max_length, + truncation="only_second", + stride=stride, + return_overflowing_tokens=True, + return_offsets_mapping=True, + padding="max_length", + ) + + sample_map = inputs.pop("overflow_to_sample_mapping") + example_ids = [] + + for i in range(len(inputs["input_ids"])): + sample_idx = sample_map[i] + example_ids.append(examples["id"][sample_idx]) + + sequence_ids = inputs.sequence_ids(i) + offset = inputs["offset_mapping"][i] + inputs["offset_mapping"][i] = [ + o if sequence_ids[k] == 1 else None for k, o in enumerate(offset) + ] + + inputs["example_id"] = example_ids + return inputs +``` + +Мы можем применить эту функцию ко всему валидационому датасету, как и раньше: + +```py +validation_dataset = raw_datasets["validation"].map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +len(raw_datasets["validation"]), len(validation_dataset) +``` + +```python out +(10570, 10822) +``` + +В данном случае мы добавили всего пару сотен примеров, поэтому контексты в валидационном датасете немного короче. + +Теперь, когда мы предварительно обработали все данные, можно приступать к обучению. + +{#if fw === 'pt'} + +## Дообучение модели с API `Trainer`[[fine-tuning-the-model-with-the-trainer-api]] + +Код обучения для этого примера будет очень похож на код из предыдущих разделов -- самым сложным будет написание функции `compute_metrics()`. Поскольку мы дополнили все примеры до максимальной длины, которую мы задали, нет никакого коллатора данных, поэтому вычисление метрики - это единственное, о чем нам нужно беспокоиться. Самым сложным будет постобработка прогнозов модели в отрезки текста оригинальных примеров; как только мы это сделаем, метрика из библиотеки 🤗 Datasets сделает за нас большую часть работы. + +{:else} + +## Дообучение модели с Keras[[fine-tuning-the-model-with-keras]] + +Код обучения для этого примера будет очень похож на код в предыдущих разделах, но вычисление метрик будет уникальным. Поскольку мы дополнили все примеры до максимальной длины, которую мы задали, нет коллатора данных, который нужно определить, поэтому вычисление метрики - это единственное, о чем нам нужно беспокоиться. Самое сложное - это постобработка прогнозов модели в отрезки текста в исходных примерах; как только мы это сделаем, метрика из библиотеки 🤗 Datasets сделает за нас большую часть работы. + +{/if} + +### Постобработка[[post-processing]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Модель выведет логиты для начальной и конечной позиций ответа во входных идентификаторах, как мы видели при изучении [конвейера `question-answering`](../chapter6/3b). Шаг постобработки будет аналогичен тому, что мы делали там, так что вот краткое напоминание о том, что мы делали: + +- Мы маскировали начальные и конечные логиты, соответствующие токенам вне контекста. +- Затем мы преобразовали начальные и конечные логиты в вероятности с помощью softmax. +- Каждой паре `(start_token, end_token)` мы присваивали оценку, взяв произведение двух соответствующих вероятностей. +- Мы искали пару с максимальной оценкой, которая давала правильный ответ (например, `start_token` меньше `end_token`). + +В данном случае мы немного изменим этот процесс, поскольку нам не нужно вычислять фактические оценки (только спрогнозированный ответ). Это означает, что мы можем пропустить шаг softmax. Чтобы ускорить процесс, мы также не будем оценивать все возможные пары `(start_token, end_token)`, а только те, которые соответствуют наибольшим `n_best` логитам (при `n_best=20`). Так как мы пропустим softmax, эти оценки будут оценками логитов, и будут получены путем взятия суммы начального и конечного логитов (вместо произведения, по правилу \\(\log(ab) = \log(a) + \log(b)\\)). + +Чтобы продемонстрировать все это, нам понадобятся некоторые прогнозы. Поскольку мы еще не обучили нашу модель, мы будем использовать модель по умолчанию для конвейера QA, чтобы сгенерировать несколько прогнозов на небольшой части набора для валидации. Мы можем использовать ту же функцию обработки, что и раньше; поскольку она опирается на глобальную константу `tokenizer`, нам просто нужно изменить этот объект на токенизатор модели, которую мы хотим временно использовать: + +```python +small_eval_set = raw_datasets["validation"].select(range(100)) +trained_checkpoint = "distilbert-base-cased-distilled-squad" + +tokenizer = AutoTokenizer.from_pretrained(trained_checkpoint) +eval_set = small_eval_set.map( + preprocess_validation_examples, + batched=True, + remove_columns=raw_datasets["validation"].column_names, +) +``` + +Теперь, когда препроцессинг завершен, мы меняем токенизатор обратно на тот, который был выбран изначально: + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Затем мы удаляем из нашего `eval_set` столбцы, которые не ожидает модель, создаем батч со всей этой небольшой валидацией и пропускаем его через модель. Если доступен GPU, мы используем его для ускорения работы: + +{#if fw === 'pt'} + +```python +import torch +from transformers import AutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("torch") + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +batch = {k: eval_set_for_model[k].to(device) for k in eval_set_for_model.column_names} +trained_model = AutoModelForQuestionAnswering.from_pretrained(trained_checkpoint).to( + device +) + +with torch.no_grad(): + outputs = trained_model(**batch) +``` + +Поскольку `Trainer` будет возвращать нам прогнозы в виде массивов NumPy, мы берем начальный и конечный логиты и конвертируем их в этот формат: + +```python +start_logits = outputs.start_logits.cpu().numpy() +end_logits = outputs.end_logits.cpu().numpy() +``` + +{:else} + +```python +import tensorflow as tf +from transformers import TFAutoModelForQuestionAnswering + +eval_set_for_model = eval_set.remove_columns(["example_id", "offset_mapping"]) +eval_set_for_model.set_format("numpy") + +batch = {k: eval_set_for_model[k] for k in eval_set_for_model.column_names} +trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoint) + +outputs = trained_model(**batch) +``` + +Для облегчения экспериментов преобразуем выходные данные в массивы NumPy: + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Теперь нам нужно найти спрогнозированный ответ для каждого примера в `small_eval_set`. Один пример может быть разбит на несколько признаков в `eval_set`, поэтому первым шагом будет сопоставление каждого примера в `small_eval_set` с соответствующими признаками в `eval_set`: + +```python +import collections + +example_to_features = collections.defaultdict(list) +for idx, feature in enumerate(eval_set): + example_to_features[feature["example_id"]].append(idx) +``` + +Имея это на руках, мы можем приступить к работе, итерируясь по всем примерам и, для каждого примера, по всем ассоциированным с ним признакам. Как мы уже говорили, мы рассмотрим оценки логитов для `n_best` начальных логитов и конечных логитов, исключая позиции, которые дают: + +- Ответ, который не вписывается в контекст +- Ответ с отрицательной длиной +- Слишком длинный ответ (мы ограничиваем возможности по `max_answer_length=30`). + +После того как мы получили все возможные ответы для одного примера, мы просто выбираем тот, который имеет лучшую оценку логита: + +```python +import numpy as np + +n_best = 20 +max_answer_length = 30 +predicted_answers = [] + +for example in small_eval_set: + example_id = example["id"] + context = example["context"] + answers = [] + + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = eval_set["offset_mapping"][feature_index] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Пропускаем ответы, которые не полностью соответствуют контексту + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Пропускайте ответы, длина которых либо < 0, либо > max_answer_length. + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answers.append( + { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + ) + + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) +``` + +Окончательный формат спрогнозированных ответов - это тот, который ожидает метрика, которую мы будем использовать. Как обычно, мы можем загрузить ее с помощью библиотеки 🤗 Evaluate: + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +Эта метрика ожидает прогнозируемые ответы в формате, который мы видели выше (список словарей с одним ключом для идентификатора примера и одним ключом для прогнозируемого текста), и теоретические ответы в формате ниже (список словарей с одним ключом для идентификатора примера и одним ключом для возможных ответов): + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Теперь мы можем убедиться, что получаем разумные результаты, посмотрев на первый элемент обоих списков: + +```python +print(predicted_answers[0]) +print(theoretical_answers[0]) +``` + +```python out +{'id': '56be4db0acb8001400a502ec', 'prediction_text': 'Denver Broncos'} +{'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} +``` + +Не так уж плохо! Теперь давайте посмотрим на оценку, которую дает нам метрика: + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Опять же, это довольно хорошо, если учесть, что согласно [его статье](https://arxiv.org/abs/1910.01108v2) DistilBERT с дообучением на SQuAD получает 79,1 и 86,9 для этих оценок на всем датасете. + +{#if fw === 'pt'} + +Теперь давайте поместим все, что мы только что сделали, в функцию `compute_metrics()`, которую мы будем использовать в `Trainer`. Обычно функция `compute_metrics()` получает только кортеж `eval_preds` с логитами и метками. Здесь нам понадобится немного больше, так как мы должны искать в датасете признаков смещения и в датасете примеров исходные контексты, поэтому мы не сможем использовать эту функцию для получения обычных результатов оценки во время обучения. Мы будем использовать ее только в конце обучения для проверки результатов. + +Функция `compute_metrics()` группирует те же шаги, что и до этого, только добавляется небольшая проверка на случай, если мы не найдем ни одного правильного ответа (в этом случае мы прогнозируем пустую строку). + +{:else} + +Теперь давайте поместим все, что мы только что сделали, в функцию `compute_metrics()`, которую мы будем использовать после обучения нашей модели. Нам нужно будет передать несколько больше, чем просто выходные логиты, поскольку мы должны искать в датасете признаков смещение, а в датасете примеров - исходные контексты: + +{/if} + +```python +from tqdm.auto import tqdm + + +def compute_metrics(start_logits, end_logits, features, examples): + example_to_features = collections.defaultdict(list) + for idx, feature in enumerate(features): + example_to_features[feature["example_id"]].append(idx) + + predicted_answers = [] + for example in tqdm(examples): + example_id = example["id"] + context = example["context"] + answers = [] + + # Итерируемся по всем признакам, ассоциированным с этим примером + for feature_index in example_to_features[example_id]: + start_logit = start_logits[feature_index] + end_logit = end_logits[feature_index] + offsets = features[feature_index]["offset_mapping"] + + start_indexes = np.argsort(start_logit)[-1 : -n_best - 1 : -1].tolist() + end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() + for start_index in start_indexes: + for end_index in end_indexes: + # Пропускаем ответы, которые не полностью соответствуют контексту + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Пропускайте ответы, длина которых либо < 0, либо > max_answer_length + if ( + end_index < start_index + or end_index - start_index + 1 > max_answer_length + ): + continue + + answer = { + "text": context[offsets[start_index][0] : offsets[end_index][1]], + "logit_score": start_logit[start_index] + end_logit[end_index], + } + answers.append(answer) + + # Выбираем ответ с лучшей оценкой + if len(answers) > 0: + best_answer = max(answers, key=lambda x: x["logit_score"]) + predicted_answers.append( + {"id": example_id, "prediction_text": best_answer["text"]} + ) + else: + predicted_answers.append({"id": example_id, "prediction_text": ""}) + + theoretical_answers = [{"id": ex["id"], "answers": ex["answers"]} for ex in examples] + return metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +Мы можем проверить ее работу на наших прогнозах: + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Выглядит отлично! Теперь давайте используем это для дообучения нашей модели. + +### Дообучение модели[[fine-tuning-the-model]] + +{#if fw === 'pt'} + +Теперь мы готовы к обучению нашей модели. Давайте сначала создадим ее, используя класс `AutoModelForQuestionAnswering`, как и раньше: + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Теперь мы готовы к обучению нашей модели. Давайте сначала создадим ее, используя класс `TFAutoModelForQuestionAnswering`, как и раньше: + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Как обычно, мы получаем предупреждение о том, что некоторые веса не используются (веса головы предварительного обучения), а другие инициализируются случайным образом (веса головы ответов на вопросы). Вы уже должны были привыкнуть к этому, но это означает, что модель еще не готова к использованию и нуждается в дообучении - хорошо, что мы сейчас этим займемся! + +Чтобы отправить нашу модель на Hub, нам нужно войти в Hugging Face. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции, которая отображает виджет, где вы можете ввести свои учетные данные: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Если вы работаете не в блокноте, просто введите следующую строку в терминале: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Когда это сделано, мы можем определить наши `TrainingArguments`. Как мы уже говорили, когда определяли нашу функцию для вычисления метрики, мы не сможем сделать обычный цикл оценки из-за сигнатуры функции `compute_metrics()`. Мы могли бы написать собственный подкласс `Trainer` для этого (такой подход можно найти в [примере скрипта ответа на вопросы](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), но это слишком длинно для данного раздела. Вместо этого мы будем оценивать модель только в конце обучения, а как проводить регулярную оценку, покажем ниже в разделе "Пользовательский цикл обучения". + +Именно здесь API `Trainer` показывает свои ограничения, а библиотека 🤗 Accelerate блистает: настройка класса под конкретный случай использования может быть болезненной, но настройка полностью открытого цикла обучения - это просто. + +Let's take a look at our `TrainingArguments`: + +```python +from transformers import TrainingArguments + +args = TrainingArguments( + "bert-finetuned-squad", + evaluation_strategy="no", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, + fp16=True, + push_to_hub=True, +) +``` + +Большинство из них мы уже видели: мы задаем некоторые гиперпараметры (например, скорость обучения, количество эпох обучения и затухание веса) и указываем, что хотим сохранять модель в конце каждой эпохи, пропускать оценку и загружать результаты в Model Hub. Мы также включаем обучение со смешанной точностью с `fp16=True`, так как это может значительно ускорить обучение на современных GPU. + +{:else} + +Теперь все готово, и мы можем создать наш TF датасет. На этот раз мы можем использовать простой коллатор данных по умолчанию: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +А теперь создадим датасет, как обычно. + +```python +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Далее мы задаем гиперпараметры обучения и компилируем нашу модель: + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Количество шагов обучения - это количество примеров в датасете, разделенное на размер батча, затем умноженное +# by the total number of epochs. Note that the tf_train_dataset here is a batched tf.data.Dataset, +# а не оригинальный датасет Hugging Face, поэтому его len() уже равен num_samples // batch_size. +num_train_epochs = 3 +num_train_steps = len(tf_train_dataset) * num_train_epochs +optimizer, schedule = create_optimizer( + init_lr=2e-5, + num_warmup_steps=0, + num_train_steps=num_train_steps, + weight_decay_rate=0.01, +) +model.compile(optimizer=optimizer) + +# Обучение со смешанной точностью float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Наконец, мы готовы к обучению с помощью `model.fit()`. Мы используем `PushToHubCallback` для загрузки модели в Hub после каждой эпохи. + +{/if} + +По умолчанию используемый розиторий будет находиться в вашем пространстве имен и называться в соответствии с заданным выходным каталогом, так что в нашем случае он будет находиться в `"sgugger/bert-finetuned-squad"`. Мы можем переопределить это, передав `hub_model_id`; например, чтобы отправить модель в организацию `huggingface_course`, мы использовали `hub_model_id="huggingface_course/bert-finetuned-squad"` (это модель, на которую мы ссылались в начале этого раздела). + +{#if fw === 'pt'} + + + +💡 Если используемый вами выходной каталог существует, он должен быть локальным клоном того розитория, в который вы хотите отправлять данные (поэтому задайте новое имя, если вы получите ошибку при определении `Trainer`). + + + +Наконец, мы просто передаем все в класс `Trainer` и запускаем обучение: + +```python +from transformers import Trainer + +trainer = Trainer( + model=model, + args=args, + train_dataset=train_dataset, + eval_dataset=validation_dataset, + tokenizer=tokenizer, +) +trainer.train() +``` + +{:else} + +```python +from transformers.keras_callbacks import PushToHubCallback + +callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) + +# Мы собираемся провести валидацию после обучения, поэтому не нужно проводить валидацию в середине обучения +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Обратите внимание, что во время обучения каждый раз, когда модель сохраняется (здесь - каждую эпоху), она загружается на Hub в фоновом режиме. Таким образом, при необходимости вы сможете возобновить обучение на другой машине. Все обучение занимает некоторое время (чуть больше часа на Titan RTX), поэтому во время его проведения вы можете выпить кофе или перечитать те части курса, которые показались вам более сложными. Также обратите внимание, что как только закончится первая эпоха, вы увидите, что некоторые веса загружены в Hub, и сможете начать играть с вашей моделью на ее странице. + +{#if fw === 'pt'} + +Когда обучение завершено, мы можем наконец оценить нашу модель (и помолиться, что не потратили все это время вычислений впустую). Метод `predict()` функции `Trainer` вернет кортеж, где первыми элементами будут предсказания модели (здесь пара с начальным и конечным логитами). Мы отправляем его в нашу функцию `compute_metrics()`: + +```python +predictions, _, _ = trainer.predict(validation_dataset) +start_logits, end_logits = predictions +compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) +``` + +{:else} + +Когда обучение завершено, мы можем наконец оценить нашу модель (и помолиться, что не потратили все это время вычислений впустую). Метод `predict()` функции `Trainer` вернет кортеж, где первыми элементами будут предсказания модели (здесь пара с начальным и конечным логитами). Мы отправляем его в нашу функцию `compute_metrics()`: + +```python +predictions = model.predict(tf_eval_dataset) +compute_metrics( + predictions["start_logits"], + predictions["end_logits"], + validation_dataset, + raw_datasets["validation"], +) +``` + +{/if} + +```python out +{'exact_match': 81.18259224219489, 'f1': 88.67381321905516} +``` + +Отлично! Для сравнения, базовые показатели, указанные в статье BERT для этой модели, составляют 80,8 и 88,5, так что мы как раз там, где должны быть. + +{#if fw === 'pt'} + +Наконец, мы используем метод `push_to_hub()`, чтобы убедиться, что мы загрузили последнюю версию модели: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Это возвращает URL только что выполненного коммита, если вы хотите его просмотреть: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +`Trainer` также создает черновик карточки модели со всеми результатами оценки и загружает ее. + +{/if} + +На этом этапе вы можете использовать виджет инференса на Model Hub, чтобы протестировать модель и поделиться ею с друзьями, семьей и любимыми питомцами. Вы успешно провели дообучение модели для задачи ответа на вопрос - поздравляем! + + + +✏️ **Ваша очередь!** Попробуйте другую архитектуру модели, чтобы узнать, лучше ли она справляется с этой задачей! + + + +{#if fw === 'pt'} + +Если вы хотите более глубоко погрузиться в тренировочный цикл, мы покажем вам, как сделать то же самое с помощью 🤗 Accelerate. + +## Пользовательский цикл обучения[[a-custom-training-loop]] + +Теперь давайте посмотрим на полный цикл обучения, чтобы вы могли легко настроить нужные вам части. Он будет очень похож на цикл обучения в [Главе 3](../chapter3/4), за исключением цикла оценки. Мы сможем регулярно оценивать модель, поскольку больше не ограничены классом `Trainer`. + +### Подготовим все для обучения[[preparing-everything-for-training]] + +Сначала нам нужно создать `DataLoader`ы из наших датасетов. Мы установим формат этих датасетов в `"torch"` и удалим столбцы в наборе валидации, которые не используются моделью. Затем мы можем использовать `default_data_collator`, предоставляемый Transformers, в качестве `collate_fn` и перемешаем обучающий набор, но не набор для валидации: + +```py +from torch.utils.data import DataLoader +from transformers import default_data_collator + +train_dataset.set_format("torch") +validation_set = validation_dataset.remove_columns(["example_id", "offset_mapping"]) +validation_set.set_format("torch") + +train_dataloader = DataLoader( + train_dataset, + shuffle=True, + collate_fn=default_data_collator, + batch_size=8, +) +eval_dataloader = DataLoader( + validation_set, collate_fn=default_data_collator, batch_size=8 +) +``` + +Затем мы реинстанцируем нашу модель, чтобы убедиться, что мы не продолжаем дообучение, а снова начинаем с предварительно обученной модели BERT: + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Тогда нам понадобится оптимизатор. Как обычно, мы используем классический `AdamW`, который похож на Adam, но с исправлением в способе применения затухания веса: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Когда у нас есть все эти объекты, мы можем отправить их в метод `accelerator.prepare()`. Помните, что если вы хотите обучать на TPU в блокноте Colab, вам нужно будет перенести весь этот код в функцию обучения, которая не должна выполнять ни одну ячейку, инстанцирующую `Accelerator`. Мы можем принудительно обучать со смешанной точностью, передав `fp16=True` в `Accelerator` (или, если вы выполняете код в виде скрипта, просто убедитесь, что заполнили 🤗 Accelerate `config` соответствующим образом). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Как вы уже поняли из предыдущих разделов, мы можем использовать длину `train_dataloader` для вычисления количества шагов обучения только после того, как она пройдет через метод `accelerator.prepare()`. Мы используем тот же линейный график, что и в предыдущих разделах: + +```py +from transformers import get_scheduler + +num_train_epochs = 3 +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, +) +``` + +Чтобы отправить нашу модель на Hub, нам нужно создать объект `Repository` в рабочей папке. Сначала войдите в Hub Hugging Face, если вы еще не вошли в него. Мы определим имя розитория по идентификатору модели, который мы хотим присвоить нашей модели (не стесняйтесь заменить `repo_name` на свое усмотрение; оно просто должно содержать ваше имя пользователя, что и делает функция `get_full_repo_name()`): + +```py +from huggingface_hub import Repository, get_full_repo_name + +model_name = "bert-finetuned-squad-accelerate" +repo_name = get_full_repo_name(model_name) +repo_name +``` + +```python out +'sgugger/bert-finetuned-squad-accelerate' +``` + +Затем мы можем клонировать этот розиторий в локальную папку. Если она уже существует, эта локальная папка должна быть клоном того розитория, с которым мы работаем: + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Теперь мы можем загрузить все, что сохранили в `output_dir`, вызвав метод `repo.push_to_hub()`. Это поможет нам загружать промежуточные модели в конце каждой эпохи. + +### Цикл обучения[[training-loop]] + +Теперь мы готовы написать полный цикл обучения. После определения прогресс-бара, чтобы следить за ходом обучения, цикл состоит из трех частей: + +- Собственно обучение, которое представляет собой классическую итерацию по `train_dataloader`, прямой проход по модели, затем обратный проход и шаг оптимизатора. +- Оценка, в которой мы собираем все значения для `start_logits` и `end_logits` перед преобразованием их в массивы NumPy. После завершения цикла оценки мы объединяем все результаты. Обратите внимание, что нам нужно произвести усечение, потому что `Accelerator` может добавить несколько примеров в конце, чтобы убедиться, что у нас одинаковое количество примеров в каждом процессе. +- Сохранение и загрузка, где мы сначала сохраняем модель и токенизатор, а затем вызываем `repo.push_to_hub()`. Как и раньше, мы используем аргумент `blocking=False`, чтобы указать библиотеке 🤗 Hub на асинхронный процесс push. Таким образом, обучение продолжается нормально, а эта (длинная) инструкция выполняется в фоновом режиме. + +Вот полный код цикла обучения: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Обучение + 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) + + # Оценка + model.eval() + start_logits = [] + end_logits = [] + accelerator.print("Evaluation!") + for batch in tqdm(eval_dataloader): + with torch.no_grad(): + outputs = model(**batch) + + start_logits.append(accelerator.gather(outputs.start_logits).cpu().numpy()) + end_logits.append(accelerator.gather(outputs.end_logits).cpu().numpy()) + + start_logits = np.concatenate(start_logits) + end_logits = np.concatenate(end_logits) + start_logits = start_logits[: len(validation_dataset)] + end_logits = end_logits[: len(validation_dataset)] + + metrics = compute_metrics( + start_logits, end_logits, validation_dataset, raw_datasets["validation"] + ) + print(f"epoch {epoch}:", metrics) + + # Сохранение и загрузка + 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 + ) +``` + +Если вы впервые видите модель, сохраненную с помощью 🤗 Accelerate, давайте посмотрим на три строки кода, которые с этим связаны: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +Первая строка не требует пояснений: она предписывает всем процессам подождать, пока все не окажутся на этой стадии, прежде чем продолжить работу. Это нужно для того, чтобы убедиться, что у нас одна и та же модель в каждом процессе перед сохранением. Затем мы захватываем `unwrapped_model`, которая является базовой моделью, которую мы определили. Метод `accelerator.prepare()` изменяет модель для работы в распределенном обучении, поэтому у нее больше не будет метода `save_pretrained()`; метод `accelerator.unwrap_model()` отменяет этот шаг. Наконец, мы вызываем `save_pretrained()`, но указываем этому методу использовать `accelerator.save()` вместо `torch.save()`. + +После этого у вас должна получиться модель, которая дает результаты, очень похожие на модель, обученную с помощью `Trainer`. Вы можете проверить модель, которую мы обучили с помощью этого кода, на [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). А если вы хотите протестировать какие-либо изменения в цикле обучения, вы можете напрямую реализовать их, отредактировав код, показанный выше! + +{/if} + +## Использование дообученной модели[[using-the-fine-tuned-model]] + +Мы уже показали вам, как можно использовать модель, дообучение которой мы проводили на Model Hub, с помощью виджета инференса. Чтобы использовать ее локально в `pipeline`, нужно просто указать идентификатор модели: + +```py +from transformers import pipeline + +# Замените здесь на выбранную вами контрольную точку +model_checkpoint = "huggingface-course/bert-finetuned-squad" +question_answerer = pipeline("question-answering", model=model_checkpoint) + +context = """ +🤗 Transformers is backed by the three most popular deep learning libraries — Jax, PyTorch and TensorFlow — with a seamless integration +between them. It's straightforward to train your models with one before loading them for inference with the other. +""" +question = "Which deep learning libraries back 🤗 Transformers?" +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.9979003071784973, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Отлично! Наша модель работает так же хорошо, как и модель по умолчанию для этого конвейера! diff --git a/chapters/ru/chapter7/8.mdx b/chapters/ru/chapter7/8.mdx new file mode 100644 index 000000000..2ef75562c --- /dev/null +++ b/chapters/ru/chapter7/8.mdx @@ -0,0 +1,22 @@ +# Освоение NLP[[mastering-nlp]] + + + +Если вы дошли до конца курса, поздравляем - теперь у вас есть все знания и инструменты, необходимые для решения (почти) любой задачи NLP с помощью 🤗 Transformers и экосистемы Hugging Face! + +Мы видели много разных коллаторов данных, поэтому сделали это небольшое видео, чтобы помочь вам определить, какой из них лучше использовать для каждой задачи: + + + +Пройдя этот молниеносный тур по основным задачам NLP, вы должны: + +* Знать, какие архитектуры (кодер, декодер или кодер-декодер) лучше всего подходят для конкретной задачи +* Понимать разницу между предварительным обучением и дообучением языковой модели +* Знать, как обучать модели Transformer, используя либо API `Trainer` и возможности распределенного обучения в 🤗 Accelerate, либо TensorFlow и Keras, в зависимости от того, какой путь вы выбрали +* Понимать значение и ограничения таких метрик, как ROUGE и BLEU, для задач генерации текста +* Знать, как взаимодействовать с вашими дообученными моделями, как на Hub, так и с помощью `pipeline` из 🤗 Transformers + +Несмотря на все эти знания, настанет момент, когда вы столкнетесь с трудной ошибкой в своем коде или у вас возникнет вопрос о том, как решить ту или иную задачу NLP. К счастью, сообщество Hugging Face готово помочь вам! В заключительной главе этой части курса мы рассмотрим, как можно отлаживать свои модели Transformer и эффективно обращаться за помощью. \ No newline at end of file diff --git a/chapters/ru/chapter7/9.mdx b/chapters/ru/chapter7/9.mdx new file mode 100644 index 000000000..3a99a72eb --- /dev/null +++ b/chapters/ru/chapter7/9.mdx @@ -0,0 +1,329 @@ + + + + +# Тест в конце главы[[end-of-chapter-quiz]] + + + +Давайте проверим, чему вы научились в этой главе! + +### 1. Какую из следующих задач можно сформулировать как проблему классификации токенов? + + + +### 2. Какая часть предварительной обработки для классификации токенов отличается от других конвейеров предварительной обработки? + +-100 для обозначения специальных токенов.", + explain: "Это не относится к классификации токенов - мы всегда используем -100 в качестве метки для токенов, которые мы хотим игнорировать в потерях." + }, + { + text: "При применении усечения/дополнения нам нужно убедиться, что метки имеют тот же размер, что и входные данные.", + explain: "Действительно! Но это не единственное отличие.", + correct: true + } + ]} +/> + +### 3. Какая проблема возникает при токенизации слов в проблеме классификации токенов и при необходимости их маркировки? + + + +### 4. Что означает "доменная адаптация"? + + + +### 5. Что такое метки в проблеме маскированного языкового моделирования? + + + +### 6. Какую из этих задач можно рассматривать как проблему преобразования последовательности-в-последовательность (sequence-to-sequence problem)? + + + +### 7. Каков правильный способ предварительной обработки данных для проблемы преобразования последовательности-в-последовательность? + +inputs=... и targets=....", + explain: "Возможно, в будущем мы добавим такой API, но сейчас это невозможно." + }, + { + text: "Входные данные и цели должны быть предварительно обработаны в двух раздельных вызовах токенизатора.", + explain: "Это правда, но неполная. Вам нужно кое-что сделать, чтобы убедиться, что токенизатор правильно обрабатывает оба варианта." + }, + { + text: "Как обычно, нам просто нужно выполнить токенизацию входных данных.", + explain: "Не в проблеме классификации последовательностей; цели - это тексты, которые мы должны преобразовать в числа!" + }, + { + text: "Входные данные должны быть переданы токенизатору, и цели тоже, но под управлением специального контекстного менеджера.", + explain: "Верно, токенизатор должен быть переведен в target режим этим менеджером контекста.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Почему существует специальный подкласс `Trainer` для проблем преобразования " последовательности-в-последовательность"? + +-100.", + explain: "Это вовсе не определенные пользователем потери, а то, как потери всегда вычисляются." + }, + { + text: "Поскольку проблемы преобразования последовательности-в-последовательность требуют специального цикла оценки", + explain: "Это верно. Предсказания моделей преобразующих последовательность-в-последовательность часто выполняются с помощью метода generate().", + correct: true + }, + { + text: "Поскольку целью являются тексты в проблемах преобразования последовательности-в-последовательность", + explain: "Trainer не особо заботится об этом, поскольку они уже были предварительно обработаны." + }, + { + text: "Поскольку в проблемах преобразования последовательности-в-последовательность мы используем две модели", + explain: "В некотором смысле мы используем две модели, энкодер и декодер, но они сгруппированы в одну модель." + } + ]} +/> + +{:else} + +### 9. Почему при вызове `compile()` для модели трансформера часто нет необходимости определять потери? + + + +{/if} + +### 10. Когда следует проводить предварительное обучение новой модели? + + + +### 11. Почему легко провести предварительное обучение языковой модели на большом количестве текстов? + + + +### 12. Какие основные проблемы возникают при предварительной обработке данных для задачи ответа на вопрос (question answering)? + + + +### 13. Как обычно выполняется постобработка в задаче ответов на вопросы? + + diff --git a/chapters/ru/chapter8/1.mdx b/chapters/ru/chapter8/1.mdx new file mode 100644 index 000000000..038e39e28 --- /dev/null +++ b/chapters/ru/chapter8/1.mdx @@ -0,0 +1,17 @@ +# Введение[[introduction]] + + + +Теперь, когда вы знаете, как решать самые распространенные задачи NLP с помощью 🤗 Transformers, вы должны быть в состоянии начать работу над своими собственными проектами! В этой главе мы рассмотрим, что делать, если вы столкнулись с проблемой. Вы узнаете, как успешно отладить свой код или обучение, как обратиться за помощью к сообществу, если не удается решить проблему самостоятельно. А если вам покажется, что вы нашли ошибку в одной из библиотек Hugging Face, мы покажем вам, как лучше сообщить об этом, чтобы проблема была решена как можно быстрее. + +Точнее, в этой главе вы узнаете: + +- Что нужно делать в первую очередь при возникновении ошибки +- Как обратиться за помощью на [форумах](https://discuss.huggingface.co/) +- Как отладить свой пайплайн обучения +- Как правильно описать проблему + +Разумеется, все это не относится исключительно к 🤗 Transformers или экосистеме Hugging Face; уроки из этой главы применимы к большинству проектов с открытым исходным кодом! \ No newline at end of file diff --git a/chapters/ru/chapter8/2.mdx b/chapters/ru/chapter8/2.mdx new file mode 100644 index 000000000..08b975198 --- /dev/null +++ b/chapters/ru/chapter8/2.mdx @@ -0,0 +1,364 @@ +# Что делать, если возникла ошибка[[what-to-do-when-you-get-an-error]] + + + +В этом разделе мы рассмотрим некоторые распространенные ошибки, которые могут возникнуть при попытке сгенерировать предсказания на основе только что настроенной модели Transformer. Это подготовит вас к [разделу 4](../chapter8/4), где мы рассмотрим, как отладить сам этап обучения. + + + +Для этого раздела мы подготовили [репозиторий](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), и если вы хотите запустить код в этой главе, вам сначала нужно скопировать модель в свой аккаунт на [Hugging Face Hub](https://huggingface.co). Для этого сначала авторизуйтесь на Hugging Face Hub. Если вы выполняете этот код в блокноте, вы можете сделать это с помощью следующей служебной функции: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +в терминале выполните следующее: + +```bash +huggingface-cli login +``` + +Вас попросят ввести имя пользователя и пароль, токен будет сохранен в *~/.cache/huggingface/*. После того как вы вошли в систему, вы можете скопировать репозиторий-шаблон с помощью следующей функции: + +```python +from distutils.dir_util import copy_tree +from huggingface_hub import Repository, snapshot_download, create_repo, get_full_repo_name + + +def copy_repository_template(): + # Клонируем репозиторий и извлекаем локальный путь + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Создайте пустое репо на хабе + имя_модели = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Клонирование пустого репо + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = имя_модели + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Копирование файлов + copy_tree(template_repo_dir, new_repo_dir) + # Отправить в хаб + repo.push_to_hub() +``` + +Теперь при вызове `copy_repository_template()` будет создана копия репозитория-шаблона под вашим аккаунтом. + +## Отладка пайплайна из 🤗 Transformers[[debugging-the-pipeline-from-transformers]] + +Чтобы начать наше путешествие в удивительный мир отладки моделей трансформеров, рассмотрим следующий сценарий: вы работаете с коллегой над проектом ответа на вопросы, который должен помочь клиентам сайта электронной коммерции найти ответы о потребительских товарах. Ваш коллега отправляет вам сообщение следующего содержания: + +> Добрый день! Я только что провел эксперимент, используя техники из [главы 7](../chapter7/7) курса Hugging Face, и получил отличные результаты на SQuAD! Думаю, мы можем использовать эту модель в качестве отправной точки для нашего проекта. ID модели на хабе - "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". Не стесняйтесь протестировать ее :) + +и первое, что приходит в голову, это загрузить модель, используя `pipeline` из 🤗 Transformers: + +```python +from transformers import pipeline + +model_checkpoint = get_full_repo_name("distillbert-base-uncased-finetuned-squad-d5716d28") +reader = pipeline("question-answering", model=model_checkpoint) +``` + +```python out +""" +OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: + +- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + +О нет, кажется, что-то пошло не так! Если вы новичок в программировании, подобные ошибки могут показаться вам немного загадочными (что такое `OSError`?!). Это сообщение является лишь последней частью гораздо большего отчета об ошибке, называемого _Python traceback_ (он же трассировка стека). Например, если вы запустите этот код в Google Colab, вы увидите что-то похожее на содержимое скриншота ниже: + +
+A Python traceback. +
+ +В этих отчетах содержится много информации, поэтому давайте вместе пройдемся по ключевым местам. Первое, что следует отметить, - это то, что трассировки следует читать _снизу вверх_. Это может показаться странным, если вы привыкли читать английский текст сверху вниз, но это логично: трассировка показывает последовательность вызовов функций, которые делает `pipeline` при загрузке модели и токенизатора. (Более подробно о том, как работает `pipeline` под капотом, читайте в [Главе 2](../chapter2/1)). + + + +🚨 Видите синюю рамку вокруг "6 frames" в трассировке Google Colab? Это специальная функция Colab, которая помещает отчет в раскрывающийся блок текста. Если вы не можете найти источник ошибки, обязательно раскройте этот блок, нажав на эти две маленькие стрелки. + + + +Последняя строка трассировки указывает на последнее сообщение об ошибке и дает имя исключения, которое было вызвано. В данном случае тип исключения - `OSError`, что указывает на системную ошибку. Если мы прочитаем сопроводительное сообщение об ошибке, то увидим, что, похоже, возникла проблема с файлом *config.json* модели, и нам предлагается два варианта ее устранения: + +```python out +""" +Make sure that: + +- 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + + + +💡 Если вы столкнулись с сообщением об ошибке, которое трудно понять, просто скопируйте и вставьте его в строку поиска Google или [Stack Overflow](https://stackoverflow.com/) (да, действительно!). Велика вероятность того, что вы не первый, кто столкнулся с этой ошибкой, и это хороший способ найти решения, которые опубликовали другие члены сообщества. Например, поиск по запросу `OSError: Can't load config for` на Stack Overflow дает несколько [результатов](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), которые можно использовать в качестве отправной точки для решения проблемы. + + + +В первом предложении нам предлагается проверить, действительно ли идентификатор модели правильный, поэтому первым делом нужно скопировать идентификатор и вставить его в строку поиска Hub: + +
+The wrong model name. +
+ +Хм, действительно, похоже, что модели нашего коллеги нет на хабе... ага, но в названии модели опечатка! В названии DistilBERT есть только одна буква "l", так что давайте исправим это и поищем вместо нее "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": + +
+The right model name. +
+ +Хорошо! Теперь давайте попробуем загрузить модель снова с правильным идентификатором модели: + +```python +model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28") +reader = pipeline("question-answering", model=model_checkpoint) +``` + +```python out +""" +OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28'. Make sure that: + +- 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is a correct model identifier listed on 'https://huggingface.co/models' + +- or 'lewtun/distilbert-base-uncased-finetuned-squad-d5716d28' is the correct path to a directory containing a config.json file +""" +``` + +Cнова неудача - добро пожаловать в повседневную жизнь инженера машинного обучения! Поскольку мы исправили ID модели, проблема должна заключаться в самом репозитории. Быстрый способ получить доступ к содержимому репозитория на 🤗 Hub - это функция `list_repo_files()` библиотеки `huggingface_hub`: + +```python +from huggingface_hub import list_repo_files + +list_repo_files(repo_id=model_checkpoint) +``` + +```python out +['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt'] +``` + +Интересно - похоже, что в репозитории нет файла *config.json*! Неудивительно, что наш `pipeline` не смог загрузить модель; наш коллега, должно быть, забыл выложить этот файл в Hub после того, как выполнил дообучение. В этом случае проблема кажется довольно простой: мы можем попросить его добавить этот файл, или, поскольку из идентификатора модели видно, что использовалась предварительно обученная модель [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), мы можем загрузить конфигурацию для этой модели и отправить ее в наше репо, чтобы посмотреть, решит ли это проблему. Давайте попробуем это сделать. Используя приемы, изученные в [Главе 2](../chapter2/1), мы можем загрузить конфигурацию модели с помощью класса `AutoConfig`: + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 Применяемый здесь подход не является надежным, поскольку наш коллега мог изменить конфигурацию `distilbert-base-uncased` перед дообучением модели. В реальной жизни мы бы хотели сначала уточнить у него, но для целей этого раздела будем считать, что он использовал конфигурацию по умолчанию. + + + +Затем мы можем отправить это в наш репозиторий моделей вместе с конфигурацией с помощью функции `push_to_hub()`: + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Теперь мы можем проверить, работает ли это, загрузив модель из последнего коммита в ветке `main`: + +```python +reader = pipeline("question-answering", model=model_checkpoint, revision="main") + +context = r""" +Extractive Question Answering is the task of extracting an answer from a text +given a question. An example of a question answering dataset is the SQuAD +dataset, which is entirely based on that task. If you would like to fine-tune a +model on a SQuAD task, you may leverage the +examples/pytorch/question-answering/run_squad.py script. + +🤗 Transformers is interoperable with the PyTorch, TensorFlow, and JAX +frameworks, so you can use your favourite tools for a wide variety of tasks! +""" + +question = "What is extractive question answering?" +reader(question=question, context=context) +``` + +```python out +{'score': 0.38669535517692566, + 'start': 34, + 'end': 95, + 'answer': 'the task of extracting an answer from a text given a question'} +``` + +Ура, сработало! Давайте вспомним, что вы только что узнали: + +- Сообщения об ошибках в Python называются _tracebacks_ и читаются снизу вверх. Последняя строка сообщения об ошибке обычно содержит информацию, необходимую для поиска источника проблемы. +- Если последняя строка не содержит достаточной информации, пройдите путь вверх по трассировке и посмотрите, сможете ли вы определить, в каком месте исходного кода возникла ошибка. +- Если ни одно из сообщений об ошибках не помогло вам отладить проблему, попробуйте поискать в Интернете решение аналогичной проблемы. +- The `huggingface_hub` +// 🤗 Hub? +предоставляет набор инструментов, с помощью которых вы можете взаимодействовать с репозиториями на Хабе и отлаживать их. + +Теперь, когда вы знаете, как отлаживать конвейер, давайте рассмотрим более сложный пример на прямом проходе самой модели. + +## Отладка прямого прохода модели[[debugging-the-forward-pass-of-your-model]] + +Хотя `pipeline` отлично подходит для большинства приложений, где вам нужно быстро генерировать предсказания, иногда вам понадобится доступ к логам модели (например, если вы хотите применить какую-то пользовательскую постобработку). Чтобы увидеть, что может пойти не так в этом случае, давайте сначала возьмем модель и токенизатор: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Далее нам нужен вопрос, поэтому давайте посмотрим, поддерживаются ли наши любимые фреймворки: + +```python +question = "Which frameworks can I use?" +``` + +Как мы видели в [Главе 7](../chapter7/1), обычные шаги, которые нам нужно предпринять, - это токенизация входных данных, извлечение логитов начальных и конечных токенов, а затем декодирование диапазона ответов: + +```python +import torch + +inputs = tokenizer(question, context, add_special_tokens=True) +input_ids = inputs["input_ids"][0] +outputs = model(**inputs) +answer_start_scores = outputs.start_logits +answer_end_scores = outputs.end_logits +# Выберем наиболее правдоподобную позицию начала ответа с помощью функции argmax +answer_start = torch.argmax(answer_start_scores) +# Выберем наиболее правдоподбную позицию окончания ответа с помощью функции argmax +answer_end = torch.argmax(answer_end_scores) + 1 +answer = tokenizer.convert_tokens_to_string( + tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) +) +print(f"Question: {question}") +print(f"Answer: {answer}") +``` + +```python out +""" +--------------------------------------------------------------------------- +AttributeError Traceback (most recent call last) +/var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_75743/2725838073.py in + 1 inputs = tokenizer(question, text, add_special_tokens=True) + 2 input_ids = inputs["input_ids"] +----> 3 outputs = model(**inputs) + 4 answer_start_scores = outputs.start_logits + 5 answer_end_scores = outputs.end_logits + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) + 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks + 1050 or _global_forward_hooks or _global_forward_pre_hooks): +-> 1051 return forward_call(*input, **kwargs) + 1052 # Do not call functions when jit is used + 1053 full_backward_hooks, non_full_backward_hooks = [], [] + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, start_positions, end_positions, output_attentions, output_hidden_states, return_dict) + 723 return_dict = return_dict if return_dict is not None else self.config.use_return_dict + 724 +--> 725 distilbert_output = self.distilbert( + 726 input_ids=input_ids, + 727 attention_mask=attention_mask, + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/torch/nn/modules/module.py in _call_impl(self, *input, **kwargs) + 1049 if not (self._backward_hooks or self._forward_hooks or self._forward_pre_hooks or _global_backward_hooks + 1050 or _global_forward_hooks or _global_forward_pre_hooks): +-> 1051 return forward_call(*input, **kwargs) + 1052 # Do not call functions when jit is used + 1053 full_backward_hooks, non_full_backward_hooks = [], [] + +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) + 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + 472 elif input_ids is not None: +--> 473 input_shape = input_ids.size() + 474 elif inputs_embeds is not None: + 475 input_shape = inputs_embeds.size()[:-1] + +AttributeError: 'list' object has no attribute 'size' +""" +``` + +Похоже, что в нашем коде есть ошибка! Но мы не боимся небольшой отладки. Вы можете использовать отладчик Python в блокноте: + + + +или в терминале: + + + +Здесь чтение сообщения об ошибке говорит нам, что у объекта `'list'` нет атрибута 'size', и мы видим стрелку `-->`, указывающую на строку, где возникла проблема в `model(**inputs)`. Вы можете отладить это интерактивно, используя отладчик Python, но сейчас мы просто распечатаем фрагмент `inputs`, чтобы посмотреть, что у нас есть: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Конечно, это выглядит как обычный `list` в Python, но давайте перепроверим тип: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Да, это точно список Python. Так что же пошло не так? Вспомним из [Главы 2](../chapter2/1), что классы `AutoModelForXxx` в 🤗 Transformers работают с _тензорами_ (либо в PyTorch, либо в TensorFlow), и обычной операцией является извлечение размерности тензора с помощью `Tensor.size()`, скажем, в PyTorch. Давайте еще раз посмотрим на трассировку, чтобы увидеть, какая строка вызвала исключение: + +``` +~/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/models/distilbert/modeling_distilbert.py in forward(self, input_ids, attention_mask, head_mask, inputs_embeds, output_attentions, output_hidden_states, return_dict) + 471 raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time") + 472 elif input_ids is not None: +--> 473 input_shape = input_ids.size() + 474 elif inputs_embeds is not None: + 475 input_shape = inputs_embeds.size()[:-1] + +AttributeError: 'list' object has no attribute 'size' +``` + +Похоже, что наш код пытался вызвать `input_ids.size()`, но это явно не сработает для Python `list`. Как мы можем решить эту проблему? Поиск сообщения об ошибке на Stack Overflow дает довольно много релевантных [результатов](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f). При нажатии на первый из них появляется вопрос, аналогичный нашему, ответ на который показан на скриншоте ниже: + +
+An answer from Stack Overflow. +
+ +В ответе рекомендуется добавить в токенизатор `return_tensors='pt'`, так что давайте посмотрим, сработает ли это для нас: + +```python out +inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt") +input_ids = inputs["input_ids"][0] +outputs = model(**inputs) +answer_start_scores = outputs.start_logits +answer_end_scores = outputs.end_logits +# Выберем наиболее правдоподобную позицию начала ответа с помощью функции argmax +answer_start = torch.argmax(answer_start_scores) +# Выберем наиболее правдоподбную позицию окончания ответа с помощью функции argmax +answer_end = torch.argmax(answer_end_scores) + 1 +answer = tokenizer.convert_tokens_to_string( + tokenizer.convert_ids_to_tokens(input_ids[answer_start:answer_end]) +) +print(f"Question: {question}") +print(f"Answer: {answer}") +``` + +```python out +""" +Question: Which frameworks can I use? +Answer: pytorch, tensorflow, and jax +""" +``` + +Отлично, это сработало! Это отличный пример того, насколько полезным может быть Stack Overflow: найдя похожую проблему, мы смогли воспользоваться опытом других членов сообщества. Однако подобный поиск не всегда дает нужный ответ, так что же делать в таких случаях? К счастью, на [Hugging Face forums](https://discuss.huggingface.co/) есть гостеприимное сообщество разработчиков, которые могут вам помочь! В следующем разделе мы рассмотрим, как составить хорошие вопросы на форуме, на которые, скорее всего, будет получен ответ. \ No newline at end of file diff --git a/chapters/ru/chapter8/3.mdx b/chapters/ru/chapter8/3.mdx new file mode 100644 index 000000000..ad9fd9330 --- /dev/null +++ b/chapters/ru/chapter8/3.mdx @@ -0,0 +1,164 @@ +# Обращение за помощью на форумах[[asking-for-help-on-the-forums]] + + + + + +Форумы [Hugging Face](https://discuss.huggingface.co) - это отличное место, где можно получить помощь от команды разработчиков библиотек и более широкого сообщества Hugging Face. Вот как выглядит главная страница в любой день: + +
+The Hugging Face forums. +
+ +С левой стороны вы можете увидеть все категории, по которым сгруппированы различные темы, а с правой - самые последние темы. Тема - это сообщение, содержащее заголовок, категорию и описание; это очень похоже на формат вопросов GitHub, который мы видели при создании нашего собственного набора данных в [Главе 5](../chapter5/1). Как следует из названия, категория [Beginners](https://discuss.huggingface.co/c/beginners/5) предназначена в первую очередь для тех, кто только начинает знакомиться с библиотеками и экосистемой Hugging Face. Здесь можно задать любой вопрос по любой из библиотек, будь то отладка кода или просьба о помощи, как что-то сделать. (При этом, если ваш вопрос касается какой-то конкретной библиотеки, вам, вероятно, следует обратиться в соответствующую категорию библиотек на форуме). + +Аналогично, категории [Intermediate](https://discuss.huggingface.co/c/intermediate/6) и [Research](https://discuss.huggingface.co/c/research/7) предназначены для более сложных вопросов, например, о библиотеках или новых крутых исследованиях в области НЛП, которые вы хотели бы обсудить. + +И, конечно же, нельзя не упомянуть категорию [Course](https://discuss.huggingface.co/c/course/20), где вы можете задавать любые вопросы, связанные с курсом "Hugging Face"! + +Выбрав категорию, вы будете готовы написать свою первую тему. На форуме вы можете найти несколько [рекомендаций](https://discuss.huggingface.co/t/how-to-request-support/3128) о том, как это сделать, а в этом разделе мы рассмотрим некоторые особенности, из которых складывается хорошая просьба о помощи. + +## Написание хорошего поста на форуме[[writing-a-good-forum-post]] + +В качестве примера предположим, что мы пытаемся сгенерировать векторные представления статей Википедии для создания пользовательской поисковой системы. Как обычно, мы загружаем токенизатор и модель следующим образом: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Теперь предположим, что мы попытаемся построить векторное представления для целого раздела [статьи с Википедии](https://en.wikipedia.org/wiki/Transformers), посвященной Трансформерам (франшизе, а не библиотеке!): + +```python +text = """ +Generation One is a retroactive term for the Transformers characters that +appeared between 1984 and 1993. The Transformers began with the 1980s Japanese +toy lines Micro Change and Diaclone. They presented robots able to transform +into everyday vehicles, electronic items or weapons. Hasbro bought the Micro +Change and Diaclone toys, and partnered with Takara. Marvel Comics was hired by +Hasbro to create the backstory; editor-in-chief Jim Shooter wrote an overall +story, and gave the task of creating the characthers to writer Dennis O'Neil. +Unhappy with O'Neil's work (although O'Neil created the name "Optimus Prime"), +Shooter chose Bob Budiansky to create the characters. + +The Transformers mecha were largely designed by Shōji Kawamori, the creator of +the Japanese mecha anime franchise Macross (which was adapted into the Robotech +franchise in North America). Kawamori came up with the idea of transforming +mechs while working on the Diaclone and Macross franchises in the early 1980s +(such as the VF-1 Valkyrie in Macross and Robotech), with his Diaclone mechs +later providing the basis for Transformers. + +The primary concept of Generation One is that the heroic Optimus Prime, the +villainous Megatron, and their finest soldiers crash land on pre-historic Earth +in the Ark and the Nemesis before awakening in 1985, Cybertron hurtling through +the Neutral zone as an effect of the war. The Marvel comic was originally part +of the main Marvel Universe, with appearances from Spider-Man and Nick Fury, +plus some cameos, as well as a visit to the Savage Land. + +The Transformers TV series began around the same time. Produced by Sunbow +Productions and Marvel Productions, later Hasbro Productions, from the start it +contradicted Budiansky's backstories. The TV series shows the Autobots looking +for new energy sources, and crash landing as the Decepticons attack. Marvel +interpreted the Autobots as destroying a rogue asteroid approaching Cybertron. +Shockwave is loyal to Megatron in the TV series, keeping Cybertron in a +stalemate during his absence, but in the comic book he attempts to take command +of the Decepticons. The TV series would also differ wildly from the origins +Budiansky had created for the Dinobots, the Decepticon turned Autobot Jetfire +(known as Skyfire on TV), the Constructicons (who combine to form +Devastator),[19][20] and Omega Supreme. The Marvel comic establishes early on +that Prime wields the Creation Matrix, which gives life to machines. In the +second season, the two-part episode The Key to Vector Sigma introduced the +ancient Vector Sigma computer, which served the same original purpose as the +Creation Matrix (giving life to Transformers), and its guardian Alpha Trion. +""" + +inputs = tokenizer(text, return_tensors="pt") +logits = model(**inputs).logits +``` + +```python output +IndexError: index out of range in self +``` + +О-о, мы столкнулись с проблемой - и сообщение об ошибке гораздо более загадочно, чем те, что мы видели в [разделе 2](2)! Мы не можем разобраться в полном её описании, поэтому решили обратиться за помощью на форум Hugging Face. Как мы можем создать тему? + +Чтобы начать, нам нужно нажать кнопку "Новая тема" в правом верхнем углу (обратите внимание, что для создания темы нам нужно войти в систему): + +
+Creating a new forum topic. +
+ +Откроется интерфейс для написания текста, где мы можем ввести название темы, выбрать категорию и составить сообщение: + +
+The interface for creating a forum topic. +
+ +Поскольку ошибка, похоже, связана исключительно с 🤗 Transformers, мы выберем эту категорию. Наша первая попытка объяснить проблему может выглядеть примерно так: + +
+Drafting the content for a new forum topic. +
+ +Хотя эта тема содержит сообщение об ошибке, с которой нам нужна помощь, есть несколько проблем с тем, как оно написано: + +1. Заголовок не очень содержателен, поэтому любой, кто просматривает форум, не сможет понять, о чем идет речь, не прочитав и основной текст. +2. В тексте недостаточно информации о том, откуда берется ошибка и как ее воспроизвести. +3. В теме прямо указывается несколько человек, причем в несколько требовательном тоне. + +Такие темы, как эта, вряд ли получат быстрый ответ (если вообще получат), так что давайте посмотрим, как можно их улучшить. Начнем с первого вопроса - выбора хорошего названия. + +### Выберите содержательное название[[choosing-a-descriptive-title]] + +Если вы пытаетесь получить помощь по поводу ошибки в вашем коде, хорошим правилом является включение достаточного количества информации в заголовок, чтобы другие могли быстро определить, смогут ли они ответить на ваш вопрос или нет. В нашем примере мы знаем имя возникающего исключения и имеем некоторые намеки на то, что оно срабатывает в прямом проходе модели, где мы вызываем `model(**inputs)`. Чтобы сообщить об этом, один из возможных вариантов заголовка может быть таким: + +> Source of IndexError in the AutoModel forward pass? + +Этот заголовок сообщает читателю, откуда, по вашему мнению, исходит ошибка, и если он уже сталкивался с `IndexError`, велика вероятность, что он поймет, как ее отладить. Конечно, заголовок может быть любым, какой вы захотите, и возможны другие варианты, например: + +> Why does my model produce an IndexError? + +также может подойти. Теперь, когда у нас есть описательный заголовок, давайте посмотрим, как улучшить само описание ошибки. + +### Отформатируйте код[[formatting-your-code-snippets]] + +Читать исходный код в IDE достаточно сложно, но еще сложнее, когда код скопирован и вставлен в виде обычного текста! К счастью, форумы Hugging Face поддерживают использование Markdown, поэтому вы всегда должны заключать свои блоки кода в три обратных знака (```), чтобы их было легче читать. Давайте сделаем это, чтобы отформатировать сообщение об ошибке - и пока мы это делаем, давайте сделаем текст немного более вежливым, чем наша первоначальная версия: + +
+Our revised forum topic, with proper code formatting. +
+ +Как видно на скриншоте, заключение блоков кода в обратные кавычки превращает исходный текст в отформатированный код, дополненный цветовой стилизацией! Также обратите внимание, что одиночные обратные кавычки могут быть использованы для форматирования встроенных переменных, как мы это сделали для `distilbert-base-uncased`. В таком оформлении сообщение выглядит гораздо лучше, и, если повезет, мы сможем найти кого-то из сообщества, кто догадается, в чем ошибка. Но вместо того, чтобы полагаться на удачу, давайте облегчим себе жизнь, включив трассировку во всех подробностях! + +### Добавьте текст полной трассировки[[including-the-full-traceback]] + +Как видно на скриншоте, заключение блоков кода в обратные знаки превращает исходный текст в отформатированный код, дополненный цветовой стилизацией! Также обратите внимание, что одиночные обратные знаки могут быть использованы для форматирования встроенных переменных, как мы это сделали для `distilbert-base-uncased`. Эта тема выглядит гораздо лучше, и, если повезет, мы сможем найти кого-то из сообщества, кто догадается, в чем ошибка. Однако вместо того, чтобы полагаться на удачу, давайте облегчим себе жизнь, включив в трассировку все подробности! + +
+Our example forum topic, with the complete traceback. +
+ +Это гораздо более информативно, и внимательный читатель сможет указать на то, что проблема, похоже, связана с передачей длинного входного сигнала из-за этой строки в трассировке: + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +Однако мы можем еще больше облегчить им задачу, предоставив фактический код, вызвавший ошибку. Давайте сделаем это прямо сейчас. + +### Предоставьте воспроизводимый пример[[providing-a-reproducible-example]] + +Если вы когда-нибудь пытались отладить чужой код, вы, вероятно, сначала пытались воссоздать проблему, о которой они сообщали, чтобы начать работать над трассировкой, чтобы точно определить ошибку. Это не отличается от того, когда речь идет о получении (или предоставлении) помощи на форумах, поэтому очень помогает, если вы можете предоставить небольшой пример, воспроизводящий ошибку. В половине случаев простое выполнение этого упражнения поможет вам понять, что происходит не так. В любом случае, недостающий фрагмент нашего примера - это показать _входы_, которые мы предоставили модели. Выполнив это, мы получим нечто похожее на следующий завершенный пример: + +
+The final version of our forum topic. +
+ +Теперь эта сообщение содержит достаточно много информации, и оно написана таким образом, что с гораздо большей вероятностью привлечет внимание сообщества и получит полезный ответ. Используя эти основные правила, вы сможете создавать отличные темы для поиска ответов на ваши вопросы по библиотеке 🤗 Transformers! + diff --git a/chapters/ru/chapter8/4.mdx b/chapters/ru/chapter8/4.mdx new file mode 100644 index 000000000..2141a5afe --- /dev/null +++ b/chapters/ru/chapter8/4.mdx @@ -0,0 +1,791 @@ + + +# Отладка обучения[[debugging-the-training-pipeline]] + + + +Вы написали прекрасный сценарий для обучения или дообучения модели на заданной задаче, послушно следуя советам из [Главы 7](/course/chapter7/1). Но когда вы запускаете команду `model.fit()`, происходит нечто ужасное: вы получаете ошибку 😱! Или, что еще хуже, все вроде бы хорошо, обучение проходит без ошибок, но результирующая модель получается плохой. В этом разделе мы покажем вам, что можно сделать для отладки подобных проблем. + +## Отладка обучающего пайплайна[[debugging-the-training-pipeline]] + + + +Проблема, когда вы сталкиваетесь с ошибкой в `trainer.train()`, заключается в том, что она может возникнуть из нескольких источников, поскольку `Trainer` обычно собирает вместе множество вещей. Он преобразует наборы данных в загрузчики данных, поэтому проблема может заключаться в том, что в вашем наборе данных что-то не так, или в том, что вы пытаетесь объединить элементы наборов данных в батч. Затем он берет батч данных и подает его в модель, так что проблема может быть в коде модели. После этого вычисляются градиенты и выполняется этап оптимизации, так что проблема может быть и в вашем оптимизаторе. И даже если во время обучения все идет хорошо, во время валидации все равно что-то может пойти не так, если проблема в метрике. + +Лучший способ отладить ошибку, возникшую в `trainer.train()`, - это вручную пройти весь пайплайн и посмотреть, где все пошло не так. В этом случае ошибку часто очень легко устранить. + +Чтобы продемонстрировать это, мы используем следующий скрипт, который (пытается) точно настроить модель DistilBERT на наборе данных [MNLI dataset](https://huggingface.co/datasets/glue): + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +trainer = Trainer( + model, + args, + train_dataset=raw_datasets["train"], + eval_dataset=raw_datasets["validation_matched"], + compute_metrics=compute_metrics, +) +trainer.train() +``` + +Если вы попытаетесь выполнить его, то столкнетесь с довольно загадочной ошибкой: + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Проверка данных[[check-your-data]] + +Это может быть очевидно, но если ваши данные повреждены, `Trainer` не сможет сформировать батчи, не говоря уже об обучении вашей модели. Поэтому прежде всего необходимо посмотреть, что находится в вашем обучающем наборе. + +Чтобы избежать бесчисленных часов, потраченных на попытки исправить то, что не является источником ошибки, мы рекомендуем использовать `trainer.train_dataset` для проверок и ничего больше. Так что давайте сделаем это здесь: + +```py +trainer.train_dataset[0] +``` + +```python out +{'hypothesis': 'Product and geography are what make cream skimming work. ', + 'idx': 0, + 'label': 1, + 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'} +``` + +Вы заметили что-то неладное? В сочетании с сообщением об ошибке `input_ids`, вы должны понять, что это тексты, а не числа, которые модель может понять. Здесь исходная ошибка вводит в заблуждение, потому что `Trainer` автоматически удаляет столбцы, которые не соответствуют сигнатуре модели (то есть аргументам, ожидаемым моделью). Это означает, что здесь было удалено все, кроме меток. Таким образом, не было никаких проблем с созданием батчей и отправкой их модели, которая, в свою очередь, жаловалась, что не получила нужных входных данных. + +Почему данные не обрабатывались? Мы действительно использовали метод `Dataset.map()` для наборов данных, чтобы применить токенизатор к каждой выборке. Но если вы внимательно посмотрите на код, то увидите, что мы допустили ошибку при передаче обучающего и валидационного наборов в `Trainer`. Вместо того чтобы использовать `tokenized_datasets`, мы использовали `raw_datasets` 🤦. Так что давайте исправим это! + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, +) +trainer.train() +``` + +Теперь этот новый код будет выдавать другую ошибку (прогресс!): + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +Посмотрев на трассировку, мы видим, что ошибка происходит на этапе сборки данных: + +```python out +~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) + 105 batch[k] = torch.stack([f[k] for f in features]) + 106 else: +--> 107 batch[k] = torch.tensor([f[k] for f in features]) + 108 + 109 return batch +``` + +Поэтому нам следует перейти к этому. Однако перед этим давайте закончим проверку наших данных, чтобы быть на 100% уверенными в их правильности. + +При отладке обучения всегда нужно смотреть на декодированные входы модели. Мы не можем понять смысл чисел, которые подаем ей напрямую, поэтому мы должны посмотреть, что эти числа представляют. В компьютерном зрении, например, это означает просмотр декодированных изображений пройденных пикселей, в речи - прослушивание декодированных образцов звука, а в нашем примере с NLP - использование нашего токенизатора для декодирования входных данных: + +```py +tokenizer.decode(trainer.train_dataset[0]["input_ids"]) +``` + +```python out +'[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]' +``` + +Так что, похоже, все правильно. Вы должны сделать это для всех ключей во входах: + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Обратите внимание, что ключи, не соответствующие входам, принимаемым моделью, будут автоматически отброшены, поэтому здесь мы оставим только `input_ids`, `attention_mask` и `label` (которая будет переименована в `labels`). Чтобы перепроверить сигнатуру модели, вы можете вывести класс вашей модели, а затем проверить ее документацию: + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Итак, в нашем случае мы можем проверить принятые параметры на [этой странице](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). "Trainer" также будет регистрировать столбцы, которые он отбрасывает. + +Мы проверили правильность входных идентификаторов, декодировав их. Далее находится `attention_mask`: + +```py +trainer.train_dataset[0]["attention_mask"] +``` + +```python out +[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] +``` + +Так как мы не применяли в препроцессинге дополнение нулями, это кажется совершенно естественным. Чтобы убедиться в отсутствии проблем с этой маской внимания, давайте проверим, что она имеет ту же длину, что и наши входные идентификаторы: + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +Это хорошо! И наконец, давайте проверим нашу метку класса: + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Как и входные идентификаторы, это число, которое само по себе не имеет смысла. Как мы уже видели, соответствие между целыми числами и именами меток хранится в атрибуте `names` соответствующей *функции* набора данных: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Итак, `1` означает `нейтральный`, а значит, два предложения, которые мы видели выше, не противоречат друг другу, и из первого не следует второе. Это кажется правильным! + +Здесь у нас нет идентификаторов типов токенов, поскольку DistilBERT их не ожидает; если в вашей модели они есть, вам также следует убедиться, что они правильно соответствуют месту первого и второго предложений во входных данных. + + + +✏️ **Ваша очередь!** Проверьте, все ли правильно со вторым элементом обучающего набора данных. + + + +В данном случае мы проверяем только обучающий набор, но, конечно, вы должны дважды проверить валидационный и тестовый наборы таким же образом. + +Теперь, когда мы знаем, что наши наборы данных выглядят хорошо, пришло время проверить следующий этап пайплайна обучения. + +### От датасетов к загрузчикам данных[[from-datasets-to-dataloaders]] + +Следующее, что может пойти не так в пайплайне обучения, - это когда `Trainer` пытается сформировать батчи из обучающего или проверочного набора. Убедившись, что наборы данных `Trainer` корректны, можно попробовать вручную сформировать батч, выполнив следующие действия (замените `train` на `eval` для валидационного загрузчика данных): + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Этот код создает загрузчик данных для обучения, затем выполняет итерации по нему, останавливаясь на первой итерации. Если код выполняется без ошибок, у вас есть первый обучающий батч, который можно проверить, а если код выдает ошибку, вы точно знаете, что проблема в загрузчике данных, как в данном случае: + +```python out +~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) + 105 batch[k] = torch.stack([f[k] for f in features]) + 106 else: +--> 107 batch[k] = torch.tensor([f[k] for f in features]) + 108 + 109 return batch + +ValueError: expected sequence of length 45 at dim 1 (got 76) +``` + +Осмотра последнего кадра трассировки должно быть достаточно, чтобы дать вам подсказку, но давайте покопаемся еще немного. Большинство проблем при создании батчей возникает из-за объединения примеров в один батч, поэтому первое, что нужно проверить при возникновении сомнений, это то, какой `collate_fn` использует ваш `DataLoader`: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +Этот `default_data_collator` не то, что нам нужно в данном случае. Мы хотим разбить наши примеры на самые длинные предложения в пакете, что делает `DataCollatorWithPadding`. И этот класс данных должен использоваться по умолчанию в `Trainer`, так почему же он не используется здесь? + +Ответ заключается в том, что мы не передали `tokenizer` в `Trainer`, поэтому он не смог создать нужный нам `DataCollatorWithPadding`. На практике вам никогда не следует стесняться явно передавать data collator, который вы хотите использовать, чтобы избежать подобных ошибок. Давайте адаптируем наш код, чтобы сделать именно это: + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +trainer.train() +``` + +Хорошие новости? Мы не получаем ту же ошибку, что и раньше, что, безусловно, является прогрессом. Плохие новости? Вместо нее мы получаем печально известную ошибку CUDA: + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +Это плохо, потому что ошибки CUDA вообще очень трудно отлаживать. Через минуту мы увидим, как решить эту проблему, но сначала давайте закончим анализ создания батчей. + +Если вы уверены, что ваш data collator правильный, попробуйте применить его на паре примеров вашего набора данных: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Этот код не сработает, потому что `train_dataset` содержит строковые колонки, которые `Trainer` обычно удаляет. Вы можете удалить их вручную, или, если вы хотите в точности повторить то, что `Trainer` делает за кулисами: нужно вызвать приватный метод `Trainer._remove_unused_columns()`, который делает это: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +actual_train_set = trainer._remove_unused_columns(trainer.train_dataset) +batch = data_collator([actual_train_set[i] for i in range(4)]) +``` + +Если ошибка не исчезнет, вы сможете вручную отладить, что происходит внутри data collator. + +Теперь, когда мы отладили процесс создания батча, пришло время пропустить его через модель! + +### Проверьте данные[[going-through-the-model]] + +Вы должны иметь возможность получить батч, выполнив следующую команду: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Если вы выполняете этот код в ноутбуке, вы можете получить ошибку CUDA, похожую на ту, что мы видели ранее, и в этом случае вам нужно перезапустить ноутбук и заново выполнить последний фрагмент без строки `trainer.train()`. Это вторая самая неприятная вещь в ошибках CUDA: они безвозвратно ломают ваше ядро. Самое неприятное в них то, что их трудно отлаживать. + +Почему? Это связано с тем, как работают графические процессоры. Они чрезвычайно эффективны при параллельном выполнении множества операций, но их недостаток в том, что когда одна из этих инструкций приводит к ошибке, вы не сразу об этом узнаете. Только когда программа вызовет синхронизацию нескольких процессов на GPU, она поймет, что что-то пошло не так, поэтому ошибка возникает в том месте, которое не имеет никакого отношения к тому, что ее создало. Например, если мы посмотрим на наш предыдущий трассировочный откат, ошибка была вызвана во время обратного прохода, но через минуту мы увидим, что на самом деле она возникла из-за чего-то в прямом проходе через модель. + +Так как же отладить эти ошибки? Ответ прост: никак. Если только ошибка CUDA не является ошибкой вне памяти (что означает, что в вашем GPU недостаточно памяти), вы всегда должны возвращаться к CPU, чтобы отладить ее. + +Чтобы сделать это в нашем случае, нам просто нужно вернуть модель на CPU и вызвать ее на нашем пакете - пакет, возвращаемый `DataLoader`, еще не был перемещен на GPU: + +```python +outputs = trainer.model.cpu()(**batch) +``` + +```python out +~/.pyenv/versions/3.7.9/envs/base/lib/python3.7/site-packages/torch/nn/functional.py in nll_loss(input, target, weight, size_average, ignore_index, reduce, reduction) + 2386 ) + 2387 if dim == 2: +-> 2388 ret = torch._C._nn.nll_loss(input, target, weight, _Reduction.get_enum(reduction), ignore_index) + 2389 elif dim == 4: + 2390 ret = torch._C._nn.nll_loss2d(input, target, weight, _Reduction.get_enum(reduction), ignore_index) + +IndexError: Target 2 is out of bounds. +``` + +Итак, картина проясняется. Вместо ошибки CUDA у нас теперь `IndexError` при вычислении функции потерь (так что обратный проход, как мы уже говорили, здесь ни при чем). Точнее, мы видим, что ошибка возникает именно в метке класса 2, так что это очень хороший момент для проверки количества меток нашей модели: + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +При двух метках в качестве значений допускаются только 0 и 1, но, согласно сообщению об ошибке, мы получили 2. Получение 2 на самом деле нормально: если мы помним имена меток, которые мы извлекли ранее, их было три, поэтому в нашем наборе данных есть индексы 0, 1 и 2. Проблема в том, что мы не сообщили об этом нашей модели, которая должна была быть создана с тремя метками. Так что давайте это исправим! + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +``` + +Мы пока не включаем строку `trainer.train()`, чтобы потратить время на проверку того, что все выглядит хорошо. Если мы запросим батч и передадим ее нашей модели, то теперь она работает без ошибок! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +Следующим шагом будет возвращение к графическому процессору и проверка того, что все по-прежнему работает: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +batch = {k: v.to(device) for k, v in batch.items()} + +outputs = trainer.model.to(device)(**batch) +``` + +Если вы все еще получаете ошибку, убедитесь, что перезагрузили ноутбук и выполнили только последнюю версию скрипта. + +### Выполнение одного шага оптиимзации[[performing-one-optimization-step]] + +Теперь, когда мы знаем, что можем создавать батчи, которые действительно проходят через модель без ошибок, мы готовы к следующему шагу пайплайна обучения: вычислению градиентов и выполнению шага оптимизации. + +Первая часть заключается в вызове метода `backward()` для функции потерь: + +```py +loss = outputs.loss +loss.backward() +``` + +Ошибки на этом этапе возникают довольно редко, но если они все же возникнут, обязательно вернитесь к процессору, чтобы получить полезное сообщение об ошибке. + +Чтобы выполнить шаг оптимизации, нам нужно просто создать `optimizer` и вызвать его метод `step()`: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Опять же, если вы используете оптимизатор по умолчанию в `Trainer`, вы не должны получить ошибку на этом этапе, но если вы используете собственный оптимизатор, здесь могут возникнуть некоторые проблемы для отладки. Не забудьте вернуться к процессору, если на этом этапе вы получите странную ошибку CUDA. Говоря об ошибках CUDA, ранее мы упоминали особый случай. Давайте посмотрим на него сейчас. + +### Как справиться с ошибками нехватки памяти[[dealing-with-cuda-out-of-memory-errors]] + +Если вы получаете сообщение об ошибке, начинающееся с `RuntimeError: CUDA out of memory`, это означает, что вам не хватает памяти GPU. Это не связано напрямую с вашим кодом, и может произойти со скриптом, который работает совершенно нормально. Эта ошибка означает, что вы пытались поместить слишком много данных во внутреннюю память вашего GPU, и это привело к ошибке. Как и в случае с другими ошибками CUDA, вам придется перезапустить ядро, чтобы снова запустить обучение. + +Чтобы решить эту проблему, нужно просто использовать меньше памяти на GPU - что зачастую легче сказать, чем сделать. Во-первых, убедитесь, что у вас нет двух моделей на GPU одновременно (если, конечно, это не требуется для решения вашей задачи). Затем, вероятно, следует уменьшить размер батча, поскольку он напрямую влияет на размеры всех промежуточных выходов модели и их градиентов. Если проблема сохраняется, подумайте о том, чтобы использовать меньшую версию модели. + + + +В следующей части курса мы рассмотрим более продвинутые техники, которые помогут вам уменьшить объем занимаемой памяти и позволят точно настроить самые большие модели. + + + +### Валидация модели[[evaluating-the-model]] + +Теперь, когда мы решили все проблемы с нашим кодом, все идеально, и обучение должно пройти гладко, верно? Не так быстро! Если вы запустите команду `trainer.train()`, сначала все будет выглядеть хорошо, но через некоторое время вы получите следующее: + +```py +# Это займет много времени и приведет к ошибке, поэтому не стоит запускать эту ячейку +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Вы поймете, что эта ошибка появляется во время фазы валидации, так что это последнее, что нам нужно будет отладить. + +Вы можете запустить цикл оценки `Trainer` независимо от обучения следующим образом: + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Перед запуском `trainer.train()` всегда следует убедиться, что вы можете запустить `trainer.evaluate()`, чтобы не тратить много вычислительных ресурсов до того, как столкнетесь с ошибкой. + + + +Прежде чем пытаться отладить проблему в цикле валидации, нужно сначала убедиться, что вы посмотрели на данные, смогли правильно сформировать батч и запустить на нем свою модель. Мы выполнили все эти шаги, поэтому следующий код может быть выполнен без ошибок: + +```py +for batch in trainer.get_eval_dataloader(): + break + +batch = {k: v.to(device) for k, v in batch.items()} + +with torch.no_grad(): + outputs = trainer.model(**batch) +``` + +Ошибка возникает позже, в конце фазы валидации, и если мы посмотрим на трассировку, то увидим следующее: + +```python trace +~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references) + 431 """ + 432 batch = {"predictions": predictions, "references": references} +--> 433 batch = self.info.features.encode_batch(batch) + 434 if self.writer is None: + 435 self._init_writer() +``` + +Это говорит нам о том, что ошибка возникает в модуле `datasets/metric.py` - так что это проблема с нашей функцией `compute_metrics()`. Она принимает кортеж с логитами и метками в виде массивов NumPy, так что давайте попробуем скормить ей это: + +```py +predictions = outputs.logits.cpu().numpy() +labels = batch["labels"].cpu().numpy() + +compute_metrics((predictions, labels)) +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Мы получаем ту же ошибку, так что проблема определенно кроется в этой функции. Если мы посмотрим на ее код, то увидим, что она просто передает `predictions` и `labels` в `metric.compute()`. Так есть ли проблема в этом методе? На самом деле нет. Давайте посмотрим на размерности: + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Наши предсказания все еще являются логитами, а не реальными предсказаниями, поэтому метрика возвращает эту (несколько непонятную) ошибку. Исправить это довольно просто: нужно просто добавить argmax в функцию `compute_metrics()`: + +```py +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + predictions = np.argmax(predictions, axis=1) + return metric.compute(predictions=predictions, references=labels) + + +compute_metrics((predictions, labels)) +``` + +```python out +{'accuracy': 0.625} +``` + +Теперь наша ошибка исправлена! Она была последней, поэтому теперь наш скрипт будет правильно обучать модель. + +Для справки, вот полностью исправленный скрипт: + +```py +import numpy as np +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + AutoModelForSequenceClassification, + DataCollatorWithPadding, + TrainingArguments, + Trainer, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) +model = AutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) + +args = TrainingArguments( + f"distilbert-finetuned-mnli", + evaluation_strategy="epoch", + save_strategy="epoch", + learning_rate=2e-5, + num_train_epochs=3, + weight_decay=0.01, +) + +metric = evaluate.load("glue", "mnli") + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + predictions = np.argmax(predictions, axis=1) + return metric.compute(predictions=predictions, references=labels) + + +data_collator = DataCollatorWithPadding(tokenizer=tokenizer) + +trainer = Trainer( + model, + args, + train_dataset=tokenized_datasets["train"], + eval_dataset=tokenized_datasets["validation_matched"], + compute_metrics=compute_metrics, + data_collator=data_collator, + tokenizer=tokenizer, +) +trainer.train() +``` + +В этом случае проблем больше нет, и наш скрипт обучит модель, которая должна дать приемлемые результаты. Но что делать, если обучение проходит без ошибок, а обученная модель совсем не работает? Это самая сложная часть машинного обучения, и мы покажем вам несколько приемов, которые могут помочь. + + + +💡 Если вы используете ручной цикл обучения, для отладки пайплайна обучения применимы те же шаги, но их проще разделить. Убедитесь, что вы не забыли `model.eval()` или `model.train()` в нужных местах, или `zero_grad()` на каждом шаге! + + +## Отладка скрытых ошибок во время обучения[[debugging-silent-errors-during-training]] + +Что можно сделать, чтобы отладить обучение, которое завершается без ошибок, но не дает хороших результатов? Мы дадим вам несколько советов, но имейте в виду, что такая отладка - самая сложная часть машинного обучения, и волшебного ответа на этот вопрос не существует. + +### Проверьте свои данные (еще раз!)[[check-your-data-again]] + +Ваша модель научится чему-то только в том случае, если из ваших данных действительно можно чему-то научиться. Если в данных есть ошибка, которая портит их, или метки приписаны случайным образом, то, скорее всего, вы не сможете обучить модель на своем наборе данных. Поэтому всегда начинайте с перепроверки декодированных входных данных и меток и задавайте себе следующие вопросы: + +- Понятны ли декодированные данные? +- Правильные ли метки? +- Есть ли одна метка, которая встречается чаще других? +- Каким должно быть значение функции потерь/метрики если модель предсказала случайный ответ/всегда один и тот же ответ? + + + +⚠️ Если вы проводите распределенное обучение, распечатайте образцы набора данных в каждом процессе и трижды проверьте, что вы получаете одно и то же. Одна из распространенных ошибок - наличие некоторого источника случайности при создании данных, из-за которого каждый процесс имеет свою версию набора данных. + + + +Просмотрев данные, проанализируйте несколько предсказаний модели и декодируйте их. Если модель постоянно предсказывает одно и то же, это может быть связано с тем, что ваш набор данных смещен в сторону одной категории (для проблем классификации); здесь могут помочь такие методы, как oversampling редких классов. + +Если значения функции потерь/метрики, которые вы получаете на начальной модели, сильно отличаются от значений функции потерь/метрик, которые можно было бы ожидать для случайных предсказаний, перепроверьте способ вычисления потерь или метрик, так как, возможно, в них есть ошибка. Если вы используете несколько функций потерь, убедитесь, что они имеют одинаковый масштаб. + +Когда вы убедитесь, что ваши данные идеальны, вы можете проверить, способна ли модель обучаться на них, с помощью одного простого теста. + +### Переобучение модели на одном батче[[overfit-your-model-on-one-batch]] + +Обычно мы стараемся избегать переобучения при тренировке модели, поскольку это означает, что модель не учится распознавать общие характеристики, а просто запоминает обучающие выборки. Однако попытка обучить модель на одной выборке снова и снова - это хороший тест, позволяющий проверить, может ли проблема в том виде, в котором вы ее сформулировали, быть решена с помощью модели, которую вы пытаетесь обучить. Это также поможет вам понять, не слишком ли высока ваша начальная скорость обучения. + +Сделать это после того, как вы определили свой `Trainer`, очень просто: просто возьмите батч обучающих данных, а затем запустите небольшой цикл ручного обучения, используя только этот батч в течение примерно 20 шагов: + +```py +for batch in trainer.get_train_dataloader(): + break + +batch = {k: v.to(device) for k, v in batch.items()} +trainer.create_optimizer() + +for _ in range(20): + outputs = trainer.model(**batch) + loss = outputs.loss + loss.backward() + trainer.optimizer.step() + trainer.optimizer.zero_grad() +``` + + + +💡 Если ваши обучающие данные несбалансированы, обязательно создайте батч обучающих данных, содержащий все метки. + + + +Результирующая модель должна иметь близкие к идеальным результаты на одном и том же батче. Вычислим метрику по полученным предсказаниям: + +```py +with torch.no_grad(): + outputs = trainer.model(**batch) +preds = outputs.logits +labels = batch["labels"] + +compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) +``` + +```python out +{'accuracy': 1.0} +``` + +Точность 100 %, вот это хороший пример переобучения (это значит, что если вы попробуете использовать модель на любом другом предложении, она, скорее всего, даст вам неправильный ответ)! + +Если вам не удается добиться от модели таких идеальных результатов, значит, что-то не так с постановкой задачи или данными, и вам следует это исправить. Только когда вам удастся пройти тест на переобучение, вы сможете быть уверены, что ваша модель действительно способна чему-то научиться. + + + +⚠️ Вам придется пересоздать модель и `Trainer` после этого теста на переобучение, поскольку полученная модель, вероятно, не сможет восстановиться и научиться чему-то полезному на полном наборе данных. + + + +### Не обучайте ничего, пока не получите первый бейзлайн.[[dont-tune-anything-until-you-have-a-first-baseline]] + +Настройка гиперпараметров всегда считается самой сложной частью машинного обучения, но это всего лишь последний шаг, который поможет вам немного улучшить метрику. В большинстве случаев гиперпараметры по умолчанию `Trainer` будут работать нормально и давать вам хорошие результаты, поэтому не приступайте к трудоемкому и дорогостоящему поиску гиперпараметров, пока у вас не будет чего-то, что превосходит базовый уровень, который у вас есть в вашем наборе данных. + +Как только у вас будет достаточно хорошая модель, вы можете начать ее немного оптимизировать. Не пытайтесь запустить тысячу раз с разными гиперпараметрами, но сравните пару запусков с разными значениями одного гиперпараметра, чтобы получить представление о том, какой из них оказывает наибольшее влияние. + +Если вы настраиваете саму модель, будьте проще и не пробуйте то, что не можете обосновать. Всегда возвращайтесь к тесту на перебор, чтобы проверить, не привело ли ваше изменение к каким-либо непредвиденным последствиям. + +### Попросите о помощи[[ask-for-help]] + +Надеемся, вы нашли в этом разделе советы, которые помогли вам решить вашу проблему, но если это не так, помните, что вы всегда можете спросить у сообщества на [форумах](https://discuss.huggingface.co/). + +Вот некоторые дополнительные ресурсы, которые могут оказаться полезными: + +- [" Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) by Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) by Cecelia Shao +- [" How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- [""A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) by Andrej Karpathy + +Конечно, не все проблемы, с которыми вы сталкиваетесь при обучении нейросетей, возникают по вашей вине! Если в библиотеке 🤗 Transformers или 🤗 Datasets вы столкнулись с чем-то, что кажется вам неправильным, возможно, вы обнаружили ошибку. Вам обязательно нужно рассказать нам об этом, и в следующем разделе мы объясним, как именно это сделать. diff --git a/chapters/ru/chapter8/4_tf.mdx b/chapters/ru/chapter8/4_tf.mdx new file mode 100644 index 000000000..c88ce58e1 --- /dev/null +++ b/chapters/ru/chapter8/4_tf.mdx @@ -0,0 +1,486 @@ + + +# Отладка обучения[[debugging-the-training-pipeline]] + + + +Вы написали прекрасный сценарий для обучения или дообучения модели на заданной задаче, послушно следуя советам из [Главы 7](/course/chapter7). Но когда вы запускаете команду `model.fit()`, происходит нечто ужасное: вы получаете ошибку 😱! Или, что еще хуже, все вроде бы хорошо, обучение проходит без ошибок, но результирующая модель получается плохой. В этом разделе мы покажем вам, что можно сделать для отладки подобных проблем. + +## Отладка обучающего пайплайна[[debugging-the-training-pipeline]] + + + +Проблема, когда вы сталкиваетесь с ошибкой в `model.fit()`, заключается в том, что она может возникнуть из нескольких источников, поскольку обучение обычно объединяет множество вещей, над которыми вы работали до этого момента. Проблема может заключаться в том, что в вашем наборе данных что-то не так, или в том, что вы пытаетесь объединить элементы наборов данных вместе. Или что-то не так в коде модели, функции потерь или оптимизаторе. И даже если при обучении все идет хорошо, во время оценки что-то может пойти не так, если возникнут проблемы с метрикой. + +Лучший способ отладить ошибку, возникшую в `model.fit()`, - это вручную пройти весь конвейер, чтобы увидеть, где что-то пошло не так. В этом случае ошибку часто очень легко устранить. + +Чтобы продемонстрировать это, мы используем следующий скрипт, который (пытается) дообучить модель DistilBERT на наборе данных [MNLI dataset](https://huggingface.co/datasets/glue): + +```py +from datasets import load_dataset +import evaluate +from transformers import ( + AutoTokenizer, + TFAutoModelForSequenceClassification, +) + +raw_datasets = load_dataset("glue", "mnli") + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) + + +def preprocess_function(examples): + return tokenizer(examples["premise"], examples["hypothesis"], truncation=True) + + +tokenized_datasets = raw_datasets.map(preprocess_function, batched=True) + +train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "labels"], batch_size=16, shuffle=True +) + +validation_dataset = tokenized_datasets["validation_matched"].to_tf_dataset( + columns=["input_ids", "labels"], batch_size=16, shuffle=True +) + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) + +model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") + +model.fit(train_dataset) +``` + +Если вы попытаетесь выполнить его, вы можете получить несколько `VisibleDeprecationWarning` при преобразовании набора данных - это известная нам проблема UX, так что, пожалуйста, игнорируйте ее. Если вы читаете курс после, скажем, ноября 2021 года, и это все еще происходит, то отправляйте гневные твиты @carrigmat, пока он не исправит это. + +Однако более серьезной проблемой является то, что мы получаем откровенную ошибку. И она действительно ужасающе длинная: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Что это значит? Мы пытались обучиться на наших данных, но не получили градиента? Это вызывает недоумение; как мы вообще можем начать отлаживать что-то подобное? Если полученная ошибка не позволяет сразу понять, в чем проблема, лучшим решением будет последовательно пройтись по всем пунктам, убеждаясь на каждом этапе, что все выглядит правильно. И, конечно, начинать всегда нужно с... + +### Проверка собственных данных[[check-your-data]] + +Это само собой разумеется, но если ваши данные повреждены, Keras не сможет исправить их за вас. Поэтому прежде всего нужно посмотреть, что находится в вашем обучающем наборе. + +Хотя заманчиво заглянуть в `raw_datasets` и `tokenized_datasets`, мы настоятельно рекомендуем обращаться к данным непосредственно в той точке, где они попадают в модель. Это означает, что надо посмотреть на выход из `tf.data.Dataset`, который вы создали с помощью функции `to_tf_dataset()`! Как же это сделать? Объекты `tf.data.Dataset` предоставляют нам целые батчи за раз и не поддерживают индексацию, поэтому мы не можем просто запросить `train_dataset[0]`. Однако мы можем вежливо попросить у него батч: + +```py +for batch in train_dataset: + break +``` + +`break` завершает цикл после одной итерации, поэтому мы берем первый батч, полученный из `train_dataset`, и сохраняем ее как `batch`. Теперь давайте посмотрим, что находится внутри: + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Все выглядит правильно, не так ли? Мы передаем модели `labels`, `attention_mask` и `input_ids`, этого достаточно для вычисления выходов и расчета функции потерь. Так почему же у нас нет градиента? Посмотрите внимательнее: мы передаем на вход один словарь, а обучающая партия обычно представляет собой входной тензор или словарь, плюс тензор меток. Наши метки - это просто ключ в нашем входном словаре. + +Является ли это проблемой? На самом деле, не всегда! Но это одна из самых распространенных проблем, с которыми вы столкнетесь при обучении трансформеров с помощью TensorFlow. Все наши модели могут вычислять потери внутри себя, но для этого необходимо передать метки во входной словарь. Именно это происходит, когда мы не указываем функцию потерь в `compile()`. С другой стороны, Keras обычно ожидает, что метки будут передаваться отдельно от входного словаря, и вычисление потерь обычно заканчивается неудачей, если этого не сделать. + +Теперь проблема стала яснее: мы передали аргумент `loss`, что означает, что мы просим Keras вычислить для нас функцию потерь, но мы передали наши метки как входные данные для модели, а не как метки в том месте, где их ожидает Keras! Нам нужно выбрать одно из двух: либо мы используем внутреннюю функцию потерь модели и оставляем метки на месте, либо мы продолжаем использовать функцию потерь Keras, но перемещаем метки в то место, где их ожидает Keras. Для простоты возьмем первый подход. Изменим вызов `compile()`: + +```py +model.compile(optimizer="adam") +``` + +Теперь мы будем использовать конкретную функцию потерь модели, и эта проблема должна быть решена! + + + +✏️ **Ваша очередь!** В качестве дополнительной задачи после решения других проблем вы можете попробовать вернуться к этому шагу и заставить модель работать с оригинальной функцией потерь, вычисленными Keras. Вам нужно будет добавить `"labels"` к аргументу `label_cols` в `to_tf_dataset()`, чтобы обеспечить корректный вывод меток, что позволит получить градиенты - но есть еще одна проблема с функцией потерь, которую мы указали. Обучение будет продолжаться и с этой проблемой, но обучение будет происходить очень медленно и застопорится на высоком уровне потерь при обучении. Можете ли вы понять, в чем дело? + +Подсказка в кодировке ROT13, если вы застряли: Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf? + +И вторая подсказка: Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf nyy gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf? + + + +Теперь попробуем провести обучение. Теперь мы должны получить градиенты, так что, надеюсь (здесь играет зловещая музыка), мы можем просто вызвать `model.fit()` и все будет работать отлично! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +О, нет. + +"nan" — не очень обнадеживающаее значение функции потерь. Тем не менее, мы проверили наши данные, и они выглядят довольно хорошо. Если проблема не в этом, как двигаться дальше? Очевидным следующим шагом будет... + +### Проверка модели[[check-your-model]] + +`model.fit()` - очень удобная функция в Keras, но она делает много вещей за вас, и это может затруднить поиск того, где именно возникла проблема. Если вы отлаживаете свою модель, одна из стратегий, которая может действительно помочь, - это передать модели только одну партию данных и подробно просмотреть выходные данные для этой одной партии. Еще один очень полезный совет, если модель выдает ошибки, - запустите `compile()` модели с `run_eagerly=True`. Это сделает ее намного медленнее, но сделает сообщения об ошибках гораздо более понятными, потому что они будут указывать, где именно в коде вашей модели возникла проблема. + +Впрочем, пока что `run_eagerly` нам не нужен. Давайте прогоним полученный ранее батч через модель и посмотрим, что получится на выходе: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Ну, это сложно. Все есть `nan`! Но это странно, не так ли? Как бы все наши логиты стали `nan`? `nan` означает "не число". Значения `nan` часто возникают, когда вы выполняете запрещенную операцию, например деление на ноль. Но в машинном обучении очень важно знать о `nan`: это значение имеет тенденцию к *распространению*. Если вы умножите число на `nan`, то на выходе также получится `nan`. И если вы получите `nan` где-нибудь в вашем выходе, потерях или градиенте, то это быстро распространится по всей вашей модели - потому что когда это `nan` значение распространяется обратно через вашу сеть, вы получите `nan` градиенты, а когда обновления веса вычисляются с этими градиентами, вы получите `nan` веса, а эти веса вычислят еще более `nan` выходы! Вскоре вся сеть будет представлять собой один большой блок `nan`. Когда это произойдет, будет довольно сложно понять, где началась проблема. Как мы можем определить, где `nan` впервые прокрался в сеть? + +Ответ заключается в том, чтобы попробовать *переинициализировать* нашу модель. Как только мы начали обучение, где-то появился `nan`, и он быстро распространился по всей модели. Итак, давайте загрузим модель из контрольной точки и не будем делать никаких обновлений весов, и посмотрим, где мы получим значение `nan`: + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +После запуска получим: + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Теперь* у нас что-то получается! В наших логарифмах нет значений `nan`, что обнадеживает. Но мы видим несколько `nan`-значений в наших потерях! Может быть, в этих образцах есть что-то особенное, что вызывает эту проблему? Давайте посмотрим, что это за выборки (обратите внимание, что если вы запустите этот код самостоятельно, то можете получить другие показатели, поскольку набор данных был перемешан): + +```python +import numpy as np + +loss = model(batch).loss.numpy() +indices = np.flatnonzero(np.isnan(loss)) +indices +``` + +```python out +array([ 1, 2, 5, 7, 9, 10, 11, 13, 14]) +``` + +Давайте посмотрим, какие индексы были у этих примеров: + +```python +input_ids = batch["input_ids"].numpy() +input_ids[indices] +``` + +```python out +array([[ 101, 2007, 2032, 2001, 1037, 16480, 3917, 2594, 4135, + 23212, 3070, 2214, 10170, 1010, 2012, 4356, 1997, 3183, + 6838, 12953, 2039, 2000, 1996, 6147, 1997, 2010, 2606, + 1012, 102, 6838, 2001, 3294, 6625, 3773, 1996, 2214, + 2158, 1012, 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], + [ 101, 1998, 6814, 2016, 2234, 2461, 2153, 1998, 13322, + 2009, 1012, 102, 2045, 1005, 1055, 2053, 3382, 2008, + 2016, 1005, 2222, 3046, 8103, 2075, 2009, 2153, 1012, + 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, 0], + [ 101, 1998, 2007, 1996, 3712, 4634, 1010, 2057, 8108, + 2025, 3404, 2028, 1012, 1996, 2616, 18449, 2125, 1999, + 1037, 9666, 1997, 4100, 8663, 11020, 6313, 2791, 1998, + 2431, 1011, 4301, 1012, 102, 2028, 1005, 1055, 5177, + 2110, 1998, 3977, 2000, 2832, 2106, 2025, 2689, 2104, + 2122, 6214, 1012, 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], + [ 101, 1045, 2001, 1999, 1037, 13090, 5948, 2007, 2048, + 2308, 2006, 2026, 5001, 2043, 2026, 2171, 2001, 2170, + 1012, 102, 1045, 2001, 3564, 1999, 2277, 1012, 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, 0, 0], + [ 101, 2195, 4279, 2191, 2039, 1996, 2181, 2124, 2004, + 1996, 2225, 7363, 1012, 102, 2045, 2003, 2069, 2028, + 2451, 1999, 1996, 2225, 7363, 1012, 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, + 0, 0, 0, 0], + [ 101, 2061, 2008, 1045, 2123, 1005, 1056, 2113, 2065, + 2009, 2428, 10654, 7347, 2030, 2009, 7126, 2256, 2495, + 2291, 102, 2009, 2003, 5094, 2256, 2495, 2291, 2035, + 2105, 1012, 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], + [ 101, 2051, 1010, 2029, 3216, 2019, 2503, 3444, 1010, + 6732, 1996, 2265, 2038, 19840, 2098, 2125, 9906, 1998, + 2003, 2770, 2041, 1997, 4784, 1012, 102, 2051, 6732, + 1996, 2265, 2003, 9525, 1998, 4569, 1012, 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], + [ 101, 1996, 10556, 2140, 11515, 2058, 1010, 2010, 2162, + 2252, 5689, 2013, 2010, 7223, 1012, 102, 2043, 1996, + 10556, 2140, 11515, 2058, 1010, 2010, 2252, 3062, 2000, + 1996, 2598, 1012, 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], + [ 101, 13543, 1999, 2049, 6143, 2933, 2443, 102, 2025, + 13543, 1999, 6143, 2933, 2003, 2443, 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, + 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0]]) +``` + +Здесь много всего, но ничто не выделяется. Давайте посмотрим на метки классов: + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +А! Все образцы `nan` имеют одну и ту же метку, и это метка 2. Это очень сильный намек. Тот факт, что мы получаем значение функции потерь `nan` только тогда, когда наша метка равна 2, говорит о том, что сейчас самое время проверить количество меток в нашей модели: + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Теперь мы видим проблему: модель считает, что существует только два класса, но метка бывает равна 2, что означает, что на самом деле существует три класса (потому что 0 - это тоже класс). Вот так мы и получили `nan` - пытаясь вычислить потери для несуществующего класса! Давайте попробуем изменить это и снова подогнать модель: + +``` +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) +model.compile(optimizer='adam') +model.fit(train_dataset) +``` + +```python out + 869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032 +``` + +Мы обучаемся! Больше никаких `nan`, и наши потери уменьшаются... вроде как. Если понаблюдать за этим некоторое время, можно начать испытывать некоторое нетерпение, потому что значение потерь остается упрямо высоким. Давайте остановим тренировку и попробуем подумать, в чем может быть причина этой проблемы. На данный момент мы уверены, что и данные, и модель в порядке, но наша модель плохо обучается. Что еще остается? Пришло время... + +### Проверка гиперпараметров[[check-your-hyperparameters]] + +Если вы посмотрите на приведенный выше код, то, возможно, вообще не увидите никаких гиперпараметров, кроме, возможно, `batch_size`, и это не кажется вероятной причиной. Однако не обманывайтесь: гиперпараметры есть всегда, и если вы их не видите, значит, вы просто не знаете, на что они настроены. В частности, запомните важную особенность Keras: если вы задаете функцию потерь, оптимизатора или активации с помощью строки, _все ее аргументы будут установлены в значения по умолчанию_. Это означает, что, несмотря на удобство использования такого способа, следует быть очень осторожным, так как это может легко скрыть от вас критические вещи. (Любой, кто попробует решить опциональную задачу, описанную выше, должен внимательно отнестись к этому факту). + +В данном случае где мы задали аргумент с помощью строки? Изначально мы так делали с функцией потерь, но мы это исправили. Теперь мы задаем оптимизатор с помощью строки. Может ли это что-то скрывать от нас? Давайте посмотрим на [его аргументы](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +Здесь что-нибудь выделяется? Правильно - скорость обучения! Когда мы просто используем строку `'adam'`, мы получим скорость обучения по умолчанию, которая составляет 0.001, или 1e-3. Это слишком много для модели трансформера! В целом, мы рекомендуем использовать для моделей скорость обучения от 1e-5 до 1e-4; это в 10-100 раз меньше, чем значение, которое мы используем в данном случае. Похоже, это может стать серьезной проблемой, так что давайте попробуем ее уменьшить. Для этого нам нужно импортировать настоящий объект `optimizer`. Пока мы это делаем, давайте заново инициализируем модель из контрольной точки, на случай если обучение с высокой скоростью обучения повредило ее веса: + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 Вы также можете импортировать функцию `create_optimizer()` из 🤗 Transformers, которая даст вам оптимизатор AdamW с правильным затуханием весов, а также прогревом и затуханием скорости обучения. Этот оптимизатор часто дает несколько лучшие результаты, чем оптимизатор Adam по умолчанию. + + + +Теперь мы можем попробовать подогнать модель под новую, улучшенную скорость обучения: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Теперь значения функции потерь действительно изменяются! Наконец-то обучение выглядит так, как будто оно работает. Здесь можно извлечь урок: если модель работает, но потери не уменьшаются, и вы уверены, что с данными все в порядке, стоит проверить гиперпараметры, такие как скорость обучения и затухание веса. Слишком высокое значение любого из них с большой вероятностью приведет к тому, что обучение "застопорится" при высоком значении потерь. + +## Другие потенциальные проблемы[[other-potential-issues]] + +Мы рассмотрели проблемы, описанные в скрипте выше, но есть еще несколько распространенных ошибок, с которыми вы можете столкнуться. Давайте рассмотрим (очень неполный) список. + +### Как справиться с ошибками нехватки памяти[[dealing-with-out-of-memory-errors]] + +Признаком нехватки памяти является ошибка типа "OOM when allocating tensor" - OOM - это сокращение от "out of memory". Это очень распространенная опасность при работе с большими языковыми моделями. Если вы столкнулись с этим, хорошая стратегия - уменьшить размер батча вдвое и попробовать снова. Однако имейте в виду, что некоторые модели *очень* велики. Например, полноразмерная модель GPT-2 имеет 1,5 млрд. параметров, что означает, что вам потребуется 6 Гб памяти только для хранения модели и еще 6 Гб для ее градиентов! Для обучения полной модели GPT-2 обычно требуется более 20 ГБ VRAM, независимо от размера батча, что есть лишь у некоторых GPU. Более легкие модели, такие как `distilbert-base-cased`, гораздо легче запускать, и они обучаются гораздо быстрее. + + + +В следующей части курса мы рассмотрим более продвинутые техники, которые помогут вам уменьшить объем занимаемой памяти и позволят дообучить самые большие модели. + + + +### "Голодный" TensorFlow 🦛[[hungry-hungry-tensorflow]] + +Одна особенность TensorFlow, о которой вам следует знать, заключается в том, что он выделяет *всю* память GPU под себя, как только вы загружаете модель или проводите обучение, а затем делит эту память по мере необходимости. Это отличается от поведения других фреймворков, например PyTorch, которые выделяют память по мере необходимости с помощью CUDA, а не делают это внутренне. Одним из преимуществ подхода TensorFlow является то, что он может часто выдавать полезные ошибки, когда у вас заканчивается память, и он может восстановиться из этого состояния без сбоя всего ядра CUDA. Но есть и важный недостаток: если вы запускаете два процесса TensorFlow одновременно, то **у вас будут проблемы**. + +Если вы работаете на Colab, то вам не нужно беспокоиться об этом, но если вы работаете локально, то это определенно то, с чем вам следует быть осторожным. В частности, имейте в виду, что закрытие вкладки ноутбука не обязательно приведет к его закрытию! Вам может понадобиться выбрать работающие блокноты (те, что с зеленым значком) и вручную закрыть их в списке каталогов. Любой запущенный блокнот, использующий TensorFlow, может по-прежнему занимать много памяти GPU, а это значит, что любой новый запущенный блокнот может столкнуться с очень странными проблемами. + +Если вы начинаете получать ошибки о CUDA, BLAS или cuBLAS в коде, который работал раньше, то очень часто причина кроется именно в этом. Вы можете использовать такую команду, как `nvidia-smi`, чтобы проверить - когда вы выключаете или перезапускаете текущий ноутбук, большая часть памяти свободна или она все еще используется? Если она все еще используется, значит, что-то еще держится за нее! + + +### Проверьте свои данные (еще раз!)[[check-your-data-again]] + +Ваша модель научится чему-то только в том случае, если из ваших данных действительно можно чему-то научиться. Если в данных есть ошибка, которая портит их, или метки приписываются случайным образом, то, скорее всего, вы не сможете обучить модель на своем наборе данных. Одним из полезных инструментов здесь является `tokenizer.decode()`. Он превратит `input_ids` обратно в строки, и вы сможете просмотреть данные и понять, обучают ли ваши тренировочные данные тому, чему вы хотите их обучить. Например, после получения `пакета` из `tf.data.Dataset`, как мы делали выше, вы можете декодировать первый элемент следующим образом: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Затем вы можете сравнить его с первой меткой, например, так: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Как только вы сможете просматривать данные в таком виде, вы сможете задать себе следующие вопросы: + +- Понятны ли декодированные данные? +- Правильные ли метки классов? +- Есть ли одна метка классов, которая встречается чаще других? +- Каким должно быть значение функции потерь/оценки, если модель предсказала случайный ответ/всегда один и тот же ответ? + +Просмотрев данные, проанализируйте несколько предсказаний модели - если модель выводит токены, попробуйте декодировать и их! Если модель всегда предсказывает одно и то же, это может быть связано с тем, что ваш набор данных смещен в сторону одной категории (для проблем классификации), поэтому такие методы, как oversampling редких классов, могут помочь. Кроме того, это может быть вызвано проблемами с обучением, например, неправильными настройками гиперпараметров. + +Если потери/метрики, которые вы получаете на начальной модели до обучения, сильно отличаются от потерь/метрик, ожидаемых для случайных прогнозов, перепроверьте способ вычисления функции потерь или метрик, так как, возможно, в них есть ошибка. Если вы используете несколько функций потерь, проверьте что они имеют одинаковый масштаб. + +Когда вы убедитесь, что ваши данные идеальны, вы можете проверить, способна ли модель обучаться на них, с помощью одного простого теста. + +### Переобучение модели на одном батче[[overfit-your-model-on-one-batch]] + +Обычно мы стараемся избегать (переобучения), поскольку это означает, что модель не учится распознавать общие характеристики, а просто запоминает обучающие выборки. Однако попытка обучить модель на одной выборке снова и снова - это хороший тест, позволяющий проверить, может ли проблема в том виде, в котором вы ее сформулировали, быть решена с помощью модели, которую вы пытаетесь обучить. Это также поможет вам понять, не слишком ли высока ваша начальная скорость обучения. + +Сделать это после того, как вы определили объект `model`, очень просто: просто возьмите батч обучающих данных, а затем рассматривайте этот `batch` как весь набор данных, подгоняя модель на большом количестве эпох: + +```py +for batch in train_dataset: + break + +# Убедитесь, что вы запустили model.compile() и установили свой оптимизатор, +# и ваши показатели потерь/метрики, если вы их используете + +model.fit(batch, epochs=20) +``` + + + +💡 Если ваши обучающие данные несбалансированы, обязательно создайте партию обучающих данных, содержащую все метки. + + + +Полученная модель должна иметь близкие к идеальным результаты для `батча`, значение функции потерь должно быстро уменьшаться до 0 (или минимальному значению для используемой вами функции потерь). + +Если вам не удается добиться идеальных результатов, значит, что-то не так с постановкой задачи или данными, и вам следует это исправить. Только когда вам удастся пройти тест на избыточную подгонку, вы сможете быть уверены, что ваша модель действительно способна чему-то научиться. + + + +⚠️ Вам придется пересоздать модель и перекомпилировать ее после этого теста на переобучение, поскольку полученная модель, вероятно, не сможет восстановиться и научиться чему-то полезному на полном наборе данных. + + + +### Не обучайте ничего, пока не получите первый бейзлайн.[[dont-tune-anything-until-you-have-a-first-baseline]] + +Интенсивная настройка гиперпараметров всегда подчеркивается как самая сложная часть машинного обучения, но это лишь последний шаг, который поможет вам немного продвинуться по метрике. *Очень* плохие значения гиперпараметров, например, использование стандартной скорости обучения Adam 1e-3 в модели Transformer, конечно, приведет к тому, что обучение будет идти очень медленно или полностью остановится, но в большинстве случаев "разумные" гиперпараметры, например, скорость обучения от 1e-5 до 5e-5, будут работать просто отлично и дадут вам хорошие результаты. Поэтому не начинайте трудоемкий и дорогостоящий поиск гиперпараметров до тех пор, пока не получите что-то, что превзойдет бейзлайн, имеющийся для вашего набора данных. + +Как только у вас будет достаточно хорошая модель, вы можете начать ее немного оптимизировать. Не пытайтесь запустить тысячу раз с разными гиперпараметрами, но сравните пару запусков с разными значениями одного гиперпараметра, чтобы получить представление о том, какой из них оказывает наибольшее влияние. + +Если вы настраиваете саму модель, будьте проще и не пробуйте то, что не можете обосновать. Всегда возвращайтесь к тесту на перебор, чтобы проверить, не привело ли ваше изменение к каким-либо непредвиденным последствиям. + +### Попросить о помощи[[ask-for-help]] + +Надеемся, вы нашли в этом разделе советы, которые помогли вам решить вашу проблему, но если это не так, помните, что вы всегда можете спросить у сообщества на [форумах](https://discuss.huggingface.co/). + +Вот некоторые дополнительные ресурсы, которые могут оказаться полезными: + +- [" Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) by Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) by Cecelia Shao +- [" How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) by Andrej Karpathy + +Конечно, не все проблемы, с которыми вы сталкиваетесь при обучении нейросетей, возникают по вашей вине! Если в библиотеке 🤗 Transformers или 🤗 Datasets вы столкнулись с чем-то, что кажется вам неправильным, возможно, вы обнаружили ошибку. Вам обязательно нужно рассказать нам об этом, и в следующем разделе мы объясним, как именно это сделать. diff --git a/chapters/ru/chapter8/5.mdx b/chapters/ru/chapter8/5.mdx new file mode 100644 index 000000000..c24eabf50 --- /dev/null +++ b/chapters/ru/chapter8/5.mdx @@ -0,0 +1,92 @@ +# Как написать хорошее сообщение об ошибке (issue)[[how-to-write-a-good-issue]] + + + +Если вы столкнулись с чем-то, что кажется неправильным в одной из библиотек Hugging Face, обязательно сообщите нам об этом, чтобы мы могли это исправить (то же самое касается любой библиотеки с открытым исходным кодом, если на то пошло). Если вы не уверены, где именно кроется ошибка - в вашем собственном коде или в одной из наших библиотек, - первым делом загляните на [форумы](https://discuss.huggingface.co/). Сообщество поможет вам разобраться в этом, а команда Hugging Face также внимательно следит за обсуждениями там. + + + +Когда вы уверены, что встретили ошибку, первым шагом будет создание минимального воспроизводимого примера. + +## Создание минимального воспроизводимого примера[[creating-a-minimal-reproducible-example]] + +Очень важно изолировать часть кода, в которой возникает ошибка, поскольку никто из команды Hugging Face не является волшебником (пока), и они не могут исправить то, чего не видят. Минимальный воспроизводимый пример, как видно из названия, должен быть воспроизводимым. Это значит, что он не должен опираться на какие-либо внешние файлы или данные, которые могут у вас быть. Попробуйте заменить используемые данные какими-нибудь фиктивными значениями, которые выглядят как настоящие и при этом выдают ту же ошибку. + + + +🚨 Многие проблемы в репозитории 🤗 Transformers остаются нерешенными, потому что данные, использованные для их воспроизведения, недоступны. + + + +Когда у вас есть что-то самодостаточное, вы можете попытаться сократить его до еще меньшего количества строк кода, создав то, что мы называем _минимальным воспроизводимым примером_. Хотя это требует немного больше работы с вашей стороны, вы почти гарантированно получите помощь и исправление, если предоставите хороший, короткий пример воспроизведения ошибки. + +Если вы чувствуете себя достаточно комфортно, просмотрите исходный код, в котором произошла ваша ошибка. Возможно, вы найдете решение проблемы (в этом случае вы даже можете предложить pull request), но в целом это поможет сопровождающим лучше понять исходный код, когда они прочитают ваш отчет. + +## Заполнение шаблона проблемы[[filling-out-the-issue-template]] + +Когда вы начнете сообщать об ошибке, вы заметите, что есть шаблон, который нужно заполнить. Здесь мы будем следовать шаблону для [🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose), но такая же информация потребуется, если вы сообщите о проблеме в другом репозитории. Не оставляйте шаблон пустым: потратив время на его заполнение, вы увеличите свои шансы на получение ответа и решение проблемы. + +В целом, при оформлении проблемы всегда оставайтесь вежливыми. Это проект с открытым исходным кодом, поэтому вы используете свободное программное обеспечение, и никто не обязан вам помогать. Вы можете включить в свой вопрос обоснованную, на ваш взгляд, критику, но тогда сопровождающие могут воспринять это плохо и не спешить вам помогать. Обязательно прочитайте [кодекс поведения](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) проекта. + +### Включая информацию о вашем окружении разработки[[including-your-environment-information]] + +🤗 Transformers предоставляет утилиту для получения всей необходимой информации о вашем окружении. Просто введите в терминале следующее: + +``` +transformers-cli env +``` + +и у вас должно получиться что-то вроде этого: + +```out +Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points. + +- `transformers` version: 4.12.0.dev0 +- Platform: Linux-5.10.61-1-MANJARO-x86_64-with-arch-Manjaro-Linux +- Python version: 3.7.9 +- PyTorch version (GPU?): 1.8.1+cu111 (True) +- Tensorflow version (GPU?): 2.5.0 (True) +- Flax version (CPU?/GPU?/TPU?): 0.3.4 (cpu) +- Jax version: 0.2.13 +- JaxLib version: 0.1.65 +- Using GPU in script?: +- Using distributed or parallel set-up in script?: +``` + +Вы также можете добавить `!` в начало команды `transformers-cli env`, чтобы выполнить ее из ячейки блокнота, а затем скопировать и вставить результат в начало описания проблемы. + +### Упоминание людей[[tagging-people]] + +Отметив людей, набрав `@`, а затем их GitHub-ник, вы отправите им уведомление, чтобы они увидели вашу проблему и могли быстрее ответить. Используйте этот способ аккуратно, потому что люди, которых вы помечаете, могут не обратить внимания на уведомления, если это что-то, к чему они не имеют прямого отношения. Если вы просмотрели исходные файлы, связанные с вашей ошибкой, вам следует отметить последнего человека, который внес изменения в строку, которая, по вашему мнению, ответственна за вашу проблему (вы можете найти эту информацию, посмотрев на указанную строку на GitHub, выбрав ее, а затем нажав "View git blame"). + +В противном случае шаблон предложит вам выбрать людей для пометки. В общем случае не следует отмечать более трех человек! + +### Включение воспроизводимого примера[[including-a-reproducible-example]] + +Если вам удалось создать самодостаточный пример, который выдает ошибку, самое время добавить его! Введите строку с тремя обратными знаками, за которыми следует `python`, например, так: + +``` +```python +``` + +затем вставьте свой минимальный воспроизводимый пример и введите новую строку с тремя обратными знаками. Это обеспечит правильное форматирование вашего кода. + +Если вам не удалось создать воспроизводимый пример, объясните в четких шагах, как вы пришли к своей проблеме. Если можно, включите ссылку на блокнот Google Colab, в котором вы получили ошибку. Чем больше информации вы предоставите, тем лучше смогут ответить вам сопровождающие. + +В любом случае вам следует скопировать и вставить все сообщение об ошибке, которое вы получаете. Если вы работаете в Colab, помните, что некоторые фреймы могут быть автоматически свернуты в трассировке стека, поэтому убедитесь, что вы развернули их перед копированием. Как и в примере кода, поместите сообщение об ошибке между двумя строками с тремя обратными знаками, чтобы оно было правильно отформатировано. + +### Описание ожидаемого поведения[[describing-the-expected-behavior]] + +Объясните в нескольких строках, что вы ожидали получить, чтобы сопровождающие получили полное представление о проблеме. Эта часть, как правило, довольно очевидна, поэтому должна уместиться в одном предложении, но в некоторых случаях вам будет что сказать. + +## И что потом?[[and-then-what]] + +Как только проблема будет зарегистрирована, не забудьте быстро проверить, все ли в порядке. Вы можете отредактировать вопрос, если допустили ошибку, или даже изменить его название, если поймете, что проблема отличается от того, что вы думали вначале. + +Нет смысла писать людям, если вы не получите ответа. Если никто не поможет вам в течение нескольких дней, скорее всего, никто не смог разобраться в вашей проблеме. Не стесняйтесь возвращаться к воспроизводимому примеру. Можете ли вы сделать его короче и понятнее? Если вы не получите ответа в течение недели, вы можете оставить сообщение с просьбой о помощи, особенно если вы отредактировали свой вопрос, чтобы включить в него больше информации о проблеме. + diff --git a/chapters/ru/chapter8/6.mdx b/chapters/ru/chapter8/6.mdx new file mode 100644 index 000000000..d0abf9c94 --- /dev/null +++ b/chapters/ru/chapter8/6.mdx @@ -0,0 +1,12 @@ +# Часть 2 завершена![[part-2-completed]] + + + +Поздравляем, вы прошли вторую часть курса! Мы активно работаем над третьей частью, поэтому подпишитесь на нашу [рассылку](https://huggingface.curated.co/), чтобы не пропустить ее выход. + +Теперь вы должны уметь решать различные задачи NLP, дообучать или обучать модели с нуля. Не забудьте поделиться своими результатами с сообществом на [Model Hub](https://huggingface.co/models). + +Нам не терпится увидеть, что вы построите на основе полученных знаний! diff --git a/chapters/ru/chapter8/7.mdx b/chapters/ru/chapter8/7.mdx new file mode 100644 index 000000000..3cfc8670b --- /dev/null +++ b/chapters/ru/chapter8/7.mdx @@ -0,0 +1,204 @@ + + +# Тест в конце главы[[end-of-chapter-quiz]] + + + +Let's test what you learned in this chapter! + +### 1. В каком порядке следует читать обратную трассировку в Python? + + + +### 2. Что такое минимальный воспроизводимый пример? + + + +### 3. Предположим, вы пытаетесь выполнить следующий код, который выдает ошибку: + +```py +from transformers import GPT3ForSequenceClassification + +# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) +# --------------------------------------------------------------------------- +# ImportError Traceback (most recent call last) +# /var/folders/28/k4cy5q7s2hs92xq7_h89_vgm0000gn/T/ipykernel_30848/333858878.py in +# ----> 1 from transformers import GPT3ForSequenceClassification + +# ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) +``` + +Что из нижеперечисленного может быть хорошим выбором для названия темы на форуме с просьбой о помощи? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: "Включение последней строки трассировки может быть описательным, но это лучше оставить для основной части темы. Попробуйте еще раз!" + }, + { + text: "Проблема с from transformers import GPT3ForSequenceClassification", + explain: "Попробуйте еще раз - хотя это и полезная информация, ее лучше оставить для основной части текста.", + }, + { + text: "Почему я не могу импортировать GPT3ForSequenceClassification?", + explain: "Отличный выбор! Это название лаконично и дает читателю понять, что может быть не так (например, что GPT-3 не поддерживается в 🤗 Transformers).", + correct: true + }, + { + text: "Поддерживается ли GPT-3 в 🤗 Transformers?", + explain: "Отличный вариант! Использование вопросов в качестве заголовков тем - отличный способ донести проблему до сообщества.", + correct: true + } + ]} +/> + +### 4. Предположим, вы пытаетесь запустить `trainer.train()` и сталкиваетесь с загадочной ошибкой, которая не говорит вам, откуда именно она взялась. Что из нижеперечисленного является первым местом, где вы должны искать ошибки в вашем конвейере обучения? + + + +### 5. Каков наилучший способ отладки ошибок CUDA? + + + +### 6. Как лучше всего сообщить о проблеме на GitHub? + + + +### 7. Почему переобучение модели с одним батчем обычно является хорошим методом отладки? + + + +### 8. Почему при создании нового вопроса в репозитории 🤗 Transformers стоит указать подробности о вашем окружении разработки с помощью `transformers-cli env`?? + + diff --git a/chapters/ru/chapter9/1.mdx b/chapters/ru/chapter9/1.mdx new file mode 100644 index 000000000..4af88378c --- /dev/null +++ b/chapters/ru/chapter9/1.mdx @@ -0,0 +1,37 @@ +# Введение в Gradio[[introduction-to-gradio]] + + + +В этой главе мы узнаем о том, как создавать **интерактивные демонстрации** для моделей машинного обучения. + +Зачем вообще создавать демо или графический интерфейс для модели машинного обучения? Демо позволяют: + +- **Разработчикам машинного обучения** легко представить свою работу широкой аудитории, включая нетехнические команды или клиентов +- **Исследователям** легче воспроизводить модели машинного обучения и их поведение +- **Тестировщики качества** или **конечные пользователи**, смогут легче выявлять и отлаживать точки отказа в моделях +- **Различные пользователи** смогут обнаружить алгоритмические ошибки в моделях + +Мы будем использовать библиотеку Gradio для создания демо для наших моделей. Gradio позволяет создавать, настраивать и распространять веб-демо для любой модели машинного обучения, полностью на языке Python. + +Вот несколько примеров демо машинного обучения, созданных с помощью Gradio: + +* Модель **распознавания эскизов (sketch recognition)**, которая принимает эскиз и выводит метки того, что, по ее мнению, нарисовано: + + + +* Экстрактивная модель **ответа на вопрос**, которая принимает контекстный параграф и задание и выдает ответ и оценку вероятности (мы обсуждали этот тип модели [в главе 7](../chapter7/7)): + + + +* Модель **удаления фона**, которая принимает изображение и возвращает его с удаленным фоном: + + + +Эта глава разбита на разделы, включающие как _концепции_, так и _приложения_. После изучения концепций в каждом разделе вы будете применять их для создания демо определенного типа, начиная от классификации изображений и заканчивая распознаванием речи. К тому времени, как вы закончите эту главу, вы сможете создавать эти демо (и многие другие!) всего в несколько строк кода на Python. + + +👀 Проверьте Hugging Face Spaces чтобы увидеть множество свежих примеров демо машинного обучения, созданных сообществом специалистов по машинному обучению! + \ No newline at end of file diff --git a/chapters/ru/chapter9/2.mdx b/chapters/ru/chapter9/2.mdx new file mode 100644 index 000000000..04abe6e19 --- /dev/null +++ b/chapters/ru/chapter9/2.mdx @@ -0,0 +1,118 @@ +# Создание вашего первого демо[[building-your-first-demo]] + + + +Давайте начнем с установки Gradio! Поскольку это пакет для Python, просто выполните: + +`$ pip install gradio ` + +Вы можете запускать Gradio где угодно, будь то ваша любимая IDE Python, Jupyter-блокнот или даже Google Colab 🤯! +Так что установите Gradio везде, где вы используете Python! + +Давайте начнем с простого примера "Hello World", чтобы познакомиться с синтаксисом Gradio: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Давайте пройдемся по приведенному выше коду: + +- Сначала мы определяем функцию `greet()`. В данном случае это простая функция, которая добавляет "Hello" перед вашим именем, но это может быть *любая* функция Python в целом. Например, в приложениях машинного обучения эта функция будет *вызывать модель для прогнозирования* на входных данных и возвращать вывод. +- Затем мы создаем интерфейс Gradio `Interface` с тремя аргументами, `fn`, `inputs` и `outputs`. Эти аргументы определяют функцию прогнозирования, а также _тип_ входных и выходных компонентов, которые мы хотим получить. В нашем случае оба компонента представляют собой простые текстовые поля. +- Затем мы вызываем метод `launch()` для созданного нами `Interface`. + +Если вы запустите этот код, нижеприведенный интерфейс автоматически появится в блокноте Jupyter/Colab или откроется в браузере на **[http://localhost:7860](http://localhost:7860/)** при запуске из скрипта. + + + +Попробуйте использовать этот GUI прямо сейчас с собственным именем или другими данными! + +Вы заметите, что в этом GUI Gradio автоматически определил имя входного параметра (`name`) +и применил его в качестве метки поверх текстового поля. Что если вы захотите изменить это? +Или если вы хотите настроить текстовое поле каким-то другим способом? В этом случае вы можете +инстанцировать объект класса, представляющий компонент ввода. + +Посмотрите на пример ниже: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# Мы инстанцируем класс Textbox +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Здесь мы создали текстовое поле ввода с меткой, заполнителем и заданным количеством строк. +То же самое можно сделать и для выходного текстового поля, но мы пока что остановимся на этом. + +Мы увидели, что с помощью всего нескольких строк кода Gradio позволяет создать простой интерфейс вокруг любой функции +с любыми входами и выходами. В этом разделе мы начали с +простого текстового поля, но в следующих разделах мы рассмотрим другие виды входов и выходов. Теперь давайте рассмотрим применение некоторого NLP в приложении Gradio. + + +## 🤖 Добавление прогнозов модели[[including-model-predictions]] + +Теперь давайте рассмотрим простой интерфейс, который позволит продемонстрировать демо модели **генерации текста (text-generation)**, такой как GPT-2. + +Мы загрузим нашу модель с помощью функции `pipeline()` из 🤗 Transformers. +Если вам нужно быстро освежить в памяти материал, вы можете вернуться к [этому разделу в Главе 1](../chapter1/3#text-generation). + +Сначала мы определяем функцию прогнозирования, которая принимает текстовую подсказку (text prompt) и возвращает ее завершение текста: + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Эта функция завершает введенные вами подсказки, и вы можете запустить ее с вашими собственными подсказками, чтобы посмотреть, как она работает. Вот пример (вы можете получить другое завершение): + +``` +predict("My favorite programming language is") +``` + +``` +>> My favorite programming language is Haskell. I really enjoyed the Haskell language, but it doesn't have all the features that can be applied to any other language. For example, all it does is compile to a byte array. +``` + +Теперь, когда у нас есть функция для генерации прогнозов, мы можем создать и запустить `Interface` таким же образом, как мы делали это ранее: + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + + +Вот и все! Теперь вы можете использовать этот интерфейс для генерации текста с помощью модели GPT-2, как показано ниже 🤯. + + + +Продолжайте читать, чтобы узнать, как создавать другие виды демо с помощью Gradio! \ No newline at end of file diff --git a/chapters/ru/chapter9/3.mdx b/chapters/ru/chapter9/3.mdx new file mode 100644 index 000000000..005bff120 --- /dev/null +++ b/chapters/ru/chapter9/3.mdx @@ -0,0 +1,186 @@ +# Понимание класса Interface[[understanding-the-interface-class]] + + + +В этом разделе мы подробно рассмотрим класс `Interface` и разберем +основные параметры, используемые для его создания. + +## Как создать Interface[[how-to-create-an-interface]] + +Вы заметите, что класс `Interface` имеет 3 обязательных параметра: + +`Interface(fn, inputs, outputs, ...)` + +Это параметры: + + - `fn`: функция прогнозирования, обернутая интерфейсом Gradio. Эта функция может принимать один или несколько параметров и возвращать одно или несколько значений + - `inputs`: тип(ы) компонента(ов) ввода. Gradio предоставляет множество готовых компонентов, таких как `"image"` или `"mic"`. + - `outputs`: тип(ы) компонента(ов) вывода. Опять же, Gradio предоставляет множество предварительно созданных компонентов, например, `" image"` или `"label"`. + +Полный список компонентов [смотрите в документации Gradio](https://gradio.app/docs). Каждый предварительно созданный компонент можно настроить, инстанцировав соответствующий ему класс. + +Например, как мы видели в [предыдущем разделе](../chapter9/2), +вместо передачи `"textbox"` в параметр `inputs`, вы можете передать компонент `Textbox(lines=7, label="Prompt")` для создания текстового поля с 7 строками и меткой. + +Давайте рассмотрим еще один пример, на этот раз с компонентом `Audio`. + +## Простой пример со звуком[[a-simple-example-with-audio]] + +Как уже говорилось, Gradio предоставляет множество различных входов и выходов. +Поэтому давайте создадим `Interface`, работающий с аудио. + +В этом примере мы создадим функцию audio-to-audio, которая принимает +аудиофайл и просто переворачивает его. + +Для ввода мы будем использовать компонент `Audio`. При использовании компонента `Audio`, +вы можете указать, будет ли источником звука файл, который загружает пользователь +или микрофон, с помощью которого пользователь записывает свой голос. В данном случае давайте +зададим `"microphone"`. Просто ради интереса добавим к `Audio` метку, которая будет гласить +"Speak here...". + +Кроме того, мы хотели бы получать аудио в виде массива numpy, чтобы можно было легко +"перевернуть" его. Поэтому мы зададим `"type"` в значение `"numpy"`, которое передаст входные +данные в виде кортежа (`sample_rate`, `data`) в нашу функцию. + +Мы также будем использовать компонент вывода `Audio`, который может автоматически +рендерить кортеж с частотой дискретизации и массивом данных numpy в воспроизводимый аудиофайл. +В этом случае нам не нужно делать никаких настроек, поэтому мы будем использовать строку +ярлык `"audio"`. + + +```py +import numpy as np +import gradio as gr + + +def reverse_audio(audio): + sr, data = audio + reversed_audio = (sr, np.flipud(data)) + return reversed_audio + + +mic = gr.Audio(source="microphone", type="numpy", label="Speak here...") +gr.Interface(reverse_audio, mic, "audio").launch() +``` + +Код, приведенный выше, создаст интерфейс, подобный приведенному ниже (если ваш браузер не +не запрашивает разрешения на использование микрофона, откройте демо в отдельной вкладке.) + + + +Теперь вы сможете записать свой голос и услышать, как вы говорите в обратную сторону - жутковато 👻! + +## Обработка нескольких входов и выходов[[handling-multiple-inputs-and-outputs]] + +Допустим, у нас есть более сложная функция, с несколькими входами и выходами. +В примере ниже у нас есть функция, которая принимает индекс выпадающего списка, значение слайдера и число, +и возвращает пример музыкального тона. + +Посмотрите, как мы передаем список входных и выходных компонентов, +и посмотрите, сможете ли вы проследить за тем, что происходит. + +Ключевым моментом здесь является то, что когда вы передаете: +* список входных компонентов, каждый компонент соответствует параметру по порядку. +* список выходных компонентов, каждый компонент соответствует возвращаемому значению. + +В приведенном ниже фрагменте кода показано, как три компонента ввода соответствуют трем аргументам функции `generate_tone()`: + +```py +import numpy as np +import gradio as gr + +notes = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"] + + +def generate_tone(note, octave, duration): + sr = 48000 + a4_freq, tones_from_a4 = 440, 12 * (octave - 4) + (note - 9) + frequency = a4_freq * 2 ** (tones_from_a4 / 12) + duration = int(duration) + audio = np.linspace(0, duration, duration * sr) + audio = (20000 * np.sin(audio * (2 * np.pi * frequency))).astype(np.int16) + return (sr, audio) + + +gr.Interface( + generate_tone, + [ + gr.Dropdown(notes, type="index"), + gr.Slider(minimum=4, maximum=6, step=1), + gr.Textbox(type="number", value=1, label="Duration in seconds"), + ], + "audio", +).launch() +``` + + + + +### Метод `launch()`[[the-launch-method]] + +До сих пор мы использовали метод `launch()` для запуска интерфейса, но мы +не обсуждали, что он делает. + +По умолчанию метод `launch()` запускает демо на веб-сервере, который +работает локально. Если вы выполняете свой код в блокноте Jupyter или Colab, то +Gradio встроит демо GUI в блокнот, чтобы вы могли легко им пользоваться. + +Вы можете настроить поведение `launch()` с помощью различных параметров: + + - `inline` - отображать ли интерфейс в виде строки в блокнотах Python. + - `inbrowser` - автоматически ли запускать интерфейс в новой вкладке браузера по умолчанию. + - `share` - создавать ли для интерфейса общедоступную ссылку с вашего компьютера. Что-то вроде ссылки на Google Drive! + +Мы рассмотрим параметр `share` более подробно в следующем разделе! + +## ✏️ Давайте применим это![[lets-apply-it]] + +Давайте создадим интерфейс, который позволит вам продемонстрировать демо модели **распознавания речи**. +Чтобы было интереснее, мы будем принимать *либо* микрофонный вход, либо загруженный файл. + +Как обычно, мы загрузим нашу модель распознавания речи с помощью функции `pipeline()` из 🤗 Transformers. +Если вам нужно быстро вспомнить, вы можете вернуться к [этому разделу в Главе 1](../chapter1/3). Далее мы реализуем функцию `transcribe_audio()`, которая обрабатывает аудио и возвращает транскрипцию. Наконец, мы обернем эту функцию в `Interface` с компонентами `Audio` на входе и просто текстом на выходе. В целом, код этого приложения выглядит следующим образом: + +```py +from transformers import pipeline +import gradio as gr + +model = pipeline("automatic-speech-recognition") + + +def transcribe_audio(mic=None, file=None): + if mic is not None: + audio = mic + elif file is not None: + audio = file + else: + return "You must either provide a mic recording or a file" + transcription = model(audio)["text"] + return transcription + + +gr.Interface( + fn=transcribe_audio, + inputs=[ + gr.Audio(source="microphone", type="filepath", optional=True), + gr.Audio(source="upload", type="filepath", optional=True), + ], + outputs="text", +).launch() +``` + +Если ваш браузер не запрашивает разрешения на использование микрофона, откройте демо в отдельной вкладке. + + + + +Вот и все! Теперь вы можете использовать этот интерфейс для транскрибирования аудио. Обратите внимание, что +передавая параметр `optional` как `True`, мы позволяем пользователю предоставить +либо микрофон, либо аудиофайл (либо ни то, ни другое, но в этом случае будет выдано сообщение об ошибке). + +Продолжайте, чтобы узнать, как поделиться своим интерфейсом с другими! \ No newline at end of file diff --git a/chapters/ru/chapter9/4.mdx b/chapters/ru/chapter9/4.mdx new file mode 100644 index 000000000..08584cea3 --- /dev/null +++ b/chapters/ru/chapter9/4.mdx @@ -0,0 +1,147 @@ +# Делимся демо с другими[[sharing-demos-with-others]] + + + +Теперь, когда вы создали демо, вы наверняка захотите поделиться им с другими. Демо Gradio +можно распространять двумя способами: используя ***временную ссылку для общего доступа*** или ***постоянный хостинг на Spaces***. + +Мы рассмотрим оба этих подхода в ближайшее время. Но прежде чем выложить свое демо, вы, возможно, захотите доработать его 💅. + +### Доработка демо Gradio:[[polishing-your-gradio-demo]] + +
+Overview of a gradio interface + +
+ +Чтобы добавить дополнительный контент в демо, класс `Interface` поддерживает некоторые необязательные параметры: + - `title`: вы можете дать название своему демо, которое будет отображаться _над_ компонентами ввода и вывода. + - `description`: вы можете дать описание (в виде текста, Markdown или HTML) для интерфейса, которое отображается над компонентами ввода и вывода и под заголовком. + - `article`: вы также можете написать расширенную статью (в текстовом формате, Markdown или HTML), объясняющую интерфейс. Если она задана, она отображается _под_ компонентами ввода и вывода. + - `theme`: не нравятся цвета по умолчанию? Установите для темы одно из значений `default`, `huggingface`, `grass`, `peach`. Вы также можете добавить префикс `dark-`, например, `dark-peach` для темной темы (или просто `dark` для темной темы по умолчанию). + - `examples`: чтобы сделать ваше демо более удобным в использовании, вы можете предоставить несколько примеров входов для функции. Они появляются под компонентами пользовательского интерфейса и могут быть использованы для заполнения интерфейса. Они должны быть предоставлены в виде вложенного списка, в котором внешний список состоит из примеров, а каждый внутренний список состоит из ввода, соответствующего каждому компоненту ввода. + - `live`: если вы хотите сделать демо "живым", то есть чтобы ваша модель запускалась заново при каждом изменении входных данных, вы можете установить `live=True`. Это имеет смысл использовать с быстрыми моделями (пример мы увидим в конце этого раздела) +Используя приведенные выше варианты, мы получим более завершенный интерфейс. Запустите приведенный ниже код, и вы сможете пообщаться с Риком и Морти: + +```py +title = "Ask Rick a Question" +description = """ +The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! + +""" + +article = "Check out [the original Rick and Morty Bot](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) that this demo is based off of." + +gr.Interface( + fn=predict, + inputs="textbox", + outputs="text", + title=title, + description=description, + article=article, + examples=[["What are you doing?"], ["Where should we time travel to?"]], +).launch() +``` + +Используя приведенные выше варианты, мы получим более завершенный интерфейс. Попробуйте интерфейс, представленный ниже: + + + +### Распространение демо с помощью временных ссылок[[sharing-your-demo-with-temporary-links]] +Теперь, когда у нас есть работающее демо нашей модели машинного обучения, давайте узнаем, как легко поделиться ссылкой на наш интерфейс. +Интерфейсами можно легко поделиться публично, установив `share=True` в методе `launch()`: + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +В результате создается общедоступная ссылка, которую вы можете отправить кому угодно! Когда вы отправляете эту ссылку, пользователь на другой стороне может опробовать модель в своем браузере в течение 72 часов. Поскольку обработка происходит на вашем устройстве (пока оно включено!), вам не нужно беспокоиться об упаковке каких-либо зависимостей. Если вы работаете в блокноте Google Colab, ссылка на общий доступ всегда создается автоматически. Обычно она выглядит примерно так: **XXXXX.gradio.app**. Хотя ссылка предоставляется через Gradio, мы являемся лишь прокси для вашего локального сервера и не храним никаких данных, передаваемых через интерфейсы. + +Однако не забывайте, что эти ссылки общедоступны, а значит, любой желающий может использовать вашу модель для прогнозирования! Поэтому не раскрывайте конфиденциальную информацию через написанные вами функции и не допускайте критических изменений на вашем устройстве. Если вы установите `share=False` (по умолчанию), будет создана только локальная ссылка. + +### Хостинг вашего демо на Hugging Face Spaces[[hosting-your-demo-on-hugging-face-spaces]] + +Ссылка на ресурс, которую можно передать коллегам, - это здорово, но как разместить демо на постоянном хостинге, чтобы оно существовало в своем собственном "пространстве (space)" в Интернете? + +Hugging Face Spaces предоставляет инфраструктуру для постоянного размещения вашей модели Gradio в интернете, **бесплатно**! Spaces позволяет вам создать и разместить в (публичном или частном) репозитории, +где ваш Gradio +код интерфейса будет существовать в файле `app.py`. [Прочитайте пошаговое руководство](https://huggingface.co/blog/gradio-spaces) чтобы начать работу, или посмотрите видео с примером ниже. + + + +## ✏️ Давайте применим это![[lets-apply-it]] + +Используя то, что мы узнали в предыдущих разделах, давайте создадим демо распознавания скетчей, которое мы видели в [первом разделе этой главы](../chapter9/1). Давайте добавим некоторые настройки в наш интерфейс и установим `share=True`, чтобы создать публичную ссылку, которую мы сможем передавать всем желающим. + +Мы можем загрузить метки из файла [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) и загрузить предварительно обученную модель pytorch из файла [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Загрузите эти файлы, перейдя по ссылке и нажав кнопку загрузки в левом верхнем углу окна предварительного просмотра файла. Давайте посмотрим на приведенный ниже код, чтобы увидеть, как мы используем эти файлы для загрузки нашей модели и создания функции `predict()`: +```py +from pathlib import Path +import torch +import gradio as gr +from torch import nn + +LABELS = Path("class_names.txt").read_text().splitlines() + +model = nn.Sequential( + nn.Conv2d(1, 32, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Conv2d(32, 64, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Conv2d(64, 128, 3, padding="same"), + nn.ReLU(), + nn.MaxPool2d(2), + nn.Flatten(), + nn.Linear(1152, 256), + nn.ReLU(), + nn.Linear(256, len(LABELS)), +) +state_dict = torch.load("pytorch_model.bin", map_location="cpu") +model.load_state_dict(state_dict, strict=False) +model.eval() + + +def predict(im): + x = torch.tensor(im, dtype=torch.float32).unsqueeze(0).unsqueeze(0) / 255.0 + with torch.no_grad(): + out = model(x) + probabilities = torch.nn.functional.softmax(out[0], dim=0) + values, indices = torch.topk(probabilities, 5) + return {LABELS[i]: v.item() for i, v in zip(indices, values)} +``` + +Теперь у нас есть функция `predict()`. Следующим шагом будет определение и запуск нашего интерфейса Gradio: + +```py +interface = gr.Interface( + predict, + inputs="sketchpad", + outputs="label", + theme="huggingface", + title="Sketch Recognition", + description="Who wants to play Pictionary? Draw a common object like a shovel or a laptop, and the algorithm will guess in real time!", + article="

Sketch Recognition | Demo Model

", + live=True, +) +interface.launch(share=True) +``` + + + + +Обратите внимание на параметр `live=True` в `Interface`, который означает, что демо скетча делает +предсказание каждый раз, когда кто-то рисует на скетчпаде (без кнопки "Исполнить (submit)"!). + +Кроме того, мы также установили аргумент `share=True` в методе `launch()`. +Это создаст общедоступную ссылку, которую вы можете +отправить кому угодно! Когда вы отправите эту ссылку, пользователь на другой стороне сможет опробовать +модель распознавания эскизов. Повторим, что модель также можно разместить на Hugging Face Spaces, +именно так мы и разместили демо выше. + +Далее мы расскажем о других способах использования Gradio в экосистеме Hugging Face! \ No newline at end of file diff --git a/chapters/ru/chapter9/5.mdx b/chapters/ru/chapter9/5.mdx new file mode 100644 index 000000000..8e180118d --- /dev/null +++ b/chapters/ru/chapter9/5.mdx @@ -0,0 +1,67 @@ +# Интеграция с Hugging Face Hub[[integrations-with-the-hugging-face-hub]] + + + +Чтобы сделать вашу жизнь еще проще, Gradio напрямую интегрируется с Hugging Face Hub и Hugging Face Spaces. +Вы можете загружать демо из Hub и Spaces, используя всего *одну строку кода*. + +### Загрузка моделей из Hugging Face Hub[[loading-models-from-the-hugging-face-hub]] +Для начала выберите одну из тысяч моделей, которые Hugging Face предлагает в Hub, как описано в [Главе 4](../chapter4/2). + +Используя специальный метод `Interface.load()`, вы передаете `"model/"` (или, эквивалентно, `"huggingface/"`) +после чего следует имя модели. +Например, здесь приведен код для создания демо для [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), большой языковой модели, добавьте пару примеров ввода: + +```py +import gradio as gr + +title = "GPT-J-6B" +description = "Gradio Demo for GPT-J 6B, a transformer model trained using Ben Wang's Mesh Transformer JAX. 'GPT-J' refers to the class of model, while '6B' represents the number of trainable parameters. To use it, simply add your text, or click one of the examples to load them. Read more at the links below." +article = "

GPT-J-6B: A 6 Billion Parameter Autoregressive Language Model

" + +gr.Interface.load( + "huggingface/EleutherAI/gpt-j-6B", + inputs=gr.Textbox(lines=5, label="Input Text"), + title=title, + description=description, + article=article, +).launch() +``` + +Код, приведенный выше, приведет к созданию интерфейса, представленного ниже: + + + +Загрузка модели таким образом использует [Inference API](https://huggingface.co/inference-api) Hugging Face, +вместо того, чтобы загружать модель в память. Это идеально подходит для огромных моделей, таких как GPT-J или T0pp, которые + которые требуют много RAM. + +### Загрузка с Hugging Face Spaces[[loading-from-hugging-face-spaces]] +Чтобы загрузить любое пространство (Space) из Hugging Face Hub и воссоздать его локально, вы можете передать `spaces/` в `Interface`, за которым следует имя пространства. + +Помните демо из раздела 1, которое удаляет фон изображения? Давайте загрузим его из Hugging Face Spaces: + +```py +gr.Interface.load("spaces/abidlabs/remove-bg").launch() +``` + + + +Одна из особенностей загрузки демо из Hub или Spaces заключается в том, что вы можете настраивать их +переопределив любой из +параметров. Здесь мы добавим заголовок и задействуем веб-камеру: + +```py +gr.Interface.load( + "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" +).launch() +``` + + + +Теперь, когда мы изучили несколько способов интеграции Gradio с Hugging Face Hub, давайте рассмотрим некоторые дополнительные возможности класса `Interface`. Этому будет посвящен следующий раздел! \ No newline at end of file diff --git a/chapters/ru/chapter9/6.mdx b/chapters/ru/chapter9/6.mdx new file mode 100644 index 000000000..202e47b0f --- /dev/null +++ b/chapters/ru/chapter9/6.mdx @@ -0,0 +1,101 @@ +# Расширенные возможности Interface[[advanced-interface-features]] + + + +Теперь, когда мы можем создать базовый интерфейс и поделиться им, давайте изучим некоторые более продвинутые возможности, такие как состояние и интерпретации. + +### Использование состояния для сохранения данных[[using-state-to-persist-data]] + +Gradio поддерживает *состояние сеанса*, когда данные сохраняются при нескольких отправках в рамках +загруженной страницы. Состояние сессии полезно для создания демо, например, чат-ботов, где необходимо +сохранять данные по мере того, как пользователь взаимодействует с моделью. Обратите внимание, что состояние сессии не позволяет обмениваться данными между разными пользователями вашей модели. + +Чтобы хранить данные о состоянии сеанса, необходимо выполнить три действия: + +1. Передайте в вашу функцию *дополнительный параметр*, который представляет собой состояние интерфейса. +2. В завершении работы функции верните обновленное значение состояния в качестве *дополнительного возвращаемого значения*. +3. Добавьте входной компонент 'state' и выходной компонент 'state' при создании вашего `Interface`. + +Посмотрите пример чатбота ниже: + +```py +import random + +import gradio as gr + + +def chat(message, history): + history = history or [] + if message.startswith("How many"): + response = random.randint(1, 10) + elif message.startswith("How"): + response = random.choice(["Great", "Good", "Okay", "Bad"]) + elif message.startswith("Where"): + response = random.choice(["Here", "There", "Somewhere"]) + else: + response = "I don't know" + history.append((message, response)) + return history, history + + +iface = gr.Interface( + chat, + ["text", "state"], + ["chatbot", "state"], + allow_screenshot=False, + allow_flagging="never", +) +iface.launch() +``` + + + +Обратите внимание, что состояние выходного компонента сохраняется при всех отправках данных. +Примечание: в параметр state можно передать значение по умолчанию, +которое используется в качестве начального значения состояния. + +### Использование интерпретации для понимания прогнозов[[using-interpretation-to-understand-predictions]] + +Большинство моделей машинного обучения представляют собой "черные ящики", и внутренняя логика функции скрыта от конечного пользователя. Чтобы стимулировать прозрачность, мы упростили добавление интерпретации в вашу модель, просто задав ключевое слово interpretation в классе Interface по умолчанию. Это позволит вашим пользователям понять, какие части входных данных отвечают за вывод. Взгляните на простой интерфейс ниже, который показывает классификатор изображений, также включающий интерпретацию: + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # загрузим модель + +# Загрузим человекочитаемые метки для ImageNet. +response = requests.get("https://git.io/JJkYN") +labels = response.text.split("\n") + + +def classify_image(inp): + inp = inp.reshape((-1, 224, 224, 3)) + inp = tf.keras.applications.mobilenet_v2.preprocess_input(inp) + prediction = inception_net.predict(inp).flatten() + return {labels[i]: float(prediction[i]) for i in range(1000)} + + +image = gr.Image(shape=(224, 224)) +label = gr.Label(num_top_classes=3) + +title = "Gradio Image Classifiction + Interpretation Example" +gr.Interface( + fn=classify_image, inputs=image, outputs=label, interpretation="default", title=title +).launch() +``` + +Проверьте функцию интерпретации, отправив входные данные и нажав кнопку Интерпретировать (Interpret) под компонентом вывода. + + + +Помимо метода интерпретации, предоставляемого Gradio по умолчанию, вы также можете задать `shap` для параметра `interpretation` и установить параметр `num_shap`. При этом используется интерпретация на основе Шэпли, о которой вы можете подробнее прочитать [здесь](https://christophm.github.io/interpretable-ml-book/shap.html). Наконец, в параметр `interpretation` можно передать собственную функцию интерпретации. Пример можно посмотреть на странице Gradio, посвященной началу работы [здесь](https://gradio.app/getting_started/). + +На этом мы завершаем наше глубокое погружение в класс `Interface` в Gradio. Как мы уже видели, этот класс позволяет создавать демо машинного обучения в несколько строк кода на Python. Однако иногда возникает необходимость доработать демо, изменив его макет или соединив несколько функций предсказания в цепочку. Было бы здорово, если бы мы могли как-то разделить `Interface` на настраиваемые "блоки"? К счастью, такая возможность есть! Этой теме посвящен последний раздел. \ No newline at end of file diff --git a/chapters/ru/chapter9/7.mdx b/chapters/ru/chapter9/7.mdx new file mode 100644 index 000000000..de0e1f8db --- /dev/null +++ b/chapters/ru/chapter9/7.mdx @@ -0,0 +1,239 @@ +# Введение в Gradio Blocks[[introduction-to-gradio-blocks]] + + + +В предыдущих разделах мы изучили и создали демо, используя класс `Interface`. В этом разделе мы представим наш **свеже разработанный** низкоуровневый API под названием `gradio.Blocks`. + +Итак, в чем разница между `Interface` и `Blocks`? + +- ⚡ `Interface`: высокоуровневый API, который позволяет создать полноценное демо машинного обучения, просто предоставив список входных и выходных данных. + +- 🧱 `Blocks`: низкоуровневый API, который позволяет вам полностью контролировать потоки данных и компоновку вашего приложения. Вы можете создавать очень сложные, многоступенчатые приложения, используя `Blocks` (как "строительные блоки"). + + +### Почему Blocks 🧱?[[why-blocks-]] + +Как мы видели в предыдущих разделах, класс `Interface` позволяет легко создавать полноценные демо машинного обучения с помощью всего нескольких строк кода. API `Interface` чрезвычайно прост в использовании, но ему не хватает гибкости, которую обеспечивает API `Blocks`. Например, вы можете захотеть: + +- Сгруппировать связанные демо в виде нескольких вкладок в одном веб-приложении +- Изменить макет вашего демо, например, указать, где расположены входные и выходные компоненты +- Многоступенчатые интерфейсы, в которых выход одной модели становится входом для следующей модели, или более гибкие потоки данных в целом +- Изменить свойства компонента (например, выбор в выпадающем списке) или его видимость на основе пользовательского ввода + +Ниже мы рассмотрим все эти понятия. + +### Создание простого демо с помощью блоков[[creating-a-simple-demo-using-blocks]] + +После того как вы установили Gradio, запустите приведенный ниже код в виде сценария на Python, блокнота Jupyter или блокнота Colab. + +```py +import gradio as gr + + +def flip_text(x): + return x[::-1] + + +demo = gr.Blocks() + +with demo: + gr.Markdown( + """ + # Flip Text! + Start typing below to see the output. + """ + ) + input = gr.Textbox(placeholder="Flip this text") + output = gr.Textbox() + + input.change(fn=flip_text, inputs=input, outputs=output) + +demo.launch() +``` + + + +В этом простом примере представлены 4 концепции, которые лежат в основе блоков: + +1. Блоки позволяют создавать веб-приложения, сочетающие в себе разметку, HTML, кнопки и интерактивные компоненты, просто инстанцируя объекты на Python в контексте `with gradio.Blocks`. + + +🙋Если вы не знакомы с оператором `with` в Python, рекомендуем ознакомиться с отличным [руководством](https://realpython.com/python-with-statement/) от Real Python. Возвращайтесь сюда после его прочтения 🤗 + + + +Порядок, в котором вы инстанцируете компоненты, имеет значение, поскольку каждый элемент отображается в веб-приложении в том порядке, в котором он был создан. (Более сложные макеты рассматриваются ниже) + +2. Вы можете определять обычные функции Python в любом месте вашего кода и запускать их с пользовательским вводом с помощью `Blocks`. В нашем примере мы используем простую функцию, которая "переворачивает" введенный текст, но вы можете написать любую функцию Python, от простого вычисления до обработки прогнозов модели машинного обучения. + +3. Вы можете назначить события для любого компонента `Blocks`. Это позволит запускать вашу функцию при нажатии на компонент, его изменении и т. д. Когда вы назначаете событие, вы передаете три параметра: `fn`: функция, которая должна быть вызвана, `inputs`: (список) входных компонентов, и `outputs`: (список) выходных компонентов, которые должны быть вызваны. + + В примере выше мы запускаем функцию `flip_text()`, когда значение в `Textbox` с названием `input` изменяется. Событие считывает значение в `input`, передает его в качестве именнованного параметра в `flip_text()`, которая затем возвращает значение, которое присваивается нашему второму `Textbox` с именем `output`. + + Список событий, которые поддерживает каждый компонент, можно найти в [документации Gradio](https://www.gradio.app/docs/). + +4. Блоки автоматически определяют, должен ли компонент быть интерактивным (принимать пользовательский ввод) или нет, основываясь на определенных вами триггерах событий. В нашем примере первый textbox является интерактивным, поскольку его значение используется функцией `flip_text()`. Второй textbox не является интерактивным, поскольку его значение никогда не используется в качестве входа. В некоторых случаях вы можете переопределить это, передав булево значение в параметр `interactive` компонента (например, `gr.Textbox(placeholder="Flip this text", interactive=True)`). + +### Настройка макета вашего демо[[customizing-the-layout-of-your-demo]] + +Как мы можем использовать `Blocks` для настройки макета нашего демо? По умолчанию `Blocks` отображает созданные вами компоненты вертикально в одной колонке. Вы можете изменить это, создав дополнительные колонки `с помощью gradio.Column():` или строки `с помощью gradio.Row():` и создав компоненты в этих контекстах. + +Вот что следует иметь в виду: все компоненты, созданные в `Column` (это также значение по умолчанию), будут располагаться вертикально. Любой компонент, созданный в `Row`, будет располагаться горизонтально, аналогично [модели flexbox в веб-разработке](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). + +Наконец, вы можете создавать вкладки для демо с помощью контекстного менеджера `with gradio.Tabs()`. В этом контексте вы можете создать несколько вкладок, указав дочерние вкладки в `with gradio.TabItem(name_of_tab):` . Любой компонент, созданный внутри контекста `with gradio.TabItem(name_of_tab):`, отображается на этой вкладке. + +Теперь давайте добавим функцию `flip_image()` в наше демо и добавим новую вкладку, которая будет переворачивать изображения. Ниже приведен пример с 2 вкладками, в котором также используется Row: + +```py +import numpy as np +import gradio as gr + +demo = gr.Blocks() + + +def flip_text(x): + return x[::-1] + + +def flip_image(x): + return np.fliplr(x) + + +with demo: + gr.Markdown("Flip text or image files using this demo.") + with gr.Tabs(): + with gr.TabItem("Flip Text"): + with gr.Row(): + text_input = gr.Textbox() + text_output = gr.Textbox() + text_button = gr.Button("Flip") + with gr.TabItem("Flip Image"): + with gr.Row(): + image_input = gr.Image() + image_output = gr.Image() + image_button = gr.Button("Flip") + + text_button.click(flip_text, inputs=text_input, outputs=text_output) + image_button.click(flip_image, inputs=image_input, outputs=image_output) + +demo.launch() +``` + + + + +Вы заметите, что в этом примере мы также создали компонент `Button` на каждой вкладке и назначили событие click для каждой кнопки, что, собственно, и запускает функцию. + +### Изучение событий и состояния[[exploring-events-and-state]] + +Так же, как вы можете управлять макетом, `Blocks` дает вам тонкий контроль над тем, какие события вызывают вызовы функций. Каждый компонент и многие макеты имеют определенные события, которые они поддерживают. + +Например, у компонента `Textbox` есть 2 события: `change()` (когда меняется значение внутри текстового поля) и `submit()` (когда пользователь нажимает клавишу ввода, будучи сфокусированным на текстовом поле). Более сложные компоненты могут иметь еще больше событий: например, компонент `Audio` также имеет отдельные события для воспроизведения аудиофайла, очистки, приостановки и так далее. О том, какие события поддерживает каждый компонент, читайте в документации. + +Вы можете прикрепить триггер события ни к одному, одному или нескольким из этих событий. Вы создаете триггер события, вызывая имя события на экземпляре компонента в виде функции - например, `textbox.change(...)` или `btn.click(...)`. Функция принимает три параметра, как говорилось выше: + +- `fn`: функция для выполнения +- `inputs`: (список (list)) компонент(ов), значения которых должны быть переданы в качестве входных параметров функции. Значение каждого компонента по порядку сопоставляется с соответствующим параметром функции. Этот параметр может иметь значение None, если функция не принимает никаких параметров. +- `outputs`: (список (list)) компонентов, значения которых должны быть обновлены на основе значений, возвращаемых функцией. Каждое возвращаемое значение устанавливает значение соответствующего компонента по порядку. Этот параметр может быть None, если функция ничего не возвращает. + +Вы даже можете сделать так, чтобы компонент ввода и компонент вывода были одним и тем же компонентом, как это сделано в данном примере, где используется модель GPT для дополнения текста: + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Используем последние 50 символов текста в качестве контекста + return text[:-50] + api(text[-50:]) + + +with gr.Blocks() as demo: + textbox = gr.Textbox(placeholder="Type here and press enter...", lines=4) + btn = gr.Button("Generate") + + btn.click(complete_with_gpt, textbox, textbox) + +demo.launch() +``` + + + +### Создание многоступенчатых демо[[creating-multi-step-demos]] + +В некоторых случаях вам может понадобиться _многоступенчатое демо_, в котором вы повторно используете выход одной функции в качестве входа для следующей. Это очень легко сделать с помощью `Blocks`, так как вы можете использовать компонент в качестве входа для одного триггера события, но выхода для другого. Посмотрите на компонент text в примере ниже: его значение является результатом работы модели преобразования речи в текст, но также передается в модель анализа настроений: + +```py +from transformers import pipeline + +import gradio as gr + +asr = pipeline("automatic-speech-recognition", "facebook/wav2vec2-base-960h") +classifier = pipeline("text-classification") + + +def speech_to_text(speech): + text = asr(speech)["text"] + return text + + +def text_to_sentiment(text): + return classifier(text)[0]["label"] + + +demo = gr.Blocks() + +with demo: + audio_file = gr.Audio(type="filepath") + text = gr.Textbox() + label = gr.Label() + + b1 = gr.Button("Recognize Speech") + b2 = gr.Button("Classify Sentiment") + + b1.click(speech_to_text, inputs=audio_file, outputs=text) + b2.click(text_to_sentiment, inputs=text, outputs=label) + +demo.launch() +``` + + + +### Обновление свойств компонента[[updating-component-properties]] + +До сих пор мы видели, как создавать события для обновления значения другого компонента. Но что делать, если вы хотите изменить другие свойства компонента, например видимость текстового поля или варианты выбора в группе радио кнопок? Вы можете сделать это, вернув метод класса компонента `update()` вместо обычного возвращаемого значения из вашей функции. + +Это легче всего проиллюстрировать на примере: + +```py +import gradio as gr + + +def change_textbox(choice): + if choice == "short": + return gr.Textbox.update(lines=2, visible=True) + elif choice == "long": + return gr.Textbox.update(lines=8, visible=True) + else: + return gr.Textbox.update(visible=False) + + +with gr.Blocks() as block: + radio = gr.Radio( + ["short", "long", "none"], label="What kind of essay would you like to write?" + ) + text = gr.Textbox(lines=2, interactive=True) + + radio.change(fn=change_textbox, inputs=radio, outputs=text) + block.launch() +``` + + + +Мы только что изучили все основные концепции `Blocks`! Как и в случае с `Interfaces`, вы можете создавать классные демо, которыми можно поделиться, используя `share=True` в методе `launch()` или развернуть на [Hugging Face Spaces](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/ru/chapter9/8.mdx b/chapters/ru/chapter9/8.mdx new file mode 100644 index 000000000..179e3eee0 --- /dev/null +++ b/chapters/ru/chapter9/8.mdx @@ -0,0 +1,24 @@ +# Gradio, проверка![[gradio-check]] + + + +На этом мы завершаем главу о создании классных демо на основе ML с помощью Gradio - надеемся, вам понравилось! Напомним, что в этой главе мы узнали: + +- Как создавать демо Gradio с помощью высокоуровневого API `Interface` и как настраивать различные модальности ввода и вывода. +- Различные способы поделиться демо Gradio, с помощью временных ссылок и хостинга на [Hugging Face Spaces](https://huggingface.co/spaces). +- Как интегрировать демо Gradio с моделями и Spaces на Hugging Face Hub. +- Расширенные возможности, такие как хранение состояния в демо или обеспечение аутентификации. +- Как получить полный контроль над потоком данных и макетом демо с помощью Gradio Blocks. + +Если вы хотите проверить свое понимание концепций, рассмотренных в этой главе, пройдите тест в следующем разделе! + +## Что дальше?[[where-to-next]] + +Если вы хотите узнать больше о Gradio, вы можете + +- Взглянуть на [Demo](https://github.com/gradio-app/gradio/tree/main/demo) репозиторий, там довольно много примеров. +- Посмотреть страницу [Guides](https://gradio.app/guides/), где вы можете найти руководства о крутых и продвинутых функциях. +- Заглянуть на страницу [Docs](https://gradio.app/docs/), чтобы узнать детали. diff --git a/chapters/ru/chapter9/9.mdx b/chapters/ru/chapter9/9.mdx new file mode 100644 index 000000000..74ce11f5d --- /dev/null +++ b/chapters/ru/chapter9/9.mdx @@ -0,0 +1,239 @@ + + +# Тест в конце главы[[end-of-chapter-quiz]] + + + +Давайте проверим, чему вы научились в этой главе! + +### 1. Для чего можно использовать Gradio? + +share=True в методе запуска, вы можете сгенерировать ссылку для общего доступа, чтобы отправить ее всем желающим.", + correct: true + }, + { + text: "Отладка вашей модели", + explain: "Одно из преимуществ демо Gradio - возможность протестировать модель на реальных данных, которые можно изменять и наблюдать за изменением прогнозов модели в режиме реального времени, что поможет вам отладить модель.", + correct: true + }, + { + text: "Обучить вашу модель", + explain: "Gradio разработана для инференса модели, ПОСЛЕ того как модель обучена.", + } + ]} +/> + +### 2. Gradio работает ТОЛЬКО с моделями PyTorch + + + +### 3. Где можно запустить демо Gradio? + + + +### 4. Gradio предназначен в первую очередь для моделей NLP + + + +### 5. Какие из следующих функций поддерживаются Gradio? + +gr.Interface.load().", + correct: true + } + ]} +/> + +### 6. Какие из следующих способов загрузки модели Hugging Face из Hub или Spaces являются правильными? + + + +### 7. Выберите все шаги, необходимые для добавления состояния в интерфейс Gradio + + + +### 8. Какие из перечисленных ниже компонентов входят в библиотеку Gradio? + + + +### 9. Что позволяет делать Gradio `Blocks`? + + + +### 10. Вы можете поделиться публичной ссылкой на демо `Blocks` и разместить демо `Blocks` в пространстве Hugging Face. + + \ No newline at end of file diff --git a/chapters/ru/events/1.mdx b/chapters/ru/events/1.mdx new file mode 100644 index 000000000..5b94f5eb6 --- /dev/null +++ b/chapters/ru/events/1.mdx @@ -0,0 +1,34 @@ +# Занятия и семинары[[live-sessions-and-workshops]] + +В связи с выходом первой и второй частей курса мы организовали несколько занятий и семинаров по программированию. Ссылки на записи этих сессий и семинаров вы можете найти ниже. + +## Занятия по программированию[[live-coding-sessions]] + +На первом занятии Сильвен вместе с вами изучит Главу 1 курса, объясняя ее шаг за шагом: + + + +На втором занятии настал черед Льюиса представить главу 2: + + + +Поскольку Глава 2 настолько классная, Сильвен также сделал видео к ней! + + + +В Главе 3 Льюис возвращается, чтобы обьяснить вам код: + + + +Наконец, Омар завершает занятия, связанные с первой частью курса, рассмотрением главы 4: + + + +## Семинары[[workshops]] +На первом семинаре Мерве приглашает Льюиса обсудить раздел 7 главы 7, посвященный задаче [ответа на вопросы](https://huggingface.co/learn/nlp-course/ru/chapter7/7). + + + +На втором семинаре Мерве приглашает Леандро рассказать о 6 разделе 7 главы, посвященном [обучению каузальной языковой модели с нуля](https://huggingface.co/learn/nlp-course/ru/chapter7/6) и приложению [CodeParrot](https://huggingface.co/codeparrot). + + diff --git a/chapters/ru/events/2.mdx b/chapters/ru/events/2.mdx new file mode 100644 index 000000000..443cc1167 --- /dev/null +++ b/chapters/ru/events/2.mdx @@ -0,0 +1,135 @@ +# Событие посвященное выходу 2 части курса[[part-2-release-event]] + +В связи с выходом второй части курса мы организовали живую встречу с двумя днями выступлений перед спринтом по доработке. Если вы пропустили это мероприятие, вы можете просмотреть все выступления, которые перечислены ниже! + +## День 1: Высокоуровневое представление о трансформерах и о том, как их обучать[[day-1-a-high-level-view-of-transformers-and-how-to-train-them]] + +**Томас Вульф:** *Трансферное обучение и рождение библиотеки Transformers* + + + +

+A visual summary of Thom's talk +

+ +Томас Вольф - соучредитель и главный научный директор компании Hugging Face. Инструменты, созданные Томасом Вольфом и командой Hugging Face, используются более чем в 5 000 исследовательских организаций, включая Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, Институт искусственного интеллекта Аллена, а также большинство университетских факультетов. Томас Вольф является инициатором и старшим председателем крупнейшей исследовательской коллаборации, когда-либо существовавшей в области искусственного интеллекта: ["BigScience"](https://bigscience.huggingface.co), а также набора широко используемых [библиотек и инструментов](https://github.com/huggingface/). Томас Вольф также является активным преподавателем, идейным лидером в области искусственного интеллекта и обработки естественного языка и постоянным приглашенным докладчиком на конференциях по всему миру [https://thomwolf.io](https://thomwolf.io). + +**Джей Аламмар:** *Легкое визуальное введение в модели трансформеры* + + + +

+A visual summary of Jay's talk +

+ +Благодаря своему популярному блогу Джей помог миллионам исследователей и инженеров наглядно понять инструменты и концепции машинного обучения - от базовых (заканчивая документами по NumPy, Pandas) до самых современных (Transformers, BERT, GPT-3). + +**Маргарет Митчелл:** *О ценностях в разработке ML* + + + +

+A visual summary of Margaret's talk +

+ +Маргарет Митчелл - исследователь, работающий в области этики ИИ. В настоящее время она занимается вопросами разработки ИИ с учетом этических норм в технологиях. Она опубликовала более 50 работ по генерации естественного языка, ассистивным технологиям, компьютерному зрению и этике ИИ, а также имеет множество патентов в области генерации диалогов и классификации настроений. Ранее она работала в Google AI в качестве штатного научного сотрудника, где основала и возглавила группу Google Ethical AI, которая занималась фундаментальными исследованиями в области этики ИИ и операционализацией этики ИИ внутри Google. До прихода в Google она работала исследователем в Microsoft Research, занимаясь вопросами создания компьютерного зрения и языка, а также была постдокторантом в Университете Джонса Хопкинса, занимаясь байесовским моделированием и извлечением информации. Она получила докторскую степень в области компьютерных наук в Абердинском университете и степень магистра в области вычислительной лингвистики в Вашингтонском университете. В период с 2005 по 2012 год она также работала в области машинного обучения, неврологических расстройств и вспомогательных технологий в Орегонском университете здоровья и науки. Она была инициатором ряда семинаров и инициатив на пересечении многообразия, инклюзивности, компьютерных наук и этики. Ее работа была отмечена наградами министра обороны Эша Картера и Американского фонда поддержки слепых, а также была внедрена несколькими технологическими компаниями. Она любит садоводство, собак и кошек. + +**Мэттью Уотсон и Чэнь Цянь:** *Рабочие процессы NLP с Keras* + + + +

+A visual summary of Matt and Chen's talk +

+ +Мэтью Уотсон - инженер по машинному обучению в команде Keras, специализирующийся на высокоуровневых API для моделирования. Он изучал компьютерную графику в бакалавриате и получил степень магистра в Стэнфордском университете. Получив английское образование, он выбрал компьютерную науку, но очень любит работать с разными дисциплинами и делать NLP доступным для широкой аудитории. + +Чен Цянь - инженер-программист из команды Keras, специализирующийся на высокоуровневых API для моделирования. Чен получил степень магистра электротехники в Стэнфордском университете и особенно интересуется упрощением имплементации в коде задач ML и крупномасштабным ML проектов. + +**Марк Саруфим:** *Как обучить модель с помощью Pytorch* + + + +

+A visual summary of Mark's talk +

+ +Марк Саруфим - инженер-партнер компании Pytorch, работающий над производственными инструментами OSS, включая TorchServe и Pytorch Enterprise. В прошлом Марк был прикладным ученым и менеджером по продуктам в Graphcore, [yuri.ai](http://yuri.ai/), Microsoft и NASA's JPL. Его главная страсть - сделать программирование более увлекательным. + +**Якоб Ушкорейт:** *Не сломалось, так не чини! Давай сломаем)* + + + +

+A visual summary of Jakob's talk +

+ +Якоб Ушкорейт - соучредитель компании Inceptive. Inceptive разрабатывает молекулы РНК для вакцин и терапевтических препаратов, используя крупномасштабное глубокое обучение в тесной связке с результативными экспериментами, с целью сделать лекарства на основе РНК более доступными, эффективными и широко применимыми. Ранее Якоб более десяти лет работал в Google, возглавляя группы исследований и разработок в Google Brain, Research and Search, работая над основами глубокого обучения, компьютерным зрением, пониманием языка и машинным переводом. + +## День 2: Инструменты, которые следует использовать[[day-2-the-tools-to-use]] + +**Льюис Танстолл:** *Простое обучение с 🤗 Transformers Trainer* + + + +Льюис - инженер по машинному обучению в компании Hugging Face, занимающейся разработкой инструментов с открытым исходным кодом и делающий их доступными для широкого круга пользователей. Он также является соавтором книги O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Вы можете следовать за ним в Twitter (@_lewtun) для получения советов и рекомендаций по NLP! + +**Мэтью Кэрриган:** *Новые функции TensorFlow для 🤗 Transformers и 🤗 Datasets* + + + +Мэтт отвечает за поддержку TensorFlow в Transformers и в конечном итоге возглавит переворот против действующей фракции PyTorch, который, вероятно, будет координироваться через его аккаунт в Twitter @carrigmat. + +**Лисандр Дебю:** *Hugging Face Hub как средство для совместной работы и обмена проектами по машинному обучению* + + + +

+A visual summary of Lysandre's talk +

+ +Лисандр - инженер по машинному обучению в компании Hugging Face, где он участвует во многих проектах с открытым исходным кодом. Его цель - сделать машинное обучение доступным для всех, разрабатывая мощные инструменты с очень простым API. + +**Люсиль Сольнье:** *Получите свой собственный токенизатор с помощью 🤗 Transformers & 🤗 Tokenizers* + + + +Люсиль - инженер по машинному обучению в компании Hugging Face, занимается разработкой и поддержкой использования инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области обработки естественного языка, таких как коллаборативное обучение и BigScience. + +**Сильвен Гуггер:** *Ускорьте цикл обучения PyTorch с помощью 🤗 Accelerate* + + + +Сильвен - инженер-исследователь в Hugging Face, один из основных сопровождающих 🤗 Transformers и разработчик 🤗 Accelerate. Ему нравится делать обучение моделей более доступным. + +**Мерве Ноян:** *Демонстрируйте свои демо моделей с помощью 🤗 Spaces* + + + +Мерве - сторонник разработчиков (developer advocate) в Hugging Face, занимается разработкой инструментов и созданием контента для них, чтобы сделать машинное обучение демократичным для всех. + +**Абубакар Абид:** *Быстрое создание приложений машинного обучения* + + + +

+A visual summary of Abubakar's talk +

+ +Абубакар Абид - CEO компании [Gradio](www.gradio.app). В 2015 году он получил степень бакалавра наук по электротехнике и информатике в Массачусетском технологическом институте, а в 2021 году - степень доктора наук по прикладному машинному обучению в Стэнфорде. В качестве генерального директора Gradio Абубакар работает над тем, чтобы облегчить демонстрацию, отладку и развертывание моделей машинного обучения. + +**Матье Десве:** *AWS ML Vision: Сделать машинное обучение доступным для всех пользователей* + + + +

+A visual summary of Mathieu's talk +

+ +Энтузиаст технологий, в свободное время занимаюсь творчеством. Мне нравятся задачи и решение проблем клиентов и пользователей, а также работа с талантливыми людьми, чтобы учиться каждый день. С 2004 года я работаю на разных должностях, начиная с фронтенда, бэкенда, инфраструктуры, заканчивая операциями и управлением. Стараюсь оперативно решать общие технические и управленческие вопросы. + +**Филипп Шмид:** *Управляемое обучение с Amazon SageMaker и 🤗 Transformers* + + + +Филипп Шмид - инженер по машинному обучению и технический руководитель в Hugging Face, где он возглавляет сотрудничество с командой Amazon SageMaker. Он увлечен демократизацией и производством передовых моделей NLP и повышением простоты использования Deep Learning. diff --git a/chapters/ru/events/3.mdx b/chapters/ru/events/3.mdx new file mode 100644 index 000000000..d65badcbc --- /dev/null +++ b/chapters/ru/events/3.mdx @@ -0,0 +1,9 @@ +# Вечеринка Gradio Blocks[[gradio-blocks-party]] + +Одновременно с выпуском главы курса Gradio компания Hugging Face провела мероприятие для сообщества, посвященное созданию крутых демо машинного обучения с помощью новой функции Gradio Blocks. + +Все демо, созданные сообществом, вы можете найти в организации [`Gradio-Blocks`](https://huggingface.co/Gradio-Blocks) на Hub. Вот несколько примеров от победителей: + +**Преобразование естественного языка в SQL** + + diff --git a/chapters/th/chapter0/1.mdx b/chapters/th/chapter0/1.mdx index 126c706be..f4f3e2304 100644 --- a/chapters/th/chapter0/1.mdx +++ b/chapters/th/chapter0/1.mdx @@ -88,7 +88,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` คุณสามารถตรวจสอบได้ว่า คุณอยู่ใน environment ใดได้ด้วยคำสั่ง `which python` ระบบจะแสดงผล environment ที่คุณกำลังใช้งานอยู่ diff --git a/chapters/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 79444c44c..a9479c075 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -84,10 +84,14 @@ model.train_on_batch(batch, labels) {/if} -Hub นั้นไม่ได้เก็บเพียงแค่โมเดล แต่ยังเก็บชุดข้อมูลในหลากหลายภาษาไว้เป็นจำนวนมาก คุณสามารถเลือกดูชุดข้อมูลต่าง ๆ ได้ที่ [here](https://huggingface.co/datasets) และเราขอแนะนำให้คุณลองโหลดและประมวลผลชุดข้อมูลชุดใหม่หลังจากที่คุณเรียน section นี้จบแล้ว (ดูเอกสารข้อมูลทั่วไปได้ที่ [here](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)) แต่ตอนนี้เรามาสนใจกับชุดข้อมูล MRPC กันก่อนนะ! ชุดข้อมูลนี้เป็นหนึ่งในสิบของชุดข้อมูลที่ใช้วัดผลใน [GLUE benchmark](https://gluebenchmark.com/) ซึ่งเป็นตัววัดผลทางวิชาการ (academic benchmark) ที่ใช้วัดประสิทธิภาพของโมเดล ML โดยให้โมเดลทำงานจำแนกข้อความแบบต่าง ๆ กัน รวม 10 งาน +Hub นั้นไม่ได้เก็บเพียงแค่โมเดล แต่ยังเก็บชุดข้อมูลในหลากหลายภาษาไว้เป็นจำนวนมาก คุณสามารถเลือกดูชุดข้อมูลต่าง ๆ ได้ที่ [here](https://huggingface.co/datasets) และเราขอแนะนำให้คุณลองโหลดและประมวลผลชุดข้อมูลชุดใหม่หลังจากที่คุณเรียน section นี้จบแล้ว (ดูเอกสารข้อมูลทั่วไปได้ที่ [here](https://huggingface.co/docs/datasets/loading)) แต่ตอนนี้เรามาสนใจกับชุดข้อมูล MRPC กันก่อนนะ! ชุดข้อมูลนี้เป็นหนึ่งในสิบของชุดข้อมูลที่ใช้วัดผลใน [GLUE benchmark](https://gluebenchmark.com/) ซึ่งเป็นตัววัดผลทางวิชาการ (academic benchmark) ที่ใช้วัดประสิทธิภาพของโมเดล ML โดยให้โมเดลทำงานจำแนกข้อความแบบต่าง ๆ กัน รวม 10 งาน ไลบรารี่ 🤗 Datasets library มีคำสั่งที่ใช้งานได้ง่ายมากในการดาวโหลดและ cache ชุดข้อมูลที่อยู่บน Hub เราสามารถดาวโหลดชุดข้อมูล MRPC ได้ดังนี้: + +⚠️ **คำเตือน** ตรวจสอบให้แน่ใจว่า `datasets` ได้ถูกติดตั้งโดยการรัน `pip install datasets` ก่อน จากนั้นโหลดชุดข้อมูล MRPC และพิมพ์เพื่อดูว่ามีอะไรบ้าง + + ```py from datasets import load_dataset @@ -235,7 +239,7 @@ tokenized_dataset = tokenizer( ซึ่งการเขียนคำสั่งแบบนี้ก็ได้ผลลัพธ์ที่ถูกต้อง แต่จะมีจุดด้อยคือการทำแบบนี้จะได้ผลลัพธ์ออกมาเป็น dictionary (โดยมี keys ต่าง ๆ คือ `input_ids`, `attention_mask` และ `token_type_ids` และมี values เป็น lists ของ lists) วิธีการนี้จะใช้การได้ก็ต่อเมื่อคอมพิวเตอร์ของคุณมี RAM เพียงพอที่จะเก็บข้อมูลของทั้งชุดข้อมูลในตอนที่ทำการ tokenize ชุดข้อมูลทั้งหมด (ในขณะที่ dataset ที่ได้จากไลบรารี่ 🤗 Datasets จะเป็นไฟล์ [Apache Arrow](https://arrow.apache.org/) ซึ่งจะเก็บข้อมูลไว้ใน disk คุณจึงสามารถโหลดเฉพาะข้อมูลที่ต้องการมาเก็บไว้ใน memory ได้) -เพื่อให้ข้อมูลของเรายังเป็นข้อมูลชนิด dataset เราจะใช้เมธอด [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) ซึ่งจะช่วยให้เราเลือกได้ว่าเราจะทำการประมวลผลอื่น ๆ นอกเหนือจากการ tokenize หรือไม่ โดยเมธอด `map()` ทำงานโดยการเรียกใช้ฟังก์ชั่นกับแต่ละ element ของชุดข้อมูล เรามาสร้างฟังก์ชั่นสำหรับ tokenize ข้อมูลของเรากันก่อน: +เพื่อให้ข้อมูลของเรายังเป็นข้อมูลชนิด dataset เราจะใช้เมธอด [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) ซึ่งจะช่วยให้เราเลือกได้ว่าเราจะทำการประมวลผลอื่น ๆ นอกเหนือจากการ tokenize หรือไม่ โดยเมธอด `map()` ทำงานโดยการเรียกใช้ฟังก์ชั่นกับแต่ละ element ของชุดข้อมูล เรามาสร้างฟังก์ชั่นสำหรับ tokenize ข้อมูลของเรากันก่อน: ```py def tokenize_function(example): diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 30369b8b3..5d7e90c33 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -29,14 +29,14 @@ library นี้ ประกอบด้วยส่วนหลักคือ `Tokenizer` class ที่มีส่วนประกอบย่อยสำคัญอื่นๆ แบ่งเป็นหลาย submodules -- `normalizers` ประกอบไปด้วย `Normalizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)). -- `pre_tokenizers` ประกอบไปด้วย `PreTokenizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)). -- `models` ประกอบไปด้วย `Model` หลายประเภทที่คุณสามารถใช้ได้ เช่น `BPE`, `WordPiece`, และ `Unigram` (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)). -- `trainers` ประกอบไปด้วย `Trainer` หลายประเภทที่คุณสามารถใช้เพื่อเทรนโมเดลด้วย corpus ที่มีได้ (แต่ละโมเดลจะมีเทรนเนอร์อย่างละตัว; รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)). -- `post_processors` ประกอบไปด้วย `PostProcessor` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)) -- `decoders` ประกอบไปด้วย `Decoder` หลายประเภทที่ใช้เพื่อ decode ผลลัพธ์จากการ tokenization (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)) - -คุณสามารถดูรายชื่อของส่วนประกอบสำคัญๆต่างๆได้[ที่นี่](https://huggingface.co/docs/tokenizers/python/latest/components.html) +- `normalizers` ประกอบไปด้วย `Normalizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` ประกอบไปด้วย `PreTokenizer` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` ประกอบไปด้วย `Model` หลายประเภทที่คุณสามารถใช้ได้ เช่น `BPE`, `WordPiece`, และ `Unigram` (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` ประกอบไปด้วย `Trainer` หลายประเภทที่คุณสามารถใช้เพื่อเทรนโมเดลด้วย corpus ที่มีได้ (แต่ละโมเดลจะมีเทรนเนอร์อย่างละตัว; รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` ประกอบไปด้วย `PostProcessor` หลายประเภทที่คุณสามารถใช้ได้ (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/api/post-processors)) +- `decoders` ประกอบไปด้วย `Decoder` หลายประเภทที่ใช้เพื่อ decode ผลลัพธ์จากการ tokenization (รายชื่อทั้งหมดดูได้[ที่นี่](https://huggingface.co/docs/tokenizers/components#decoders)) + +คุณสามารถดูรายชื่อของส่วนประกอบสำคัญๆต่างๆได้[ที่นี่](https://huggingface.co/docs/tokenizers/components) ## การโหลด corpus diff --git a/chapters/tr/chapter0/1.mdx b/chapters/tr/chapter0/1.mdx index 54ffbbd66..eb9e9c49c 100644 --- a/chapters/tr/chapter0/1.mdx +++ b/chapters/tr/chapter0/1.mdx @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` `which python` komutunu kullanarak hangi Python kurulumunda olduğunuzu öğrenebilirsiniz: Eğer sanal ortamı gösteriyorsa sizi tebrik ederim başarıyla sanal ortam kurmuşsunuz demektir! diff --git a/chapters/vi/chapter0/1.mdx b/chapters/vi/chapter0/1.mdx index 6dbefb5ca..256b82c4c 100644 --- a/chapters/vi/chapter0/1.mdx +++ b/chapters/vi/chapter0/1.mdx @@ -94,7 +94,7 @@ Bạn có thể vào và thoát ra khỏi môi trường ảo của mình bằng source .env/bin/activate # Huỷ kích hoạt môi trường ảo -source .env/bin/deactivate +deactivate ``` Bạn có thể đảm bảo rằng môi trường đã được kích hoạt bằng cách chạy lệnh `which python`: nếu nó trỏ đến môi trường ảo thì bạn đã kích hoạt nó thành công! diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index af766f084..3d2b3af2d 100644 --- a/chapters/vi/chapter3/2.mdx +++ b/chapters/vi/chapter3/2.mdx @@ -84,7 +84,7 @@ Trong phần này, chúng tôi sẽ sử dụng tập dữ liệu MRPC (Microsof {/if} -Hub không chỉ chứa các mô hình; nó cũng có nhiều bộ dữ liệu nhiều ngôn ngữ khác nhau. Bạn có thể xem qua tập dữ liệu [tại đây](https://huggingface.co/datasets) và chúng tôi khuyên bạn nên thử tải và xử lý bộ dữ liệu mới khi bạn đã xem qua phần này (xem tài liệu chung [tại đây](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Nhưng hiện tại, hãy tập trung vào bộ dữ liệu MRPC! Đây là một trong 10 bộ dữ liệu tạo nên [bộ chuẩn GLUE](https://gluebenchmark.com/), là một điểm chuẩn học thuật được sử dụng để đo hiệu suất của các mô hình ML trên 10 tác vụ phân loại văn bản khác nhau. +Hub không chỉ chứa các mô hình; nó cũng có nhiều bộ dữ liệu nhiều ngôn ngữ khác nhau. Bạn có thể xem qua tập dữ liệu [tại đây](https://huggingface.co/datasets) và chúng tôi khuyên bạn nên thử tải và xử lý bộ dữ liệu mới khi bạn đã xem qua phần này (xem tài liệu chung [tại đây](https://huggingface.co/docs/datasets/loading)). Nhưng hiện tại, hãy tập trung vào bộ dữ liệu MRPC! Đây là một trong 10 bộ dữ liệu tạo nên [bộ chuẩn GLUE](https://gluebenchmark.com/), là một điểm chuẩn học thuật được sử dụng để đo hiệu suất của các mô hình ML trên 10 tác vụ phân loại văn bản khác nhau. Thư viện 🤗 Datasets cung cấp một lệnh rất đơn giản để tải xuống và lưu vào bộ nhớ cache một tập dữ liệu trên Hub. Chúng ta có thể tải xuống bộ dữ liệu MRPC như sau: @@ -235,7 +235,7 @@ tokenized_dataset = tokenizer( Điều này hoạt động tốt, nhưng nó có nhược điểm là trả về từ điển (với các khóa của chúng tôi, `input_ids`, `attention_mask` và `token_type_ids`, và các giá trị là danh sách các danh sách). Nó cũng sẽ chỉ hoạt động nếu bạn có đủ RAM để lưu trữ toàn bộ tập dữ liệu của mình trong quá trình tokenize (trong khi các tập dữ liệu từ thư viện 🤗 Datasets là các tệp [Apache Arrow](https://arrow.apache.org/) được lưu trữ trên đĩa, vì vậy bạn chỉ giữ các mẫu bạn yêu cầu đã tải trong bộ nhớ). -Để giữ dữ liệu dưới dạng tập dữ liệu, chúng ta sẽ sử dụng phương thức [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Điều này cũng cho phép chúng ta linh hoạt hơn, nếu chúng ta cần thực hiện nhiều tiền xử lý hơn là chỉ tokenize. Phương thức `map()` hoạt động bằng cách áp dụng một hàm trên mỗi phần tử của tập dữ liệu, vì vậy hãy xác định một hàm tokenize các đầu vào của chúng ta: +Để giữ dữ liệu dưới dạng tập dữ liệu, chúng ta sẽ sử dụng phương thức [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map). Điều này cũng cho phép chúng ta linh hoạt hơn, nếu chúng ta cần thực hiện nhiều tiền xử lý hơn là chỉ tokenize. Phương thức `map()` hoạt động bằng cách áp dụng một hàm trên mỗi phần tử của tập dữ liệu, vì vậy hãy xác định một hàm tokenize các đầu vào của chúng ta: ```py def tokenize_function(example): diff --git a/chapters/vi/chapter5/2.mdx b/chapters/vi/chapter5/2.mdx index 25f910110..660739c0a 100644 --- a/chapters/vi/chapter5/2.mdx +++ b/chapters/vi/chapter5/2.mdx @@ -128,7 +128,7 @@ DatasetDict({ -Tham số `data_files` của hàm `load_dataset()` khá linh hoạt và có thể là một đường dẫn tệp duy nhất, danh sách các đường dẫn tệp hoặc từ điển ánh xạ các tên tách thành đường dẫn tệp. Bạn cũng có thể tập hợp các tệp phù hợp với một mẫu được chỉ định theo các quy tắc được sử dụng bởi Unix shell (ví dụ: bạn có thể tổng hợp tất cả các tệp JSON trong một thư mục dưới dạng một lần tách duy nhất bằng cách đặt `data_files="*.json"`). Xem [tài liệu](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) 🤗 Datasets để biết thêm chi tiết. +Tham số `data_files` của hàm `load_dataset()` khá linh hoạt và có thể là một đường dẫn tệp duy nhất, danh sách các đường dẫn tệp hoặc từ điển ánh xạ các tên tách thành đường dẫn tệp. Bạn cũng có thể tập hợp các tệp phù hợp với một mẫu được chỉ định theo các quy tắc được sử dụng bởi Unix shell (ví dụ: bạn có thể tổng hợp tất cả các tệp JSON trong một thư mục dưới dạng một lần tách duy nhất bằng cách đặt `data_files="*.json"`). Xem [tài liệu](https://huggingface.co/docs/datasets/loading#local-and-remote-files) 🤗 Datasets để biết thêm chi tiết. @@ -160,6 +160,6 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -✏️ **Thử nghiệm thôi!** Chọn một tập dữ liệu khác được lưu trữ trên GitHub hoặc [Kho lưu trữ Học Máy UCI](https://archive.ics.uci.edu/ml/index.php) và thử tải nó cả cục bộ và từ xa bằng cách sử dụng các kỹ thuật đã giới thiệu ở trên. Để có điểm thưởng, hãy thử tải tập dữ liệu được lưu trữ ở định dạng CSV hoặc dạng văn bản (xem [tài liệu](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) để biết thêm thông tin trên các định dạng này). +✏️ **Thử nghiệm thôi!** Chọn một tập dữ liệu khác được lưu trữ trên GitHub hoặc [Kho lưu trữ Học Máy UCI](https://archive.ics.uci.edu/ml/index.php) và thử tải nó cả cục bộ và từ xa bằng cách sử dụng các kỹ thuật đã giới thiệu ở trên. Để có điểm thưởng, hãy thử tải tập dữ liệu được lưu trữ ở định dạng CSV hoặc dạng văn bản (xem [tài liệu](https://huggingface.co/docs/datasets/loading#local-and-remote-files) để biết thêm thông tin trên các định dạng này). diff --git a/chapters/vi/chapter5/3.mdx b/chapters/vi/chapter5/3.mdx index 70eb79d19..1c0035536 100644 --- a/chapters/vi/chapter5/3.mdx +++ b/chapters/vi/chapter5/3.mdx @@ -237,7 +237,7 @@ Như bạn có thể thấy, điều này đã loại bỏ khoảng 15% bài đ -✏️ **Thử nghiệm thôi!** Sử dụng hàm `Dataset.sort()` để kiểm tra các bài đánh giá có số lượng từ lớn nhất. Tham khảo [tài liệu](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) để biết bạn cần sử dụng tham số nào để sắp xếp các bài đánh giá theo thứ tự giảm dần. +✏️ **Thử nghiệm thôi!** Sử dụng hàm `Dataset.sort()` để kiểm tra các bài đánh giá có số lượng từ lớn nhất. Tham khảo [tài liệu](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.sort) để biết bạn cần sử dụng tham số nào để sắp xếp các bài đánh giá theo thứ tự giảm dần. @@ -384,7 +384,7 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -Ôi không! Nó đã không hoạt động! Tại sao không? Nhìn vào thông báo lỗi sẽ cho chúng ta manh mối: có sự không khớp về độ dài của một trong các cột, một cột có độ dài 1,463 và cột còn lại có độ dài 1,000. Nếu bạn đã xem [tài liệu](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map) về `Dataset.map()`, bạn có thể nhớ rằng đó là số các mẫu được truyền vào hàm mà chúng ta đang ánh xạ; ở đây 1,000 mẫu đó đã cung cấp 1,463 đặc trưng mới, dẫn đến lỗi hình dạng. +Ôi không! Nó đã không hoạt động! Tại sao không? Nhìn vào thông báo lỗi sẽ cho chúng ta manh mối: có sự không khớp về độ dài của một trong các cột, một cột có độ dài 1,463 và cột còn lại có độ dài 1,000. Nếu bạn đã xem [tài liệu](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) về `Dataset.map()`, bạn có thể nhớ rằng đó là số các mẫu được truyền vào hàm mà chúng ta đang ánh xạ; ở đây 1,000 mẫu đó đã cung cấp 1,463 đặc trưng mới, dẫn đến lỗi hình dạng. Vấn đề là chúng ta đang cố gắng kết hợp hai tập dữ liệu khác nhau với các kích thước khác nhau: cột `drug_dataset` sẽ có một số mẫu nhất định (lỗi phía chúng ta là 1,000), nhưng `tokenized_dataset` mà chúng ta đang xây dựng sẽ có nhiều hơn (1,463 trong thông báo lỗi). Điều này không hoạt động đối với `Dataset`, vì vậy chúng ta cần xóa các cột khỏi tập dữ liệu cũ hoặc làm cho chúng có cùng kích thước với chúng trong tập dữ liệu mới. Chúng ta có thể thực hiện điều đầu thông qua tham số `remove_columns`: diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx index 381224111..c105b0b28 100644 --- a/chapters/vi/chapter5/4.mdx +++ b/chapters/vi/chapter5/4.mdx @@ -54,7 +54,7 @@ Chúng ta có thể thấy rằng có 15,518,009 hàng và 2 cột trong tập d -✎ Theo mặc định, 🤗 Datasets sẽ giải nén các tệp cần thiết để tải tập dữ liệu. Nếu bạn muốn bảo toàn dung lượng ổ cứng, bạn có thể truyền `DownloadConfig(delete_extracted=True)` vào tham số `download_config` của `load_dataset()`. Xem [tài liệu](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig) để biết thêm chi tiết. +✎ Theo mặc định, 🤗 Datasets sẽ giải nén các tệp cần thiết để tải tập dữ liệu. Nếu bạn muốn bảo toàn dung lượng ổ cứng, bạn có thể truyền `DownloadConfig(delete_extracted=True)` vào tham số `download_config` của `load_dataset()`. Xem [tài liệu](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) để biết thêm chi tiết. diff --git a/chapters/vi/chapter5/5.mdx b/chapters/vi/chapter5/5.mdx index d8faafb4f..9b05d75d6 100644 --- a/chapters/vi/chapter5/5.mdx +++ b/chapters/vi/chapter5/5.mdx @@ -435,7 +435,7 @@ Tuyệt vời, chúng ta đã đưa tập dữ liệu của mình vào Hub và n -💡 Bạn cũng có thể tải tập dữ liệu lên Hugging Face Hub trực tiếp từ thiết bị đầu cuối bằng cách sử dụng `huggingface-cli` và một chút phép thuật từ Git. Tham khảo [hướng dẫn 🤗 Datasets](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) để biết chi tiết về cách thực hiện việc này. +💡 Bạn cũng có thể tải tập dữ liệu lên Hugging Face Hub trực tiếp từ thiết bị đầu cuối bằng cách sử dụng `huggingface-cli` và một chút phép thuật từ Git. Tham khảo [hướng dẫn 🤗 Datasets](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) để biết chi tiết về cách thực hiện việc này. diff --git a/chapters/vi/chapter5/6.mdx b/chapters/vi/chapter5/6.mdx index cfaca863b..6c29fff33 100644 --- a/chapters/vi/chapter5/6.mdx +++ b/chapters/vi/chapter5/6.mdx @@ -190,7 +190,7 @@ Dataset({ -✏️ **Thử nghiệm thôi!** Cùng xem liệu bạn có thể sử dụng `Dataset.map()` để khám phá cột `comments` của `issues_dataset` _mà không cần_ sử dụng Pandas hay không. Nó sẽ hơi khó khăn một chút; bạn có thể xem phần ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) của tài liệu 🤗 Datasets, một tài liệu hữu ích cho tác vụ này. +✏️ **Thử nghiệm thôi!** Cùng xem liệu bạn có thể sử dụng `Dataset.map()` để khám phá cột `comments` của `issues_dataset` _mà không cần_ sử dụng Pandas hay không. Nó sẽ hơi khó khăn một chút; bạn có thể xem phần ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) của tài liệu 🤗 Datasets, một tài liệu hữu ích cho tác vụ này. diff --git a/chapters/vi/chapter6/8.mdx b/chapters/vi/chapter6/8.mdx index 7ce6fd78e..aa93a0fed 100644 --- a/chapters/vi/chapter6/8.mdx +++ b/chapters/vi/chapter6/8.mdx @@ -25,14 +25,14 @@ Thư viện 🤗 Tokenizers đã được xây dựng để cung cấp nhiều s Chính xác hơn, thư viện được xây dựng tập trung vào lớp `Tokenizer` với các khối được tập hợp lại trong các mô-đun con: -- `normalizers` chứa tất cả các kiểu `Normalizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers)). -- `pre_tokenizers` chứa tất cả các kiểu `PreTokenizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers)). -- `models` chứa tất cả các kiểu `Model` bạn có thể sử dụng, như `BPE`, `WordPiece`, and `Unigram` (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models)). -- `trainers` chứa tất cả các kiểu `Trainer` khác nhau bạn có thể sử dụng để huấn luyện mô hình của bạn trên kho ngữ liệu (một cho mỗi loại mô hình; hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers)). -- `post_processors` chứa tất cả các kiểu `PostProcessor` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors)). -- `decoders` chứa tất cả các kiểu `Decoder` đa dạng bạn có thể sử dụng để giải mã đầu ra của tokenize (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders)). - -Bạn có thể tìm được toàn bộ danh sách các khối tại [đây](https://huggingface.co/docs/tokenizers/python/latest/components.html). +- `normalizers` chứa tất cả các kiểu `Normalizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/normalizers)). +- `pre_tokenizers` chứa tất cả các kiểu `PreTokenizer` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/pre-tokenizers)). +- `models` chứa tất cả các kiểu `Model` bạn có thể sử dụng, như `BPE`, `WordPiece`, and `Unigram` (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/models)). +- `trainers` chứa tất cả các kiểu `Trainer` khác nhau bạn có thể sử dụng để huấn luyện mô hình của bạn trên kho ngữ liệu (một cho mỗi loại mô hình; hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/trainers)). +- `post_processors` chứa tất cả các kiểu `PostProcessor` bạn có thể sử dụng (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/api/post-processors)). +- `decoders` chứa tất cả các kiểu `Decoder` đa dạng bạn có thể sử dụng để giải mã đầu ra của tokenize (hoàn thiện danh sách tại [đây](https://huggingface.co/docs/tokenizers/components#decoders)). + +Bạn có thể tìm được toàn bộ danh sách các khối tại [đây](https://huggingface.co/docs/tokenizers/components). ## Thu thập một kho ngữ liệu diff --git a/chapters/vi/chapter7/7.mdx b/chapters/vi/chapter7/7.mdx index b183ef44b..516daa7a9 100644 --- a/chapters/vi/chapter7/7.mdx +++ b/chapters/vi/chapter7/7.mdx @@ -355,8 +355,6 @@ print(f"Theoretical answer: {answer}, decoded example: {decoded_example}") 'Theoretical answer: a Marian place of prayer and reflection, decoded example: [CLS] What is the Grotto at Notre Dame? [SEP] Architecturally, the school has a Catholic character. Atop the Main Building\'s gold dome is a golden statue of the Virgin Mary. Immediately in front of the Main Building and facing it, is a copper statue of Christ with arms upraised with the legend " Venite Ad Me Omnes ". Next to the Main Building is the Basilica of the Sacred Heart. Immediately behind the basilica is the Grot [SEP]' ``` -
- Bây giờ chúng ta đã thấy từng bước cách tiền xử lý dữ liệu huấn luyện của mình, chúng ta có thể nhóm nó trong một hàm mà ta sẽ áp dụng trên toàn bộ tập dữ liệu huấn luyện. Chúng ta sẽ đệm mọi đặc trưng đến độ dài tối đa mà ta đã đặt, vì hầu hết các ngữ cảnh sẽ dài (và các mẫu tương ứng sẽ được chia thành nhiều đặc trưng), vì vậy không có lợi ích thực sự nào khi áp dụng đệm động ở đây: Thật vậy, chúng ta không thấy câu trả lời bên trong ngữ cảnh. diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 08335aaf7..0ce25e361 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -57,7 +57,7 @@ title: 使用 Trainer API 或者 Keras 微调一个模型 local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - local: chapter3/4 - title: 一个完成的训练过程 + title: 一个完整的训练过程 - local: chapter3/5 title: 微调,章节回顾! - local: chapter3/6 diff --git a/chapters/zh-CN/chapter0/1.mdx b/chapters/zh-CN/chapter0/1.mdx index 45af8dfc9..8bac2386f 100644 --- a/chapters/zh-CN/chapter0/1.mdx +++ b/chapters/zh-CN/chapter0/1.mdx @@ -1,6 +1,6 @@ # 课程简介 [[课程简介]] -欢迎来到拥抱脸课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 +欢迎来到Hugging Face课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 我们将在本课程中使用的所有库都以 Python 包的形式提供,因此在这里我们将向您展示如何设置 Python 环境并安装您需要的特定库。 @@ -50,9 +50,9 @@ import transformers 如果您更喜欢使用 Python 虚拟环境,那么第一步是在您的系统上安装 Python。我们建议您按照[这个教程](https://realpython.com/installing-python/)进行配置。 -安装 Python 后,您应该能够在终端中运行 Python 命令。您可以先运行以下命令来检验爱装是否正确,然后再继续下一步:**python --version**。这应该会打印出您系统上现在可用的 Python 版本。 +安装 Python 后,您应该能够在终端中运行 Python 命令。您可以先运行以下命令来检验爱装是否正确,然后再继续下一步:`python --version`。这应该会打印出您系统上现在可用的 Python 版本。 -在终端中运行 Python 命令(例如 **python --version**)时,您应该将运行命令的这个Python视为系统上的“默认”Python。我们建议保持这个默认的Python安装程序没有任何包,当运行某个程序的时候就为那个程序创建一个单独的运行环境 - 这样,每个应用程序都可以有自己的依赖项和包,您无需担心与其他应用程序潜在的兼容性问题。 +在终端中运行 Python 命令(例如 `python --version`)时,您应该将运行命令的这个Python视为系统上的“默认”Python。我们建议保持这个默认的Python安装程序没有任何包,当运行某个程序的时候就为那个程序创建一个单独的运行环境 - 这样,每个应用程序都可以有自己的依赖项和包,您无需担心与其他应用程序潜在的兼容性问题。 在 Python 中,这是通过 [*虚拟环境*](https://docs.python.org/3/tutorial/venv.html)实现的,虚拟环境会创建许多目录树,每个目录树都包含具有特定 Python 版本的 Python 安装以及应用程序所需的所有包。可以使用许多不同的工具来创建这样的虚拟环境,但在此我们将使用官方 Python 包:[`venv`](https://docs.python.org/3/library/venv.html#module-venv). @@ -86,7 +86,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` 您可以通过运行 `which python` 命令来检测虚拟环境是否被激活:如果它指向虚拟环境,那么您已经成功激活了它! diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 18d638949..1300aa63b 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -68,14 +68,14 @@ - **如果我有问题,我可以在哪里提问?** 如果您对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动重定向到 [Hugging Face 论坛](https:// discuss.huggingface.co/): -拥抱脸论坛链接 +Hugging Face论坛链接 请注意,如果您想在完成课程后进行更多练习,论坛上还提供了[项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表。 - **我在哪里可以获得课程的代码?** 对于每个部分,单击页面顶部的横幅可以在 Google Colab 或 Amazon SageMaker Studio Lab 中运行代码: -拥抱脸课程笔记本链接 +Hugging Face课程笔记本链接 包含课程所有代码的 Jupyter 笔记本托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。 如果您希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 917ff91db..119f15600 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -17,7 +17,8 @@ ## Transformer被应用于各个方面! [[Transformer被应用于各个方面!]] Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: -![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +使用 Hugging Face 的公司 + [🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! @@ -65,15 +66,15 @@ classifier( 目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: -* **特征提取**(获取文本的向量表示) -* **填充空缺** -* **ner**(命名实体识别) -* **问答** -* **情感分析** -* **文本摘要** -* **文本生成** -* **翻译** -* **零样本分类** +* `feature-extraction`(获取文本的向量表示) +* `fill-mask` +* `ner`(命名实体识别) +* `question-answering` +* `sentiment-analysis` +* `summarization` +* `text-generation` +* `translation` +* `zero-shot-classification` 让我们来看看其中的一些吧! @@ -97,7 +98,7 @@ classifier( 此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! -✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 +✏️**快来试试吧!** 使用您自己的序列和标签,看看模型的行为。 ## 文本生成 [[文本生成]] @@ -119,7 +120,7 @@ generator("In this course, we will teach you how to") 您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 -✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 +✏️**快来试试吧!** 使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 ## 在pipeline中使用 Hub 中的其他模型 [[在pipeline中使用 Hub 中的其他模型]] @@ -148,7 +149,7 @@ generator( 通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 -✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! +✏️**快来试试吧!** 使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! ## 推理 API [[推理 API]] @@ -177,7 +178,7 @@ unmasker("This course will teach you all about models.", top_k=2) **top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 -✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? +✏️**快来试试吧!** 在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? ## 命名实体识别 [[命名实体识别]] @@ -199,7 +200,7 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") 我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 -✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? +✏️**快来试试吧!** 在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? ## 问答系统 [[问答系统]] @@ -280,7 +281,7 @@ translator("Ce cours est produit par Hugging Face.") -✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 +✏️**快来试试吧!** 搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index e68a74f0d..4f7f407bc 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -9,15 +9,14 @@ 创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: - **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 -- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 +- **灵活**:所有模型的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**类,可以像在其各自的机器学习 (ML) 框架中的任何其他模型一样方便地进行处理。 +- **简单**:库中几乎没有任何抽象。 “All in one file”(所有代码在一个文件中)是一个核心概念:模型的前向传播完全定义在一个文件中,因此代码本身易于理解和修改 -最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 +最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的网络结构(layers)。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 -本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 - -然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 +本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和*tokenizer*分词器来复制[Chapter 1](/course/chapter1)中引入的函数`pipeline()`. 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 +接下来,我们将介绍 *tokenizer* API,它是 `pipeline()` 函数的另一个主要组件。*tokenizer* 负责第一个和最后一个处理步骤,处理从文本到神经网络数值输入的转换,以及在需要时将数值转换回文本。最后,我们将向您展示如何处理将多个句子作为一个准备好的批次发送到模型中,然后通过更深入地了解高级 `tokenizer()` 函数来总结所有内容。 -⚠️ 为了从模型集线器和🤗Transformers的所有可用功能中获益,我们建议creating an account. +⚠️ 为了从Model Hub和🤗Transformers的所有可用功能中获益,我们建议创建帐户. \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index bebc97b26..c0242bb79 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -53,7 +53,7 @@ classifier( {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -正如我们在[Chapter 1](/course/chapter1)中看到的,此管道将三个步骤组合在一起:预处理、通过模型传递输入和后处理: +正如我们在[Chapter 1](/course/chapter1)中看到的,这个 pipeline 集成了三个步骤:预处理、模型推理和后处理:
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. @@ -64,15 +64,19 @@ classifier( ## 使用分词器进行预处理 [[使用分词器进行预处理]] -与其他神经网络一样,Transformer模型无法直接处理原始文本, 因此我们管道的第一步是将文本输入转换为模型能够理解的数字。 为此,我们使用*tokenizer*(标记器),负责: +与其他神经网络一样,Transformer模型无法直接处理原始文本, 因此pipeline的第一步是将文本输入转换为模型能够理解的数字。 为此,我们使用*tokenizer*(分词器),负责: - 将输入拆分为单词、子单词或符号(如标点符号),称为标记(*token*) - 将每个标记(token)映射到一个整数 -- 添加可能对模型有用的其他输入 +- 添加其他可能对模型有用的输入:例如,位置编码等信息。 +- - 位置编码:指示每个词元在句子中的位置。 +- - 段落标记:区分不同段落的文本。 +- - 特殊标记:例如 [CLS] 和 [SEP] 标记,用于标识句子的开头和结尾。 -所有这些预处理都需要以与模型预训练时完全相同的方式完成,因此我们首先需要从[Model Hub](https://huggingface.co/models)中下载这些信息。为此,我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称,它将自动获取与模型的标记器相关联的数据,并对其进行缓存(因此只有在您第一次运行下面的代码时才会下载)。 -因为`sentiment-analysis`(情绪分析)管道的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`(你可以看到它的模型卡[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)),我们运行以下程序: +所有预处理都需要以与模型预训练时完全相同的方式完成(保持分词方式的一致性非常重要,可以确保模型的性能和准确性),因此我们首先需要从[Model Hub](https://huggingface.co/models)中下载这些信息。为此,我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称,它将自动获取与模型的标记器相关联的数据,并对其进行缓存(因此只有在您第一次运行下面的代码时才会下载)。 + +因为`sentiment-analysis`(情绪分析)Pipeline的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`(你可以看到它的模型介绍页[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)),我们运行以下程序: ```python from transformers import AutoTokenizer @@ -81,9 +85,9 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" tokenizer = AutoTokenizer.from_pretrained(checkpoint) ``` -一旦我们有了标记器,我们就可以直接将我们的句子传递给它,然后我们就会得到一本字典,它可以提供给我们的模型!剩下要做的唯一一件事就是将输入ID列表转换为张量。 +一旦我们有了tokenizer,就可以直接将句子传递给它,然后我们会得到一个字典,它包含了可以输入到模型的数据。接下来,我们只需要将输入 ID 列表转换为张量即可。 -您可以使用🤗 Transformers,而不必担心哪个ML框架被用作后端;它可能是PyTorch或TensorFlow,或Flax。但是,Transformers型号只接受*张量*作为输入。如果这是你第一次听说张量,你可以把它们想象成NumPy数组。NumPy数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。它实际上是张量;其他ML框架的张量行为类似,通常与NumPy数组一样易于实例化。 +使用 🤗 Transformers 时,您无需担心它背后的机器学习框架,它可以是 PyTorch、TensorFlow或 Flax。不过,Transformer 模型只接受*张量*作为输入。如果您不熟悉张量,可以把它看作 NumPy 数组。NumPy 数组可以是标量 (0D)、向量 (1D)、矩阵 (2D) 或更高维度的数组,本质上就是一个张量。其他机器学习框架中的张量也类似,通常与 NumPy 数组一样容易创建。 要指定要返回的张量类型(PyTorch、TensorFlow或plain NumPy),我们使用`return_tensors`参数: @@ -107,7 +111,7 @@ print(inputs) ``` {/if} -现在不要担心填充和截断;我们稍后会解释这些。这里要记住的主要事情是,您可以传递一个句子或一组句子,还可以指定要返回的张量类型(如果没有传递类型,您将得到一组列表)。 +暂时不用担心填充和截断,我们稍后会解释它们。这里主要需要记住的是,您可以传入单个句子或句子列表,并指定想要返回的张量类型(如果不指定类型,则会返回嵌套列表)。 {#if fw === 'pt'} @@ -145,12 +149,11 @@ print(inputs) ``` {/if} -输出本身是一个包含两个键的字典,`input_ids`和`attention_mask`。`input_ids`包含两行整数(每个句子一行),它们是每个句子中标记的唯一标记(token)。我们将在本章后面解释什么是`attention_mask`。 - -## 浏览模型 [[浏览模型]] +输出本身是一个包含两个键的字典,`input_ids` 和 `attention_mask`。 `input_ids` 包含两行整数(每个句子一行),它们是每个句子中各个 token 的唯一标识符。我们将在本章后面解释 `attention_mask` 的作用。 +## 模型推理 [[模型推理]] {#if fw === 'pt'} -我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类还具有`from_pretrained()`方法: +我们可以像使用*tokenizer*一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类也有`from_pretrained()`方法: ```python from transformers import AutoModel @@ -159,35 +162,33 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModel.from_pretrained(checkpoint) ``` {:else} -我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`TFAutoModel`类,该类还具有`from_pretrained()`方法: +我们可以像使用*tokenizer*一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类也有`from_pretrained()`方法: ```python -from transformers import TFAutoModel +from transformers import AutoModel checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModel.from_pretrained(checkpoint) +model = AutoModel.from_pretrained(checkpoint) ``` {/if} -在这个代码片段中,我们下载了之前在管道中使用的相同检查点(它实际上应该已经被缓存),并用它实例化了一个模型。 +在这段代码中,我们加载了之前在 pipeline 中使用的相同检查点(实际上它应该已经被缓存)并用它实例化了一个模型。 -这个架构只包含基本转换器模块:给定一些输入,它输出我们将调用的内容*隐藏状态(hidden states)*,亦称*特征(features)*。对于每个模型输入,我们将检索一个高维向量,表示**Transformer模型对该输入的上下文理解**。 - -如果这不合理,不要担心。我们以后再解释。 - -虽然这些隐藏状态本身可能很有用,但它们通常是模型另一部分(称为*头部(head)*)的输入。 在[Chapter 1](/course/chapter1)中,可以使用相同的体系结构执行不同的任务,但这些任务中的每个任务都有一个与之关联的不同头。 +这个架构只包含基本的 Transformer 模块:给定一些输入,它会输出我们称之为*隐藏状态 (hidden states)* 的内容,也称为*特征 (features)*。对于每个模型输入,我们将检索一个高维向量,该向量表示 **Transformer 模型对该输入的上下文理解**。 +如果你现在对隐藏状态和上下文理解的概念还不理解,也不用担心,我们会在后续的内容中详细解释这些概念,并举例说明它们是如何工作的。 +虽然这些隐藏状态本身可能很有用,但它们通常作为下游任务的输入,例如模型*头部(head)*的输入。在[Chapter 1](/course/chapter1)中,我们介绍了可以使用相同的架构执行不同的任务,但每个任务都会有不同的头部与之关联。 ### 高维向量? [[高维向量?]] -Transformers模块的矢量输出通常较大。它通常有三个维度: +Transformers模块的向量输出通常较大。它通常有三个维度: -- **Batch size**: 一次处理的序列数(在我们的示例中为2)。 -- **Sequence length**: 序列的数值表示的长度(在我们的示例中为16)。 -- **Hidden size**: 每个模型输入的向量维度。 +- **批次大小(Batch size)**: 一次处理的序列数(在我们的示例中为2)。 +- **序列长度 (Sequence length)**: 序列的数值表示的长度(在我们的示例中为16)。 +- **隐藏层维度(Hidden size)**: 每个模型输入的向量维度。 -由于最后一个值,它被称为“高维”。隐藏的大小可能非常大(768通常用于较小的型号,而在较大的型号中,这可能达到3072或更大)。 +之所以说它是“高维度”的,是因为最后一个维度值。隐藏层维度可以非常大(对于较小的模型768 是常见的,而在较大的模型中,它可以达到 3072 或更多)。 -如果我们将预处理的输入输入到模型中,我们可以看到这一点: +我们可以通过将预处理后的输入数据传递给模型来验证这一点: {#if fw === 'pt'} ```python @@ -209,12 +210,10 @@ print(outputs.last_hidden_state.shape) ``` {/if} -注意🤗 Transformers模型的输出与`namedtuple`或词典相似。您可以通过属性(就像我们所做的那样)或键(`输出["last_hidden_state"]`)访问元素,甚至可以通过索引访问元素,前提是您确切知道要查找的内容在哪里(`outputs[0]`)。 - -### 模型头:数字的意义 [[模型头:数字的意义]] - -模型头将隐藏状态的高维向量作为输入,并将其投影到不同的维度。它们通常由一个或几个线性层组成: +🤗 Transformers 模型的输出类似于 `namedtuple` 或字典。您可以通过属性 (就像我们之前所做的那样) 或键 (例如` outputs["last_hidden_state"]`) 来访问元素,甚至可以通过索引访问元素 (例如 `outputs[0]`),前提是您知道要查找的内容的位置。 +### 模型头:数值的意义 [[模型头:从数值中提取意义]] +模型头接收隐藏状态的高维向量作为输入,并将其映射到另一个维度。它们通常由一个或几个线性层构成:
A Transformer network alongside its head. @@ -238,8 +237,10 @@ Transformers模型的输出直接发送到模型头进行处理。 - 以及其他 🤗 {#if fw === 'pt'} -对于我们的示例,我们需要一个带有序列分类头的模型(能够将句子分类为肯定或否定)。因此,我们实际上不会使用`AutoModel`类,而是使用`AutoModelForSequenceClassification`: +对于我们的示例,我们需要一个带有序列分类头部的模型(能够将句子分类为肯定或否定)。 因此,我们实际上不会使用 `AutoModel` 类,而是使用 `AutoModelForSequenceClassification`(`AutoModel` 类只包含基本的 Transformer 模块,它可以提取文本的特征,但不能进行分类。 +`AutoModelForSequenceClassification` 类在 `AutoModel` 的基础上添加了一个序列分类头部,可以将文本分类为不同的类别。 +): ```python from transformers import AutoModelForSequenceClassification @@ -259,8 +260,8 @@ outputs = model(inputs) ``` {/if} -现在,如果我们观察输出的形状,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): - +现在,如果我们观察输出的shape,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): +正如您所见,输出向量的尺寸与输入向量相比要小得多。这是因为模型头将输入向量中的信息压缩成两个值,每个标签一个 ```python print(outputs.logits.shape) ``` @@ -279,7 +280,7 @@ torch.Size([2, 2]) {/if} -因为我们只有两个句子和两个标签,所以我们从模型中得到的结果是2 x 2的形状。 +因为我们只有两个句子和两个标签,所以我们从模型中得到的结果是2 x 2的维度。 ## 对输出进行后处理 [[对输出进行后处理]] @@ -302,8 +303,7 @@ tensor([[-1.5607, 1.6123], ``` {/if} -我们的模型预测第一句为`[-1.5607, 1.6123]`,第二句为`[ 4.1692, -3.3464]`。这些不是概率,而是*logits*,即模型最后一层输出的原始非标准化分数。要转换为概率,它们需要经过[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)层(所有🤗Transformers模型输出logits,因为用于训练的损耗函数通常会将最后的激活函数(如SoftMax)与实际损耗函数(如交叉熵)融合): - +我们的模型预测第一句为 `[-1.5607, 1.6123]`,第二句为 `[4.1692, -3.3464]`。这些不是概率,而是 *logits*,即模型最后一层输出的原始、未经归一化的分数。为了将其转换为概率,它们需要经过[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)层(所有 🤗 Transformers 模型都输出 logits,因为用于训练的损失函数通常会将最后的激活函数(例如 SoftMax)与实际的损失函数(例如交叉熵)融合在一起。 {#if fw === 'pt'} ```py import torch @@ -333,9 +333,12 @@ tf.Tensor( ``` {/if} -现在我们可以看到,模型预测第一句为`[0.0402, 0.9598]`,第二句为`[0.9995, 0.0005]`。这些是可识别的概率分数。 +现在我们可以看到,模型预测第一句为`[0.0402, 0.9598]`,第二句为`[0.9995, 0.0005]`。这些数字代表了模型预测每个类别(否定或肯定)的概率分值。 为了获得每个位置对应的标签,我们可以检查模型配置的`id2label`属性(下一节将对此进行详细介绍): +为了将这些概率分值转换为可识别的标签(“否定”或“肯定”),我们需要参考模型配置中的 id2label 属性。该属性将每个模型输出的 ID 映射到相应的标签。 + + ```python model.config.id2label @@ -345,13 +348,13 @@ model.config.id2label {0: 'NEGATIVE', 1: 'POSITIVE'} ``` -现在我们可以得出结论,该模型预测了以下几点: - +根据 id2label 属性,我们可以得出以下结论: + + - 第一句:否定:0.0402,肯定:0.9598 - 第二句:否定:0.9995,肯定:0.0005 -我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 - +至此,我们已经成功完成了文本分类任务的三个步骤:使用*tokenizer*对文本进行预处理;将预处理后的文本输入到模型中;对模型的输出结果进行后处理,并将预测结果转换为可识别的标签。接下来,我们将对这三个步骤进行更详细的解释。 ✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 67946ab1a..47a19592e 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -30,19 +30,19 @@ {#if fw === 'pt'} 在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 +AutoModel类,它极大程度的方便您从*checkpoint*实例化任何模型。 -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 +AutoModel 类与其相关类本质上是对库中各种模型的简化包装。其智能之处在于能够自动识别检查点所对应的模型架构,并据此实例化相应的模型。 {:else} 在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 +AutoModel类,它极大程度的方便您从*checkpoint*实例化任何模型。 -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 +AutoModel 类与其相关类本质上是对库中各种模型的简化包装。其智能之处在于能够自动识别检查点所对应的模型架构,并据此实例化相应的模型。 {/if} -但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 +如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 ## 创建转换器 [[创建转换器]] @@ -88,7 +88,7 @@ BertConfig { } ``` -虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 +虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:`hidden_size`属性定义了`hidden_states`向量的大小,`num_hidden_layers`定义了Transformer模型的层数。 ### 不同的加载方式 [[不同的加载方式]] @@ -116,12 +116,12 @@ model = TFBertModel(config) {/if} -该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 +该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练,我们可以根据手头的任务从头开始训练模型,但正如您在 [Chapter 1](/course/chapter1) -,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 +所见,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 -加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() +加载已经训练过的Transformers模型很简单-我们可以使用`from_pretrained()` 方法: {#if fw === 'pt'} @@ -131,7 +131,7 @@ from transformers import BertModel model = BertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如您之前看到的,我们可以用等效的`AutoModel`类替换`BertModel`类。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个*checkpoint*,那么它应该与另一个*checkpoint*无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 {:else} ```py @@ -140,18 +140,16 @@ from transformers import TFBertModel model = TFBertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如您之前看到的,我们可以用等效的`AutoModel`替换`BertModel`类。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个*checkpoint*,那么它应该与另一个*checkpoint*无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 {/if} -在上面的代码示例中,我们没有使用BertConfig - -,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 +在上面的代码示例中,我们没有使用BertConfig,而是通过`bert-base-cased`标识符加载了一个预训练模型。这是一个模型检查点,由`BERT`的作者自己训练;您可以在 [model card](https://huggingface.co/bert-base-cased)中找到更多细节. -该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 +该模型现在使用*checkpoint*的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 @@ -163,7 +161,7 @@ HF_HOME -用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 +用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与`BERT`体系结构兼容。可以找到可用的`BERT`*checkpoint*的完整列表 [here](https://huggingface.co/models?filter=bert) . ### 保存模型 [[保存模型]] @@ -196,21 +194,21 @@ config.json tf_model.h5 如果你看一下 config.json -文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 +文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如*checkpoint*的来源以及上次保存检查点时使用的🤗 Transformers版本。 {#if fw === 'pt'} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 +这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件密切相关;这个配置是了解您的模型架构所必需的,而模型权重则是您模型的参数。 {:else} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 +这个 *tf_model.h5* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件密切相关;这个配置是了解您的模型架构所必需的,而模型权重则是您模型的参数。 {/if} ### 使用Transformers模型进行推理 [[使用Transformers模型进行推理]] -既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 +既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论*Tokenizer*之前,让我们先探讨模型接受哪些输入。 -标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 +*Tokenizer*可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 假设我们有几个序列: @@ -218,8 +216,8 @@ config.json sequences = ["Hello!", "Cool.", "Nice!"] ``` -分词器将这些转换为词汇表索引,通常称为 -input IDs +*Tokenizer*将这些转换为词汇表索引,通常称为 +*input IDs* . 每个序列现在都是一个数字列表!结果是: ```py no-format @@ -230,7 +228,7 @@ encoded_sequences = [ ] ``` -这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: +这是一个编码序列的列表:一个嵌套列表(列表的列表)。张量只接受矩形形状(可以想象成矩阵)。这个“数组”已经是矩形形状,因此将其转换为张量很容易: {#if fw === 'pt'} ```py @@ -259,6 +257,5 @@ output = model(model_inputs) -虽然模型接受许多不同的参数,但只需要 -input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 -Transformer模型可以理解的输入的标记 + +虽然模型接受许多不同的参数,但只有输入 ID 是必需的。 我们将在后面解释其他参数的作用以及何时需要它们,但首先我们需要仔细研究构建 Transformer 模型可以理解的输入的*tokenizer*。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index 2f35dbcb9..576835144 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -189,7 +189,7 @@ batched_ids = [ids, ids] -✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) +✏️ **试试看!** 将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) 批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 @@ -326,7 +326,7 @@ tf.Tensor( -✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! +✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待Hugging Face课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 94fd5d1eb..03f5e61c0 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -84,7 +84,7 @@ model.train_on_batch(batch, labels) {/if} -模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 +模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: @@ -235,7 +235,7 @@ tokenized_dataset = tokenizer( 这很有效,但它的缺点是返回字典(字典的键是**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)**,字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以[Apache Arrow](https://arrow.apache.org/)文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。 -为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: +为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: ```py def tokenize_function(example): @@ -321,8 +321,6 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se 毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: -```py: - ```py batch = data_collator(samples) {k: v.shape for k, v in batch.items()} diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx index 06f6dbccd..bf93482ba 100644 --- a/chapters/zh-CN/chapter3/6.mdx +++ b/chapters/zh-CN/chapter3/6.mdx @@ -11,11 +11,12 @@ Test what you learned in this chapter! -### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? + +### 1.`emotion`数据集包含标记有情绪的 Twitter 消息。在[Hub](https://huggingface.co/datasets)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? -### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? +### 2.在[Hub](https://huggingface.co/datasets)中搜索`ar_sarcasm`数据集,它支持哪个任务? dataset card !" + explain: "不是这样的ーー再看看 数据集卡 !" }, { text: "命名实体识别", - explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + explain: "不是这样的ーー再看看 数据集卡 !" }, { text: "回答问题", - explain: "Alas, this question was not answered correctly. 再试一次!" + explain: "哎呀, 问题回答不正确. 再试一次!" } ]} /> -### 3.BERT 模型期望如何处理一对句子? +### 3.BERT模型期望如何处理一对句子? [SEP]标记来分割两个句子,但这并不是唯一的需求!" }, { - text: "表示句子的记号表示句子的记号", - explain: "一个 < code > [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" + text: "[CLS]句子1 句子2", + explain: "确实在最开始需要一个[CLS]标记的,但是这不是唯一的事情!" }, { - text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", + text: "[CLS]句子1 [SEP] 句子2 [SEP]", explain: "没错!", correct: true }, { - text: "表示句子1[ SEP ]的符号表示句子2", - explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" + text: "[CLS]句子1 [SEP] 句子2", + explain: "开头需要一个[CLS]特殊标记,还需要一个[SEP]特殊标记来分隔两个句子,但这还不是全部!" } ]} /> {#if fw === 'pt'} -### 4.‘ Dataset.map ()’方法的好处是什么? +### 4.`Dataset.map()`方法的好处是什么? @@ -126,25 +127,25 @@ Test what you learned in this chapter! choices={[ { text: "它确保数据集中的所有序列具有相同的长度。", - explain: "校对函数用于处理单个批处理,而不是整个数据集。此外,我们讨论的是通用的校对函数,而不是特定的 < code > > DataCollatorWithPadding 。" + explain: "校对函数用于处理单个批次,而不是整个数据集。此外,我们讨论的是通用的排序函数,而不是专门讨论DataCollatorWithPadding。" }, { - text: "它把所有的样品一批一批地放在一起。", - explain: "正确! You can pass the collate function as an argument of a DataLoader. We used the DataCollatorWithPadding function, which pads all items in a batch so they have the same length.", + text: "它把所有的样品放在一起成一批", + explain: "正确的!可以将校对函数作为DataLoader的参数传递。我们使用了DataCollatorWithPadding函数,该函数填充批处理中的所有项,使它们具有相同的长度。", correct: true }, { - text: "它预处理整个数据集。", + text: "它对整个数据集进行预处理。", explain: "这将是一个预处理函数,而不是校对函数。" }, { text: "它截断数据集中的序列。", - explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" + explain: "校对函数用于处理单个批次,而不是整个数据集。如果您对截断感兴趣,可以使用标记器truncate参数。" } ]} /> -### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? +### 7.当你用一个预先训练过的语言模型(例如`bert-base-uncased`)实例化一个`AutoModelForXxx`类,这个类对应于一个不同于它所被训练的任务时会发生什么? Trainer 所做的,而不是 Accelerate 库。再试一次!", + explain: "正确的。例如,当我们使用AutoModelForSequenceClassification配合bert-base-uncase 时,我们在实例化模型时会得到警告。预训练的头部不用于序列分类任务,因此它被丢弃,并用随机权重实例化一个新的头部。", correct: true }, { text: "丢弃预先训练好的模型头部。", - explain: "Something else needs to happen. 再试一次!" + explain: "还有其他事情需要发生。 再试一次!" }, { - text: "没有,因为模型仍然可以针对不同的任务进行微调。", - explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + text: "无事发生,因为模型仍然可以针对不同的任务进行微调。", + explain: "预训练模型的头部没有被训练来解决这个任务,所以我们应该丢弃头部!!" } ]} /> -### 8.训练争论的目的是什么? +### 8.`TrainingArguments`的目的是什么? 训练器进行训练和评估的所有超参数。", explain: "正确!", correct: true }, { text: "它指定模型的大小。", - explain: "模型大小是由模型配置定义的,而不是类 < code > TrainingArguments 。" + explain: "模型大小是由模型配置定义的,而不是类TrainingArguments。" }, { text: "它只包含用于评估的超参数。", - explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" + explain: "在举例中,我们指定了模型及其检查点的保存位置。再试一次!" }, { - text: "您可以轻松地计算与数据集相关的指标。", - explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" + text: "它只包含用于训练的超参数。", + explain: "在举例中,我们还使用了求值策略,因此这会影响求值。再试一次!" } ]} /> -### 9.为什么要使用 Accelerate 库? +### 9.为什么要使用 🤗Accelerate 库? Trainer, not the 🤗 Accelerate library. 再试一次!" + explain: "这是我们在Trainer所做的,而不是🤗Accelerate库。再试一次!”" }, { text: "它使我们的训练循环工作在分布式策略上", - explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", + explain: "正确! 随着🤗Accelerate库,你的训练循环将为多个GPU和TPU工作。", correct: true }, { text: "它提供了更多的优化功能。", - explain: "不,Accelerate 库不提供任何优化功能。" + explain: "不,🤗Accelerate 库不提供任何优化功能。" } ]} /> {:else} -### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? +### 4.当你用一个预先训练过的语言模型(例如`bert-base-uncased`)实例化一个`TFAutoModelForXxx`类时,会发生什么? TFAutoModelForSequenceClassification配合bert-base-uncase时,我们在实例化模型时会得到警告。预训练的头部不用于序列分类任务,因此它被丢弃,并用随机权重实例化一个新的头部。", correct: true }, { text: "丢弃预先训练好的模型头部。", - explain: "Something else needs to happen. 再试一次!" + explain: "还有其他事情需要发生。 再试一次!" }, { - text: "没有,因为模型仍然可以针对不同的任务进行微调。", - explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + text: "无事发生,因为模型仍然可以针对不同的任务进行微调。", + explain: "预训练模型的头部没有被训练来解决这个任务,所以我们应该丢弃头部!!" } ]} /> -### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? +### 5.来自`transfomers`的 TensorFlow 模型已经是 Keras 模型,这有什么好处? TPUStrategy scope 中的所有内容,包括模型的初始化。" + text: "这些模型在一个开箱即用的TPU上工作。", + explain: "差不多了!还需要进行一些小的额外更改。例如,您需要在TPUStrategy范围内运行所有内容,包括模型的初始化。" }, { - text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () 和 < code > predict () 。", + text: "您可以利用现有的方法,如compile()fit()predict() 。", explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", correct: true }, { - text: "你可以学习 Keras 和变形金刚。", + text: "你可以学习 Keras 和 transformers库。", explain: "没错,但我们要找的是别的东西:)", correct: true }, { - text: "困惑", - explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" + text: "您可以轻松地计算与数据集相关的指标。", + explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的指标。" } ]} /> -### 6.如何定义自己的定制度量? +### 6.如何定义自己的定制指标? tfkeras.metrics. Metric 。", + text: "通过子类化tf.keras.metrics.Metric。", explain: "太好了!", correct: true }, @@ -274,7 +275,7 @@ Test what you learned in this chapter! explain: "再试一次!" }, { - text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", + text: "通过使用带签名的可调用metric_fn(y_true, y_pred) 。", explain: "正确!", correct: true }, diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index 9be4ff273..ba5e26550 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -49,9 +49,9 @@ {/if} -将文件上传到集线器的最简单方法是利用 **push_to_hub** API 接口。 +将文件上传到模型中心的最简单方法是利用 **push_to_hub** API 接口。 -在继续之前,您需要生成一个身份验证令牌,以便 **huggingface_hub** API 知道您是谁以及您对哪些名称空间具有写入权限。确保你在一个环境中 **transformers** 已安装(见[Setup](/course/chapter0))。如果您在笔记本中,可以使用以下功能登录: +在继续之前,您需要生成一个身份验证令牌,以便 **huggingface_hub** API 知道您是谁以及您对哪些名称空间具有写入权限。确保你在一个环境中 **transformers** 已安装(见[安装](/course/chapter0))。如果您在笔记本中,可以使用以下功能登录: ```python from huggingface_hub import notebook_login @@ -65,7 +65,7 @@ notebook_login() huggingface-cli login ``` -在这两种情况下,系统都会提示您输入用户名和密码,这与您用于登录 Hub 的用户名和密码相同。如果您还没有 Hub 配置文件,则应该创建一个[here](https://huggingface.co/join)。 +在这两种情况下,系统都会提示您输入用户名和密码,这与您用于登录 Hub 的用户名和密码相同。如果您还没有 Hub 配置文件,则应该创建一个[创建一个账户](https://huggingface.co/join)。 好的!您现在已将身份验证令牌存储在缓存文件夹中。让我们创建一些存储库! @@ -81,7 +81,7 @@ training_args = TrainingArguments( ) ``` -你声明 **trainer.train()** 的时候, 这 **Trainer** 然后每次将您的模型保存到您的命名空间中的存储库中时(这里是每个时代),它将上传到集线器。该存储库将命名为您选择的输出目录(此处 **bert-finetuned-mrpc** ) 但您可以选择不同的名称 **hub_model_id = a_different_name** 。 +你声明 **trainer.train()** 的时候, 这 **Trainer** 然后每次将您的模型保存到您的命名空间中的存储库中时(这里是每个时代),它将上传到模型中心。该存储库将命名为您选择的输出目录(此处 **bert-finetuned-mrpc** ) 但您可以选择不同的名称 **hub_model_id = a_different_name** 。 要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 @@ -193,7 +193,7 @@ tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token= ## 使用 huggingface_hub python库 [[使用 huggingface_hub python库]] 这 **huggingface_hub** Python 库是一个包,它为模型和数据集中心提供了一组工具。它为常见任务提供了简单的方法和类,例如 -获取有关集线器上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub +获取有关模型中心上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub 在您的项目和库中。 类似于使用 **push_to_hub** API,这将要求您将 API 令牌保存在缓存中。为此,您需要使用 **login** 来自 CLI 的命令,如上一节所述(同样,确保在这些命令前面加上 **!** 字符(如果在 Google Colab 中运行): @@ -229,7 +229,7 @@ from huggingface_hub import ( 此外,它还提供了非常强大的 **Repository** 用于管理本地存储库的类。我们将在接下来的几节中探讨这些方法和该类,以了解如何利用它们。 -这 **create_repo** 方法可用于在集线器上创建新存储库: +这 **create_repo** 方法可用于在模型中心上创建新存储库: ```py @@ -348,7 +348,7 @@ repo.git_tag() 另外!我们建议您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有关所有可用方法的概述。 -目前,我们有一个模型和一个标记器,我们希望将其推送到集线器。我们已经成功克隆了存储库,因此我们可以将文件保存在该存储库中。 +目前,我们有一个模型和一个标记器,我们希望将其推送到模型中心。我们已经成功克隆了存储库,因此我们可以将文件保存在该存储库中。 我们首先通过拉取最新更改来确保我们的本地克隆是最新的: @@ -363,7 +363,7 @@ model.save_pretrained("") tokenizer.save_pretrained("") ``` -这 **path_to_dummy_folder** 现在包含所有模型和标记器文件。我们遵循通常的 git 工作流程,将文件添加到暂存区,提交它们并将它们推送到集线器: +这 **path_to_dummy_folder** 现在包含所有模型和标记器文件。我们遵循通常的 git 工作流程,将文件添加到暂存区,提交它们并将它们推送到模型中心: ```py repo.git_add() @@ -414,7 +414,7 @@ README.md 如果您刚刚使用 Hugging Face Hub 创建了您的存储库 **create_repo** 方法,这个文件夹应该只包含一个隐藏的 **.gitattributes** 文件。如果您按照上一节中的说明使用 Web 界面创建存储库,则该文件夹应包含一个自述文件文件旁边的隐藏 **.gitattributes** 文件,如图所示。 -添加一个常规大小的文件,例如配置文件、词汇文件,或者基本上任何几兆字节以下的文件,就像在任何基于 git 的系统中所做的一样。但是,更大的文件必须通过 git-lfs 注册才能将它们推送到拥抱脸。 +添加一个常规大小的文件,例如配置文件、词汇文件,或者基本上任何几兆字节以下的文件,就像在任何基于 git 的系统中所做的一样。但是,更大的文件必须通过 git-lfs 注册才能将它们推送到Hugging Face。 让我们回到 Python 来生成我们想要提交到我们的虚拟存储库的模型和标记器: @@ -570,7 +570,7 @@ Objects not staged for commit: {/if} -Let's proceed to the final steps, committing and pushing to 让我们继续最后的步骤,提交并推动拥抱脸远程仓库: +让我们继续最后的步骤,提交并推送到Hugging Face远程仓库: ```bash git commit -m "First model version" diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx index 806ab4352..8308af695 100644 --- a/chapters/zh-CN/chapter4/6.mdx +++ b/chapters/zh-CN/chapter4/6.mdx @@ -102,28 +102,28 @@ ]} /> -### 5.哪些🤗 Transformers 库的对象可以直接在 Hub 上通过push _ to _ Hub ()共享? +### 5.哪些🤗 Transformers 库的对象可以直接在 Hub 上通过`push_to_Hub()`共享? {#if fw === 'pt'} push_to_hub方法,使用该方法将把所有标记器文件(词汇表、标记器的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", correct: true }, { text: "模型配置", - explain: "对!所有模型配置都有 push _ to _ hub 方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", + explain: "对!所有模型配置都有push_to_hub方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", correct: true }, { text: "一个模型", - explain: "正确! 所有模型都有 push_to_hub 方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", + explain: "正确! 所有模型都有push_to_hub方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", correct: true }, { text: "Trainer", - explain: "没错————Trainer也实现了push _ to _ hub方法,并且使用它将模型、配置、标记器和模型卡草稿上传到给定的存储器。试试其他答案!", + explain: "没错————Trainer也实现了push_to_hub方法,并且使用它将模型、配置、标记器和模型卡草稿上传到给定的存储器。试试其他答案!", correct: true } ]} @@ -133,29 +133,29 @@ choices={[ { text: "一个标记器", - explain: "正确! 所有标记器都有push_to_hub方法,使用该方法将把所有标记器文件(词汇表、标记器的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", + explain: "正确! 所有标记器都有push_to_hub方法,使用该方法将把所有标记器文件(词汇表、标记器的架构等)推送到给定的存储库。不过,这不是唯一正确的答案!", correct: true }, { text: "模型配置", - explain: "对!所有模型配置都有 push _ to _ hub 方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", + explain: "对!所有模型配置都有push_to_hub方法,使用这个方法可以将它们推送到给定的存储库。你还有其他能共享的吗?", correct: true }, { text: "一个模型", - explain: "正确! 所有模型都有 push_to_hub 方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", + explain: "正确! 所有模型都有push_to_hub方法,使用它会将它们及其配置文件推送到给定的存储库。不过,这并不是您可以共享的全部内容。", correct: true }, { text: "以上都有专用的回调函数", - explain: "正确————在训练期间,PushToHubCallback会定期将所有这些对象发送到存储器。", + explain: "正确————在训练期间,PushToHubCallback会定期将所有这些对象发送到存储器。", correct: true } ]} /> {/if} -### 6.当使用push _ to _ hub ()方法或 CLI 工具时,第一步是什么? +### 6.当使用`push_to_hub()`方法或 CLI 工具时,第一步是什么? huggingface _ hub 实用程序: 不需要额外的包装!" + text: "在 Python 运行时中,将它们包装在huggingface_hub实用程序中。", + explain: "模型和标记器已经受益于huggingface_hub 实用程序: 不需要额外的包装!" }, { - text: "将它们保存到磁盘并调用 < code > transformers-cli upload-model ", - explain: "命令 < code > upload-model 不存在。" + text: "将它们保存到磁盘并调用 transformers-cli upload-model", + explain: "命令 upload-model 不存在。" } ]} /> -### 8.您可以使用'Repository'类执行哪些 git 操作? +### 8.您可以使用`Repository`类执行哪些 git 操作? git _ commit () 方法就是为此而存在的。", + explain: "正确,git_commit() 方法就是为此而存在的。", correct: true }, { - text: "拉一下", - explain: "这就是 < code > git _ pull () 方法的目的。", + text: "拉取", + explain: "这就是 git_pull() 方法的目的。", correct: true }, { - text: "推一下", - explain: "方法 < code > git _ push () 可以做到这一点。", + text: "推送", + explain: "方法 git_push() 可以做到这一点。", correct: true }, { diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index 95b8691b1..555e35f70 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -160,7 +160,7 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -✏️ **试试看!** 选择托管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一个数据集并尝试使用上述技术在本地和远程加载它。另外,可以尝试加载CSV或者文本格式存储的数据集(有关这些格式的更多信息,请参阅[文档](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files))。 +✏️ **试试看!** 选择托管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一个数据集并尝试使用上述技术在本地和远程加载它。另外,可以尝试加载CSV或者文本格式存储的数据集(有关这些格式的更多信息,请参阅[文档](https://huggingface.co/docs/datasets/loading#local-and-remote-files))。 diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index 70ef713ca..bf34117ff 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -46,7 +46,7 @@ Dataset({ -✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig)。 +✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。 diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index a18ee94f4..f6454f914 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -360,7 +360,7 @@ Dataset({ -💡 您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) 指南。 +💡 您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index 8de5fb335..6ded366a9 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -177,7 +177,7 @@ Dataset({ -✏️ **Try it out!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) 对这个任务很有用。 +✏️ **试试看!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx index 502c0d8b5..503108c9c 100644 --- a/chapters/zh-CN/chapter5/8.mdx +++ b/chapters/zh-CN/chapter5/8.mdx @@ -44,8 +44,8 @@ dataset = load_dataset("glue", "mrpc", split="train") dataset.sample (50) ", - explain: "这是不正确的——没有 < code > Dataset.sample () 方法。" + text: "dataset.sample(50)", + explain: "这是不正确的——没有 Dataset.sample() 方法。" }, { text: "dataset.shuffle().select(range(50))", @@ -63,17 +63,17 @@ dataset = load_dataset("glue", "mrpc", split="train") pets _ dataset. filter (lambda x: x ['name'] . startswith ('L')) ", + text: "pets_dataset.filter(lambda x: x['name'].startswith('L'))", explain: "正确! 为这些快速过滤使用 Python lambda 函数是一个好主意。你还能想到其他解决方案吗?", correct: true }, { - text: "< code > pets _ dataset. filter (lambda x ['name'] . startswith ('L') ", - explain: "这是不正确的—— lambda 函数采用通用格式 < code > lambda * arguments * : * expression * , 因此在这种情况下需要提供参数。" + text: "pets_dataset.filter(lambda x['name'].startswith('L')", + explain: "这是不正确的—— lambda 函数采用通用格式 lambda * arguments * : * expression * , 因此在这种情况下需要提供参数。" }, { - text: "创建一个类似于 < code > def filter _ names (x) : return x ['name'] . startswith ('L') 的函数并运行 < code > pets _ dataset. filter (filter _ names) 。", - explain: "正确!就像使用 < code > Dataset.map () 一样,你可以将显式函数传递给 < code > Dataset.filter () 。当你有一些不适合于简短 lambda 函数的复杂逻辑时,这是非常有用的。其他解决方案中还有哪一个可行?", + text: "创建一个类似于 def filter_names(x) : return x['name'].startswith('L') 的函数并运行 pets_dataset.filter(filter_names) 。", + explain: "正确!就像使用 Dataset.map() 一样,你可以将显式函数传递给 Dataset.filter() 。当你有一些不适合于简短 lambda 函数的复杂逻辑时,这是非常有用的。其他解决方案中还有哪一个可行?", correct: true } ]} @@ -133,13 +133,13 @@ dataset[0] explain: "这是不正确的---- 流数据集是动态解压的, 你可以用非常小的RAM处理TB大小的数据集!", }, { - text: "它尝试访问 < code > IterableDataset 。", - explain: "正确! < code > IterableDataset 是一个生成器, 而不是一个容器, 因此你应该使用 < code > next (iter (dataset)) 来访问它的元素。", + text: "它尝试访问 IterableDataset 。", + explain: "正确! IterableDataset 是一个生成器, 而不是一个容器, 因此你应该使用 next(iter(dataset)) 来访问它的元素。", correct: true }, { - text: "数据集 < code > allocine 没有分割< code >训练集。", - explain: "这是不正确的---- 查看 Hub 上的[ < code > allocine dataset card ]( https://huggingface.co/datasets/allocine ), 看看它包含哪些拆分。" + text: "数据集 allocine 没有分割train。", + explain: "这是不正确的---- 查看 Hub 上的[allocine数据集卡](https://huggingface.co/datasets/allocine), 看看它包含哪些拆分。" } ]} /> diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index 7e6802bdc..73e07c7fa 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -27,14 +27,14 @@ 更准确地说,该库是围绕一个中央“Tokenizer”类构建的,构建这个类的每一部分可以在子模块的列表中重新组合: -- `normalizers` 包含你可以使用的所有可能的Normalizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers))。 -- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers))。 -- `models` 包含您可以使用的各种类型的Model,如BPE、WordPiece和Unigram(完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models))。 -- `trainers` 包含所有不同类型的 trainer,你可以使用一个语料库训练你的模型(每种模型一个;完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers))。 -- `post_processors` 包含你可以使用的各种类型的PostProcessor(完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors))。 -- `decoders` 包含各种类型的Decoder,可以用来解码标记化的输出(完整列表[在这里](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders))。 - -您可以[在这里](https://huggingface.co/docs/tokenizers/python/latest/components.html)找到完整的模块列表。 +- `normalizers` 包含你可以使用的所有可能的Normalizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/normalizers))。 +- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer类型(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/pre-tokenizers))。 +- `models` 包含您可以使用的各种类型的Model,如BPE、WordPiece和Unigram(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/models))。 +- `trainers` 包含所有不同类型的 trainer,你可以使用一个语料库训练你的模型(每种模型一个;完整列表[在这里](https://huggingface.co/docs/tokenizers/api/trainers))。 +- `post_processors` 包含你可以使用的各种类型的PostProcessor(完整列表[在这里](https://huggingface.co/docs/tokenizers/api/post-processors))。 +- `decoders` 包含各种类型的Decoder,可以用来解码标记化的输出(完整列表[在这里](https://huggingface.co/docs/tokenizers/components#decoders))。 + +您可以[在这里](https://huggingface.co/docs/tokenizers/components)找到完整的模块列表。 ## 获取语​​料库 [[获取语​​料库]] diff --git a/chapters/zh-CN/chapter7/5.mdx b/chapters/zh-CN/chapter7/5.mdx index a91f71926..f50469f85 100644 --- a/chapters/zh-CN/chapter7/5.mdx +++ b/chapters/zh-CN/chapter7/5.mdx @@ -663,8 +663,10 @@ trainer.push_to_hub(commit_message="Training complete", tags="summarization") 'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' ``` + 上面的代码会把 checkpoint 和配置文件保存到 `output_dir` ,然后将所有文件上传到 Hub。我们还可以通过 `tags` 参数指定模型的类型,这样就可以确保在 Hub 上的小工具会是一个摘要生成的小工具,而不是与 mT5 架构的默认文本生成小工具(关于模型标签的更多信息,请参见 [🤗Hub文档](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined) )。 `trainer.push_to_hub()` 的输出是带有 Git 提交哈希的 URL,所以你可以打开 URL 轻松查看模型库的修改记录! + 在结束本节之前,让我们看一下如何使用 🤗 Accelerate 提供的底层 API 对 mT5 进行微调。 {:else} diff --git a/chapters/zh-TW/_toctree.yml b/chapters/zh-TW/_toctree.yml index ddbeae3b5..cb40a3984 100644 --- a/chapters/zh-TW/_toctree.yml +++ b/chapters/zh-TW/_toctree.yml @@ -57,7 +57,7 @@ title: 使用 Trainer API 或者 Keras 微調一個模型 local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - local: chapter3/4 - title: 一個完成的訓練過程 + title: 一個完整的訓練過程 - local: chapter3/5 title: 微調,章節回顧! - local: chapter3/6 diff --git a/chapters/zh-TW/chapter0/1.mdx b/chapters/zh-TW/chapter0/1.mdx index 95d80cf5f..05d6f8f35 100644 --- a/chapters/zh-TW/chapter0/1.mdx +++ b/chapters/zh-TW/chapter0/1.mdx @@ -89,7 +89,7 @@ ls -a source .env/bin/activate # Deactivate the virtual environment -source .env/bin/deactivate +deactivate ``` 你可以執行 `which python` 指令來確認你的虛擬環境是否有被啟用:如果它指向虛擬環境的目錄,那表示你的虛擬環境已經啟用了! diff --git a/chapters/zh-TW/chapter1/3.mdx b/chapters/zh-TW/chapter1/3.mdx index 31e4c8296..206c7dd68 100644 --- a/chapters/zh-TW/chapter1/3.mdx +++ b/chapters/zh-TW/chapter1/3.mdx @@ -17,7 +17,8 @@ ## Transformer被應用於各個方面! Transformer 模型用於解決各種 NLP 任務,就像上一節中提到的那樣。以下是一些使用 Hugging Face 和 Transformer 模型的公司和組織,他們也通過分享他們的模型回饋社區: -![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +使用 Hugging Face 的公司 + [🤗 Transformers 庫](https://github.com/huggingface/transformers)提供了創建和使用這些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含數千個任何人都可以下載和使用的預訓練模型。您還可以將自己的模型上傳到 Hub! @@ -65,15 +66,15 @@ classifier( 目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: -* **特徵提取**(獲取文本的向量表示) -* **填充空缺** -* **ner**(命名實體識別) -* **問答** -* **情感分析** -* **文本摘要** -* **文本生成** -* **翻譯** -* **零樣本分類** +* `feature-extraction`(獲取文本的向量表示) +* `fill-mask` +* `ner`(命名實體識別) +* `question-answering` +* `sentiment-analysis` +* `summarization` +* `text-generation` +* `translation` +* `zero-shot-classification` 讓我們來看看其中的一些吧! @@ -97,7 +98,7 @@ classifier( 此pipeline稱為zero-shot,因為您不需要對數據上的模型進行微調即可使用它。它可以直接返回您想要的任何標籤列表的概率分數! -✏️**快來試試吧!**使用您自己的序列和標籤,看看模型的行為。 +✏️**快來試試吧!** 使用您自己的序列和標籤,看看模型的行為。 ## 文本生成 @@ -119,7 +120,7 @@ generator("In this course, we will teach you how to") 您可以使用參數 **num_return_sequences** 控制生成多少個不同的序列,並使用參數 **max_length** 控制輸出文本的總長度。 -✏️**快來試試吧!**使用 num_return_sequences 和 max_length 參數生成兩個句子,每個句子 15 個單詞。 +✏️**快來試試吧!** 使用 num_return_sequences 和 max_length 參數生成兩個句子,每個句子 15 個單詞。 ## 在pipeline中使用 Hub 中的其他模型 @@ -148,7 +149,7 @@ generator( 通過單擊選擇模型後,您會看到有一個小組件,可讓您直接在線試用。通過這種方式,您可以在下載之前快速測試模型的功能。 -✏️**快來試試吧!**使用標籤篩選查找另一種語言的文本生成模型。使用小組件測試並在pipeline中使用它! +✏️**快來試試吧!** 使用標籤篩選查找另一種語言的文本生成模型。使用小組件測試並在pipeline中使用它! ## 推理 API @@ -177,7 +178,7 @@ unmasker("This course will teach you all about models.", top_k=2) **top_k** 參數控制要顯示的結果有多少種。請注意,這裡模型填充了特殊的< **mask** >詞,它通常被稱為掩碼標記。其他掩碼填充模型可能有不同的掩碼標記,因此在探索其他模型時要驗證正確的掩碼字是什麼。檢查它的一種方法是查看小組件中使用的掩碼。 -✏️**快來試試吧!**在 Hub 上搜索基於 bert 的模型並在推理 API 小組件中找到它的掩碼。這個模型對上面pipeline示例中的句子預測了什麼? +✏️**快來試試吧!** 在 Hub 上搜索基於 bert 的模型並在推理 API 小組件中找到它的掩碼。這個模型對上面pipeline示例中的句子預測了什麼? ## 命名實體識別 @@ -199,7 +200,7 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") 我們在pipeline創建函數中傳遞選項 **grouped_entities=True** 以告訴pipeline將對應於同一實體的句子部分重新組合在一起:這裡模型正確地將「Hugging」和「Face」分組為一個組織,即使名稱由多個詞組成。事實上,正如我們即將在下一章看到的,預處理甚至會將一些單詞分成更小的部分。例如,**Sylvain** 分割為了四部分:**S、##yl、##va** 和 **##in**。在後處理步驟中,pipeline成功地重新組合了這些部分。 -✏️**快來試試吧!**在模型中心(hub)搜索能夠用英語進行詞性標注(通常縮寫為 POS)的模型。這個模型對上面例子中的句子預測了什麼? +✏️**快來試試吧!** 在模型中心(hub)搜索能夠用英語進行詞性標注(通常縮寫為 POS)的模型。這個模型對上面例子中的句子預測了什麼? ## 問答系統 @@ -280,7 +281,7 @@ translator("Ce cours est produit par Hugging Face.") -✏️**快來試試吧!**搜索其他語言的翻譯模型,嘗試將前一句翻譯成幾種不同的語言。 +✏️**快來試試吧!** 搜索其他語言的翻譯模型,嘗試將前一句翻譯成幾種不同的語言。 diff --git a/chapters/zh-TW/chapter2/1.mdx b/chapters/zh-TW/chapter2/1.mdx index 70ce241f2..618d780bb 100644 --- a/chapters/zh-TW/chapter2/1.mdx +++ b/chapters/zh-TW/chapter2/1.mdx @@ -9,15 +9,15 @@ 創建 🤗Transformers 庫就是為了解決這個問題。它的目標是提供一個API,通過它可以加載、訓練和保存任何Transformer模型。這個庫的主要特點是: - **易於使用**:下載、加載和使用最先進的NLP模型進行推理只需兩行代碼即可完成。 -- **靈活**:所有型號的核心都是簡單的 PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它們各自的機器學習(ML)框架中的任何其他模型一樣進行處理。 +- **靈活**:所有模型的核心都是簡單的 PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它們各自的機器學習(ML)框架中的任何其他模型一樣進行處理。 - **簡單**:當前位置整個庫幾乎沒有任何摘要。“都在一個文件中”是一個核心概念:模型的正向傳遞完全定義在一個文件中,因此代碼本身是可以理解的,並且是可以破解的。 -最後一個特性使🤗 Transformers與其他ML庫截然不同。這些模型不是基於通過文件共享的模塊構建的;相反,每一個模型都有自己的菜單。除了使模型更加容易接受和更容易理解,這還允許你輕鬆地在一個模型上實驗,而且不影響其他模型。 +最後一個特性使🤗 Transformers與其他ML庫截然不同。這些模型不是基於通過文件共享的模塊構建的;相反,每一個模型都有自己的網絡結構(layers)。除了使模型更加容易接受和更容易理解,這還允許你輕鬆地在一個模型上實驗,而且不影響其他模型。 本章將從一個端到端的示例開始,在該示例中,我們一起使用模型和tokenizer分詞器來複制[Chapter 1](/course/chapter1)中引入的函數 pipeline(). 接下來,我們將討論模型API:我們將深入研究模型和配置類,並向您展示如何加載模型以及如何將數值輸入處理為輸出預測。 然後我們來看看標記器API,它是 pipeline() 函數的另一個主要組件。它是作用分詞器負責第一個和最後一個處理步驟,處理從文本到神經網絡數字輸入的轉換,以及在需要時轉換回文本。最後,我們將向您展示如何處理在一個準備好的批處理中通過一個模型發送多個句子的問題,然後詳細介紹 pipeline() 函數。 -⚠️ 為了從模型集線器和 🤗Transformers 的所有可用功能中獲益,我們建議creating an account. +⚠️ 為了從模型中心和 🤗Transformers 的所有可用功能中獲益,我們建議creating an account. \ No newline at end of file diff --git a/chapters/zh-TW/chapter2/5.mdx b/chapters/zh-TW/chapter2/5.mdx index dd09f943d..add0490a6 100644 --- a/chapters/zh-TW/chapter2/5.mdx +++ b/chapters/zh-TW/chapter2/5.mdx @@ -189,7 +189,7 @@ batched_ids = [ids, ids] -✏️ **Try it out!** 試試看!將此列表轉換為張量並通過模型傳遞。檢查您是否獲得與之前相同的登錄(但是隻有兩次) +✏️ **試試看!** 將此列表轉換為張量並通過模型傳遞。檢查您是否獲得與之前相同的登錄(但是隻有兩次) 批處理允許模型在輸入多個句子時工作。使用多個序列就像使用單個序列構建批一樣簡單。不過,還有第二個問題。當你試圖將兩個(或更多)句子組合在一起時,它們的長度可能不同。如果您以前使用過張量,那麼您知道它們必須是矩形,因此無法將輸入ID列表直接轉換為張量。為了解決這個問題,我們通常填充輸入。 @@ -326,7 +326,7 @@ tf.Tensor( -✏️ 試試看!在第2節中使用的兩個句子上手動應用標記化(“我一生都在等待擁抱課程。”和“我非常討厭這個!”)。通過模型傳遞它們,並檢查您是否獲得與第2節中相同的登錄。現在使用填充標記將它們批處理在一起,然後創建適當的注意掩碼。檢查通過模型時是否獲得相同的結果! +✏️ 試試看!在第2節中使用的兩個句子上手動應用標記化(“我一生都在等待Hugging Face課程。”和“我非常討厭這個!”)。通過模型傳遞它們,並檢查您是否獲得與第2節中相同的登錄。現在使用填充標記將它們批處理在一起,然後創建適當的注意掩碼。檢查通過模型時是否獲得相同的結果! diff --git a/chapters/zh-TW/chapter3/2.mdx b/chapters/zh-TW/chapter3/2.mdx index 1b9045aab..5dd10b0a0 100644 --- a/chapters/zh-TW/chapter3/2.mdx +++ b/chapters/zh-TW/chapter3/2.mdx @@ -84,7 +84,7 @@ model.train_on_batch(batch, labels) {/if} -模型中心(hub)不只是包含模型;它也有許多不同語言的多個數據集。點擊[數據集](https://huggingface.co/datasets)的鏈接即可進行瀏覽。我們建議您在閱讀本節後閱讀一下[加載和處理新的數據集](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)這篇文章,這會讓您對huggingface的darasets更加清晰。但現在,讓我們使用MRPC數據集中的[GLUE 基準測試數據集](https://gluebenchmark.com/),它是構成MRPC數據集的10個數據集之一,這是一個學術基準,用於衡量機器學習模型在10個不同文本分類任務中的性能。 +模型中心(hub)不只是包含模型;它也有許多不同語言的多個數據集。點擊[數據集](https://huggingface.co/datasets)的鏈接即可進行瀏覽。我們建議您在閱讀本節後閱讀一下[加載和處理新的數據集](https://huggingface.co/docs/datasets/loading)這篇文章,這會讓您對huggingface的darasets更加清晰。但現在,讓我們使用MRPC數據集中的[GLUE 基準測試數據集](https://gluebenchmark.com/),它是構成MRPC數據集的10個數據集之一,這是一個學術基準,用於衡量機器學習模型在10個不同文本分類任務中的性能。 🤗 Datasets庫提供了一個非常便捷的命令,可以在模型中心(hub)上下載和緩存數據集。我們可以通過以下的代碼下載MRPC數據集: @@ -235,7 +235,7 @@ tokenized_dataset = tokenizer( 這很有效,但它的缺點是返回字典(字典的鍵是**輸入詞id(input_ids)** , **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)**,字典的值是鍵所對應值的列表)。而且只有當您在轉換過程中有足夠的內存來存儲整個數據集時才不會出錯(而🤗數據集庫中的數據集是以[Apache Arrow](https://arrow.apache.org/)文件存儲在磁盤上,因此您只需將接下來要用的數據加載在內存中,因此會對內存容量的需求要低一些)。 -為了將數據保存為數據集,我們將使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map)方法,如果我們需要做更多的預處理而不僅僅是標記化,那麼這也給了我們一些額外的自定義的方法。這個方法的工作原理是在數據集的每個元素上應用一個函數,因此讓我們定義一個標記輸入的函數: +為了將數據保存為數據集,我們將使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map)方法,如果我們需要做更多的預處理而不僅僅是標記化,那麼這也給了我們一些額外的自定義的方法。這個方法的工作原理是在數據集的每個元素上應用一個函數,因此讓我們定義一個標記輸入的函數: ```py def tokenize_function(example): diff --git a/chapters/zh-TW/chapter3/6.mdx b/chapters/zh-TW/chapter3/6.mdx index 9f90c3fc3..b0a72ac28 100644 --- a/chapters/zh-TW/chapter3/6.mdx +++ b/chapters/zh-TW/chapter3/6.mdx @@ -11,7 +11,7 @@ Test what you learned in this chapter! -### 1.「情緒」數據集包含標記有情緒的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集線器)中搜索它,然後讀取數據集卡。哪一個不是它的基本情感? +### 1.「情緒」數據集包含標記有情緒的 Twitter 消息。在[模型中心](https://huggingface.co/datasets)中搜索它,然後讀取數據集卡。哪一個不是它的基本情感? -### 2.在[ Hub ]( https://huggingface.co/datasets 集線器)中搜索‘ ar _ sarcasm’數據集,它支持哪個任務? +### 2.在[模型中心](https://huggingface.co/datasets)中搜索‘ ar _ sarcasm’數據集,它支持哪個任務? ") tokenizer.save_pretrained("") ``` -這 **path_to_dummy_folder** 現在包含所有模型和標記器文件。我們遵循通常的 git 工作流程,將文件添加到暫存區,提交它們並將它們推送到集線器: +這 **path_to_dummy_folder** 現在包含所有模型和標記器文件。我們遵循通常的 git 工作流程,將文件添加到暫存區,提交它們並將它們推送到模型中心: ```py repo.git_add() @@ -414,7 +414,7 @@ README.md 如果您剛剛使用 Hugging Face Hub 創建了您的存儲庫 **create_repo** 方法,這個文件夾應該只包含一個隱藏的 **.gitattributes** 文件。如果您按照上一節中的說明使用 Web 界面創建存儲庫,則該文件夾應包含一個自述文件文件旁邊的隱藏 **.gitattributes** 文件,如圖所示。 -添加一個常規大小的文件,例如配置文件、詞彙文件,或者基本上任何幾兆字節以下的文件,就像在任何基於 git 的系統中所做的一樣。但是,更大的文件必須通過 git-lfs 註冊才能將它們推送到擁抱臉。 +添加一個常規大小的文件,例如配置文件、詞彙文件,或者基本上任何幾兆字節以下的文件,就像在任何基於 git 的系統中所做的一樣。但是,更大的文件必須通過 git-lfs 註冊才能將它們推送到Hugging Face。 讓我們回到 Python 來生成我們想要提交到我們的虛擬存儲庫的模型和標記器: @@ -570,7 +570,7 @@ Objects not staged for commit: {/if} -Let's proceed to the final steps, committing and pushing to 讓我們繼續最後的步驟,提交併推動擁抱臉遠程倉庫: +讓我們繼續最後的步驟,提交併推送到Hugging Face遠程倉庫: ```bash git commit -m "First model version" diff --git a/chapters/zh-TW/chapter5/2.mdx b/chapters/zh-TW/chapter5/2.mdx index f47239575..90cbfdc43 100644 --- a/chapters/zh-TW/chapter5/2.mdx +++ b/chapters/zh-TW/chapter5/2.mdx @@ -160,7 +160,7 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -✏️ **試試看!** 選擇託管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一個數據集並嘗試使用上述技術在本地和遠程加載它。另外,可以嘗試加載CSV或者文本格式存儲的數據集(有關這些格式的更多信息,請參閱[文檔](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files))。 +✏️ **試試看!** 選擇託管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一個數據集並嘗試使用上述技術在本地和遠程加載它。另外,可以嘗試加載CSV或者文本格式存儲的數據集(有關這些格式的更多信息,請參閱[文檔](https://huggingface.co/docs/datasets/loading#local-and-remote-files))。 diff --git a/chapters/zh-TW/chapter5/4.mdx b/chapters/zh-TW/chapter5/4.mdx index 4cd7ae730..96e11bb8c 100644 --- a/chapters/zh-TW/chapter5/4.mdx +++ b/chapters/zh-TW/chapter5/4.mdx @@ -46,7 +46,7 @@ Dataset({ -✎ 默認情況下, 🤗 Datasets 會自動解壓加載數據集所需的文件。 如果你想保留硬盤空間, 你可以傳遞 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`參數. 有關更多詳細信息, 請參閱文檔](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig)。 +✎ 默認情況下, 🤗 Datasets 會自動解壓加載數據集所需的文件。 如果你想保留硬盤空間, 你可以傳遞 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`參數. 有關更多詳細信息, 請參閱文檔](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。 diff --git a/chapters/zh-TW/chapter5/5.mdx b/chapters/zh-TW/chapter5/5.mdx index f65b1c0e2..6f9953910 100644 --- a/chapters/zh-TW/chapter5/5.mdx +++ b/chapters/zh-TW/chapter5/5.mdx @@ -423,7 +423,7 @@ Dataset({ -💡 您還可以使用一些 Git 魔法直接從終端將數據集上傳到 Hugging Face Hub。有關如何執行此操作的詳細信息,請參閱 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) 指南。 +💡 您還可以使用一些 Git 魔法直接從終端將數據集上傳到 Hugging Face Hub。有關如何執行此操作的詳細信息,請參閱 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 diff --git a/chapters/zh-TW/chapter5/6.mdx b/chapters/zh-TW/chapter5/6.mdx index b5646f739..8f578f8f6 100644 --- a/chapters/zh-TW/chapter5/6.mdx +++ b/chapters/zh-TW/chapter5/6.mdx @@ -188,7 +188,7 @@ Dataset({ -✏️ **Try it out!** 看看能不能不用pandas就可以完成列的擴充; 這有點棘手; 你可能會發現 🤗 Datasets 文檔的 ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) 對這個任務很有用。 +✏️ **試試看!** 看看能不能不用pandas就可以完成列的擴充; 這有點棘手; 你可能會發現 🤗 Datasets 文檔的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 對這個任務很有用。 diff --git a/chapters/zh-TW/chapter6/8.mdx b/chapters/zh-TW/chapter6/8.mdx index 9a31a2bf1..c61e14d7f 100644 --- a/chapters/zh-TW/chapter6/8.mdx +++ b/chapters/zh-TW/chapter6/8.mdx @@ -27,14 +27,14 @@ 更準確地說,該庫是圍繞一個中央「Tokenizer」類構建的,構建這個類的每一部分可以在子模塊的列表中重新組合: -- `normalizers` 包含你可以使用的所有可能的Normalizer類型(完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.normalizers))。 -- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer類型(完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.pre_tokenizers))。 -- `models` 包含您可以使用的各種類型的Model,如BPE、WordPiece和Unigram(完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.models))。 -- `trainers` 包含所有不同類型的 trainer,你可以使用一個語料庫訓練你的模型(每種模型一個;完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.trainers))。 -- `post_processors` 包含你可以使用的各種類型的PostProcessor(完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.processors))。 -- `decoders` 包含各種類型的Decoder,可以用來解碼標記化的輸出(完整列表[在這裡](https://huggingface.co/docs/tokenizers/python/latest/components.html#decoders))。 - -您可以[在這裡](https://huggingface.co/docs/tokenizers/python/latest/components.html)找到完整的模塊列表。 +- `normalizers` 包含你可以使用的所有可能的Normalizer類型(完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/normalizers))。 +- `pre_tokenizesr` 包含您可以使用的所有可能的PreTokenizer類型(完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/pre-tokenizers))。 +- `models` 包含您可以使用的各種類型的Model,如BPE、WordPiece和Unigram(完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/models))。 +- `trainers` 包含所有不同類型的 trainer,你可以使用一個語料庫訓練你的模型(每種模型一個;完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/trainers))。 +- `post_processors` 包含你可以使用的各種類型的PostProcessor(完整列表[在這裡](https://huggingface.co/docs/tokenizers/api/post-processors))。 +- `decoders` 包含各種類型的Decoder,可以用來解碼標記化的輸出(完整列表[在這裡](https://huggingface.co/docs/tokenizers/components#decoders))。 + +您可以[在這裡](https://huggingface.co/docs/tokenizers/components)找到完整的模塊列表。 ## 獲取語​​料庫 diff --git a/chapters/zh-TW/chapter7/5.mdx b/chapters/zh-TW/chapter7/5.mdx index eeab998ed..49f6cdd4a 100644 --- a/chapters/zh-TW/chapter7/5.mdx +++ b/chapters/zh-TW/chapter7/5.mdx @@ -665,7 +665,7 @@ trainer.push_to_hub(commit_message="Training complete", tags="summarization") 'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' ``` -這會將檢查點和配置文件保存到 **output_dir** , 在將所有文件上傳到集線器之前。通過指定 **tags** 參數,我們還確保集線器上的小部件將是一個用於彙總管道的小部件,而不是與 mT5 架構關聯的默認文本生成小部件(有關模型標籤的更多信息,請參閱[🤗 Hub 文檔](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined))。輸出來自 **trainer.push_to_hub()** 是 Git 提交哈希的 URL,因此您可以輕鬆查看對模型存儲庫所做的更改! +這會將檢查點和配置文件保存到 **output_dir** , 在將所有文件上傳到模型中心之前。通過指定 **tags** 參數,我們還確保模型中心上的小部件將是一個用於彙總管道的小部件,而不是與 mT5 架構關聯的默認文本生成小部件(有關模型標籤的更多信息,請參閱[🤗 Hub 文檔](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined))。輸出來自 **trainer.push_to_hub()** 是 Git 提交哈希的 URL,因此您可以輕鬆查看對模型存儲庫所做的更改! 在結束本節之前,讓我們看一下如何使用 🤗 Accelerate 提供的底層API對 mT5 進行微調。 diff --git a/requirements.txt b/requirements.txt index 8f94be377..0a14ffdfa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ nbformat>=5.1.3 PyYAML>=5.4.1 -black==22.3.0 \ No newline at end of file +black>=24.1.1 \ No newline at end of file diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index f4e77cd62..9ed74f976 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -131,6 +131,7 @@ def build_notebook(fname, title, output_dir="."): """ sections = read_and_split_frameworks(fname) sections_with_accelerate = [ + "chapter3/3", # "Fine-tuning a model with the Trainer API or Keras", "chapter3/4", # "A full training", "chapter7/2_pt", # "Token classification (PyTorch)", "chapter7/3_pt", # "Fine-tuning a masked language model (PyTorch)"